Compare commits

..

259 Commits

Author SHA1 Message Date
Brennen Herbruck f39a35a090 Restore solidcore/ files an over-broad rename touched
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:22:11 -05:00
Brennen Herbruck f2bed78bbb Merge remote-tracking branch 'origin/main' into remove-view-cube 2026-11-10 02:18:59 -05:00
Brennen Herbruck 40b1815d34 Merge main; finish removing the extra navigator style's leftovers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:18:52 -05:00
Brennen Herbruck 96526c6f3a Merge remote-tracking branch 'origin/main' into remove-view-cube
# Conflicts:
#	crates/ui-egui/src/ui_kit/settings_search.rs
2026-11-10 01:47:43 -05:00
Karl Von Richter 52ea418a2a Kernel: variable rounds are the union of their balls by default; retire fillet_gn_section_generator (moved from solidcore#2936) (#13945)
* test: the default kernel's variable rounds are the union of their balls (failing, #2273)

On main the default kernel sweeps great circles: the 20 cube's top front
edge rounded by the linear law 3, 2, 4, 2, 3 comes back with a round whose
net doubles back (check_brep #5), and the folded law 2, 2, 40, 3, 2 fails
in the surface (1001) with no reason, where it is a fold of the balls
(NoRoom, BallsFold, naming 19.63563).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: the G^n section and the generic blend's checks are the default; their flags are retired (WR-685, WR-696, #2273)

`fillet_gn_section_generator` is removed and every read site takes its
side: a blend-curve round is the G^n section, a variable round is the
union of its balls (touch circles, law corners), the ease takes the
order-2 G^n section, and BallIsRegular, BandCutsThePart, the collapsed
offset and the refusals' fit sizes run on every kernel.

Retired with it, each now dead or a prerequisite of the G^n path:
- fillet_refuse_ball_not_fitting (WR-462): BallIsRegular replaced
  BallFitsItsFaces, which never ran with it; ball_fit.rs is removed.
- the executive's blend_curve_at_thirds option (WR-525), which the fork
  removed with its G^n section, and the ease's at-thirds target.
- fillet_blend_curve_revolved_on_arc (WR-581): only a blend-curve
  generator took it, and the executive builds none now.
- fillet_surface_plane_curve_on_plane_equation (WR-682): without it a
  variable round's side-face curve is not found (1001).
- fillet_corner_face_curve_halves (WR-659): an eased corner's patch does
  not close against the G^n ease without the halves.

The ExecutiveAccess adapter (the orchestration mocks, which hold no
solver geometry) reports nothing to judge, as its rail check already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend tests: the default kernel's rounds re-derived (WR-685, WR-696, WR-581, WR-659, WR-682, #2273)

The tests that set the retired flags drop the setting and keep their
on halves. Values pinned to the great-circle sweep are re-derived from
the union of the balls: wr646 against a quadrature of the envelope
(64.15953 removed; the round 64.1638, 6.7e-5 off), wr609 likewise
(5831.254415), wr631 against the fork's G^n builds. The refusals that
BallIsRegular now finds first name the same sizes (BallDoesNotFit).
The blend curve's own tests (at thirds, the revolved dome, the
great-circle marks) are removed with it. Known failures, recorded:
wr643's mirrored r 2 knife edge (#2963) and the r 9.99 G2 rim (#2964).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: the surface-plane trace stays on its surface and is bounded (WR-682, #2273)

With its flag retired the trace runs on every kernel, and the goldens'
generated blend.fillet_geom_p2 cases showed two defects it carried from
the fork:
- gen0014: the march left the surface's domain (u = -6e5 on [0, 1]),
  marching the B-spline's extrapolation, and the refinement doubled
  its stations each pass until the allocation aborted (4.9 GB). A
  station off the domain, in a direction the surface isn't closed in,
  is the curve leaving the surface: refused (1001, as main's golden).
  The trace also holds at most MAX_STATIONS (the march's own 100 000).
- gen0123: start and end coincide, no curve between two points; it is
  not the trace's event, and the intersectors refuse it as before (1007).

(gen0015 and gen0170, whose spans at an end never met the check while
the curve ended at the given points, build since main's #2907 ends the
curve on the crossing.)

gen0014_the_trace_stays_on_its_surface aborts without the domain event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: clippy's items_after_statements and field_reassign_with_default (#2273)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: a band on a sheet has no part to cut (WR-696, #2273)

BandCutsThePart and the break-through check ask sm_BlendAddsMaterial,
whose IsConcaveEdge looks for a use of the edge on a material side and
fails (SM_ERR from fillet_solver::p5) where there is none: a sheet's
edges bound no material. The fillet failed with it on every open sheet,
on SolidCraft's set and, with the generic blend's checks now the
default, on the default kernel (the goldens' stubs4 rounds of
upstream's test8, shell_fillet, shell_fillet2, sphere_plane,
coons_01_03_29, test5 and open_shell, which main's default built).
An edge that bounds no material is its own event: nothing to judge
(edge_bounds_material). Not the fork's, which fails there.

a_round_on_a_sheet_has_no_part_to_cut fails without it with the same
"Fillet Surface Computation Failure: blend::fillet_solver::p5".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: one half of #880's rim chamfers to a valid solid on the default kernel (#2273)

The issue's own pick, one half-ellipse rim edge chamfered by the offsets
of setbacks 1 and 2, was refused with 1001 on main's default kernel; with
the generic blend's sections and checks the default it builds a closed
solid (4 faces to 7) whose check_brep is clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* records: the trace's and the band check's fixes, and the known failing rollover case (WR-682, WR-685, #2273)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: the gn flag's uses main added since are retired too (WR-685, WR-696, #2273)

- fillet_corner::topology::join: the unequal-blend test needs only the
  round's definition.
- session::finish: the refusal of great circles over a law corner
  (#2727) ran only without the G^n section; every kernel now builds the
  round as the union of its balls there, so it is removed, and its test
  keeps the union half.
- trim_at_creases: the strip's fit is noted on every kernel.
- Tests drop the setting; fillet_variable_radius_closes_onto_vertex's
  default kernel removes the balls' union (22.594877) as every fix does.
- The WR-682 record drops the span-end fix, superseded by #2907.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: cargo fmt (#2273)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: the move to SolidCraft's kernel (#2273)

- The kernel's own later uses of fillet_gn_section_generator and
  fillet_surface_plane_curve_on_plane_equation (exact_pieces'
  certified-lift test, issue_2972's bare-net kernel) are dropped.
- The rollover case cyl_on_box1 edge 12 r 2 is known failing again in
  blend.fillet_executive_p3 (lost in the last rebase), and its golden
  line is dropped. Its issue moved to tangency-labs/solidcraft#13694.
- fillet_law_face_owned_drop_point pins what kernel builds: one face,
  not the law corner's three it built on solidcore main 99ed57c4d. Why
  the split no longer happens is still open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: the blend-curve generator is deleted with its flag (WR-685, #2273)

SmBlendCurveCrossSectionFSG was reachable only from a caller that
builds it directly: every blend-curve request is the G^n section since
fillet_gn_section_generator is retired, and nothing in solidcore, the
ops or SolidCraft builds the old generator. docs/generic-blend.md
deletes the old path in the same change, so it goes here (review of
tangency-labs/solidcore#2936, point 3):
- BlendCurveCrossSectionFsg, its CreateSurface and CreateSurfaceFromCurves,
  FilletSurfaceGenerator::BlendCurve, blend_curve, blend_curve_default and
  blend_scale.
- The corners' copies of a blend-curve generator take the G^n section
  only (fillet_corner p1 and p2); sm_SectionContactOrder refuses a
  blend-curve type, whose order gn_section_contact_order gives.
- Goldens runners: blend.fillet_standard_solver_p1's blend-curve generator
  cases are still drawn (so the cases after them stay as they were) and
  dropped; blend.fillet_solver_p1, blend.fillet_corner_p1 and
  blend.fillet_corner_p3 build the G^n section where they built a blend
  curve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the variable rounds are their definitions; stale flag mentions (#2273)

Review of tangency-labs/solidcore#2936, points 2 and 4:
- wr696_default_variable_rounds: with SolidCraft's fix set every face the
  law-cornered rounds make (6 for 3, 2, 4, 2, 3; 3 for 4, 2, 4: the
  pieces and the corner balls' zones) is a procedural blend, and
  definition_check finds none bare. The default kernel builds the same
  faces as their nets, since fillet_round_as_definition is a fix of its
  own and stays off by default.
- wr_659 and issue_2704's headers point at the WR-685 addendum for the
  retired flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: a smooth law's round is one face (#2273)

The law 4, 2, 4 of fillet_law_face_owned_drop_point is a quadratic
Bezier, smooth, with no corner: its round has no C0 knot to be split at,
so it adds one face. The rebased commit's +3 was wrong, and kernel's
expectation is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* smlib: drop the retired fillet flags from SolidCraft's preset

UpstreamFixes no longer has fillet_refuse_ball_not_fitting,
fillet_corner_face_curve_halves, fillet_blend_curve_revolved_on_arc,
fillet_surface_plane_curve_on_plane_equation or
fillet_gn_section_generator: their behaviour is the kernel's default, so
the preset stops setting them (it did not build: E0609). The comments
that named the flag as the condition now read as the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* blend: review points (comments, gen0014's test builds only its case)

- fillet_corner p1: the face-curve-halves comment says what an easing
  corner does, and no longer calls the halves opt-in.
- fillet_executive p3: why the adapter over ExecutiveAccess passes the
  ball, fit and band checks (upstream's executive, the goldens' runners).
- goldens fillet_geom_p2: gen0014's test draws that case from its own
  seed instead of generating the unit's 1100 cases.
- WR-659 addendum: "the 40 mm box".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* goldens: fillet_geom_p2's Rng is in scope (clippy unused_qualifications)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* goldens: bless the five blend units the retirement moves (#2273)

Blessed on Ubuntu 24.04 (CI's glibc) at 39548d6e49. The counts match
CI's goldens on that head exactly; every other unit passes. Only the
cases that move past the tolerance take new lines: the other cases a
bless rewrites differ by about 1e-15 (another CPU's last digits) and
keep main's lines, which CI passes.

- fillet_executive_p3, 67 past the tolerance (69 lines):
  - six law-cornered rounds and issue-880's rim, refused before, build,
    each check_brep clean: the round is the union of its balls (WR-696);
  - law rounds that built move 1e-6 to 7e-5 in volume, the same union
    of balls in place of great circles (WR-685, WR-696);
  - constant rounds move 1e-10 to 1.3e-7: their side curves come from
    the surface-plane trace on the plane's equation (WR-682) and their
    corners' face curves are those curves' halves (WR-659);
  - blend-curve requests with G1 or thumbweight 1 are invalid input,
    1007 (WR-685 addendum): attributed-box-edge0-blend-g2, the eight
    issue919 rim cases, law-cube20-e0-deg2-smooth-xsect-blend-curve;
  - two rounds that built bodies check_brep rejects (bad loop
    orientation) are refused with a fit size: sweep4-brepForFillet2-
    all-r0.1 (CutsThePart) and stubs4-degenerate-caps_coons-e5-r0.5
    (BallDoesNotFit);
  - refused on both sides, the body left after the refusal or the
    retry step moves (corner3x2, stubs3, nurb_cone, sphere_diff).
- fillet_executive_p2, 1: set_params_single_fsg_3's blend curve is
  invalid input (WR-685 addendum).
- fillet_geom_p2, 12: the surface-plane trace runs on every kernel
  (WR-682); gen0014 refuses its station off the surface.
- fillet_solver_p1, 3: the runner's blend-curve kind builds the G^n
  section, which takes its thumbweight, not the old generator's scale.
- fillet_standard_solver_p1, 220 removed: the retired blend-curve
  generator's cases (their seeds are still drawn).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 06:42:48 +00:00
0rdnungsamt e6789d881d Tests: the Insert Design menu test waits for its first file read (#14390) (#14444)
* claim: flaky Insert Design menu test (#14390)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tests: the Insert Design menu test waits for its first file read (#14390)

The first step picks a file that is read in the background since #13445, but only waited three frames. On a busy runner the read was still going when the test ran a command, and run() refuses while a file loads. Settle as the later steps do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 06:27:06 +00:00
0rdnungsamt 6e63b38ba6 Agents and scripts: Coil describes its exact parameters and reply (part of #2829) (#13390)
* claim: solid.coil schemas (part of #2829)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agents and scripts: Coil describes its exact parameters and reply (part of #2829)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 06:27:03 +00:00
Bryan 857b592b5d Sketch: point docs say text naming no parameter leaves the point free (#14290) (#14291)
* Sketch: point docs say text naming no parameter leaves the point free

A coordinate typed as text that names no parameter ("5") is just that
number and the point stays free; only text naming a parameter fixes it.
sketch.line, sketch.point, the rectangle and circle commands and the
point and corner schemas now say so. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch: point docs say text naming no parameter is in the design unit and does not itself fix the point (#14290)

Review follow-up: text such as "5" is read in the design's length unit
(a JSON number is mm), and a point can still be fixed by other means
(e.g. a default connection to the origin), so the docs now say such text
does not itself fix the point rather than that the point is free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 06:08:27 +00:00
Brett Lawson ad85824c38 solidcore: retire the primitive flags that change no result (A11) (#14448)
The code each flag turned on is now the only path; the fields, preset lines, fixes.json entries and the tests' flag-off halves are gone.

Part of #14246


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 06:08:25 +00:00
Brett Lawson 339cadb815 solidcore: the rest of nurbs and every nl_ser collapse carry a typed reason (part of #13790) (#14331)
* solidcore: claim the rest of nurbs and every nl_ser collapse (part of #13790)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: B-spline method failures carry a typed reason and NLib collapses keep their cause (part of #13790)

The B-spline curve and surface methods (b_spline_curve_n_lib, b_spline_surface_n_lib,
b_spline_surface, b_spline_curve/p2, nurbs_crv's guard) return a BSplineFailure under
Reason::Nurbs instead of a bare Upstream status, with the same operation path and the
same comparison status (SM_ERR, SM_ERR_NULL_POINTER for a missing input,
SM_ERR_INVALID_INPUT for a zero axis), so kind() and sm_status() are unchanged.

nl_ser's replacement, SolidError::nurbs_routine, keeps the NLib error as the cause of a
BSplineFailure::RoutineFailed and passes Unported, Cancelled and PreparationRequired
through unwrapped, as nl_ser does. Every nl_ser site but the seven in
b_spline_curve/p1.rs (held for #13946) uses it; nl_ser goes with that file.

The callers that branch on an Upstream status read upstream_status() instead, which
answers alike for an Upstream and a BSplineFailure, so control flow is unchanged. The
InvalidInput reasons that cited NL_MAXDEG/NL_DEGREE describe the limit in solidcore's
terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* goldens, smlib: read a B-spline failure's status as the Upstream it replaced (part of #13790)

The goldens runners that recorded an Upstream code read
solidcore::error::nurbs::upstream_status, so every row stays as it was. SolidCraft's
blend status() passes a BSplineFailure on as its status, as it did the Upstream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a B-spline failure that keeps a port failure as its cause is one too, as #13988's cause chain reads it (part of #13790)

nurbs_routine passes Unported, Cancelled and PreparationRequired through unwrapped; any
other error becomes the cause of a RoutineFailed, so a PortInvariant there makes the
method's failure a port failure and no status answer, which the test now pins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore tests: the intersection-curve and four-arc sphere refusals are read as B-spline failures (part of #13790)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 06:08:22 +00:00
Rafacerete 6d80b31ee3 Let agents compare parameter variants without editing the design (#14317)
* Compare parameter variants without editing the design

* Point the kernel rig to the parameter variant tests
2026-11-10 05:56:08 +00:00
Daniel Amesberger deb9b3ed14 solidcore: notify record resets visit only what changed; large flat extrusions about 10× faster (#13844) (#14065)
* bench: linear_sweep_scaling, one extrusion timed against its side-face count (#13844)

A regular N-gon of line segments, or a square with N/4 square holes, swept by
create_linear_sweep; prints the face count and the time. On main (78d1eb12d), release:
125 faces 63 ms, 250 0.24 s, 500 1.1 s, 1,000 5.4 s, 2,000 24 s, about N^2.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf: a reset of the notify records visits only what changed since the last (#13844)

Every edit of a stitch (kernel_named_edit's reseed per vertex glue, the arena store per
edge glue) reset the record store to a default record for every live object of the
B-rep. tangency-labs/solidcore#2324 reset only the records handed out since, but still built the key list of
every object and matched it against every key of the map, and forgot the seeding
progress, so each edit was O(B-rep) and a stitch of N faces O(N^2).

The store now remembers the B-rep value and key scheme it was last made exact for. A
B-rep value logs the objects it removes (arenas only append and empty slots for good;
a copy, a snapshot or a value put back starts a log of its own). So another reset for
the same value and scheme drops the records of the objects removed since, adds those of
the slots added since, and settles each record handed out since, live or not. Every
other record is still the default record of a live object, so the store ends exactly as
before. Any other case (another scheme or value, a record removed by hand, a bulk
access) walks everything, as before. The reset keeps which keys passed the reserved-key
check, which does not depend on the records, and marks the B-rep's objects as seeded.
seed_records_once adds its records through the store's own methods, so a reset sees
them.

linear_sweep_scaling, release: 1,000 faces 5.4 s to 1.7 s, 2,000 24 s to 7.2 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf: a sweep's faces reuse the kernel's arena record store (#13844)

with_face_env made a fresh ArenaNotifyState for each face of a sweep: a record for
every object of the B-rep, then a key check and seeding of all of them. It now takes
the kernel's arena store, as the stitch's glues do, reset to the same records
(ArenaNotifyState::reusing), so each face costs what it adds.

linear_sweep_scaling, release: 1,000 faces 1.7 s to 0.73 s, 2,000 7.2 s to 2.9 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf: rebinding geometry owners after an edit reads first and writes what it rebinds (#13844)

rebind_geometry_owners ran at the end of every kernel-named edit (each vertex glue of a
stitch) and took each edge, edge use and face mutably to compare its owner, so every
glue copied the id lists and went through the copy-on-write arenas' make_mut for the
whole B-rep. It now finds the geometry still owned under the session identity by
reading, and takes only that mutably, asking again there. The result is the same: the
arenas a query snapshot shares are never edited.

linear_sweep_scaling, release: 1,000 faces 0.73 s to 0.43 s, 2,000 2.9 s to 1.8 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:39:22 +00:00
Bryan c191ae9b70 sketch: editing a mid-timeline sketch rebuilds later features once, at Finish (#14010) (#14151)
sketch.edit without restore_marker left the timeline in place, so every
sketch command rebuilt every later feature (their fingerprints chain
through the sketch). Now it rolls to the sketch and restores the current
position on Finish/Cancel, as the app's Edit Sketch does; restore_marker:
false keeps the old in-place edit. A save during the edit writes the
restored position, and the rename an unnamed design gets on save no
longer breaks Finish folding the edit or Cancel.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:38:15 +00:00
lewis2e 966b98d4dc Face edits: a pick on a face two bodies share warns instead of silently changing the first body, and Replace Face works on a flush face (#12385) (#12387)
* Face edits: change the body the user picked where two bodies touch flush

Offset Face, Delete Faces, Replace Face, Shell, Draft and Split Face take
picked points. When two bodies touch flush, a point on the shared face lies
on a face of each body. Without `body`, evaluation took the body whose
bounding box was nearest the point, so the first body in the list won the
tie and the other body was never changed.

The engine now works out the body when one of these commands runs without
`body` and the model has more than one body. If every pick lies on faces of
one body, the feature saves that body. If a pick lies on coincident faces of
several bodies, the command is refused and the error names them, so MCP
and script callers no longer change the wrong body without notice.

The dialogs already send the body of the picked faces since #7174. A new
dialog test checks that Offset Face from the dialog changes the body that
was clicked where two bodies touch flush.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Replace Face: check the picked body's face is planar, not any face there

Replace Face checked that each picked face is planar before it worked out
the body. On a face flush with another body's, that lookup tied between the
two bodies and failed, so Replace Face reported "replacing a face that is
not planar" for a flat face, even with `body` given.

Replace Face now works out the body first and checks that body's face.
Without `body`, a pick on the shared face is refused with the error that
names both bodies, as the other face edits do.

Found by CodeRabbit's review of #12387.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Face edits: warn about a pick on a face shared by two bodies instead of refusing it

A face edit given no `body` refused a pick on coincident faces of two
bodies. That turned a call that worked, though on the first body, into an
error, which changes what the command does when used the usual way.

The command now runs as before: evaluation chooses the body, as it always
has. Its result carries a warning that names the bodies, the body that
changed, and asks for `body`. A pick on one body's face still saves that
body in the feature. Replace Face looks up the planar face of a shared pick
on one of its bodies, so it no longer reports a flat face as not planar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Face edits: decide which bodies a pick lies on from exact geometry (#12385)

The body lookup compared display-mesh distances. Two discs touching
along a line (gears in mesh) both hold a point on that line, but one
tessellation came out nearer, so the edit saved that disc as `body` and
changed it, where main changes the first. Caught by
an_editor_accepts_the_ratios_rolling_the_marker_works_out.

faces_body now asks each body's exact boundary (classify_point ==
OnBoundary), culled by its exact box. Both discs hold the point, so the
pick is shared: evaluation decides as before and the result warns.

Test: a_face_pick_where_two_round_walls_touch_on_a_line_is_on_both
fails without this change (changes the second disc, no warning).

cargo xtask ci: 21 failed, all in tests/KNOWN_FAILING_TESTS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Face edits tests: doc comments on the new tests and helper (#12385)

cargo xtask ci: 21 failed, all in tests/KNOWN_FAILING_TESTS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:38:13 +00:00
ancient_astronaut_theorist 1a4f3e4a6f Planar faces: a flat B-spline face (a loft side through three or more sections) is planar (#14346) (#14394)
* claim: #14346

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* Planar faces: a flat B-spline face (a loft side through three or more sections) is planar (#14346)

#13546 builds a two-section loft of lines with flat sides from plane
faces, which fixed the design in #14346. A loft through three or more
sections still skins every side as a B-spline patch, flat or not, and
Body::planar_face took only plane surfaces, so an extrude from such a
side or a sketch on it failed: "the selected planar face no longer
exists".

planar_face also takes a Spline or RationalSpline face whose control
points all lie on one plane within the body's tolerance: a B-spline
surface with positive weights lies in its control points' convex hull,
so the whole face is on that plane. The normal comes from the net's
oriented area (along dS/du x dS/dv), turned with the face as a plane
surface's is. The work is one pass over at most 65,536 points.

Fixes #14346

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:37:59 +00:00
Brett Lawson 77e9cae1d0 solidcore: check_brep's per-face cache and loop-intersection test stop growing with the whole body (fixes #14381) (#14408)
* solidcore: claim #14381 (check_brep per-face cache map and test 38 all-pairs)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: check_brep's face cache and loop test stop scaling with the body

face_surface_cache built a map of every loop, edge and vertex use of the
whole B-rep for each face it cached, only to turn a use handle back into
its entity; check_brep caches every face, so the check was O(faces x uses).
The handle is the use's notification reference, so it is now decoded
directly (cache_entity), with the same rejection of anything that is not
a use reference.

Face test 38 intersected every UV trim of each loop with every trim of
each other loop. It now skips loop pairs whose boxes are apart and only
intersects trim pairs a box index finds within twice the UV tolerance;
GlobalCurveIntersect has no solution for the pairs skipped. Trims are
still read in SMLib's order, so the trims built, the verdict and the
first error are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: test 38 borrows its loop pair without split_at_mut

Take the second loop's trims out of their slot while the first is
borrowed and put them back afterwards, so the loop-pair walk has no
panic-prone slice split; read the cached UV tolerance with if-let.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:37:32 +00:00
MarkusRe 8adda81024 Tests: the Offset Plane edit test no longer fails at random on a busy machine (#13448) (#14258)
* claim: #13448

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Tests: hold the Offset Plane edit's result until OK is seen computing (#13448)

`an_edit_without_a_preview_adopts_its_suffix_without_window_evaluation`
clicked OK and then asserted that the dialog was still computing. The
click settles over at least four frames, and from the second one OK
applies a result that is already in, so a worker finishing within those
frames left `applying` empty at the assertion. The test now holds the
real worker's result with `apply_gate` until the assertion has run, as
`ok_computing_off_the_window_thread_does_not_apply_in_the_frame_it_starts`
does.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-11-10 05:16:03 +00:00
lychee888 13f2c2aa71 Sheet metal: one edge flange splits into stretches that each bend to their own angle and height, and the solidcraft-sheetmetal agent skill (#5927, #5928, #5946, #5947, #5948, #5949) (#12804)
* claim: #5927 #5928

* Sheet metal: flange segments on one edge (#5927, #5928)

An edge flange's `edges` entry can now split the picked edge into segments:
`{point, segments: [{from, to, angle?, height?, flip?}]}`. `from` and `to` are
fractions of the stretch the sheet still has after cut-outs; they must tile it
(first from 0, last to 1, each to the next from), so a segment never leaves
material out. Each segment takes its own angle, height and side; consecutive
segments that match make one bend. The base is trimmed back by each segment's
own setback over that stretch only, so its outline steps where the angles
differ, and the flange's far edge steps where the heights differ. Two bends of
one split edge butt at the junction: no corner is closed there and no bend
relief is cut.

Refused, with a message naming the segment: a partition that does not tile the
edge, more than 64 segments, an unknown key, a non-bool flip, an angle outside
0..180 degrees, a height below the bend's minimum, `segments` on a base or
contour flange, `then` with `segments`, and a split curved edge.

The Flange dialog has Segments rows (equal stretches with their own angle,
height and turns) and restores them when the feature is edited. The design
format gains one pending change, sheet-flange-segments, with no step: an older
design reads an edge as having no segments.

Tests (crates/engine/src/cmd/sheet_tests.rs) derive the numbers by hand for the
AL2 rule (2 mm, K 0.5, R 1 mm): a partition that matches builds the same part
as a whole-edge flange (46856.637 mm3, flat Y 117.1415926535898), per-segment
heights 45856.637, per-segment angles 46245.422 with the base stepping by
1.7573593128807148, per-segment sides 46856.637 in one closed body, expressions
following parameters through save/reopen and undo, and every refusal.

* Sheet metal: the solidcraft-sheetmetal agent skill (#5946, #5947, #5948, #5949)

Adds skills/solidcraft-sheetmetal with SKILL.md and reference.md: when to load it, the rule-first
workflow, every sheet metal command shape on this branch including `segments`, the segment contract
with its refusals verbatim, the bend arithmetic the engine uses, the FR-17 to FR-20 inference
recipes (segment inference, angles with confidence and an alternative, natural language to
parameters, a bounded optimisation loop) and a worked example that turns a request in words into
one `sheet.flange` call.

The MCP server embeds the skill in `skill_reference`; AGENTS.md routes to it and skills/README.md
lists it. The worked example runs in CI (`sheetmetal_skill_example_builds_as_documented`), which
builds the segmented flange, checks the volume against the hand formula and checks the bend
table's lengths, angles and flat centre lines.

AGENTS.md is trimmed to stay under the 5000-character router cap.

Verified: cargo xtask skills ("AGENTS.md and 18 skills are consistent"), cargo check
-p solidcraft-mcp, the skill tests (8 passed, including the worked example), the full mcp lib
suite (109 passed), clippy on solidcraft-mcp with -D warnings, cargo fmt --check, cargo xtask
lists.

* Sheet metal: the segment strings in the five complete catalogs (#5927, #5928)

* Sheet metal: the dialog keeps each edge's segments, a shortened straight edge splits, one junction tolerance (#5927, #5928)

The Flange dialog rebuilt a segmented edge from the first edge's list alone and gave every
picked edge equal stretches, so opening a feature and pressing OK silently repartitioned it.
The dialog now holds the segments of each picked edge (fractions, angle, height and the side
as an explicit or inherited value) and emits them per edge, so an edit sends the design back
as it was.

A straight edge that a cut-out has taken part of was refused as curved, because the split
check only matched exact outline endpoints. A stretch that lies inside one straight line of
the outline is now accepted; a chord of an arc is still refused.

`sheet.flange` accepted segment junctions within 1e-9 while the build merged matching
stretches only within 1e-12, so a list the command accepted could stay split. Both sides
read SEGMENT_JOIN_EPS = 1e-9, and a first `from` or last `to` inside it snaps to the edge's
end, so every accepted list builds exactly what the whole-edge flange builds.

* Sheet metal: a deleted stretch keeps the partition, `then` is refused, a split edge covers one line, a junction snaps (#5927, #5928)

Deleting a stretch in the Flange dialog now leaves a partition the command accepts: the stretch
before the one removed reaches to its end (the one after it, when the removed one was first,
starts at its start), the ends stay at 0 and 1, and removing the only stretch left makes the
whole edge.

`then` with a split edge is refused, naming the edge, instead of being dropped without a word;
the guide carries the same message.

A split edge's stretch must lie on one straight line of the outline: the outline's lines on that
line must cover it with no gap, so a notch or a step across it is refused while a cut-out that
shortened the line still splits.

A junction within SEGMENT_JOIN_EPS is snapped to the previous run whatever the two stretches'
angle, height or side are; only the 1e-12 comparison decides whether they are one bend, so no
interior gap reaches trimming or the flat pattern.

* Sheet metal: deleting a stretch keeps the other boundaries, and a junction never builds a reversed stretch (#5927, #5928)

The Flange dialog's delete bridged the gap from the removed row's own `to`/`from`. A
row mid-edit has those fields empty, so the repair fell back to equal stretches and moved
boundaries the user never touched (the untouched 0.8 became 2/3). The gap is now bridged
from the neighbours: the stretch before the removed one reaches to where the stretch after
it starts, or to the edge's end when none follows. Equal stretches remain the fallback only
when a neighbour's value does not parse.

The build snapped every accepted junction to the previous run's end without checking the
stretch's own end. A stretch narrower than `SEGMENT_JOIN_EPS` then came out reversed (for
the spans 0…0.5, 0.4999999995…0.4999999997, 0.4999999997…1 the second run was
(0.5, 0.4999999997)), and `trim_stretch` measured it as a positive length. Such a stretch
is no bend of its own now: the junction closes over it, so the build tiles the edge. The
span handling also refuses a reversed span instead of measuring it as a distance, at the
edge check and in the stretch loop.

Tests: a rendered-dialog delete with the removed row's `to` cleared and with its values
intact, and the three-span case at `position` inside and outside compared with the exact
partition. The skill reference and the sheet metal guide say what a delete and a junction
do, and the reference states that the volume check assumes K 0.5.

* Sheet metal: a collapsed stretch is absorbed so the edge stays covered (#5927, #5928)

A stretch narrower than `SEGMENT_JOIN_EPS` that carries another bend's attributes was
dropped without moving the run before it, so the next stretch no longer joined that run
and started one of its own: the interval between them was left out of trimming and the
flat pattern. For the spans 0…0.5, 0.5…0.50000000075, 0.50000000075…0.5000000015 and
0.5000000015…1 with heights 20/15/10/20 mm the flat pattern had one bend ending at 100
and the next starting at 100.0000003, and three slivers in a row did not build at all
(`SMLib failed (status 1001)`).

The run before a sliver now absorbs it: its end moves on to the sliver's end, never back
before it, so the bends tile the edge and the final end still snaps to 1 within the
tolerance. A sliver with no run before it still starts its own run, the sliver's angle and
height still do not build, and a stretch wide enough to be its own bend still starts where
the run before it ends.

Tests: the four-span list at `position` inside and outside, asserting the bends cover the
200 mm edge with no gap or overlap and build the merged partition's part (same volume, same
bends), and three consecutive slivers doing the same. The skill reference and the sheet
metal guide say the run before a sliver takes its stretch.

* Sheet metal: runs start where the edge is covered, so a backward junction never overlaps (#5927, #5928)

* Sheet metal: a segmented flange's first bend starts at the edge's start (#5927, #5928)

Leading stretches that cannot be a bend of their own are still absorbed, but
the run that covers them now begins at the edge's start (0) however far the
absorbed prefix advanced `covered`. Before, the run began at the absorbed
prefix's end: for [0, 7.5e-10], [7.5e-10, 1.5e-9], [1.5e-9, 1] it began at
1.5e-9, past the first-end snap's tolerance, so the edge's first 1.5e-9 never
built and the flange was shortened. A list every stretch of which is absorbed
(a stored design; `sheet.flange` cannot write one) now builds the whole edge
instead of losing the flange.

The invariant is stated in the code: the first run starts at 0, the last ends
at 1, runs are sorted, contiguous and non-overlapping, and every accepted
stretch is covered. reference.md and the sheet-metal guide say the same.

Tests: the exact three-stretch prefix (inside and outside) must build the
whole-edge flange (volume, flat size, bend table); the same prefix before a
differing-height stretch must not shift the junction; a single leading sliver
must build the whole edge; a sliver-only stored list must build the whole
edge. Three of the four fail on d6b69517 (bend starts at 3e-7 mm; the
sliver-only list builds no flange); the single-sliver one already passed there
because the old first-end snap fired within 1e-9, and it guards that path now.

* Sheet metal: each picked edge's stretches keep their own row ids (#5927, #5928)

The Flange dialog drew every picked edge's `Segment N from/to/angle/height` rows in one scope,
and a row's scrub (label drag) id is that scope id plus the label. Two edges with the same number
of stretches therefore shared ids: dragging one edge's `Segment 1 height` moved every edge's
value, and OK persisted geometry the user did not ask for.

Each edge's block is its own scope now, so the id carries the edge; the labels the user sees are
unchanged.

Tests: two edges and three edges with matching stretch counts, dragging one field per edge and
asserting every other edge does not move, and a byte-identical round trip after a drag (the other
edge comes back exactly as the design wrote it).

* Sheet metal: the reference states what the junction tolerance guarantees (#5927, #5928)

Judge round 8 flagged that "every list the command accepts builds exactly what the whole-edge
flange builds" overstates it: matching stretches build the whole-edge bend, differing stretches
build one bend each. The sentence now says what the shared tolerance actually guarantees: the
accepted list tiles the edge with no gap and no overlap.

* Sheet metal: opposite-side junctions are cut in the blank, segment names stay on their stretch, stored bounds are refused (#5927, #5928)

CodeRabbit's review of #12804, the real items (local only, not pushed):

- A junction between stretches that bend to opposite sides has no slit in the flat
  pattern or its DXF: the blank stays joined where the folded part separates, so a
  blank cut from that DXF cannot fold. `flat_regions` now carries a zero-width cut
  along each such junction, from the bend line out to the shorter stretch's far edge
  (`SheetBody::junction_slits`, `slit_loop`, `split_loop_at`). Same-side junctions and
  a whole-edge flange stay joined. `flat_material` keeps the material's loops (no
  self-touching loop), so the kernel booleans, the flat solid, machining and the flat
  prism cuts are unchanged.
- Every segment's angle and height shared one label ("Segment angle"/"Segment height"),
  so `timeline.redefine` matched a stored name by position and inserting or reordering
  stretches moved a named height onto another stretch. `input_keys` now keys a segment
  input by its edge and by the stretch it describes (`from`/`to`), matched before the
  label-and-kind pass, so a name stays on its own stretch.
- A stored segment list bypassed the command's partition checks: `eval_more` builds the
  runs from `from`/`to` as stored. A `from` or `to` outside 0…1, a gap wider than
  `SEGMENT_JOIN_EPS` or a last `to` short of the edge's end is now refused with the edge
  and segment named; the all-sliver fallback still builds the edge whole.
- The `flip` doc comments on `FlangeSegment` and `EdgeFlange` said a segment's flip turns
  the bend the other way than the feature's: it is an absolute override
  (`seg.flip.unwrap_or(*flip)`). Both comments, the guide and the skill reference now
  say so.
- A top-level `segments` beside `edges` was silently ignored with parameter checking off.
  `FlangeParams` takes it as a typed field and `sheet.flange` refuses it, naming where it
  belongs; the schema still lists the key.
- The skill reference said any numeric field can be an expression: `segments[].from` and
  `segments[].to` are plain numbers. Fixed, with the junction cut and the per-segment flip
  documented there and in the guide; SKILL.md stays under 3600 characters (3565).
- The CI skill-example test now checks the volumes the reference publishes against the
  arithmetic it already computes, so a wrong reference fails it.

Tests: engine sheet suite 260 passed; doc sheet tests 107; schema sheet tests 10; edit
tests 55 passed (the one known-failing variable-fillet test is in KNOWN_FAILING_TESTS);
ui-egui sheet dialog tests 43; the mcp skill-example test passes. The five new or extended
tests fail on the base commit and pass here. The documented example still builds at
46051.02928860431 mm3 (three stretches) and 46551.02929133257 mm3 (two).

* Sheet metal: a junction cut ends on a stepped far edge's corner (#5927, #5928)

split_loop_at refused a point that already sat at a segment's end, so slit_loop read the
far edge's step corner as off the loop and a blank whose opposite-side stretches differ in
height stayed joined. A point at a segment's end is a vertex of the loop as it stands; both
the line and the arc arm now say so, and the differing-height junction carries its cut.

* Sheet metal: the junction test asserts the differing-height cut and its DXF lines (#5927, #5928)

The far edge steps at that junction, so the cut ends on the step's corner. The test now
checks the flat loop and both DXF lines for 20/20, for 20/15, and that a same-side junction
stays joined.

* Sheet metal: a junction cut trimmed away is clipped to the material left, a name follows the surviving stretch (#5927, #5928)

* Sheet metal: a junction cut ends on a circular notch's arc exactly, a split edge's names stay on the edge's pick point (#5927, #5928)

clip_junction_slit walked the 0.001 mm chords of every boundary segment, so where a round
cut-out took an opposite-side junction's far edge away the crossing it returned sat up to
0.001 mm inside the arc. slit_loop's 1e-6 anchor tolerance then missed it and the required
junction cut was dropped again, leaving the blank joined where the folded part separates. A
line and a circular arc are now intersected exactly (the circle-line roots within the arc's
sweep, the crossing computed on the arc at the angle split_loop_at splits it at), so the cut
ends on the curve itself; only free-form segments still follow chords.

input_keys keyed a segment input by the edge's index in the edges list, so swapping two edges
with identical spans and different heights moved their names between the physical edges: an
expression reading the 20 mm edge read 15 mm. A segment input is now keyed by the edge's pick
point (the point the flange was made on), and an edge's own angle and height the same way, so
reordering the list keeps every name on its own edge.

Tests: a round notch over a junction asserts the flat loop and the DXF carry the cut to the
arc (the cut ends on the circle to 1e-9 and the DXF splits the arc there), and swapping two
edges asserts each named height still reads its own point's height; both failed before the fix.

* Sheet metal: the hole dialog's preview test handles egui's texture delta

epaint 0.37 panics when a TexturesDelta with unapplied deltas is dropped, and this test dropped
the FullOutput run_ui handed back. The crate's other tests clear the delta before dropping it;
this one now does the same, so `cargo test -p solidcraft-ui-egui --lib dialog` is green.

The panic was there before the junction-cut and edge-key fixes (it fails on 04f31e0a too); the
test-only change touches no product code.

* Sheet metal: an arc's ends are judged by distance, every junction bridge is cut, an edge's names follow its pick point (#5927, #5928)

- `split_loop_at` decides "already a vertex" by the distance `slit_loop` anchors with (1e-6 mm), for
  a line's and an arc's ends alike, instead of a share of the arc's sweep: on an R = 100 mm arc with
  a 1 rad sweep, share 5e-7 is 5e-5 mm from the start, so the split said "vertex" and the caller's
  distance test then dropped the cut. Regression: the slit at exactly that anchor is inserted.
- `clip_junction_slits` returns every stretch of the junction line that still runs through material,
  ordered outward from the bend line, and `flat_regions` slits each one. A re-entrant notch pattern
  leaves the line crossing material more than once, and only the first bridge was parted before.
  Regression: two notches leave two bridges at x = 50 and both are cut in the flat loop and the DXF.
- `redefine` matches a `sheet.flange` edge's own `angle`/`height` by the pick point its key carries
  and by nothing else, so a deleted edge's name cannot land on an edge inserted in the same redefine.
  Regression: two named edges, one deleted and another inserted; the survivor keeps its own name and
  the deleted name is gone.
- The `flip` docs (guide, skill reference, both doc comments) state that a segment's `flip` replaces
  the feature's outright: `false` is the default direction and `true` the other one, independent of
  the feature's `flip`.

* Sheet metal: a moved pick keeps its edge's names, a junction cut stays in its own segment (#5927, #5928)

- `redefine` matches a `sheet.flange` edge's and its stretches' pick points by the document's own
  pick tolerance (`appearance::pick_tolerance` of the sheet body the feature is on, 1e-6 mm where the
  model does not hold it yet) instead of by the f64 bits their keys carry, so redefining the same
  physical edge with a pick that moved by a hair keeps every name on it and the expressions that read
  them. The nearest old point wins among the candidates; when none is within the tolerance, the
  nearest edge point on the same feature is taken within a sane bound, so a pick that moved further
  along the same edge still keeps its names, while a genuinely different edge keeps the
  constructor's name. Regression: a named edge height and a named stretch survive a 0.001 mm move,
  a downstream expression still reads 20/15 mm, and an inserted edge gets its own name.
- `clip_junction_slits` accepts a crossing only within the junction's own length from the bend line
  (plus 1e-6 mm), in the line, arc and free-form arms alike. The junction line is a ray: past its tip
  it runs on through material the bend never reaches (a C-shaped blank's foot, for one), and slitting
  there cut unrelated material. Regression: the C-shaped blank keeps its foot whole in the flat loop
  and the DXF while the junction's own bridge is cut; the notched, two-notch, round-notch and
  20/20, 20/15, same-side cases still pass.
- The documented examples are a test now (`crates/engine/tests/sheetmetal_documented_examples.rs`):
  46551.02929133257 mm³ for the two-stretch part and 46051.02928860431 mm³ for the three-stretch one.
- The guide and the skill reference say both: a moved pick is the same edge within the document's
  pick tolerance, and a junction cut stops at the junction's own tip.

* Sheet metal: an opposite-side junction cut starts at the inner bend line (#5927, #5928)

Stretches that bend to opposite sides at different angles have different setbacks, so their
bend lines are offset. The folded part separates over the strip between those lines, and the
flat pattern and its DXF have to cut it too: the cut starts at the inner bend line, not the
farther-out one, which left the strip joined. The outer end stays at the nearer far edge.

* Sheet metal dialog: follow upstream's label and layout paths after the merge

i18n::tr_label moved to solidcraft_ui_text::tr_label, and shell's LayoutMode import became
private: the dialog uses the public ui_kit re-export. Both were merge drift, not behavior.

* AGENTS.md: route the sheet-metal skill from the router, inside the size cap

Upstream's router rewrite dropped the row the branch had added, and adding it back alone
pushed the file past 5000 characters: the row is one word and the optional plan/ note goes.

* Sheet metal: a stored overlap is refused, and the docs say what a zero-width slit and a moved setback mean (#5927, #5928)

A stored segment list that overlaps by more than the junction tolerance is now refused at the
build, like a gap: the second bend used to start at the first's end and silently drop the
overlap. The skill says an unchanged volume does not prove a bend failed when it only turns the
other way; the reference says different angles can offset the bend lines depending on the rule's
position, and that the slit is zero-width, so the DXF carries two coincident outline lines.

* Extrude convention test: the release format is below this build's pending changes (#5927, #5928)

The branch adds a pending design-format change (edge flange segments), so the build writes one
number past the last folded format. The older-extrude test simulated the last release by
subtracting one from that number, which then pointed at the pending format instead of the
release, so the extrude skipped its migration and built with the wrong sides. Subtract the
pending changes too.

* AGENTS.md: keep the router inside its 5000-character cap after the merge

Upstream's merge added a line and left the router at 5001: one redundant word goes.
2026-11-10 05:05:30 +00:00
Brett Lawson 10d0e43c6b solidcore: retire five flags that no code reads (fillet corner/domain statuses, offset gap, ssi alignment, closed-curve trim arc) (#14091)
* solidcore: retire five flags that no code reads

fillet_corner_statuses, fillet_edge_curve_domain_statuses, offset_tangent_gap_statuses, ssi_alignment_statuses and offset_closed_curve_trim_arc were already unread; remove the fields, every setting of them, and record where each fix lives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: drop the kernel helpers' now-empty let bindings in two thicken tests

Removing the unread offset_closed_curve_trim_arc setting left a mutable binding returned at once, which clippy denies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:04:33 +00:00
bherbruck 1e01efa18c ci: the merge-of-main self-test turns off background git gc in its throwaway repos (#14438)
A detached `git gc --auto` could still be writing into .git while tearDown removed the
directory, failing the test with "Directory not empty: '.git'" (merge-queue run 42751096942).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 05:04:13 +00:00
Bryan c7768a592d inspect.measure: a face or edge by index needs no point (#14011) (#14031)
* inspect.measure: a face or edge by index needs no point (#14011)

A face or edge item in inspect.measure can now be named by body and
index alone, as model.faces and model.edges list it. Before, `point`
was required even with `index`, so a caller whose body a joint had
moved had to compute a point on the face just to name it. A point
still works as before; an unknown index or neither field is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* inspect.measure: params text says a face or edge takes a point, an index or both (#14011)

The merge of main kept main's wording here, which still said `point` was
required; this puts back the text the rest of the PR documents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:46:33 +00:00
0rdnungsamt 24de8ea67f Agents and scripts: Circular Pattern describes its exact parameters and reply (part of #2829) (#14312)
* claim: solid.pattern.circular schemas (part of #2829)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give solid.pattern.circular input and output schemas (part of #2829)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close the circular pattern reply test the merge left open

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:45:09 +00:00
Brett Lawson ea23289d67 solidcore: a twisted side round is its walls' round between sections, not only at them (#13774) (#13984)
* solidcore: claim sc#13774 (twisted-disc side rounds as their definition)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a round by its definition rolls on the extension its trace rolled on

A round whose rails run on past its faces (the twisted disc's side rounds,
past the side edges' ends) rolled its definition's ball on the walls' plain
B-spline bases, whose end spans continue otherwise than the extension the
trace's offsets evaluated there. The first station's feet missed the rails
(by 0.89 and 1.15), so the round fell back to the trace's skinned B-spline,
which missed the 2 mm round of its walls by up to 1.9e-3 between sections.

Where the offset of a B-spline, surface of revolution or surface of
extrusion holds an extension (pending or stored), the definition now rolls
on it. Stations given on a knot line of a face (the extension's old boundary,
only G1) take their sides as a crossing station does; without them the net
left the definition by 2e-3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a blend by its definition takes the side inside its domain at its ends

At an end of its domain a definition took its partials on the side its
caller's flags named. Where the end station's foot is on a support's knot
line (the old boundary of the support's extension, only G1 there) that is
the side past it: the round's tangent along s at its end was the
extension's. The B-spline block's R1.5 round over its arc, whose ends are
there now that the definition rolls on the extension, integrated with a
1001 at 1e-11 and moved its volume 4.5e-10 further from the closed form.
At its ends the side is now the one inside the domain, as a B-spline's end
is its end span's.

The twisted disc's top round R2 is now the ball's piece over each of its 4
side rounds (it was refused naming 1.9586): its test is retitled and
asserts that, with check_brep Level2 clean and a manifold solid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a sided station solve keeps the pieces its seed's sides name

A station whose foot is within the margin of a face's knot line is solved
again on the piece its side names. That piece was chosen at each Newton
iterate, so an iterate just past the margin's edge took the other piece and
Newton cycled between the two. On the teardrop boss's foot round, now its
definition on the wall's extension, feet 2e-9 off the extension's old
boundary (only G1) failed in a band of 2e-9 in s, and the body could not be
integrated at 1e-11. The solve now keeps, for every iterate, the pieces the
sides name at its seed, which every caller places on the knot line.

Two pinned results move because their rounds are now definitions (#13774):
the teardrop boss's foot volumes (+3.1e-4, +4.9e-4; the rounds are now the
wall's and plate's rounds to rounding, where the skinned B-splines were
off by 1.5e-4 at their 90th percentile) and the hull rim chamfer's gain at
its 1e-6 pin (80.0551 to 80.0532; integrated to 1e-10, 80.11434 to
80.11271).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a G^n certificate computes only the coefficients it solves, without subnormal assists

The definition path's time is the G^n certificate's enclosures (gn_enclose_over):
6925 of them for the r0.5 round after the boss foot, 2 to 9 ms each. Three changes
make each one cheaper and leave every value bit for bit the same:

- The feet's Taylor coefficients are solved order by order, and each order evaluated
  G at the whole truncation. A coefficient of a sum or product depends only on the
  operands' coefficients at lower or equal powers of sigma and eps, so G is now
  evaluated only up to that order's total degree and the powers of eps it solves
  (`Trunc::computing`), and not at all at an order with none to solve. Same in the
  station solve's jets.
- An interval end about 1e-300 from 0 (every interval that holds 0) made the
  widening's product `|x| 2.3e-16` subnormal, and a product of such an end with a
  number under about 2e-8 subnormal too. An Intel core takes a microcode assist for
  each subnormal result (28 ns and 41 ns against about 2 ns). Both are now computed in
  integers there, rounded as the floating operations round them; unit tests check
  them bit for bit against the floating operations over every binade concerned.
- The composition's 324 scale-and-add steps per face are one in-place step each.

The certificate's output (every span's bound, in the same order) is identical before
and after on the boss-on-bar case. Its round takes 5.7 s instead of 11.5 s (goldens
profile, back to back on one core).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: test the twisted disc's top round below its side rounds' radius (#13772)

The 12 top edges rounded at 1 and at 1.101826756514697 over the exact side rounds:
Level2 clean and manifold. On main the side rounds' skinned nets left top-round
vertices 2.5e-5 and 5.6e-5 off their faces (6 and 4 Level2 lines).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a G^n certificate tightens each proved feet's box before its Taylor solve

Krawczyk's test proved a piece's feet in a box and stopped. The box was still as wide as
the iterations left it, and the feet's Taylor coefficients over it are enclosed with a
factor 1 / (1 - q) at every order, q the contraction over that box. On wide pieces q was
near 1, so the enclosures blew up and the certificate went to finer levels (pieces) to
prove its bound: on the boss-on-bar round, 4 to 6 orders of magnitude between one level
and the next.

Now, once proved, Krawczyk's operator runs again about the box's own centre, with the
Jacobian over the box, and the box becomes its intersection with K, while that shrinks
it by a tenth (at most 2 steps). This is sound by construction: every root in a box is
in its K, so the box keeps the feet, and the C used for the coefficients is over a box
that holds the final one. One step is enough: 4 steps add 26 Jacobians on the case and
no fewer enclosures.

The boss-on-bar r0.5 round: 6925 enclosures before, 3946 now (-43%), and one level
fewer on most spans. Checks:
- a new unit test samples 201 stations over a piece (plane and NURBS cylinder) and finds
  every foot and Taylor coefficient (orders 0 to 4, which the floating solve reaches)
  inside the piece's enclosure;
- the twisted disc's side rounds: the net is within its certified bound at all 11011
  samples per round (new assertion), the ball-centre miss is 2.3e-14 to 2.9e-14 (as
  before), Level2 clean and manifold, and the #13772 top rounds are clean;
- goldens blend.*, corpus.parity and surface.offset_surface: 0 rows changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a halved G^n span's halves start from its finest proved level

The section build halves a span whose certified bound is over the target and
certified each half afresh from level 0. gn_certify_span_reusing takes the
halved span's finest proved level instead: its pieces over a half cover it and
their bounds hold over every point of them, so they are the half's level one
coarser, proved without an enclosure, and the levels go on from the next one.
Only pieces inside the half steer the least-value stop; row terms are taken
again about the half's first station. gn_certify_span keeps its behaviour (no
inheritance), operation for operation (3946 enclosures on boss-on-bar before
and after the refactor).

boss-on-bar round: 3946 -> 2758 enclosures, 3.90 s -> 2.98 s (rig case,
P-core 2, three runs each). Goldens 27/27 unchanged, twisted-disc miss
2.26e-14..2.93e-14 unchanged, issue_2652/wr697/2907c pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: G^n series leave out the terms that are zero by construction

The feet's composition through the faces' partials forms du^a dv^b for every
a + b up to the truncation's degree, and the inverse square root forms z^m;
du, dv and z have no constant term, so those powers are 0 below their degree.
Ser::mul_valued and Ser::add_scaled_from leave those terms out (in mul's
order), and the monomials past the degree a masked truncation computes are not
formed. In f64 the results are bit for bit the old ones (the terms left out
are signed zeros; unit test over several truncations and every masked degree);
in intervals they enclose the same values and are the same or narrower
(unit test against the old product and the f64 one).

boss-on-bar round: 2.95 s -> 2.00 s (rig case, P-core 2, alternating runs),
enclosures unchanged at 2758. Goldens 27/27 unchanged, twisted-disc miss
2.26e-14..2.93e-14 unchanged, issue_2652/wr697/2907c pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a G^n span's levels start where the span before proved

Every span tried levels 0, 1, 2, ... and each coarse level failed on its
first piece after solving all its end stations: a span that proves at level 5
first solves 26 stations and fails 5 enclosures. Where a span inherits no
level, the section build now starts it one level coarser than the level the
span before first proved (SpanReuse::start). A level not tried bounds nothing,
so the returned bound is as sound as before. A level's end stations that the
level before solved (every other one, the same s from the same seed) are taken
from it, bit for bit what solving them again gives.

boss-on-bar round: enclosures 2758 -> 2681, enclosures on levels that fail
174 -> 97, time in end-station solves 300 ms -> 184 ms; wall time within
noise of the previous commit (best of five 2.49 s -> 2.44 s). Goldens 27/27
unchanged, twisted-disc miss unchanged, issue_2652/wr697/2907c pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: a G^n re-certificate goes on to its target, not by the halving guess

Re-certifying a blend proves its net within the bound stored when it was
built. The certificate stops refining a span once a level fails to halve the
bound, a guess at the next level's cost. Where the re-certificate's rounding
differs from the build's (it works in the round's own frame), its bounds
differ in the last digits and the guess can stop it a level short of one that
certifies.

wr698 `a_circle_round_collapses_at_its_radius`, the offset of the circle root
round by -2.5, span [69.92, 76.57]. Built by levels 2..5 at 9.55e-4, 8.61e-6,
3.09e-6, 2.39e-6, so stored 2.92e-6. Re-certified at 8.61e-5, 6.04e-6,
3.05e-6: 3.05e-6 is over half of 6.04e-6, so it stopped there, over its
2.92e-6 target, and the offset was refused ("the net is not within its bound
of its definition").

SpanReuse::to_target goes on while the bound is over the target. The least
value still stops it, since past it no level certifies the span. recertify
uses it. The build's certificates are unchanged. The test fails without this
on this branch: the tighter certificate moved the base round's bound and
exposed the stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: pins of the G^n certificate follow its tighter bounds

Four tests pinned the fork's certified bounds and span counts. The box
tightening and the structural zeros make each bound smaller, and still sound:
every test keeps its checks of the net against the definition. With fewer
spans halved, the span counts drop.

- wr698 root round: bound 9.034561390963572e-5 -> 7.805948063924082e-5, 16 spans
  (the net within the bound at all 121 points checked, as before)
- wr698 square corner: 1.0369852135855476e-24 -> 1.0012759448840962e-24
  (at rounding either way), 1 span
- wr698 gate ball build: 5.4032051671095375e-9 -> 5.402814802230933e-9, 40 spans
- query_normal_definition rounded wedge: 181 -> 169 inner knots
- fork_7959c0e round on a round: 45 -> 31 spans (32 from the tightening, 31
  with the halves' reuse), the net at 0.36 of its bound

Each pin keeps the new exact value, with the fork's in its comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: the rim chamfer's quoted gain is this branch's

Main's #14224 checks the rim chamfer's gain by its flux, cross-checked by
sections, and quotes the flux. Two of the chamfers are their definitions on
this branch (#13774). The flux is 80.12239 at 1e-11 and 80.12228 at 1e-9, where
it was 80.12092 and 80.12081. The independent estimate is 79.82 +- 0.53 on both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:44:44 +00:00
bherbruck 1735fef326 defeature: a kept feature's refusal names its limit, a typed Reason::Defeature (tangency-labs/solidcore#3218) (#13899)
Rebased onto main after tangency-labs/solidcraft#13680 merged: its own change (against its two parents merged), three-way per file.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:29:51 +00:00
Mateusz Sych c2ec24f0ae MCP server crate: an app can serve prompts, resource templates, completions and Markdown or binary resources (#13336) (#14243)
* claim: #13336

* MCP server crate: an app can serve prompts, resource templates, completions and Markdown or binary resources (#13336)

The shared crate (`solidcraft-mcp-server`) served only tools and JSON resources: `prompts/list`
and `resources/templates/list` always answered empty, anything else was method-not-found, the
`initialize` capabilities always named tools and resources, and every resource read went out as a
JSON string.

- `Tools` gains provided methods for resource templates, prompts (`prompts/list`, `prompts/get`)
  and completion (`completion/complete`); the default is none.
- `initialize` names only what the app serves: resources when it has resources or templates,
  prompts when it has prompts, completions when it has either.
- `read_resource` returns `Contents`: text or bytes, each with its MIME type; bytes go out as a
  base64 blob.
- `prompts/get` checks the arguments against the prompt's listing before the app fills it in;
  completion refuses an unlisted prompt and sends the first 100 suggestions with the total.

SolidCraft's replies stay byte-identical: its two resources are JSON (`Contents::json`) and it
serves no prompts or templates, so `prompts/get` and `completion/complete` stay method-not-found.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-10 04:10:16 +00:00
Brett Lawson 89ad4a55c1 solidcore: retire the mass flags that change no result (A8) (#14429)
The code each flag turned on is now the only path; the fields, preset lines, fixes.json entries and the tests' flag-off halves are gone.

Part of #14246


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:09:38 +00:00
Brett Lawson 765ed3e9e0 ci: run the kernel rig's cases 16 at a time (#14403)
* ci: run the kernel rig's cases 16 at a time

The rig can run each case in a process of its own (#14277). Set
KERNEL_CASES_JOBS=16 for the rig step, one per vCPU of the
blacksmith-16vcpu-ubuntu-2404 runner (64 GB), so the rig no longer
runs its 1081 cases one after another, and a hung case is killed at
its CPU-time limit while the rest still run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore ci: run the kernel rig's cases 4 at a time

The standalone workflow's kernel-rig job runs on a 4-vCPU runner. Set
KERNEL_CASES_JOBS=4 there too, one case process per vCPU, so the
mirror's rig also stops running its cases one after another.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:00:53 +00:00
0rdnungsamt 4fceca239d Tests: Add agent from Home no longer fails when the agent search finishes mid-test (#14384) (#14392)
* claim: #14384 (flaky add_agent_opens_its_dialog_from_home)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tests: Add agent from Home no longer races the agent search (#14384)

The test opened Settings › AI, which starts the background search for
installed agents. A search finishing between the frame that drew Add
agent and the click replaced the Detecting line above the button, so
the button moved and the click missed. The test now marks the search
as done first, as the page's other tests do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 03:31:24 +00:00
ancient_astronaut_theorist 73cba34d13 CI: the slowest test starts first, and the slow-test priority list gains the tests over 100 s (#14362) (#14365)
* claim: #14362

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* CI: the slowest test starts first, and the slow-test priority list gains the tests over 100 s (#14362)

thread_tests::a_long_thread_cuts_without_a_deep_tree (median 950 s on
the 32 vCPU runner) was in no priority list: it started about 4 minutes
into Native Linux tests and every full run ended when it did. It now
has its own priority 100, beside the action matrix sample, so it starts
at once. The slow-test list moves to 99 and gains the 17 tests of 100 s
or more it lacked (medians from 24 full runs). Every test still runs
once; no filter, timeout or shard changes.

Fixes #14362

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 03:31:21 +00:00
ancient_astronaut_theorist 39e850d803 Render job test: the batch drop probe uses the long-name temporary folder (#14320) (#14342)
* claim: #14320

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* Render job test: the batch drop probe uses the long-name temporary folder (#14320)

On a Windows account whose %TEMP% is a short (8.3) path, the path check
refuses the probe's path before asking whether it is in the scope. The
probe's path and its scope now come from solidcraft_io::vfs::temp_dir(),
which spells the folder with long names, as #8968 did for three tests of
the same kind.

Fixes #14320

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 03:31:19 +00:00
1993eg1 a9a4cd2889 Add Revolve command guide (#14336)
* Add Revolve command guide

Adds a practical guide for Revolve: Profile, Axis, Type (Angle or To Object), Direction, Angle and Operation, with a stepped pin example and a cut groove. Tested on v0.40.0-rc.3.

* Fix Revolve example steps

Add Finish Sketch before Revolve, set Operation to New Body for the pin, use the round side of the pin (not the flat end) as the axis, and give the groove rectangle coordinates that cut into the shaft. Per CodeRabbit review.
2026-11-10 03:31:16 +00:00
1993eg1 168a074c4c Add Shell command guide (#14332)
* Add Shell command guide

Adds a practical guide for Shell: Faces/Body, Thickness, Direction (Inside, Outside, Both) and Tangent chain, with an open-top box example. Tested on v0.40.0-rc.3.

* Round the rim after Shell

Advise rounding the edges around the opening after Shell, matching the features guide, since Shell can fail on a round tangent to the opening (#13910). Removed the untested wall-vs-fillet tip. Per CodeRabbit review.
2026-11-10 03:31:14 +00:00
1993eg1 16a0d135f3 Add Rectangular Pattern command guide (#14328)
* Add Rectangular Pattern command guide

Adds a practical guide for Rectangular Pattern: Object Type, Direction and Direction 2, Quantity and Spacing (center-to-center, counts the original) and Alignment, with a 5 x 3 hole grid example. Tested on v0.40.0-rc.3.

* Clarify Center alignment

Center puts an extra copy on one side when Quantity is even; Flip moves it. Added a tip to check Alignment if copies land off the part. Per CodeRabbit review.
2026-11-10 03:31:11 +00:00
1993eg1 5335627e75 Add Circular Pattern command guide (#14326)
* Add Circular Pattern command guide

Adds a practical guide for Circular Pattern: Object Type, Axis, Quantity (counts the original), Total angle (partial arcs include both ends) and Alignment, with a bolt circle example. Tested on v0.40.0-rc.3

* Clarify Center alignment and confirm Hole step

Center puts an extra copy on one side when Quantity is even; Flip moves it. Step 2 now says to click OK so the Hole is in the History before patterning. Per CodeRabbit review.
2026-11-10 03:31:09 +00:00
SS-EN d7ad752198 Joint drive: cancel stale slider poses after 500 ms (#14264) (#14270)
A joint pose for a value the slider has left is cancelled once it has
run 500 ms, so the latest value starts and the preview follows the
slider. The latest value still solves to the end.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 03:31:04 +00:00
TaeHeon Kim 79ba1e8367 GPU tessellation: while loops in tess.wgsl, so an OCLP Mac does not hang on curved faces (#10108)
On an OpenCore Legacy Patcher Mac (Radeon R9 M370X, Iris Pro 5200,
Metal) the for loops of the NURBS, revolved and normal-fallback paths
hang the GPU: WindowServer's watchdog resets the machine after about
three minutes. The same loops written as while loops run. The
arithmetic is unchanged; only the loop form is.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 03:30:49 +00:00
yhamil04 abea010d04 Cylinder and Sphere: Edit Feature shows the diameter, with a Diameter | Radius choice (#6609) (#7017)
* claim: #6609

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cylinder and Sphere: Edit Feature shows the diameter, with a Diameter | Radius choice (#6609)

A diameter given to Cylinder or Sphere is kept as the radius `(D) / 2`; Edit
Feature now shows that diameter on Diameter, as the window that made it asked.
Any other radius opens on Radius as stored. A Size row (Diameter | Radius)
switches between them and converts the value, so OK sends the one shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 03:30:46 +00:00
dudedagger42 ebc613fe84 Mirror: a cut starting on a mirrored feature's face mirrors with it (#11792) (#13043)
* Mirror: test that a cut starting on a mirrored feature's face mirrors with it (#11792)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Mirror and patterns: copies find the faces their sources name before any copy is made (#11792)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* patterns_tests: no blank line at the end of the file

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 03:30:32 +00:00
MarkusRe b32f45ae38 Kernel: a B-spline surface that mixes weighted and unweighted control points is refused when it is built (#13751) (#14292)
* claim: #13751

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Test: a B-spline surface net mixing rational and non-rational points is refused where it is built (#13751)

The new unit test builds nets with one weighted point among non-rational
ones (and the reverse) through Surface::new and Surface::with_indices and
expects InvalidInput; storage past the indices in use stays outside the
check. The fuzz corpus now expects fuzz00295 (one weight 1 among -f64::MAX
markers) to be refused by Surface::new instead of building the face cache.

Both fail on main: the constructors accept the net.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* srf_basic: Surface::new and Surface::with_indices refuse a net mixing rational and non-rational points (#13751)

A control point's weight is either a real weight or the non-rational
marker NOW (-f64::MAX). A net that mixes both was accepted by the
constructors; only counts_are_valid noticed it, and paths that never call
it (the face's trimmed-surface cache, fuzz00295) took -f64::MAX as a weight
and subdivided past the fuzz time limit.

Both constructors now refuse such a net with InvalidInput, over the points
in use only. counts_are_valid shares the check, for nets mixed later
through the mutable accessors.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* goldens: nlib.srf_basic records a surface its constructor refuses as rejected (#13751)

The generator makes a net mixing rational and non-rational points in about
5 % of its cases. Surface::with_indices now refuses such a net, so the
runner records the constructor's reason as `rejected` instead of a harness
error; no op runs on the refused surface.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* Test: a rational and a non-rational half of a surface join back into it (#13751)

Splitting the sample surface, giving one half weights 1.0 and joining the
halves must remove the joint knot again and give back the surface, with
every weight 1.0. On main the joint knot removal runs on a net that mixes
weights with the non-rational marker NOW (-f64::MAX).

Co-Authored-By: Claude Code <noreply@anthropic.com>

* srf_tool::p3::join: a rational and a non-rational surface join with real weights (#13751)

join loaded both nets as they are, so the joined net mixed real weights with
the non-rational marker NOW (-f64::MAX); the joint knot removal ran on that
net, and only then was NOW set to 1.0. Surface::new now refuses such a net.

The non-rational piece now gets weights 1.0 before its points are loaded
(make_rat_compatible). The joined net before the removal is upstream's, bit
for bit (0.5 * w1 + 0.5 * w2 equals the old 0.5 * (w1 + w2)), and the
removal sees real weights.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* goldens: nlib.bezier records a surface its constructor refuses as rejected (#13751)

The generator mixes rational and non-rational control points in about 5 %
of its point sets, surfaces included. Surface::with_indices now refuses
such a net, so the surface-strip extension cases record the constructor's
reason as `rejected` instead of a harness error.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* plan: WR-773, N_SrfJoin removes the joint knot on a net mixing weights with NL_NOW (#13751)

Records the upstream order that srf_tool::p3::join no longer follows, with
the nlib.srf_tool_p3 cases it moves: 12 joins with tol -1 that removed the
joint knot anyway (0.23 to 7.0e3 off the exact join) and 2 with tol 1e3
that remove a different number of knots, within tol before and after.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* goldens: bless nlib.srf_basic, nlib.bezier and nlib.srf_tool_p3 for the refused mixed nets (#13751)

nlib.srf_basic, 179 of 3220 cases: each input net mixes rational and
non-rational control points. Old: the op ran on it (23 inspect cases with
counts_are_valid NL_SUR_ERR, 156 op results, 63 of them holding inf or NaN).
New: rejected, "Surface::with_indices: invalid input: control points mix
rational and non-rational". The net is no NURBS surface; the constructor
refuses it now.

nlib.bezier, 7 of 2223 cases: surface-strip extensions of such a net. Old:
status 0 with inf or NaN in all 7 records. New: rejected, same reason.

nlib.srf_tool_p3, 14 of 2524 cases: joins of a rational and a non-rational
surface (WR-773). 12 with tol -1: old removed the joint knot once, 0.23 to
7.0e3 off the exact join; new keeps it and is the exact join (distance 0
on a 41 x 41 grid). 2 with tol 1e3: old 174 / 0, new 0 / 1.63 off the exact
join, all within tol.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* srf_basic, srf_tool::p3: tests check the refusal's reason and a join with real weights; docs name what still mixes (#13751)

- The constructor test uses a 3 x 3 net, so the odd point also sits inside
  the net and on an edge, and checks the reason, not only InvalidInput.
- The join test adds a rational piece whose weights are not 1.0: with
  tol < 0 the joined net keeps its points and weights, the averaged joint
  column, then the other piece's points with weight 1.0.
- The srf_basic header no longer says no path sees a mixed net: the mutable
  accessors, set_cpts_and_knots and set_size_indices can still make one, and
  counts_are_valid reports it.
- The p3 header says what is equal to upstream: the net after its NOW loop,
  with the joint weight equal while w1 + w2 is finite.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* plan: WR-773 gets a schematic of the join's order, and says what equals upstream (#13751)

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fuzz corpus: fuzz01010 of blend.fillet_custom_solver is refused by Surface::new too (#13751)

Its surface has one control point whose rationality differs from the
rest (found as a 343 GB allocation abort; the C++ reference refused it as
"mixed rationality"). Surface::new now refuses it before the solver runs.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-11-10 03:29:47 +00:00
saosebastiao 875f03f2be Naming: a stored piece name finds its face through the blend that replaced the edge it named (#4325) (#14268)
* Naming: a stored piece name finds its face through the blend that replaced the edge it named (#4325)

A piece of a face keeps the names of the faces that tell it apart
(`F2:side:l1#[F2:top]`). A fillet or chamfer replaces the edge between two
faces with a face of its own, named after that edge
(`F3:blend:F2:side:l1|F2:top`), so the two no longer touch: the stored name
found nothing, and the feature using it failed and asked for its face again.

The blend's name holds the edge it replaced, so the face it separated from is
still there to read, and the name now finds its face. A blend face's own name
is that edge, so a piece of one is qualified inside it
(`F4:blend:F2:side:c1#[F2:side:l3]|F2:side:l3`); that is read too, so a blend
finds its pieces once it is one face again.

The design in #5040 now rebuilds its timeline to the draft instead of asking
for a reselect, and the Create Sketch the issue reports attaches exactly to a
rim a join left flush with a boss.

* Tests: a blend piece name is refused when two pieces answer, and a copy's qualifier keeps its instance (#4325)

The blend-aware piece matching lets a stored qualifier name a face that a fillet
replaced. Two guard tests hold its edges:

- two pieces of one face that both answer to the stored name still ask for the
  face again, instead of one of them being picked;
- a pattern copy's blend piece matches with the copy's instance in the
  qualifier, as the faces around the copy carry it, and not in another copy.

* Tests: the two-piece refusal test shows the same name resolves once the slot is suppressed (#4325)

The ambiguity guard asserted only the reselect, which a name that finds nothing
would also produce. Suppressing the slot leaves one piece, and the same stored
name then finds it, so the reselect is the second piece answering rather than an
empty lookup.

* Tests: two pieces told apart by one blend both answer a stored name, and the lookup picks neither (#4325)

The refusal in the engine test could hold for a name that finds nothing. This
asserts the ambiguity itself, on a real body: a box with its top front edge
rounded and a slot cut through the front wall leaves two pieces of that wall,
each keeping the round along its own top edge. Both answer `F1:-y#[F1:+z]` as
`Match::Piece` or better, and the lookup chooses neither.
2026-11-10 03:06:44 +00:00
0rdnungsamt 68c4b80fb1 Agents and scripts: Blend Surface describes its exact parameters and reply (part of #2829) (#14240)
* claim: surface.blend schemas (#2829)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* surface.blend: input and output schemas, part of #2829

The contract keeps the handler's messages. In strict mode it also refuses
a malformed edge point and an unknown key inside `from` or `to`, which the
handler silently drops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 03:06:29 +00:00
Mateusz Sych b57026e1fd Assemblies: an Angle joint keeps its part on the same side of the hinge as its angle changes, and carries on past 180 degrees (#12426) (#14350)
* claim: #12426

* Assemblies: a test that an angle relation on a hinge keeps its side and carries on past 180 degrees (#12426)

* Assemblies: an Angle joint keeps its part on the same side of the hinge as its angle changes, and carries on past 180 degrees (#12426)

An Angle relation ties only the angle between two faces, so at every angle its part has two places
to be: one on each side. joint.edit started a relation whose tilt changed again from where its
parts were, taking the values that move the part least. Past 180 degrees that is the way back:
170 to 190 turned a hinged leaf back to where 170 had it, and 260 landed where 100 does. From
straight up both sides are as near, so successive values in the Edit Joint dialog (which sends
the type, offset and flip with every edit) landed on either side.

A tilt change alone now turns on from the relation's values: the solver closes the loop from
there, so the part keeps to its side and carries on round past 180 degrees. A change of type,
offset, flip or sides still starts again from where the parts are.

* Assemblies: a suppressed Angle joint whose angle is edited starts again from where its part is (#12426)

Its part may have moved while the joint was off; keeping the joint's values would carry the part
back to where it was once the joint is turned on again. Only an active Angle joint keeps its
values on an angle-only edit. Found in review.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-10 03:06:12 +00:00
jyviko 7e902aa473 UI kit: no SolidCraft content; tabs, text and catalogues are host data (part of #14251) (#14399)
* UI kit: chord labels are kit code; the text menu no longer reads the keymap

The text fields' menu showed its shortcuts through `keymap::display`, a pure
formatter (Ctrl as Cmd and Alt as Option on macOS). It is now the kit's
`chord_label` in ui_kit/shortcuts.rs, beside the chord keycaps; the keymap
calls it too, so there is one implementation (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Navigation bar: the slot catalogue takes the canvas caps as data

navigation_bar.rs read the app to decide what the canvas allows
(`canvas_caps`, from dialogs_sheet and render_workspace) and the keymap for the
zoom slot's command. `canvas_caps` moves to the host's navigation_bar_view.rs,
`Slot::available` takes `orbit` and `pick`, and the zoom command is the
catalogue's own `ZOOM_TOOL`, which the keymap's `DRAG_ZOOM` names (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Preferences window: a reveal expires on the kit's clock

prefs_window.rs compared a reveal's `until` with the app's `now_ms`, while the
kit set `until` from its own clock. `Reveal::expired` reads the kit's clock for
both (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Tree rows: the host installs how row labels are shown

ui_kit/tree.rs called SolidCraft's `i18n::tr_name`/`tr_dyn`, which translate
its default names (Sketch1, Body2, Untitled) and browser labels. The kit now
reads a host-installed `RowNames` translator (`install_row_names`, once per
process, like the icon set); `i18n::install` installs SolidCraft's, so Browser
rows translate as before. With none installed a given name shows as typed (in
display order) and any other label through the catalogs (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Menus: draw menu icons through the kit's icon set

ui_kit/menu.rs painted its icons through the app's `crate::icons`; it now
uses `ui_kit::icons`, which draws from the set the host installs (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Theme: the host lists its tabs; the theme defines only the accent ramps

The theme crate named SolidCraft's sixteen workspace tabs (`TAB_NAMES`) and mapped each to its
colours (`TabAccent::for_tab`, `icon_accent_for_tab`). It now defines the accent ramps
(`AccentRamp`: the token slots of each ramp) and how a tab's accent is resolved from a table the
host gives (`TabTable` of `TabStyle { name, ramp, icon_accent }`, `TabAccent::for_ramp`), and
holds the host's table once installed (`install_tabs`, `installed_tabs`) for theme files keyed by
tab name. `TabAccent::set_current` takes the icon accent and the ramp a focused number editor
takes, instead of a tab name and whether a sketch is open.

SolidCraft's table is `workspace::TAB_ACCENTS`, with the same ramp and icon accent for every tab;
`workspace::tab_accent` replaces `TabAccent::for_tab` at every call site and a test pins each
tab's colours. The app installs the table next to its icons (`workspace::install_tabs`), and
`theme_files` validates per-tab tables against the installed table. A history chip takes its
icon accent as data (`HistoryChip::icon_accent`); the kind-to-accent mapping moved to the host
(`workspace::feature_icon_accent`). Tests that read theme files without an app or test context
install the host's data first (`test_support::host_data`). No colour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Theme files: icon roles and ramps come from the installed icon set

`theme_files` read SolidCraft's icon vocabulary (`icon_svg::ROLE_NAMES`, `ACCENT_NAMES`) to
import and export a theme's icon palette. It now reads the roles and ramps of the icon set the
host installed (`ui_kit::icons::installed()`), and its palette type is the kit's
(`ui_kit::icons::Palette`), so the module no longer names SolidCraft's icon table. Same palettes
for SolidCraft, whose set is installed before any theme file is read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* UI kit: Render and workspace-menu names come from the host

Three kit components named SolidCraft's workflows. The inspector header published Render's close
handle and remove entry (`render:appearance:close`, `render:appearance:remove`,
`render.inspector_remove`); it now takes them as `InspectorNames`. The workspace dock's grip menu
entry was the `view.render_dock` command; `WorkspaceOptions::move_command` names it. The review
card labelled its impact rows and secondary action with Render's messages and published Render's
acknowledgement handle; `ReviewAndApply` takes `replaces_label`, `kept_label`, `secondary` and
`acknowledge_handle`. The workspace menu matched SolidCraft's six workspaces by label and carried
their descriptions, tab lists, Render accent and footer; it now draws a `WorkspaceMenu` whose
`style` gives each entry's `WorkspaceStyle` (slug, description, tabs, accent ramp), with
SolidCraft's table in `context_menu::workspace_menu`. The host passes the same names, so handles,
labels and pixels are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit gallery: neutral sample data in the kit's specimens

The kit's gallery specimens drew SolidCraft data: command ids (`solid.fillet`, `sketch.create`,
...), the Model and Surface tabs, a ribbon path and an engine lookup (`find_command`), which a
shared kit cannot reach. They now use sample ids (`sample.action_a`...), tabs "Tab A" and "Tab B"
and the placeholder icon; SolidCraft-flavoured specimens stay in the host's `kit_gallery_tests`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit guard: shared code holds no host tab names or command ids

`shared_code_holds_no_host_tab_names_or_command_ids` reads the non-test code of the theme crate,
the kit, the components and the theme-file modules, and fails on a string literal (macro bodies
included) that is one of SolidCraft's tab names (`workspace::TAB_ACCENTS`) or an engine command
id (`command_specs`), whole or as a `:`/`/`-separated part. The navigation bar's slot catalogue
is still in its component: its ten command ids are listed in `PENDING_HOST_NAMES`, which only
shrinks (a listed name the file no longer holds fails). On the code before the previous commits
it reports the theme's tab names, the history chip's tab mapping and `view.render_dock`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* UI kit: SolidCraft's preference text and page figures come from the host

The kit's widgets named SolidCraft's message ids. Each now takes its text as
data: text emphasis (EmphasisLabels), the History Play control (PlayLabels,
carried by history_bar::Bar), shortcut keycaps (the unassigned text), the theme
browser's arrows (PageLabels), the layout preview's note, the theme options list
and notice (ThemeOptionsText, the dismiss label), the plugin card
(PluginCardLabels) and the axis table (AxisColumns). SolidCraft's own page
figures (the scale specimen, the status colour chips, the autosave timeline and
the agent pointer preview) move to the host as prefs::figures, with their
gallery specimens in kit_gallery_tests. Same text and pixels as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Navigation bar: SolidCraft's slot catalogue is host data

The navigation bar component held SolidCraft's slots and their commands. The
catalogue (and the zoom tool id) moves to the host module navigation_slots,
which installs it once per process next to the tab table (SolveApp::new,
test_support::host_data, both apps' main); the component checks stored bars
against the installed table. Which canvases a slot works on is now slot data
(needs_orbit, needs_pick), with the same results. The kit guard's pending list
of host names is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit gallery: a test file by name, like the kit's test support

ui_kit/gallery.rs is a #[cfg(test)] module, but the source guards tell test code
by the `_tests.rs` name, so font_sizes_and_dense_form_metrics_come_from_tokens
read its specimen sizes as form metrics. It becomes gallery_tests.rs, loaded as
`gallery` through #[path] as test_support is; the guards' gallery.rs special
cases go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit guard: kit code names no SolidCraft message ids

kit_code_names_no_host_message_ids fails when non-test code under ui_kit/ holds a
message id (`area.thing`) of SolidCraft's English catalog outside the kit's own
vocabulary: ui., color., search. and navbar.custom. (KIT_MESSAGE_PREFIXES). On
the kit before the previous commits it listed choice, history, preferences*,
shortcuts and theme_picker; a companion test pins what it finds and skips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Viewport layout tests: measure the bar with SolidCraft's slots installed

The navigation bar reads the host's installed slot catalogue (f6836614d); the
viewport layout tests measured the default bar without it. Their measuring
helper installs SolidCraft's slots first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Theme options: SolidCraft names the standard options and why one is off

theme_options held SolidCraft's message ids for the standard options' names
and help and for an unsupported option. A Provider is now behaviour only; the
host names the standard options (prefs::standard_option_text, passed to
theme_option_rows) and words an unavailable reason (theme_option_reason), with
Availability::Unsupported for a standard option without a provider. Same text
in Preferences and in the ui.prefs reply. The message-id guard also scans
theme_files.rs and theme_options.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Navigation bar: SolidCraft's view styles and default bar are host data

The bar component kept SolidCraft's choices: the visual styles and their
icons, the visual-style slot's icon switch, which menus show their value
and the slots a new bar holds. They move to navigation_slots, installed
with the slot catalogue as one Catalogue; the component reads it: a slot's
icon follows its menu value through value_icons, menu_display reads the
host's value and icon-only menus, and the default bar is the host's list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit guard: the navigation bar names no SolidCraft message ids

The message-id guard now scans navigation_bar.rs with the other
components that move with the kit; the host-names guard already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Selection ink test: read SolidCraft's built-in themes with the host's data installed

The test resolves SolidCraft's built-in themes, whose tab accents come from the host's tab
table now; under nextest (one process per test) nothing had installed it, so no theme
resolved and the list came back empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:37:02 -05:00
PeppaWutz 28521d6861 Properties: click a value to copy it (#12218) (#12273)
* Properties: click a value to copy it (#12218)

Each value in the Properties card (mass, volume, area, centre of mass,
bounding box, counts, first moments and the moment of inertia cells)
copies itself as shown when clicked, and the status bar says so. The
cells' tooltip says they copy.

* Properties: copy cells read as buttons and show keyboard focus; Korean strings (#12218)

* Properties: a copy cell reads as what it is and its value (#12218)

A copy cell's accessible name was its value only. It is now the row's label and the value ("Mass: 12.500 g"), and an inertia cell's its entry ("Ixy: …"), or the row's label and the cell's place for the principal moments.
2026-11-10 02:39:42 +00:00
Brett Lawson 20810ac2a5 solidcore: retire the intersect flags that change no result (A7) (#14347)
volume_discontinuity_checks_vw_unaligned, seam_point_solutions_are_one,
is_point_on_curve_propagates_eval_failure and ssi_start_loop_trace_budget are removed:
the code each turned on is now the only path, with the field, preset line, fixes.json
entry and the tests' flag-off halves gone.

Part of #14246


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:39:13 +00:00
ancient_astronaut_theorist 63807695e9 solidcore: no deprecation warning from fetch_update on Rust 1.99, keeping rust-version 1.85 (#14360) (#14378)
* claim: #14360

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* solidcore: no deprecation warning from fetch_update on Rust 1.99, keeping rust-version 1.85 (#14360)

Rust 1.99 deprecates AtomicU64::fetch_update as a rename of try_update,
so every build with it printed a warning from geom/context.rs. The
rename would break rust-version 1.85, which has no try_update (E0599;
solidcore builds on 1.85 today), so the call stays and its statement
allows the deprecation with a reason, as solidcore's other allows do.

Fixes #14360

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:21:14 +00:00
1993eg1 8005413fca Add Mirror command guide (#14321)
* Add Mirror command guide

Adds a practical guide for Mirror: Object Type (Bodies or Features), Mirror Plane, and New Body vs Join, with a model-half-and-mirror example and a feature mirror. Tested on v0.40.0-rc.3.

* Fix block size in Mirror example

Put the 1.5 in short edge on the Y axis so the mirrored block is 4 in wide, per CodeRabbit review.
2026-11-10 02:10:27 +00:00
Mateusz Sych f35dc6e1c5 Motion Study: a part that reaches its saved position at a step is shown there, not left at the previous step's pose (#13511) (#14307)
* claim: #13511

* Motion Study: a test that a step putting a part back where the design has it shows it there (#13511)

* Motion Study: a part that reaches its saved position at a step is shown there, not left at the previous step's pose (#13511)

The dialog worked out the pose to show as the moves it showed last plus every part whose place
at the step differs from the design. A part the step puts back where the design has it was left
out, so it stayed at the pose of the step shown before: Go to End showed 90/90 with the part at
step 89, or at step 0 right after Go to Start. motion.play itself was right.

The pose is now worked out over the moves from before the dialog showed anything (the ones it
keeps to put back when it closes), so a part back at its saved position shows there.

* Motion Study test: let the compiler deref the shown move (clippy explicit_auto_deref)

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-10 02:10:25 +00:00
Kaz Walker bba766a94f Sketch: a horizontal or vertical distance edited from 0 to a positive value solves (#14298)
* Sketch: a horizontal or vertical distance edited from 0 to a positive value solves

A DistanceX or DistanceY dimension is solved as the magnitude of the offset, which has no
slope where the two points line up, so a value edited from 0 to 0.5 left the solver without
a direction and the sketch failed as a conflict. Give it the same opening slope a point
distance from coincidence already has.

Co-Authored-By: Claude <noreply@anthropic.com>

* Parameters tests: a horizontal distance from the origin changed from 0 moves the point

Co-Authored-By: Claude <noreply@anthropic.com>

* Sketch profiling test: stall on a linear diameter, not a horizontal distance

A horizontal distance from coincident points now opens and solves. A linear diameter of a point
on its line still has a zero slope and no opening direction, so the stalled-solve example uses it.

Co-Authored-By: Claude <noreply@anthropic.com>

* Parameters tests: check the point moves, not only that the change succeeds

The disc's volume doesn't change when it moves, so the test also checks
the middle of its box in x after each edit: 0.5 for 0.5 mm, 0 for 0 mm.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 02:10:22 +00:00
Mateusz Sych c6ba960e47 Agent chat: the conversation refers to design versions by an opaque handle, so the panel no longer needs the engine's document (#13438) (#14266)
* claim: #13438

* Agent chat: the conversation refers to design versions by an opaque handle, so the panel no longer needs the engine's document (#13438)

The chat kept whole engine documents in its conversation state: `Item::Changes` (the design right
after a message's changes, to offer Undo while it is still the open design), `Turn::start_doc`
(the design before a message, to merge its undo steps) and `AgentChat::seen` (the design the agent
last saw, for the changed-design note, #10854). `PanelView::doc` existed only to compare with
`Item::Changes`. That tied the panel to `solidcraft_engine::doc`, so it could not move to a shared
crate (#12948).

`Revision` is an opaque handle the host makes from its design (`Revision::of`). The chat stores it
and compares it by identity (`is`, `same`), the same pointer identity `Arc::ptr_eq` gave before,
and never reads it. The host functions get the design back with `Revision::get` where they need
it (the changed-design note) and keep merging undo and undoing to a revision as before.

No behaviour changes: the existing agent chat tests are unchanged; one new test pins the handle's
identity rule (an equal design is another design, and only the host's type comes back).

* Agent chat: merge_undo keeps taking the host's design; its callers get it back from the turn's revision

The existing test merging_finds_the_turns_start_when_the_history_was_trimmed calls merge_undo with
a design, so the helper stays a host function on documents and the agent chat tests stay unchanged.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-10 02:09:45 +00:00
leValdemar19 226a074cf9 Sketch: a point placed on a midpoint's alignment guide stays lined up with that midpoint (#13413) (#14225)
* claim: #13413

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch: a point placed on a midpoint's guide stays lined up with it (#13413)

With "Line up with other sketch points while drawing" on, a hovered
line's midpoint guide is now one of the points to line up with. A click
on that guide adds the Horizontal or Vertical constraint to a Midpoint
point on the line, in the same undo step as the point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:09:28 +00:00
Mek e24ceda806 Linked design: a sketch on a linked face follows it through Update Links (#14221)
A face of a linked design (Insert Design) is named after its name in that
design (`link:…`), so joints on it follow it when Update Links brings in an
edit (#3330). A sketch placed on such a face also carries the imported
face's certificate, which holds the digest of the whole imported file: any
edit of the source, even a body added far away, changes that digest, and
the sketch failed with "immutable import face certificate no longer exists",
taking every feature after it along.

When the certificate no longer matches and the face has a link name, the
face is now found by that name on the same Import feature, as joints find
it. A face named only by its place (`face:<k>`) is not followed, and a face
the source no longer has still fails.

Co-authored-by: Mek <mf@linescode.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:09:26 +00:00
Kaz Walker 2950976116 Sketch: Draw Dimensioned reports the sketch's freedom after its dimensions (#14132)
The reply's top-level `sketch` came from the shape command, before the
dimensions were added, and `locate` started from the last per-role reply in
role-name order rather than placement order. Both now read the sketch as
solved after everything the command did.

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 02:09:23 +00:00
Bryan 2ea022be86 mcp: running answers at once while a long batch runs (#14007) (#14083)
* mcp: `running` answers at once while a long `batch` runs (#14007)

The stdio server read its next request only after the previous call
returned, and a bridged `batch` waits up to an hour for the app, so a
`running` call sent meanwhile sat unread until the batch finished.
stdin is now read on a thread of its own: calls are still handled one
at a time, in order, but a `running` call that arrives while another
call is in progress is answered at once on a fresh connection to the
app, which answers `control.running` on any connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* mcp: `running` right behind a `batch` is not queued behind it (#14007)

Count a message as in flight when the stdin reader queues it, not when
the serve loop takes it, so a `running` read before the loop has picked
up the `batch` ahead of it is still answered apart; and know how to
reach the app before the first message. The tests now hold the batch
until the first reply has been read, so the reply order is not a race,
and check that the app was asked `control.running` while it held the
batch (only possible on another connection), for `running` sent after
the app has the batch and in the same write as it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:09:21 +00:00
PeppaWutz 12afa8bb35 Agents and scripts: Open and the recovery commands describe their exact parameters and reply (#12279) (#12352)
* Agents and scripts: Open and the recovery commands describe their exact parameters and reply (#12279)

doc.open, doc.recover, doc.recovery_discard and doc.recovery_list now publish
typed input and output schemas (part of #2829). The schemas read each key the
way the commands read it today and give the same error messages, and the
replies are built from the typed values, so what the commands do and return is
unchanged. Their lines leave unconverted.txt.

* Open: the reply's newer_file and warnings in the schema test (#12279)

* Schema: doc.open takes folder, as main added it

* Schema test: doc.open keys include origin
2026-11-10 02:08:59 +00:00
Dahak Builds 7ce3432667 Revolve: an axis picked from another sketch no longer picks the wrong line (#6548) (#8026)
* claim: #6548

* Revolve: resolve a cross-sketch axis line in its owning sketch (#6548)

The Revolve and Surface Revolve dialog builders dropped the owning-sketch
qualifier #4081's curve picks carry, so a bare line id resolved in the
profile sketch and a colliding id picked the wrong axis line. The builders
now emit {sketch, curve}; axis_param accepts it, AxisRef::SketchLine keeps
the owning sketch, and evaluation resolves the line there and follows it
when that sketch is edited. Bare ids keep resolving in the profile sketch,
so old designs replay identically.

Fixes #6548

Written by Qwen.

* fmt: apply rustfmt to revolve axis changes (#6548)

* clippy: use expect_err in revolve axis sketch test (#6548)

* revolve: keep bare axis id when the line is in the profile sketch itself (#6548)

* fmt: apply rustfmt to revolve axis changes (#6548)

* chore: re-trigger checks

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-10 02:08:44 +00:00
bherbruck 1e3582f700 solidcore-tess: well-shaped flat faces with no size, and the 50k-face plate in under 9 s (#14242)
* Move SolidCraft's mesher and its evaluator into solidcore (RFC 0003, item 1.1)

A move only, so history follows the files: SolidCraft's planner
(crates/kernel/src/tess/) becomes solidcore-tess's src/mesher/, and its plan
evaluator (crates/geom/src/tess.rs and tess/) becomes the kernel's
src/eval/plan.rs and src/eval/plan/, next to solidcore::eval. No content
changes; the next commits wire them in and remove solidcore-tess's own
planner, so one mesher remains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: remove its own planner and plan, replaced by SolidCraft's

solidcore-tess's planner (src/planner/) and plan (src/plan.rs) were a port
of SolidCraft's; SolidCraft's own moved in with the previous commit, so one
mesher remains (RFC 0003, item 1.1). The next commit wires the moved code in
behind solidcore-tess's public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Wire in SolidCraft's mesher and evaluator; SolidCraft meshes through solidcore-tess

The moved plan evaluator is the kernel's `solidcore::eval::plan`, in the
kernel's vectors, beside `solidcore::eval` (where the two overlap, they are
unified in a later step). The moved planner is solidcore-tess's `mesher`,
behind its public API: `plan`, `TessPlan` and `FacePlan` (SolidCraft's plan
types, now in solidcore-tess's `plan`, with its `poles` field), `evaluate`
returning the mesh and the faces left out, `PlanStats` and its records,
`MeshTolerance`, `preview` and `simplify`.

- The mesher evaluates surfaces with the kernel's plan evaluator and edge
  curves and pcurves with the kernel through solidcore-convert; it works in
  the kernel's vectors and hands the plan the record's. It keeps SolidCraft's
  text (rustfmt skips it) and allows the lints it does not meet yet, with the
  reason; a panic in planning is caught at `plan` until the next step brings
  checked access.
- SolidCraft's geom re-exports the evaluator as `geom::tess`, through one line
  in xtask/solidcore-seam.txt (until RFC 0003 item 1.8 moves model-evaluation
  callers to ops and tess queries), with converters between its vectors and
  the kernel's and its partials in its own vectors; the B-spline basis
  functions it shares with geom's splines moved into `eval::plan::basis`.
- The kernel crate meshes with `solidcore_tess::plan`; its `MeshTolerance` and
  `DISPLAY_ANGLE` are solidcore-tess's, and its worker map that only the
  mesher used is gone.
- Tests move with what they test: the planner's tests that build bodies with
  SolidCraft's kernel are crates/kernel/src/display_plan_tests.rs, the plan
  types' are in solidcore-tess, the evaluator's cross-check with geom's NURBS
  surface is in geom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kernel rig: the shared core names the mesher's new home

tests/kernel-cases/PATHS-ALL still listed crates/kernel/src/tess/**, which the
move emptied, and the rig plan's check fails a glob that matches no file. The
mesher and its plan evaluator are now solidcore-tess's src/mesher/ and the
kernel's src/eval/plan.rs and plan/; they are inside solidcore/** already, and
are named there so the move stays visible in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the kernel decides the moved mesher's poles, once per iso-line

SolidCraft's mesher, moved into solidcore-tess, told a pole by the surface's
derivative speeds at the point, evaluated afresh at every question. It now asks
the kernel's certified classifier (solidcore::eval::collapse), which decides on
the iso-line through the point, and keeps each answer under its iso-line
(direction, other parameter, reach), so the kernel is asked once per iso-line
however many nodes and passes look at it. There is one pole check: where the
kernel cannot tell, the face is refused, named with the point and the kernel's
reason, and nothing is taken for an answer. On the 1,101 kernel-case bodies,
the primitives and 26 STEP files the kernel refused no point at either
tolerance. The speed-based checks the mesher used (`degenerate`,
`degenerate_within` in eval::plan::uv) are deleted.

Pole questions and kernel classifier calls (export): coil split by a plane
(793) 1,084,906 -> 1,393; coil through a cylinder wall (4505) 996,266 ->
12,877; boss-root fillet 54,244 -> 310; twisted closed sweep 80,112 -> 562.
The 160 spline-heavy bodies mesh in 8.0 s instead of 13.2-13.7 s
single-threaded, with the same triangles; all 1,107 kernel-case bodies and
primitives keep their counts.

solidcore-tess's pole pass is back on this mesher: a pole of higher order
gets the kernel's exact limit normal per triangle, or the mean of its
triangles' normals where the kernel has none (named in
PlanStats::averaged_poles); a normal neither rule gives refuses the face in
`evaluate`. At a degenerate vertex the CPU evaluation takes the kernel's limit
normal instead of a step inside. tests/pole.rs runs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: a node just past the range is classified on it, within the face's tolerance

The kernel's pole classifier refuses a point off the surface's parameter
range, and the mesher meets boundary nodes a little past it: pcurves made to a
STEP file's uncertainty end there. A node past the range by no more than the
face's own tolerance (the largest of its edges' and vertices') is classified at
its nearest point of the range, where it lies on the face to within that
tolerance. Farther out, the face is refused with the distance named; nothing is
extrapolated and there is no other pole check.

On the 319 files of solidcore's STEP bench corpus this classifies the six
faces the classifier refused: tork's faces 217, 243, 250 and 269 (nodes at most
9.8e-7 mm off, tolerances 1e-3 mm and up), nist_ctc_02_asme1_rc's face 647
(0.113 mm, tolerance 0.119 mm and up) and nist_ctc_05_asme1_ap203's face 143
(0.034 mm, tolerance 0.051 mm and up). No face of the corpus is refused, and
the 1,107 kernel-case bodies and primitives keep their triangle counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: a face the mesher cannot read is left out, not the body

SolidCraft's mesher, moved into solidcore-tess, refused a whole body when one
face's surface did not evaluate, one edge could not be sampled or split, or one
face's boundary could not be read. Each of these now leaves out only the faces
concerned, each named with why in PlanStats::unmeshed, and the rest of the
body is meshed, as solidcore-tess's own planner did:

- a face whose surface does not evaluate;
- every face an edge bounds that cannot be sampled, or split further for a
  thin loop or a crossing boundary (an edge that bounds no face is named in
  PlanStats::unsampled_edges);
- a face whose boundary cannot be read;
- an edge whose coarser sampling for a crowded face fails keeps its nodes,
  named in PlanStats::kept_strict.

`rod_doubled_edge` (tests/step-regressions), refused whole before ("edge 7:
more than 20000 nodes"), now meshes four of its five faces and names the
fifth; nothing else in the 1,101 kernel-case bodies, the primitives or the 26
STEP files changes. Tests: that body in the kernel's plan tests, and
solidcore-tess's refusal tests on its fixtures (a face off its pcurve, a free
edge, an edge both of whose faces are left out, a label without its form).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: checked access in the moved mesher and evaluator, no panic guard

The moved mesher and plan evaluator indexed slices, arrays and Vecs directly,
within bounds they checked beforehand. That is now checked access: get/get_mut,
zips and iterators, slice patterns for windows(2), and a rotate for a
triangle's three corners. A direction index into a (u, v) pair goes through
one small helper that reads NaN out of range, so no comparison on it holds.
Arithmetic and its order are unchanged. clippy::indexing_slicing is out of
both modules' allow lists.

plan() no longer wraps plan_at in the panic guard: planning returns its
failures as errors, and the doc comment says so. The worker pool's per-item
guard in par::map is untouched.

Triangle counts are identical, body for body, on the 1,107 kernel-case bodies
and primitives, at both the display and export settings. solidcore-tess tests
(62 passed, 2 ignored, plus tests/pole.rs) and solidcore eval:: tests pass;
clippy -D warnings is clean on solidcore, solidcore-tess and diagnostics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: ask the moved mesher's pole question once per node

The sag refinement and the flips ask, at every triangle corner, side and
round, whether a node is on a pole, and the self-crossing check asked it twice
per node, once for each chord the node ends. Whether a node is on a pole is a
property of its parameters: a `sag::Poles` memo, shared by refinement and the
flips of a face, asks it once per node, and `tangle::crossing` keeps each
node's answer for both its chords.

This is #13559's fix for solidcore-tess's own planner, which the move replaced,
carried over to the moved mesher with its test
(`the_pole_question_is_asked_once_per_node`: 27,452 questions for 349 nodes
without the memo, at most two per node with it). Triangle counts are unchanged
on the 1,107 kernel-case bodies and primitives, and the coil's test
(`the_spline_coil_keeps_its_triangles`) still passes.

Ported from #13559 by @bherbruck (co-authored by jyviko).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: drop the plan reports SolidCraft's mesher never writes

`PlanStats::over_tolerance` (faces a memory bound limited) and
`PlanStats::edges_capped` (edges a sampling bound stopped), with their record
types `OverTolerance`, `Cap`, `CappedEdge` and `EdgeCap`, came from
solidcore-tess's own planner, which the move replaced. SolidCraft's mesher has
no such reports, so after the move they were always empty, read as "nothing
was limited" whatever happened. They are removed; nothing reads them. The
other fields are written: the face counts, `lenient` and `unmeshed` by the
mesher, `averaged_poles` by the pole pass, `unsampled_edges` and
`kept_strict` by the per-face refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: keep the over-tolerance and capped-edge reports

This reverts 0836c4b02f. PlanStats::over_tolerance and PlanStats::edges_capped
are solidcore-tess's own reports and part of its public API; the next commit
wires them into the moved mesher instead of dropping them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the moved mesher reports capped edges and faces left over tolerance

SolidCraft's mesher stopped at its bounds without saying so: an edge's
sampling at its depth (24 halvings) or node bound (20,000) with a chord still
wanting a split, a face's grid thinned to fit its point bound, a face's sag
refinement at the points it may add. These now fill solidcore-tess's own
reports:

- `PlanStats::edges_capped`: the edge, the bound, the farthest its chords lie
  from its curve, how far its chords exceed their rules, and whether it is
  within the tolerance. A capped segment is the only one whose rules are now
  checked at the bound; the nodes are the same.
- `PlanStats::over_tolerance`: a face whose grid was thinned or whose
  refinement ran out of points, with the bounds and the sag its final
  triangles reach (measured as refinement measures it, at each triangle's
  middle and sides' middles), and how many middles could not be measured.

Tests, ported from solidcore-tess's own planner:
an_edge_stopped_at_its_depth_bound_is_named_within_its_tolerance and
a_face_whose_refinement_reaches_its_bound_is_named_with_its_sag. Triangle
counts are unchanged on the 1,107 kernel-case bodies and primitives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: shape flat faces with no size, and stop shaping crowding a corner

A flat face with no size asked of it was meshed from its boundary nodes alone, which fans a
disc's rim into slivers (sheet_disc: 7,668 triangles under 1 degree). Such a face now gets
Ruppert refinement for shape alone, to 15 degrees, with at most as many interior points as it
has boundary nodes and its boundary kept as it is, so neighbours' nodes are untouched.

Shaping (both this and the curved faces' pass) no longer splits triangles smaller than the
tolerance squared: round a sharp corner where two pieces' cuts meet it crowded points ever
closer together, down to triangles with no area (the extra open edge on face 6 of
851-chamfer-arc-top-edge-9-5).

A face's vertices are matched to ones already placed through a grid as fine as the tolerance
rather than a search of all of them, and the triangulation's bookkeeping drops two hash maps:
the 50,000-hole toothed plate plans in 8.8 s (103 s before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore: no panic-prone calls in the moved plan evaluator

f64::clamp panics on NaN bounds, so Domain::clamp and wrap_into take max then min. The
derivatives and starting-grid vectors grow as they are filled instead of preallocating a
product that could overflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: shaping's floor is a thousandth of the tolerance, squared

At the tolerance squared, display's coarser tolerance left the slivers of flat faces with no
size unshaped (sheet_disc's p99 aspect 5.6, the toothed plate's 99,536 slivers). A side a
thousandth of the tolerance, where sag refinement stops too, still ends the crowding round a
sharp corner (851-chamfer-arc-top-edge-9-5 has no open edge) and shapes flat faces fully.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: keep the reasons a face is refused for

- A face whose pole question the kernel could not answer is refused with that reason even
  when planning then failed on it: the refusal is read before the plan's own error, which may
  be a later symptom ("boundary crosses itself").
- A node the kernel's evaluator refuses (other than a pole node, which has its own rule)
  refuses the face in TessPlan::evaluate with the node and the reason, instead of taking the
  plan evaluator's normal, which can be made up where the derivatives do not evaluate.
- The poles' range check takes max then min: f64::clamp panics on a NaN bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:07:52 +00:00
bherbruck a7daab9a3b Move geom's pcurve, label and record checks into solidcore::eval with their history (RFC 0003, 1.8 slice) (#13997)
* Move geom's pcurve, label and check into solidcore::eval (pure git mv)

crates/geom/src/brep/{pcurve,pcurve_tests,label,check}.rs move unchanged to
solidcore/crates/solidcore/src/eval/. The next commits retype them to the kernel's evaluation
types and switch their callers; this commit is the move alone so their history follows them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore::eval: pcurve, label and check on the kernel's evaluation types

The moved files take eval::geometry's Curve, Curve2, Surface and Frame (the record's kinds,
field for field) and the kernel's evaluator, in place of the record's types and
solidcraft-geom's evaluator. What reads a record as a record leaves: the structural checks
(counts, budgets, a use's interval in its curve's natural range), the edge and face readers
(edge_curve_interval, form_turns on a Face) and the trim request types go to solidcore-ops,
which converts. Results are unchanged: the pcurve keeps its own pole test (the evaluator's
speed rule), knot span search and B-spline cut, not the kernel's collapse; the 80 eval tests
pass, the pcurve suite among them.

Lint-driven rewrites that keep each result: checked indexing, no pre-reserving from input counts,
the loop rotation without a remainder, a clamp that cannot panic on a reversed domain. The
hostile-record cases that depend on the record's checks are kept as no-panic checks here and as
refusals in ops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-ops: pcurve, label and record_check over the record

The record's side of the moved kernel routines: each checks the record's structure as the
record's validation does (one budget per face, a use's interval in its curve's natural range, a
given pcurve as a 2D B-spline), converts through solidcore-convert, calls solidcore::eval and
converts back. The trim request types (TrimFace, TrimEdge, TrimUse, Trims) hold records, so they
live here. No arithmetic: ops' float-arithmetic and geometry-method lints pass unchanged.

The hostile-record refusals the record checks give (empty and non-finite intervals, a NaN
coordinate, a circle of no radius) are tested here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Call pcurve, label and the record checks through solidcore-ops

geom, kernel, smlib and step call solidcore_ops::{pcurve, label, record_check} where they called
solidcraft_geom::brep::{pcurve, label, check}, which moved into solidcore::eval. One
implementation of each remains, in the kernel; geom's brep module no longer declares them.
solidcore-ops becomes a workspace dependency of the four crates (the seam check's public list
already allows it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move geom's record agreement check into solidcore::eval (pure git mv)

crates/geom/src/brep/geometry.rs moves unchanged to solidcore/crates/solidcore/src/eval/agreement.rs
(the kernel's eval already has a geometry module). The next commit retypes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore::eval::agreement on a plain view; ops reads a record into it

The moved check reads a record through agreement::View (edges, vertices, faces and their uses,
in the kernel's evaluation types) in place of the record's Brep, which the kernel never sees.
solidcore-ops' record_check::agreement converts a record's curves, surfaces and pcurves into the
view and calls it; Mismatch, Problem and Record keep their wording. The pole test is the
pcurve's speed rule, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Check record agreement through solidcore-ops

kernel, smlib, step and geom's tests call solidcore_ops::record_check::agreement where they
called solidcraft_geom::brep::geometry::check, which moved into solidcore::eval::agreement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Leave kernel/src/tess on geom's pcurve path until the tess move

crates/kernel/src/tess is moving into solidcore-tess in its own pull request, so its three
pcurve::eval call sites are not edited here: geom::brep::pcurve re-exports
solidcore_ops::pcurve::eval for them, and goes when the tess move switches them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* smlib export tests: call the pcurve evaluator through solidcore-ops

A test main added after the move still named solidcraft_geom::brep::pcurve, which moved into
solidcore::eval (this PR); it calls solidcore_ops::pcurve::eval like every other caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* smlib export tests: sort the imports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 02:07:49 +00:00
PeppaWutz c0f6725b6e Preferences: step arrows on the whole-number Design fields (#11829) (#11900)
* Preferences: step arrows on the whole-number Design fields (#11829)

* Preferences: a step arrow never pulls a value past its limits the other way (#11829)

* UI kit: gallery entry for the whole-number field (#11829)
2026-11-10 01:40:07 +00:00
Frank b41c817b18 Coincident: put a sketch point on an origin axis, picked in the view or the browser (#10669)
* Coincident: put a sketch point on an origin axis, picked in the view or the browser

Either Coincident pick may now be the X, Y or Z axis: clicked in the view,
clicked in the browser while the tool runs, or preselected. An axis along the
sketch's own x/y uses that axis; any other is projected (linked) into the
sketch, so the constraint stays parametric.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Dimension: a line picked with an origin axis measures their distance

The Dimension tool ignored an axis pick, so a line then an axis gave the
line's length. An axis (in the view or the browser) is now projected and
measured against the other pick. A free line dimensioned to a linked line
is also kept parallel to it, unless its direction is already set, so the
whole line holds the distance instead of tilting about one end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Coincident and Dimension with an origin axis: either pick order, the measured value, preselection and a typed value (#10669)

A line dimensioned to an origin axis takes the axis as the reference in either pick order, so
the line is kept parallel to it; the command reports what the dimension measures once solved,
found by its id, not the Parallel added after it. A selected point and origin axis go to
Coincident from the toolbar. An axis picked in the browser no longer replaces a value already
typed into the open dimension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Origin axis picks: lowercase references, a centerline first, a set angle kept, no axis for Horizontal/Vertical, an unambiguous Coincident axis (#10669)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Coincident axis: a driven angle does not count as setting the line's direction (#10669)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Merge main into #10669: world_state is a Result; an origin axis object is X, Y or Z, as main's references are

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Origin axis dimension: Horizontal/Vertical on the line's two points sets its direction too (#10669)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Axis picks and dimensions: keep old behaviour outside origin axes

A free line dimensioned to a projected edge keeps its old dimension again:
only an origin or sketch axis is the reference in either pick order and
keeps the line parallel. A driven dimension now reports its solved value
(its id was not recorded). Symmetry no longer takes an axis whose x / y a
curve or point of the sketch has as its id, as Coincident already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch Dimension: a click on an origin axis places a full dimension

Once a dimension has its two picks, a click on an origin axis was taken as
a third pick and refused, so it never placed the dimension. The axis is a
pick only while the dimension takes one; then the click places it, as a
click on empty space does (midpoint_pairs_dimension_sketch_and_projected_model_edges).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch Dimension: keep a line parallel to the axis unless its direction is fixed

A line dimensioned to an origin axis got no Parallel to the axis when it
was parallel (or at an angle) to another line, even a free one; both lines
could then turn together and only one end kept the distance. Parallel is
now skipped only when a chain of such relations reaches a line whose
direction is fixed (Horizontal, Vertical, an angle) or that is linked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 01:40:05 +00:00
Guntars Strogonovs 0f6c13a23f Latvian interface language (#6587) (#6589)
* claim: Latvian interface language

* Latvian interface language

Add lv.json with every English message translated into Latvian and register
Latviešu in the language list (English first, then by id), so it can be
chosen in Preferences > General > Language. Latvian stays out of the
COMPLETE catalogs, so a new English label doesn't need a Latvian one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Latvian: follow English messages that changed since the translation

Update 37 Latvian messages whose English wording changed after the
translation was made, including the macOS update step that now says to
quit SolidCraft first, the shorter assistant permission labels and the
Preferences page headings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Latvian: the render messages main added since

Translate the 56 messages main has added since (CPU workers, denoising
diagnostics and its mask, supersampling, texture filtering, area and
environment caustics), and follow the path depth help's new range (1–64).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: guntarsS <guntars@udlive.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 01:40:02 +00:00
Brett Lawson f5daed22c7 solidcore: retire the curve flags that change no result (A4) (#14288)
conic_evaluate_uses_computed_second_deriv and curve_max_distance_single_sample_midpoint
are removed: the code each turned on is now the only path, the field, its preset line,
its fixes.json entry and the tests' flag-off halves are gone. Conic::evaluate_with_fixes
is folded into Conic::evaluate.

Part of #14246


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 01:38:50 +00:00
Brett Lawson 949576ba8b solidcore: the kernel rig runs one process per case, in parallel, with a CPU-time hang limit (#14277)
* solidcore: claim the kernel rig's per-case processes (#14060)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: the kernel rig runs one process per case, in parallel, with a CPU-time hang limit

KERNEL_CASES_JOBS=N grades each selected case in a process of its own, N at
a time: the test re-runs its own binary for one case, the case process
hands its grade back as JSON, and the parent merges the grades in the
selection's order into the same report. A case past KERNEL_CASES_TIMEOUT_S
of CPU time (from /proc on Linux, wall time elsewhere; 600 s by default in
this mode, with a 4x wall backstop) is killed and graded a hang, and the
other cases still run. A process that crashes or ends without a whole grade
for its case is a failure, or a moved known-failing case. The processes are
killed and their files removed when the parent ends or panics; a case
process also ends when the parent's stdin pipe closes.

KERNEL_CASES_SHARD=i/n selects every n-th case from the i-th.

Unset, KERNEL_CASES_JOBS keeps today's single-process loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 01:38:46 +00:00
bherbruck f232e4a40c solidcore-tess: a face the moved mesher cannot read is left out, not the body (#14033)
* Move SolidCraft's mesher and its evaluator into solidcore (RFC 0003, item 1.1)

A move only, so history follows the files: SolidCraft's planner
(crates/kernel/src/tess/) becomes solidcore-tess's src/mesher/, and its plan
evaluator (crates/geom/src/tess.rs and tess/) becomes the kernel's
src/eval/plan.rs and src/eval/plan/, next to solidcore::eval. No content
changes; the next commits wire them in and remove solidcore-tess's own
planner, so one mesher remains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: remove its own planner and plan, replaced by SolidCraft's

solidcore-tess's planner (src/planner/) and plan (src/plan.rs) were a port
of SolidCraft's; SolidCraft's own moved in with the previous commit, so one
mesher remains (RFC 0003, item 1.1). The next commit wires the moved code in
behind solidcore-tess's public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Wire in SolidCraft's mesher and evaluator; SolidCraft meshes through solidcore-tess

The moved plan evaluator is the kernel's `solidcore::eval::plan`, in the
kernel's vectors, beside `solidcore::eval` (where the two overlap, they are
unified in a later step). The moved planner is solidcore-tess's `mesher`,
behind its public API: `plan`, `TessPlan` and `FacePlan` (SolidCraft's plan
types, now in solidcore-tess's `plan`, with its `poles` field), `evaluate`
returning the mesh and the faces left out, `PlanStats` and its records,
`MeshTolerance`, `preview` and `simplify`.

- The mesher evaluates surfaces with the kernel's plan evaluator and edge
  curves and pcurves with the kernel through solidcore-convert; it works in
  the kernel's vectors and hands the plan the record's. It keeps SolidCraft's
  text (rustfmt skips it) and allows the lints it does not meet yet, with the
  reason; a panic in planning is caught at `plan` until the next step brings
  checked access.
- SolidCraft's geom re-exports the evaluator as `geom::tess`, through one line
  in xtask/solidcore-seam.txt (until RFC 0003 item 1.8 moves model-evaluation
  callers to ops and tess queries), with converters between its vectors and
  the kernel's and its partials in its own vectors; the B-spline basis
  functions it shares with geom's splines moved into `eval::plan::basis`.
- The kernel crate meshes with `solidcore_tess::plan`; its `MeshTolerance` and
  `DISPLAY_ANGLE` are solidcore-tess's, and its worker map that only the
  mesher used is gone.
- Tests move with what they test: the planner's tests that build bodies with
  SolidCraft's kernel are crates/kernel/src/display_plan_tests.rs, the plan
  types' are in solidcore-tess, the evaluator's cross-check with geom's NURBS
  surface is in geom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kernel rig: the shared core names the mesher's new home

tests/kernel-cases/PATHS-ALL still listed crates/kernel/src/tess/**, which the
move emptied, and the rig plan's check fails a glob that matches no file. The
mesher and its plan evaluator are now solidcore-tess's src/mesher/ and the
kernel's src/eval/plan.rs and plan/; they are inside solidcore/** already, and
are named there so the move stays visible in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the kernel decides the moved mesher's poles, once per iso-line

SolidCraft's mesher, moved into solidcore-tess, told a pole by the surface's
derivative speeds at the point, evaluated afresh at every question. It now asks
the kernel's certified classifier (solidcore::eval::collapse), which decides on
the iso-line through the point, and keeps each answer under its iso-line
(direction, other parameter, reach), so the kernel is asked once per iso-line
however many nodes and passes look at it. There is one pole check: where the
kernel cannot tell, the face is refused, named with the point and the kernel's
reason, and nothing is taken for an answer. On the 1,101 kernel-case bodies,
the primitives and 26 STEP files the kernel refused no point at either
tolerance. The speed-based checks the mesher used (`degenerate`,
`degenerate_within` in eval::plan::uv) are deleted.

Pole questions and kernel classifier calls (export): coil split by a plane
(793) 1,084,906 -> 1,393; coil through a cylinder wall (4505) 996,266 ->
12,877; boss-root fillet 54,244 -> 310; twisted closed sweep 80,112 -> 562.
The 160 spline-heavy bodies mesh in 8.0 s instead of 13.2-13.7 s
single-threaded, with the same triangles; all 1,107 kernel-case bodies and
primitives keep their counts.

solidcore-tess's pole pass is back on this mesher: a pole of higher order
gets the kernel's exact limit normal per triangle, or the mean of its
triangles' normals where the kernel has none (named in
PlanStats::averaged_poles); a normal neither rule gives refuses the face in
`evaluate`. At a degenerate vertex the CPU evaluation takes the kernel's limit
normal instead of a step inside. tests/pole.rs runs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: a node just past the range is classified on it, within the face's tolerance

The kernel's pole classifier refuses a point off the surface's parameter
range, and the mesher meets boundary nodes a little past it: pcurves made to a
STEP file's uncertainty end there. A node past the range by no more than the
face's own tolerance (the largest of its edges' and vertices') is classified at
its nearest point of the range, where it lies on the face to within that
tolerance. Farther out, the face is refused with the distance named; nothing is
extrapolated and there is no other pole check.

On the 319 files of solidcore's STEP bench corpus this classifies the six
faces the classifier refused: tork's faces 217, 243, 250 and 269 (nodes at most
9.8e-7 mm off, tolerances 1e-3 mm and up), nist_ctc_02_asme1_rc's face 647
(0.113 mm, tolerance 0.119 mm and up) and nist_ctc_05_asme1_ap203's face 143
(0.034 mm, tolerance 0.051 mm and up). No face of the corpus is refused, and
the 1,107 kernel-case bodies and primitives keep their triangle counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: a face the mesher cannot read is left out, not the body

SolidCraft's mesher, moved into solidcore-tess, refused a whole body when one
face's surface did not evaluate, one edge could not be sampled or split, or one
face's boundary could not be read. Each of these now leaves out only the faces
concerned, each named with why in PlanStats::unmeshed, and the rest of the
body is meshed, as solidcore-tess's own planner did:

- a face whose surface does not evaluate;
- every face an edge bounds that cannot be sampled, or split further for a
  thin loop or a crossing boundary (an edge that bounds no face is named in
  PlanStats::unsampled_edges);
- a face whose boundary cannot be read;
- an edge whose coarser sampling for a crowded face fails keeps its nodes,
  named in PlanStats::kept_strict.

`rod_doubled_edge` (tests/step-regressions), refused whole before ("edge 7:
more than 20000 nodes"), now meshes four of its five faces and names the
fifth; nothing else in the 1,101 kernel-case bodies, the primitives or the 26
STEP files changes. Tests: that body in the kernel's plan tests, and
solidcore-tess's refusal tests on its fixtures (a face off its pcurve, a free
edge, an edge both of whose faces are left out, a label without its form).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: checked access in the moved mesher and evaluator, no panic guard

The moved mesher and plan evaluator indexed slices, arrays and Vecs directly,
within bounds they checked beforehand. That is now checked access: get/get_mut,
zips and iterators, slice patterns for windows(2), and a rotate for a
triangle's three corners. A direction index into a (u, v) pair goes through
one small helper that reads NaN out of range, so no comparison on it holds.
Arithmetic and its order are unchanged. clippy::indexing_slicing is out of
both modules' allow lists.

plan() no longer wraps plan_at in the panic guard: planning returns its
failures as errors, and the doc comment says so. The worker pool's per-item
guard in par::map is untouched.

Triangle counts are identical, body for body, on the 1,107 kernel-case bodies
and primitives, at both the display and export settings. solidcore-tess tests
(62 passed, 2 ignored, plus tests/pole.rs) and solidcore eval:: tests pass;
clippy -D warnings is clean on solidcore, solidcore-tess and diagnostics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: ask the moved mesher's pole question once per node

The sag refinement and the flips ask, at every triangle corner, side and
round, whether a node is on a pole, and the self-crossing check asked it twice
per node, once for each chord the node ends. Whether a node is on a pole is a
property of its parameters: a `sag::Poles` memo, shared by refinement and the
flips of a face, asks it once per node, and `tangle::crossing` keeps each
node's answer for both its chords.

This is #13559's fix for solidcore-tess's own planner, which the move replaced,
carried over to the moved mesher with its test
(`the_pole_question_is_asked_once_per_node`: 27,452 questions for 349 nodes
without the memo, at most two per node with it). Triangle counts are unchanged
on the 1,107 kernel-case bodies and primitives, and the coil's test
(`the_spline_coil_keeps_its_triangles`) still passes.

Ported from #13559 by @bherbruck (co-authored by jyviko).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: drop the plan reports SolidCraft's mesher never writes

`PlanStats::over_tolerance` (faces a memory bound limited) and
`PlanStats::edges_capped` (edges a sampling bound stopped), with their record
types `OverTolerance`, `Cap`, `CappedEdge` and `EdgeCap`, came from
solidcore-tess's own planner, which the move replaced. SolidCraft's mesher has
no such reports, so after the move they were always empty, read as "nothing
was limited" whatever happened. They are removed; nothing reads them. The
other fields are written: the face counts, `lenient` and `unmeshed` by the
mesher, `averaged_poles` by the pole pass, `unsampled_edges` and
`kept_strict` by the per-face refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: keep the over-tolerance and capped-edge reports

This reverts 0836c4b02f. PlanStats::over_tolerance and PlanStats::edges_capped
are solidcore-tess's own reports and part of its public API; the next commit
wires them into the moved mesher instead of dropping them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the moved mesher reports capped edges and faces left over tolerance

SolidCraft's mesher stopped at its bounds without saying so: an edge's
sampling at its depth (24 halvings) or node bound (20,000) with a chord still
wanting a split, a face's grid thinned to fit its point bound, a face's sag
refinement at the points it may add. These now fill solidcore-tess's own
reports:

- `PlanStats::edges_capped`: the edge, the bound, the farthest its chords lie
  from its curve, how far its chords exceed their rules, and whether it is
  within the tolerance. A capped segment is the only one whose rules are now
  checked at the bound; the nodes are the same.
- `PlanStats::over_tolerance`: a face whose grid was thinned or whose
  refinement ran out of points, with the bounds and the sag its final
  triangles reach (measured as refinement measures it, at each triangle's
  middle and sides' middles), and how many middles could not be measured.

Tests, ported from solidcore-tess's own planner:
an_edge_stopped_at_its_depth_bound_is_named_within_its_tolerance and
a_face_whose_refinement_reaches_its_bound_is_named_with_its_sag. Triangle
counts are unchanged on the 1,107 kernel-case bodies and primitives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore: no panic-prone calls in the moved plan evaluator

f64::clamp panics on NaN bounds, so Domain::clamp and wrap_into take max then min. The
derivatives and starting-grid vectors grow as they are filled instead of preallocating a
product that could overflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: keep the reasons a face is refused for

- A face whose pole question the kernel could not answer is refused with that reason even
  when planning then failed on it: the refusal is read before the plan's own error, which may
  be a later symptom ("boundary crosses itself").
- A node the kernel's evaluator refuses (other than a pole node, which has its own rule)
  refuses the face in TessPlan::evaluate with the node and the reason, instead of taking the
  plan evaluator's normal, which can be made up where the derivatives do not evaluate.
- The poles' range check takes max then min: f64::clamp panics on a NaN bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:52:02 +00:00
Yeonghun Lee 9d6e8f00aa Assemblies: a ball joint at rest tilts toward a drag in every direction (#12873) (#13385)
A ball joint's yaw and roll turn about the same axis at pitch 0, so the
reach solver found no way to tilt it sideways. Reach now solves a ball
joint over a small turn from its start pose (a quaternion), converts it
back to yaw, pitch and roll nearest the start, and holds each within its
limits, so pitch limits act as a cone.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:36:27 +00:00
ancient_astronaut_theorist 6ba3988166 Exploded views: a part inside a turned component moves in the direction given (#13939) (#13948)
* claim: #13939

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* Exploded views: move a part inside a turned component in the direction given (#13939)

An exploded view stores each move as a world translation, but an
occurrence's placement is relative to its parent component, so a part
inside a turned component moved along the component's axes: after turning
Frame 90 degrees, a move of 80 mm along x sent its Pin along y.

explode_moves now turns each translation into the parent's frame before
it is applied, so showing a view, its animation and the check for a shown
view all move the part the way the view gives. Top-level parts, and so the
automatic explode, are unchanged; a component placed more than once moves
its parts the way given in its first placement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:17:55 +00:00
DasWookie 0fb4bdc6f7 feat(ci): add automated build and atomic deployment scripts for Windows and Linux (#13548)
* feat(ci): add automated build and atomic deployment scripts for Windows and Linux

* feat(ci): add automated build and atomic deployment scripts for Windows and Linux

Summary of Changes:
This PR introduces cross-platform automation scripts (update-solidcraft.sh for Linux and update-solidcraft.ps1 for Windows) designed for local scheduled task runners (such as cron or Windows Task Scheduler).

Key Features & Improvements:
- Isolated Worktrees: Fetches and compiles updates inside a temporary detached Git worktree, leaving the operator's active checkout and uncommitted changes completely untouched.
- Incremental & Force Builds: Tracks commit and configuration state under target/.solidcraft-build-state to skip redundant builds, with an explicit -ForceBuild / --force override.
- Independent Target Toggles: GUI (solidcraft), CLI (solidcraft-cli), and Web (solidcraft-web) can be enabled or disabled independently.
- Reliable Deployment & Backups: Uses staging files and backup rotation under target/<profile>/backup/ prior to binary replacement.
- CodeRabbit Review Hardening:
  * Aligned Linux BUILD_WEB default to false.
  * Corrected WebAssembly artifact paths to target solidcraft-web.wasm.
  * Added strict error checking for git fetch operations and improved PowerShell binary swap rollback safety.

Validation:
- Test Suite Verification: Ran cargo test --workspace successfully across environments.
- Deployment Resilience: Simulated locked-file scenarios and cron/Task Scheduler runs to verify clean fallback and error propagation.

* docs: remove sudo examples and clarify non-privileged web deployment paths

* refactor: address CodeRabbit feedback on locking and remote fetch logic

* Update scripts/update-solidcraft.ps1

Resolves cleanup when force build is used a bit better and will clean up with various cargo artifacts.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(windows): align test failure handling and exit code with Linux script

* fix(windows): protect Wasm target dir and check WebInstallDir overlap

* fix(windows): validate WEB_INSTALL_DIR overlap before any forced profile cleanup

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-11-10 00:17:52 +00:00
thocsmith 0610e9875a Export View as DXF: a camera without fov exports an orthographic view instead of being refused (#13112) (#13137)
* claim: #13112

* Export View as DXF: a camera without fov is an orthographic view, as the docs say (#13112)

file.export_view_dxf read its camera into the render Camera type, whose
fov field had no serde default, so a camera without fov was refused with
"invalid camera". docs/mcp.md says a camera without fov is orthographic.

Camera::fov now defaults to 0 like roll and up, and the view commands'
Camera schema lists fov as optional with a default of 0, so the schema
and the reader still agree. Saved render cameras (SavedCamera) still
require fov.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:17:30 +00:00
dudedagger42 2e0f0dcd23 Sketch offset: param and param2 name the driving dimensions (#12295) (#12912)
* Sketch offset: tests for naming the driving dimensions (#12295)

They fail on main: `param` comes back as d1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch offset: param and param2 name the driving dimensions (#12295)

sketch.offset documented `param`/`param2` but never read them, so the
dimension was always dN. Name it as asked; refuse a taken or invalid name,
the same name twice, and param2 without distance2.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch offset: refuse a non-string param name; a chosen param2 is made before a generated one (#12295)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 00:17:27 +00:00
Ahmad Khan bdff77283b Sketch: a Line started on an arc or circle snaps tangent to it (T) and is constrained tangent (#12179) (#12405)
* claim: #12179

* Test: a Line started on an arc or circle snaps along its tangent (#12179)

* Sketch: a Line started on an arc or circle snaps along its tangent there (#12179)

The Line tool's tangent inference worked only from a point outside the
curve. From a point on an arc (its end) or a circle, the cursor near the
curve's tangent direction now snaps to it with the T hint, and placing the
point adds the Tangent constraint, as the existing tangent snap does. A
harness test draws a line off a 45-degree arc end, where no H or V snap can
stand in.

---------

Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
2026-11-10 00:17:24 +00:00
P'tite Tête 30968f1572 Components: a point or direction written {x, y, z} maps into a moved component like [x, y, z] (#12297)
* fix: A point or direction written {x, y, z} maps into a moved component like [x, y, z]

- Mapper::make reads the object form that point_param accepts, keeping expressions
- Mapper::dir and the axis direction read both forms with params::vec3
- test: object-form revolve axis origin (numbers, expressions) and direction, moved and turned

* fix: A pick written {x, y, z} sets the frame a direction turns with

- Mapper::pick reads both forms with params::vec3, so solid.hole's direction turns with the picked body (CodeRabbit)
- test: an object-form hole in a turned component picked from the root

* fix: A pick written {x, y, z} in a list maps into a moved component

- Mapper::picks takes a {x, y, z} element as a pick, as it takes [x, y, z]
- test: a fillet edge picked with {x, y, z} in a moved component

* test: The object-form hole is saved where and as the array-form one is

---------

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-11-10 00:17:21 +00:00
phanoob ea3351400b Measure: a picked face shows its perimeter length, with outer and inner lengths when the face has holes (#12048) (#12272)
* claim: #12048

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Measure: show a picked face's perimeter, split into outer and inner on a face with holes

The Measure tool and the quick measure readout showed only the area of a
picked face. They now also show its perimeter: the length of the visible
edges around the face, where each edge counts what Measure shows for that
edge on its own (r x angle for a circle or arc). A flat face with holes also
shows its outer perimeter and the total length around its holes.
inspect.measure returns the same values as perimeter_mm, outer_perimeter_mm
and inner_perimeter_mm.

Fixes #12048

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Measure: find a face's outer loop from boxes built in one pass

Each loop's box is now gathered in a single pass over the border points,
instead of rescanning every border edge for each loop the comparison
looks at. A face with many holes no longer pays loops x points to find
its outer loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Group a face's border loops by their exact shared vertex points

The kernel ends every edge polyline on its B-rep vertex points, so two
edges of one loop share an end to the last bit. A bounds-scaled
tolerance could join a hole to the outer loop where they pass close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Measure tests: use the flat_face_at helper the area tests already have

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:17:19 +00:00
railconstr 94663f8db0 Sketch: type an offset while placing parallel, horizontal, and vertical construction lines (#10076)
* Sketch: type an offset while placing parallel, horizontal, and vertical construction lines

A locked Offset box places the line at that distance and stores it as a driving dimension. Parallel lines get a point-to-line distance from the picked line; horizontal lines get a Y distance from the sketch origin; vertical lines get an X distance from the origin. The side follows the cursor, and editing the parameter later keeps that side.

* Sketch: a changed construction offset keeps its reference and reaches the new distance

Editing the parameter a point-to-line distance names holds that line, so the offset point takes the new distance instead of tilting the reference. The axis and parallel hints are translated for the complete catalogs.

* Sketch: a typed construction offset stays an infinite line

The field beside the pointer is the distance from the reference. Typing it places an infinite construction line at that distance, with the driving constraint in the same undo step, and does not pin the click as a segment start. The length box remains the control that makes a dimensioned segment.

* Sketch: keep a typed length as the construction default

A missing or null offset is through the click, and an empty Offset box no longer blocks placement. A bare digit still fills Length, so two clicks draw a dimensioned segment; Offset takes a digit only after that box is focused. Parameter edits hold a construction offset's reference, and an expression match that holds nothing no longer hides a later direct dimension.

---------

Co-authored-by: railconstr <335242259+railconstr@users.noreply.github.com>
2026-11-10 00:17:17 +00:00
SilentWolf75 066b3c8ab8 Dragged Distance and Radius fields follow the Length decimals setting (#9035) (#9039)
* claim: #9035

Co-authored-by: Cursor <cursoragent@cursor.com>

* Round dragged Distance values to Length decimals (#9035)

Snapped arrow/extrude/fillet drags wrote raw f64 text (1.2000000000000002 mm). Format through design_length micrometre clean-up and Length decimals (never coarser than the snap step). Also covers #9036.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): only snapped drag writes use Length decimals (#9035)

Restore full-precision model() for computed field writes; keep model_drag for snapped arrows. Update the extrude selection test for padded drag text.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Keep the exact depth when the extrude arrow stops on a face

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:17:14 +00:00
Armin 02e6a492cb Drawing: format tolerances and dimension text (#6782) (#6788)
Co-authored-by: Armin <192400139+armin0411@users.noreply.github.com>
2026-11-10 00:17:12 +00:00
clocker 7830a6aa8a Sketch Project: an edge saved between numbered faces of an older design is found again (#13164) (#13175)
* Sketch Project: an edge saved between numbered faces of an older design is found again (#13164)

0.30.1 split each round face of a revolve into numbered pieces, so a projected
edge was saved as `F4:side:l6#2|F4:side:l7#2`. The faces now evaluate whole and
the same edge is `F4:side:l6|F4:side:l7`, so the sketch failed and everything
built from it. A Sketch Project edge link now finds it by the whole faces' name,
only when the saved point lies on the edge and the faces have no other piece;
other edge references keep the stored name as picked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch Project: tell whole faces apart by name, not by the mesh (#13164)

The edge is verified by its name, so the faces it separates are the
whole stems of that name: check each has one face, with no mesh lookup
(mesh-decisions refused `.mesh()` here). Suggested in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:16:45 +00:00
Brazz 76a410a6b2 fillet: a round's rail rolls over a smooth far edge of its face, the whole length (#14136) (#14137)
* test(blend): rounds whose ball rolls over a smooth far edge of their face, the whole length (failing)

Concave: an L step, a wall standing on a strip whose far rim is rounded first, the wall's foot
then rounded larger than the flat the rim's round leaves, so the ball rolls off the flat onto the
rim's round along the whole edge and the strip is consumed. Convex: a block whose top's front rim
is rounded first, the back edge then rounded larger than the flat left, the ball touching the
rim's round from inside. Today both are refused (the rail leaves its face).

Expected: valid at CheckLevel::All. Concave: the volume the closed form of the section (Green's
theorem over its arcs, checked against a polygon) to 1e-6. Convex: every face the closed form to
1e-9 (the round's area and centroid, the rim's round's band, the back face), and the volume within
the end faces' trims' tolerance. Controls (the round within the flat; no rim round) build today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyVCQEUJSVmETqNuK5e

* fix(blend): a round's rail rolls over a smooth far edge of its face, the whole length

A rolling ball's support on each side is a chain of faces meeting smoothly, not one face. Where
the face beside a lone constant round is narrower than the ball reaches and its far edge (running
the filleted edge's whole length) is smooth to the face beyond, the solver now rolls the ball on
the face beyond before its trace: that side's offset surface is the face beyond's, the rail is
traced, classified and trimmed on the face it lies on, and the face it rolled off is consumed.
The end corners solve such a rail's end on the next side edge, across the rolled-over edge's
vertex, as a rail continues across a seam's vertex. Before, the ball rolled on the face continued
and the round was refused as a blend leaving its face.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyVCQEUJSVmETqNuK5e

* fix(blend): a rollover onto a face whose offset by the ball folds is declined

A convex round larger than the convex round beyond the face beside it cannot roll onto that
round: its ball, inside the material, would have to fit inside it. The rollover now checks the
face beyond before taking it: the face's offset by the ball's signed offset must not fold across
the edge (1 - kappa d > 0, kappa the face's normal curvature square to the edge). When it folds
the side is left to the refusals as before, so a round too large for its flat is refused as too
large with the flat as its fit (the crease trim's CreaseAcrossTangentEdge where that applies),
not sent down the rollover to fail there with BallDoesNotFit, NoRoom or a bare 1001.

Restores wr696_fit_sizes::a_round_onto_a_tangent_round_fits_where_its_rail_stays_on_the_top
(a large front round on a block whose back edge is rounded smaller). New test: two convex rounds larger
than the round beyond, refused with the flat as their fit (failed before: NoRoom, 1001).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyVCQEUJSVmETqNuK5e

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:16:05 +00:00
JSwetz b73b603b53 Kernel: report knots in correct order and interval for reversed offset curves (#13962) (#14030)
* claim: #13962

* Kernel: report knots in correct order and interval for reversed offset curves (#13962)

Co-authored-by: Antigravity <antigravity-agent@google.com>

* Kernel: preserve boundary knots for reversed offset curve queries (#13962)

Expand base query interval by EFF_ZERO to guard against floating-point roundoff dropping boundary knots when mapping reversed query intervals. Filter mirrored knots directly in the target interval.

Co-authored-by: Antigravity <antigravity-agent@google.com>

* Bless curve.offset_curve golden for reversed knot order (#13962)

OffsetCurve::knots now mirrors and orders knot sequences and filters against the mapped query interval when reversed. 16 generated test cases (gen0073, gen0078, gen0237, gen0441, gen0480, gen0605, gen0680, gen0701, gen0791, gen0969, gen1274, gen1381, gen1406, gen1670, gen1673, gen1742) in curve.offset_curve changed outputs to reflect the correctly ordered, mapped knots and multiplicities on reversed curves.

Co-authored-by: Antigravity <antigravity-agent@google.com>

* Avoid panic-prone Vec::with_capacity in OffsetCurve::knots (#13962)

Replace explicit Vec::with_capacity and manual push loop with iterator filter_map and unzip to satisfy the zero-panic site check.

Co-authored-by: Antigravity <antigravity-agent@google.com>

---------

Co-authored-by: Antigravity <antigravity-agent@google.com>
2026-11-10 00:15:48 +00:00
jyviko 14b5da363d UI kit: icons, glyphs, menus and tests no longer reach into the app (part of #14251) (#14319)
* frame: publish the drag band through the registry; dialog tests to the host

frame.rs called `crate::scenario::publish_handle`, the app's re-export of
`solidcraft_ui_registry::publish_handle`; it now calls the registry directly.
Its tests drive the app (Harness, dialogs), so they move to the host module
dock_dialog_ui_tests.rs unchanged. frame.rs now uses only egui and the
registry, ready to move with the kit (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Menus: the list-menu drawing moves into the kit's menu module

`menu_frame`, `button`, `button_enabled`, `list_row` and `list_row_lit` drew
kit rows from `context_menu.rs`; they now live in `ui_kit::menu` with the same
signatures, and the Item-to-row mapping is `MenuItem::from_item`. context_menu
keeps SolidCraft's menu contents and dispatch and calls the kit's drawing.
Every caller uses `crate::ui_kit::menu::…`. The i18n literal scanner reads
`menu::button`/`button_enabled` as kit calls, so its context_menu carrier rule
goes (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit gallery: move it out of ui_kit into the host

The gallery draws the kit with SolidCraft's data (tab icons, the workspace
and radial menus, a sample app), so it is now the host test module
kit_gallery.rs, same elements and test names. The kit opens what it reads:
`file_view`, `preferences_cards`, `preferences_figure_data`,
`preferences_figures` and the test fixtures become `pub(crate)` modules, and
`Window::content`, `Confirm::content`, `panel::page_list`, `card_look` and
`card_width` become `pub(crate)` (#14251).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Format the moved gallery and dock dialog tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* UI kit: draw icons from a host-installed icon set

The kit ships no artwork. Its renderer (ui_kit::icons) parses and
rasterises SVG masters from an IconSet the host builds from its own
tables (colour roles, palette, accents, colour-vision variants, masters,
aliases, default accents, the danger ramp) and installs once per process,
like the message catalogs. With no set installed the painters draw
nothing; an unknown name draws the set's placeholder master, as before.

SolidCraft keeps compiling its masters in build.rs into icon_svg::SET
and installs it in both app mains, in SolveApp::new and in
fonts::test_context. icon_svg and icons keep their names as SolidCraft's
lookups and re-exports, so host call sites are unchanged. The renderer's
internal test moves to the kit on a tiny test set of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* UI kit: kit and components draw icons through ui_kit::icons

The kit modules, the ribbon, the application bar and the view compass
call the kit's own icon painters and primary-stroke lookup instead of
reaching into SolidCraft's icons and icon_svg modules. Only the paths
change; the code-drawn glyphs (assets/icons/ui/source) are still reached
through crate::icons until where they live is settled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Sphere tile: size the name's label from a laid-out row

The tile reserved two rows for the material name from the font's raw row height, while layout
rounds each row to the pixel grid. With egui's default faces the two-row name (26.0) overflowed
its label (25.3); a laid-out row gives the height the galley will have, whatever face the host
installs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit tests: run on a kit test context; tests of SolidCraft's use move to SolidCraft

The kit's and the components' tests no longer reach the app (#14251):
- `ui_kit::test_support::context()` is egui's default faces with the kit's weight families bound
  (`type_face::install_weight_families`), no Plex, no icon set. It still installs the English
  catalog through `crate::i18n` (asked in B-1). The generic frame, pointer, key and shape helpers
  move there; `crate::test_support` re-exports them for SolidCraft's tests.
- Kit tests read the widget registry from `solidcraft_ui_registry` instead of `crate::scenario`.
- 35 tests that drive the whole app (scenario::Harness or SolveApp) move unchanged:
  19 Preferences search tests to `prefs::settings_search_tests`; the Preferences pages/footer and
  recent-colour persistence tests (3) to `prefs::kit_use_tests`; 6 universal panel chrome tests
  to `panel_runtime/tests.rs`; the save prompt frames test to `prompt_cards_tests.rs`; the
  dialogs/Preferences/menu checkbox parity test to `ui_kit_guard_tests::checkbox_in_use`; 5
  navigation bar tests to `navigation_bar_view::tests`.
- 3 tests that compare against SolidCraft's icon artwork (the host's set) move unchanged to
  `ui_kit_guard_tests::disclosure_artwork`: the two disclosure tests and the panel disclosure
  crossfade.
Path-only edits in moved bodies: `super::super::checks::tests::*` and `ui_kit::panel_host::*`
become the `crate::ui_kit` re-exports.

Expected values changed: none (notes/B-expectations.md). The two tests that installed
SolidCraft's fonts directly (layout label column, growing form) use the kit context and keep
their expectations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit gallery: name the host module kit_gallery_tests

It is a test-only module, so it follows the crate's `*_tests.rs` naming; the
source guards that skip the gallery by name (colour literals) skip it again.
docs/ui-kit.md names the new module path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Colour guard: the menu swatch literal moved with the menu drawing

The one colour literal context_menu.rs was allowed (a menu entry's swatch,
labelled as the radial menu's shadow) now lives in ui_kit/menu.rs with
`MenuItem::from_item`; the allowance moves with it, same count, with the
right reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Colour guard: the icon renderer's literals moved into the kit

The white opacity tint moved from icons.rs to ui_kit/icons/mod.rs with
the painters, and the role colour conversion (the installed set's own
palette data) now lives in ui_kit/icons/svg.rs: the allowances move
with them, one each. The renderer's tests are named svg_tests.rs so the
guard skips them like every other test file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* UI kit: code-drawn glyphs are host-supplied too

The kit and components drew SolidCraft's code-drawn glyphs (transport,
window caption, badge, pin, cross, breadcrumb, compass axis tip, agent
pointer, brand mark) straight from assets/icons/ui/source. They now call
ui_kit::icons::glyphs, which dispatches to a Glyphs table of painters
(and the compass hit radius as data) the host installs beside its icon
set; before that every glyph draws nothing. The caption type is the
kit's own window_chrome::Caption.

SolidCraft fills icons::GLYPHS with its existing painters (nothing under
assets/ moves) and icon_svg::install installs both. The host's scale-1
axis_tip wrapper is gone; its two tests call axis_tip_scaled at 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit gallery: kit specimens back in ui_kit, SolidCraft's in the host

The kit's gallery (ui_kit/gallery.rs) holds every specimen drawn from plain
data and moves with the kit; the radial and workspace menus and the settings
tab chips there are drawn from plain items and icon names. The host's
kit_gallery_tests.rs keeps what needs SolveApp: the radial and workspace
menus from context_menu, the history strip and browser, the continuity rows,
the command-search, menu, artwork and File view boards. It checks its
elements with the kit's `check_elements`. card_look and card_width are
pub(super) again; their tests stay in the kit gallery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit tests: the kit's own English test catalog; translated tests move to SolidCraft

Message catalogs are host data, like fonts and icons (B-1). The kit test context no longer
installs SolidCraft's catalogs: `ui_kit::test_support::install_catalog()` installs the kit's
English test catalog (`ui_kit/test_support/catalog.json`), holding the one id the kit's tests read
(`ui.expand`, "Expand"), with the same English. The prefs window tests drop their own
`crate::i18n::install()` and use it.

Tests that assert SolidCraft's catalogs move to SolidCraft (12):
- `i18n_ui_tests::kit_translation`: German labels translated once, Japanese segment options,
  the theme notice fitting German text.
- `fonts::cjk_loader_tests`: the four CJK font loader tests (SolidCraft's languages decide CJK).
- `prefs::settings_search_tests`: four search tests over French, Hebrew, Arabic and every
  locale's Preferences labels.
- `navigation_bar_view::tests`: the slot descriptions in English, Japanese and Chinese.
They run on `crate::fonts::test_context()` as before. Only `a_label_is_translated_once` changes
its body: it reads the label a row paints instead of calling the kit-private `indented_label`,
with the same three expected strings. No expected value changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Kit gallery: every gallery item is test code, as the module is

The font-literal guard reads every ui_kit item not marked as test code. The gallery module is
test-only, but the items made pub(crate) for the host's gallery test lost their own
#[cfg(test)], so their specimen sizes were read as form metrics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 00:01:41 +00:00
0rdnungsamt fa2c8cfa45 Section Analysis: a slider on Distance moves the cut live (#14013)
* claim: #12569 (Section Analysis Distance slider)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Section Analysis: a slider on Distance moves the cut live

Once a plane is picked, a slider under the Distance field spans the
model's extent along the plane's normal. Dragging writes the field, so
the existing live preview follows and a typed expression is left alone
until the slider is touched. A value typed outside the model widens
the range instead of being clamped.

Part of #12569.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:53:59 +00:00
0rdnungsamt 9215f965ee Agents and scripts: Press Pull and Replace Face describe their exact parameters and reply (part of #2829) (#13380)
* claim: part of #2829 (Press Pull, Replace Face schemas)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agents and scripts: Press Pull and Replace Face describe their exact parameters and replies

solid.press_pull and solid.replace_face get typed input contracts and
output schemas, so strict parameter checking refuses unknown keys and
agents can read each command's exact shape. Missing or malformed values
keep the handlers' own error messages. Both rows leave unconverted.txt.

Part of #2829.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:53:56 +00:00
bherbruck cadcb89f9d Make the insert-design UI test wait for the insert instead of assuming three frames (#14367)
The test pressed Insert Design for a saved part and then ran exactly three frames before asserting the linked component existed. The file pick and insert finish on later frames, so on a loaded merge-queue runner the assertion ran too early and failed a whole batch. It now polls for the body like the STEP half of the same test already does.

Changes a default: no

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 19:49:51 -05:00
0rdnungsamt 46df489abc Mouse gestures: the standard views and Look At can go on the ring of tools (#12406) (#14016)
* claim: #12406

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mouse gestures: the standard views and Look At can go on the ring of tools (#12406)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mouse gestures: Add a tool keeps its automatic choice with the views on offer (#12406)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:17:27 +00:00
SS-EN 235ce58358 Joint sweep: stop at a time or work budget with what it found, leave out known pairs, name pairs by occurrence (#13923)
* Joint sweep: stop at a time or work budget, exclude pairs, name pairs by occurrence (#5963)

joint.sweep takes budget_s and returns stopped_by: budget with the last
value checked clear instead of failing on its work caps; joint.sweep,
joint.find_limits, motion.interference and inspect.interference take
exclude_pairs; reported pairs carry a_part/b_part places. Contact drives
stop at their budget only when asked (stop_at_budget).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Format the #5963 changes with rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:17:25 +00:00
dudedagger42 c251fd5a9b Align: To Snap Onto Plane sets the bodies on the To face's plane, along its normal only (#13476) (#13477)
A planar face picked as To went in as to_face, which brings the From point
onto the picked point, so the part slid across the face as well as onto it.
With To Snap Onto Plane (bodies only) the face goes in as to_plane {origin,
normal}: the bodies turn to meet it and move along its normal only. A To
that is not a planar face or a plane is refused.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:17:22 +00:00
dudedagger42 bef796cbbe Sketch on an extrude cap saved by 0.30.1 keeps its plane when a join left the cap uncertified (#13323) (#13326)
* Test: a sketch on a cap the join left uncertified is found by its name (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Test: import the plane trait (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Test: report the first feature that fails (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Test: the fixture carries its parameters (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* A sketch attached to an extrude cap by 0.30.1 keeps its plane when the join left the cap uncertified (#13323)

When the cap certificate finds no face, the attachment falls back on the face name it also
stores, with guards: the name finds a face made by the certified extrude, a saved point lies
on it, it still lies in the saved plane, and no body still has the certified cap.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Name the Vec3 type in full (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Test: pin the origin and x direction of the found plane (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Copy the saved plane instead of cloning it (#13323)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Old-cap fallback: an offset body stays lost

The fallback compared the cap with the body as built, before its offset, and
returned the saved plane. A body offset along the cap's own plane passed every
check and left the sketch at its old place. An offset body now keeps the
explicit lost-face error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 23:17:01 +00:00
Daniel e4f3656621 Component commands: a feature named like a number is that feature, not the one with that id (#12327) (#12328)
* Component commands: tests for a feature named like another feature's id

move_features and browser.reparent given "2" act on the feature with id 2 even when another feature is named "2", and move_sketches.to_parts looks at the sketch numbered 2. These tests fail on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Component commands: a feature named like a number is that feature, not the one with that id

component.move_features, browser.reparent and component.move_sketches.to_parts read their feature keys with name_or_id_values and resolve them as the timeline commands do since #9748: a string is an exact name first, a number is an id. name_or_id_list, which turned every key into a string and left the lookup to try an id first, has no callers left and is removed.

The browser's Move Sketches to Their Parts item sends the sketches' ids as numbers: as strings they would now be read as names first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* component.move_sketches: tests for a sketch named like another sketch's id

component.move_sketches given "2" moves the sketch with id 2 even when another sketch is named "2"; that test fails on main. Two more pass today and guard the command's callers: Move Sketches to Their Parts moves the sketch it was given, and dragging a sketch onto a part moves the dragged one, when another sketch is named like its id. The to_parts test shares a fixture with the existing one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* component.move_sketches: a sketch named like a number is that sketch, not the one with that id

component.move_sketches reads its keys with name_or_id_values and resolves them as the other component commands now do: a string is an exact name first, a number is an id. Its two callers that passed ids as strings send numbers: Move Sketches to Their Parts, and the browser's drag of sketches onto a component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move Sketches to Component: the parameter check takes sketch ids as numbers, as the handler now does

Main typed component.move_sketches' sketches as names only; this PR's handler reads names and ids (#12327), so the check and the schema follow it, with the handler's messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:16:59 +00:00
Homero Thompson 57146b872f Sketch: an intersected face's section keeps its link (#13471) (#14040)
* Sketch: an intersected face's section keeps its link (#13471)

A face taken with `Intersect` is the face's section with the sketch plane: an open chain of
segments, not a closed boundary. The refresh that follows closing the sketcher read every
face link as a projected boundary, and the closed-boundary gates never accept an open
chain, so the link was marked lost and the sketch stopped following the body.

The closed-boundary rules now apply only to links whose geometry is a projected boundary:
`Project` and `Include` on a face. An intersected section keeps its link across a refresh
and follows the body when it changes.

https://github.com/tangency-labs/solidcraft/issues/13471
2026-11-09 23:16:57 +00:00
dudedagger42 feb8a6ac11 Loft: two sections of lines with flat sides build planar side faces, so the lofted hopper converts to sheet metal (#13331) (#13546)
* test(loft): a loft between parallel squares has planar sides and converts to sheet metal (#13331)

Kernel: lofts of a 110 mm square to a 200 mm square and to an equal square,
120 mm apart, have six plane faces and the frustum's volume. Engine: the
reporter's hopper (loft, shell 1.5 mm open at both ends, Convert to Sheet
Metal with three corner bends) converts, and the prism has six planes. Both
fail on main: the skin gives B-spline walls, counted as "other".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(loft): a two-section loft of lines with flat sides is built from planar faces (#13331)

The skin primitive returns every wall of a loft as a B-spline surface, flat
or not; in the record that is a spline surface, which nothing downstream
reads as a plane: inspect.measure counts it as "other" and Convert to Sheet
Metal refuses it as curved. A plane may not carry a NURBS form in the
record, and a tapered duct's sides are trapezoids that no plane
parameterises affinely, so the walls are built as planes rather than
relabelled: `planar_loft`, for two sections of lines in equal number, each
one loop, every side quad planar within 1e-9 of the loft's size, makes the
sides as planar faces on their 3D lines and the sections as the caps, then
stitches and orients them, as the pyramid loft does. Every other loft goes
to the skin as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* style(loft tests): rustfmt (#13331)

The test module's mount in alphabetical order, three long lines wrapped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 23:16:34 +00:00
Brett Lawson af58e10ba3 solidcore: boolean failures carry a typed reason and cause chain instead of bare statuses (part of #13790) (#13988)
* claim: sc#13790 boolean slice of the typed errors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: Boolean failures carry a typed reason and cause chain, and is_port_failure follows it (part of #13790)

The Boolean operation no longer reports bare SMLib statuses. Its stage
failures return SolidError::Failed with a solidcore operation path and a
phrase, or a typed BooleanFailure (error/boolean.rs) for the relate, imprint,
split, close-loop and coincidence stages and the opt-in union solid-leak
refusal. The comparison status each site recorded stays the same (1006 for
what was null_pointer, 1001 for sm_err), so difftest and the goldens read the
same numbers. The collapsed tree failure keeps its cause through failed_after.

SolidError::is_port_failure now follows the cause chain, and solidcore-ops
session::answer uses it: a status error whose chain holds an unported call,
a missing preparation or a cancellation fails the call instead of being
skipped as an answer.

The SMLib names in the Boolean operation paths and messages are replaced
by solidcore names. The boolean goldens units read statuses through
sm_status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore tests: Boolean failures are read through sm_status, kind and operation, not through the Upstream variant or status text

Eight integration tests matched SolidError::Upstream or the text of a
Boolean error ("upstream status 1001", "code: 1001"). They now read the
comparison status, the operation path and the kind, which the typed errors
keep. The session answer test uses is_err.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* smlib: boolean failures keep their SMLib status and the ops answer rule is shared with the app (part of #13790)

solidcore now reports Boolean errors as typed `Failed` errors with a cause chain, so two app-side
readers of the old `Upstream` shape changed behaviour:

- `smlib::core::answer` skipped any error with an SMLib status, including a status error whose
  cause chain holds an unported call or a cancellation. It now applies the rule of solidcore-ops'
  `session::answer`, both through the new `SolidError::is_status_answer`.
- `blend::status` turned `Upstream{code}` into `Error::Status(code)`; a Boolean failure reaching it
  from slice or sweep fell through to a message. A `Failed` from a `boolean::` stage is again the
  status it stands for, and one with unported code in its chain is not supported.

Tests: the two rules in boolean_tests.rs, mirroring the session test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* goldens: sweep.topology_sweep reads a typed failure's status, so Boolean failures inside the whole-B-rep sweep keep status 1001

The runner's status mapping had no arm for SolidError::Failed, so the three
non-manifold whole cases whose Boolean step now fails with a typed
BooleanFailure reported -1 instead of the SMLib status 1001.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:00:42 +00:00
bherbruck c95739a354 CI: check translations without building the app in the format job (#14314)
* CI: the format job no longer builds the app to check translations

The "Format and repository checks" job ran `cargo xtask i18n --check`, which built a release solidcraft-cli (now with the vendored solidcore kernel) to read the command registry: 514 s and 788 s on two runs today. Every pull request and merge-queue batch waits on that job.

The check is split. The format job runs `--no-registry` (catalog coverage only). The Native Linux tests job, which already builds the workspace in the ci profile, builds the CLI there and runs `--cli <binary>` for the command labels, when a package that feeds the command table is selected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n check: say command labels were not checked under --no-registry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:00:39 +00:00
bherbruck b9aa35e45e blend: the kernel returns the chamfer fit and refuses overlapping setbacks; ops computes neither (#3145) (#13569)
Rebased onto main (its net change from 8ffb97d541, three-way per file).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 23:00:36 +00:00
Brazz 0dc6aee6d9 Fillets: a round at the foot of a boss a few degrees off square builds again, keeping no exact definition where it cannot be certified (#14000) (#14138)
* blend: a round its certificate cannot prove keeps no definition and is built, as #13695 describes (#14000)

A round's exact definition is certified to rounding. Where the certificate cannot prove
the net on its class's surface but the net lies within the body's tolerance of it, the
round is built and keeps no definition (#13695); only a net further off is an error.
`keep_round_if_proved` did that. Since #13900 `keep_definition` calls
`round.recertify(..)?` instead, for the circle class's certificate on its own torus, and
any other class's failed certificate refuses the fillet.

A round of the canal class depends on it: an offset plane cut by an offset cylinder not
square to it, so the spine is an ellipse. `check_exact_net` proves the spine on both
offsets to rounding (`spine_on_offsets`, no tolerance of its own), and a spine computed
in floating point fails it in a band of tilts. A boss turned 0.5 to 3 degrees off square
to the block it stands on, its root rounded, was refused with "Fillet Surface
Computation Failure" (status 1001); it built on the parent.

The documented behaviour is back for a failed certificate: `keep_round_if_proved`, with
`ExactRound::miss`, `net_miss` and `Implicit::distance` that it needs. The circle class keeps #13900's certificate on its own torus:
its refusal is not `check_exact_net`'s and stays an error, as does a net further off its
class than the tolerance.

Test: `a_round_on_a_boss_tilted_a_few_degrees_is_built` (solidcraft-kernel), the boss at
0, 0.5, 1, 2 and 3 degrees: refused at 0.5 before, every tilt built after, valid, and
meshed at a 1e-4 chord to the same volume; 0 degrees against Pappus's closed form to
1e-9 of the body, the tilts within 1 % of it (the corner's area changes to first order
in the tilt and cancels round the root). The kernel cases (1081) report identically
before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyW6gE7DcVNPHLaqorN

* blend: a chord's miss measured against its plane, as its certificate checks it (#14000 review)

`ExactRound::miss` measured every class against the supports' class surface, while
`recertify` checks a chord against the plane through its contacts (`chord_implicit`).
`keep_round_if_proved` reads `miss` only after `recertify` refused, so a chord net off its
plane could be kept bare if it lay near the other surface. `miss` now takes the surface
`recertify` checks. The mismatch predates 59a85a164; the restored function carried it.

`net_miss`'s doc now states it is sampled: it can miss a deviation between its samples,
bounds nothing, and decides only whether a refused net is kept bare or is an error.

Tests unchanged and green: a_round_on_a_boss_tilted_a_few_degrees_is_built, fillet_exact
(13), issue_2489_blend_faces_are_definitions (2), noisy_round_tests (2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyW6gE7DcVNPHLaqorN

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:45:43 +00:00
Werner1303 492b9eb8ac solidcore: the panic-site check's --base ratchet and site scan work on Windows and inside SolidCraft (#13623) (#14117)
* claim: #13623

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore: tests that the panic-site check reads Windows paths and its list inside SolidCraft (#13623)

On Windows, clippy names files with backslashes, so no span is a library
site, and git show does not resolve plan\panic-sites.tsv, so --base skips
the ratchet. Inside SolidCraft, git show <rev>:plan/panic-sites.tsv names
the repository's root, not solidcore/, so --base skips it on Linux too.
Three of the four new tests fail; the fourth, CI's base commit of
solidcore/ alone, passes and stays so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore: the panic-site check reads Windows paths and its base list inside SolidCraft (#13623)

library_span matches clippy's file names with backslashes turned into
slashes. base_list asks git for the list with a POSIX path, relative to
the root it checks first (solidcore/ inside SolidCraft), then at the top
of the base commit (CI's commit of solidcore/ alone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:45:41 +00:00
Brazz d0b739704d Fillet: a round ending against a riser lower than its reach builds; a chamfer past a step's foot builds by the trim (#14021) (#14022)
* fix(fillet): a blend whose leg runs past a wall's foot builds by the trim at creases

A chamfer on a step's rim wider than the step has its leg on the step's plane past
the step's foot, under the wall. The subtraction builds it (the step's plane carried
on to the leg, d^2/2 per length taken off), but the trim at creases refused it, for
two reasons in sm_Extend's extension of the step's plane:

- takes_off probed the material a hundredth of the run's length beside the run. On
  a strip narrower than that (the leg just past the foot) the probe landed on the
  strip's far side, on the body's boundary, and the one loop that closes was
  refused ("does not close on the face in one way"). The probe now stays within
  half the loop's width across the run, measured on the loop's own points.
- the extension's runs from the chain's ends to its feet lie on the walls beside it
  (the bar's end faces), but only the sheet's section was imprinted there. That
  section ends inside the wall, so nothing fenced what the blend takes off it and
  the trim refused ("reaches every face of the body"). The runs are now imprinted
  with the section.

The trim and the subtraction now give the same solid there.

Tests:
- chamfer_past_wall_foot_trimmed (new): the stepped bar of
  wr631_cliff_overflow_choice, chamfers 3.2, 4 and 4.9 on the 3 wide step's rim, at
  the closed form d^2/2 times the length within 1e-6 relative, a manifold solid,
  check_brep Level2 clean. Before: refused.
- wr695_overflow_trim_at_creases: chamfer_reaching_every_face_is_refused becomes
  chamfer_past_the_step_foot_builds_as_the_subtraction (both paths at 1540).
- issue_2715_refused_fillet_keeps_geometry: its refused-trim example moves to
  wr696's backed block (a round onto a tangent round, CreaseAcrossTangentEdge),
  still refused by the trim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyUWYrVisqsvztWRkGu

* fix(fillet): an end whose rail runs past its side edge's far end overflows its end face

A round ending at an Nx1 corner against a riser lower than its reach has its rail
past the far end P of the side edge between its face and the end face. The End
refused it: "the round's contact line runs past the end of its side edge at the
corner (onto the faces beyond)".

The cliff overflow already builds this end: an overflowing blend's End is left open
and the trim at creases cuts it by its end face's plane. But EndSectionsFitTheirFaces
(the fork's a307480), which makes an end that runs off its end face an overflow,
locates each rail's crossing of its side edge at the vertex, and leaves the end
unjudged when there is none, as for a rail past P. Now a rail with no crossing
whose end beside the vertex projects past P on the side edge's line, where the end
face has a concave edge at P (something stands on it, so the section runs under
material), makes the fillet overflow its end face.

Test round_end_past_side_edge (new): a stepped block whose raised top edge ends
against a riser 2 high, radii 2.5 to 6; and a pocket's wall as the end face with a
hole beside it cutting the rail's face away past P, radii 3 and 4. Each at the
closed form r^2 (1 - pi/4) times the edge's length within 1e-6 relative, a
manifold solid, check_brep Level2 clean; radius 1.5 (the rail stays above P)
unchanged. Before: refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLpvyUWYrVisqsvztWRkGu

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:45:27 +00:00
ChrisNo 301cf0370b Kernel: projected curve-pair maxima return the farthest pair, or refuse (#13698) (#13963)
* refactor: native Line definitions enclose their projected points

The projected pair search needs an enclosure of a Line edge's native
definition over a parameter interval. AffineDefinition::jet_over gives it
for analytic Lines, in curve/native_affine.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: projected curve-pair maxima return the pair's true maximum

The projected curve/curve maximum returned points that a strictly better
feasible pair beat (#13698). A branch-and-bound search over both
parameter domains now returns a candidate only when the global upper bound
fits that candidate's own arithmetic enclosure, and refuses otherwise.
35 curve.curve_p9 and 12 topology_solver_p2 golden records change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: refuse a projected face maximum its surface does not attain

A face answer comes from a traced silhouette point. On the full torus that
point measures 1.94e-6 below the value it reports (#13678). The solver now
evaluates the point on the native surface and refuses with Unsupported
when the two differ by more than 8 ulp of the objective's scale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: record the pair maxima, their native witnesses and the torus refusal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: picture two curve pairs, add the gen1839 oracle, check both All pairs

Review findings on #13963 (CodeRabbit):
- Pictures: gen0011 and gen1086 before and after, same view per row,
  drawn from the case inputs; each drawn pair re-measures exactly to its
  golden value.
- The gen1839 comment cites projected_pair_monotone_witness_3060.py, which
  was missing. It is added; it reads projected_pair_all_cases_3060.json
  and reproduces 4.4080155996034689 at the expected corner.
- The finding said every value is a certified maximum and counted 39
  answers. An All request's further answers are retained points, not
  maxima, and the 35 rows hold 37 answers.
- original_affine_all_cases_have_complete_near_best_corner_sets now checks
  each answer's native distance and both returned pairs of gen2050 and
  gen2585 directly, not only their count and values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:45:12 +00:00
bherbruck 2f7c118631 sweep: stations from laws, exact on lines, arcs, helices and corners (tangency-labs/solidcore#3139) (#13688)
* query: whether an edge's faces meet tangentially, proved, and the turn and convexity at a vertex (#3141)

query: tangent_edge's verdict is enclosed between its points; convexity read on the material side.

ops: tangent chains on record order, and the face chain across smooth edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* query: tangent_edge grows its vectors as it fills them (panic-site inventory)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sweep: stations from laws, exact on lines, arcs, helices and corners (#3139)

Moved from tangency-labs/solidcore#3188.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sweep: stations from laws, exact on lines, arcs, helices and corners (#3139)

Moved from tangency-labs/solidcore#3188.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fmt tangent_edge.rs after merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #3188 follow-up: the caller's twist and scale limits in Laws; rail helpers crate-visible

- Laws::max_twist and Laws::end_scale_within: the caller's limits (SolidCraft's four
  turns and 0.01..100), refused past by name; defaults take any finite twist and
  positive scale. Ops passes its constants, no float checks there.
- rail_crossing (now returning the rail's parameter too), rail_frame, Map and apply
  are pub(crate) for the rail-guided loft (#3140).


Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:45:09 +00:00
Yaroslav Luchinsky d485d241e0 Customize Toolbar: each command in the list shows its ribbon icon beside its name (#11471) (#11606)
* claim: #11471

* Customize Toolbar: each command in the list shows its ribbon icon beside its name (#11471)

The Customize Toolbar window listed commands by name only. Each row now
draws the command's ribbon icon between the check box and the label, so a
row matches the button it adds. ui_kit gains checkbox_icon for this; the
icon is part of the click target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Customize Toolbar: a row shows the icon its ribbon button shows now

A row took the command's static icon. A ribbon button can show another
one while its command's state changes it: Play History shows Stop while
History plays. The row now resolves its icon through
interactive_command_info, as the ribbon does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:25:55 +00:00
Mateusz Sych e357d9289f Agent batches refused before they start now report the design is unchanged (#10749) (#11504)
* claim: #10749

* Batches refused before their first step say the design is back where it started (#10749)

A batch refused before its first step (an unknown command, a step without a command, a broken
contract under strict checking) replied `rolled_back: false`, which docs/control-protocol.md
reads as "the checkpoint could not restore it". Nothing ran, so the design is where the batch
started: the reply now says `rolled_back` as a rollback that went back does, `true`, or `false`
when the batch asked for no rollback, in the control channel's `engine.batch` (document and
layout batches), the MCP `batch` tool (in-process, headless layout batches and bridged at strict
checking), the MCP's refusal of a step that can't be one and its refusal of an atomic layout
batch it can't run. Both docs now give `rolled_back` the same meaning.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-09 22:25:53 +00:00
kaltenbrunnermarkus ada220bb24 Hole dialog: the tap drill follows the chosen inch tap size (#10353) (#10369)
* claim: 10353

* Hole dialog: the tap drill follows the chosen inch tap size (#10353)

A UNC or UNF size in the Hole dialog showed its tap drill only in
millimetres (1/4-20 UNC: "5.1054 mm"), so an inch user could not
recognise the #7 drill. Unified sizes now read in inches ("0.201 in"),
with the millimetre bore on hover; both side by side did not fit the
form. The catalogue, the bore and saved designs are unchanged; metric
and G/Rp sizes keep millimetres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:25:50 +00:00
Rick Morgans c5259f9190 NURBS curves: splitting and knot insertion stay exact with heavy weights (#10046) (#10047)
* claim: #10046

* geom: a NURBS curve checked when made, split into exact Bézier pieces in one pass; knot insertion keeps a weight's precision (#10046)

- insert_knot takes both factors from knot differences: 1 − α near 1 lost a
  heavy weight's precision (8.3e-4 mm off on a 30 mm curve beside a weight of 1e12).
- NurbsCurve (private fields, from_homogeneous checks degree, knot count and order,
  domain, finite points within 1e12 mm, positive weights) and pieces(): one pass
  (NURBS Book A5.6), linear in length, rational or not, clamped or not.
- Clamping and the split run in outward-rounded intervals, so each piece holds the
  exact curve as given; one implementation of insertion and clamping serves f64
  and interval points (Blend).
- The hidden-line views use pieces() in place of their own quadratic split.

Tests against an independent double-double de Boor evaluation, including a fuzz
over 3000 random curves, valid and not.

* geom: a NURBS curve refuses a broken inner knot, a knot vector with no room to clamp and a weight too small to divide by when made; the heavy-weight insertion tested (#10046)

Review of #10047:
- `insert_knot`'s factors from knot differences had no test of their own: the heavy-weight
  case used a curve already clamped. `boehm_insertion_keeps_the_curve` now inserts beside a
  weight of 10^12, which `1 - α` puts 8.3e-4 mm off.
- `from_homogeneous` refuses an inner knot repeated more than the degree (before, only
  `pieces()` did), knots too many to clamp (`pieces()` refused those as "too many knots"),
  and a subnormal weight, which was reported as a point that isn't finite.
- The random test checks every piece, not the first two.
2026-11-09 22:25:48 +00:00
CamWight 07839056c9 Geometry: closest_param finds the nearest part of the curve (#10007) (#10008)
`Seg2::closest_param` took the lowest of 65 samples and ran Newton from it. The lowest sample can
sit on the wrong part of the curve, Newton could walk away from a right answer at an end, and a
closed segment could be held at the wrong end by the clamp.

Arcs are now exact, from the angle. Other curves are refined from every dip in the samples, the
ends counted, each within its neighbours. Lines are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:25:45 +00:00
Frank 5aff21fea0 Test: the Browser drops sketches after a manual history-marker rollback (#8282) (#8297)
Main (#5576) already hides sketch and construction rows past the marker,
and keeps feature rows listed after a manual Roll Back Here. This adds the
missing regression test for sketches after timeline.roll_to.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:25:39 +00:00
Christian Hammacher 936f007e7c Tabs: a middle click closes a design's tab, as its × does (#7875) (#7877)
* Tabs: a middle click closes a design's tab, as its × does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tabs: a middle click on a tab's × closes it too (#7875)

The × lies over the tab and takes the clicks on it, so a middle click
there reached the × rather than the tab and left the tab open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:25:36 +00:00
semmelrinde 0d9c1943f9 Sketch: updating a projection keeps constraints and curves attached to it (#7677) (#7680)
* claim: #7677

* Sketch: a rebuilt projection keeps what the sketch held on it (#7677)

When a projection changes shape, update_link rebuilt it by deleting the old
projected points and curves first, and every constraint on them went too: a
point held on a projected edge, a corner coincident with a projected point.
A line that shared a projected corner was left on a free point, so the sketch's
lines no longer followed the projection.

The rebuild now makes the new entities first, moves the sketch's own curves and
constraints onto the new entities that match the old ones (the same corner, the
same circle, the same side or the longest piece of it), and removes the old
ones last. Matched entities keep their ids, so the solve after it sees where
they moved from and names still find them. Curve points with no match keep a
free point where they were; only constraints on entities with no match go. A
failed rebuild leaves the sketch as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* link_rebuild: import Vec2 from solidcore_record and the geom prelude

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>
2026-11-09 22:25:34 +00:00
MarkusRe 1ea760b912 kernel cases: KERNEL_CASES_SMB skips synthetic cases and reports failed writes (#14085)
Review follow-ups on the render export: a synthetic case without a name
writes nothing instead of panicking; a rerun removes the numbered files a
previous run left past the current bodies; a failed write is printed
instead of turning the case into "panicked", and the directory is checked
before the replay; an unexpected refusal writes its inputs too.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:25:12 +00:00
dudedagger42 a5395893aa Browser History rows: a click on a feature row goes to the open dialog (#13170) (#13236)
The bottom and left History hand a clicked feature to an open dialog whose active input takes features (Pattern, Mirror). The History rows in the Browser only selected it.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 22:25:09 +00:00
dudedagger42 4c596e8a10 Mirror and Pattern: repeat an Offset Face at the copied faces (#12585) (#12957)
replay() handled fillets, chamfers, moves and tool features; an Offset
Face fell through to "cannot be patterned or mirrored yet". Each copy
now moves the picked face points and offsets the faces found there.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 22:25:07 +00:00
PaddleStroke 2f61a563ef Browser: Collapse All on a component rolls up the whole tree (#11866) (#12707)
* claim: #11866

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* wip: Browser Collapse All on a component rolls up the whole tree (#11866)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:24:52 +00:00
thocsmith 7418d72129 History: the controls step through the active component's features (#11430) (#11433)
* claim: #11430

* History controls: step through the features the bottom bar shows (#11430)

While a component is edited or picked, the bottom History bar shows only
its features (timeline_scope), but First, Back, Forward and Last stepped
over every feature of the design. They now step through the bar's own
timeline_fold::View, never move against their direction from a marker
outside the shown features, and stop playback when they leave the
marker. Preference history_steps_component (Preferences > Layout, on by
default) keeps the old stepping through the whole design.

Rebased onto main after #8886 rewrote the History bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:24:50 +00:00
bherbruck 94df38ca7c solidcore-tess: the kernel decides the moved mesher's poles, once per iso-line (#14029)
* Move SolidCraft's mesher and its evaluator into solidcore (RFC 0003, item 1.1)

A move only, so history follows the files: SolidCraft's planner
(crates/kernel/src/tess/) becomes solidcore-tess's src/mesher/, and its plan
evaluator (crates/geom/src/tess.rs and tess/) becomes the kernel's
src/eval/plan.rs and src/eval/plan/, next to solidcore::eval. No content
changes; the next commits wire them in and remove solidcore-tess's own
planner, so one mesher remains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: remove its own planner and plan, replaced by SolidCraft's

solidcore-tess's planner (src/planner/) and plan (src/plan.rs) were a port
of SolidCraft's; SolidCraft's own moved in with the previous commit, so one
mesher remains (RFC 0003, item 1.1). The next commit wires the moved code in
behind solidcore-tess's public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Wire in SolidCraft's mesher and evaluator; SolidCraft meshes through solidcore-tess

The moved plan evaluator is the kernel's `solidcore::eval::plan`, in the
kernel's vectors, beside `solidcore::eval` (where the two overlap, they are
unified in a later step). The moved planner is solidcore-tess's `mesher`,
behind its public API: `plan`, `TessPlan` and `FacePlan` (SolidCraft's plan
types, now in solidcore-tess's `plan`, with its `poles` field), `evaluate`
returning the mesh and the faces left out, `PlanStats` and its records,
`MeshTolerance`, `preview` and `simplify`.

- The mesher evaluates surfaces with the kernel's plan evaluator and edge
  curves and pcurves with the kernel through solidcore-convert; it works in
  the kernel's vectors and hands the plan the record's. It keeps SolidCraft's
  text (rustfmt skips it) and allows the lints it does not meet yet, with the
  reason; a panic in planning is caught at `plan` until the next step brings
  checked access.
- SolidCraft's geom re-exports the evaluator as `geom::tess`, through one line
  in xtask/solidcore-seam.txt (until RFC 0003 item 1.8 moves model-evaluation
  callers to ops and tess queries), with converters between its vectors and
  the kernel's and its partials in its own vectors; the B-spline basis
  functions it shares with geom's splines moved into `eval::plan::basis`.
- The kernel crate meshes with `solidcore_tess::plan`; its `MeshTolerance` and
  `DISPLAY_ANGLE` are solidcore-tess's, and its worker map that only the
  mesher used is gone.
- Tests move with what they test: the planner's tests that build bodies with
  SolidCraft's kernel are crates/kernel/src/display_plan_tests.rs, the plan
  types' are in solidcore-tess, the evaluator's cross-check with geom's NURBS
  surface is in geom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kernel rig: the shared core names the mesher's new home

tests/kernel-cases/PATHS-ALL still listed crates/kernel/src/tess/**, which the
move emptied, and the rig plan's check fails a glob that matches no file. The
mesher and its plan evaluator are now solidcore-tess's src/mesher/ and the
kernel's src/eval/plan.rs and plan/; they are inside solidcore/** already, and
are named there so the move stays visible in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the kernel decides the moved mesher's poles, once per iso-line

SolidCraft's mesher, moved into solidcore-tess, told a pole by the surface's
derivative speeds at the point, evaluated afresh at every question. It now asks
the kernel's certified classifier (solidcore::eval::collapse), which decides on
the iso-line through the point, and keeps each answer under its iso-line
(direction, other parameter, reach), so the kernel is asked once per iso-line
however many nodes and passes look at it. There is one pole check: where the
kernel cannot tell, the face is refused, named with the point and the kernel's
reason, and nothing is taken for an answer. On the 1,101 kernel-case bodies,
the primitives and 26 STEP files the kernel refused no point at either
tolerance. The speed-based checks the mesher used (`degenerate`,
`degenerate_within` in eval::plan::uv) are deleted.

Pole questions and kernel classifier calls (export): coil split by a plane
(793) 1,084,906 -> 1,393; coil through a cylinder wall (4505) 996,266 ->
12,877; boss-root fillet 54,244 -> 310; twisted closed sweep 80,112 -> 562.
The 160 spline-heavy bodies mesh in 8.0 s instead of 13.2-13.7 s
single-threaded, with the same triangles; all 1,107 kernel-case bodies and
primitives keep their counts.

solidcore-tess's pole pass is back on this mesher: a pole of higher order
gets the kernel's exact limit normal per triangle, or the mean of its
triangles' normals where the kernel has none (named in
PlanStats::averaged_poles); a normal neither rule gives refuses the face in
`evaluate`. At a degenerate vertex the CPU evaluation takes the kernel's limit
normal instead of a step inside. tests/pole.rs runs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: a node just past the range is classified on it, within the face's tolerance

The kernel's pole classifier refuses a point off the surface's parameter
range, and the mesher meets boundary nodes a little past it: pcurves made to a
STEP file's uncertainty end there. A node past the range by no more than the
face's own tolerance (the largest of its edges' and vertices') is classified at
its nearest point of the range, where it lies on the face to within that
tolerance. Farther out, the face is refused with the distance named; nothing is
extrapolated and there is no other pole check.

On the 319 files of solidcore's STEP bench corpus this classifies the six
faces the classifier refused: tork's faces 217, 243, 250 and 269 (nodes at most
9.8e-7 mm off, tolerances 1e-3 mm and up), nist_ctc_02_asme1_rc's face 647
(0.113 mm, tolerance 0.119 mm and up) and nist_ctc_05_asme1_ap203's face 143
(0.034 mm, tolerance 0.051 mm and up). No face of the corpus is refused, and
the 1,107 kernel-case bodies and primitives keep their triangle counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: ask the moved mesher's pole question once per node

The sag refinement and the flips ask, at every triangle corner, side and
round, whether a node is on a pole, and the self-crossing check asked it twice
per node, once for each chord the node ends. Whether a node is on a pole is a
property of its parameters: a `sag::Poles` memo, shared by refinement and the
flips of a face, asks it once per node, and `tangle::crossing` keeps each
node's answer for both its chords.

This is #13559's fix for solidcore-tess's own planner, which the move replaced,
carried over to the moved mesher with its test
(`the_pole_question_is_asked_once_per_node`: 27,452 questions for 349 nodes
without the memo, at most two per node with it). Triangle counts are unchanged
on the 1,107 kernel-case bodies and primitives, and the coil's test
(`the_spline_coil_keeps_its_triangles`) still passes.

Ported from #13559 by @bherbruck (co-authored by jyviko).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore: no panic-prone calls in the moved plan evaluator

f64::clamp panics on NaN bounds, so Domain::clamp and wrap_into take max then min. The
derivatives and starting-grid vectors grow as they are filled instead of preallocating a
product that could overflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: keep the reasons a face is refused for

- A face whose pole question the kernel could not answer is refused with that reason even
  when planning then failed on it: the refusal is read before the plan's own error, which may
  be a later symptom ("boundary crosses itself").
- A node the kernel's evaluator refuses (other than a pole node, which has its own rule)
  refuses the face in TessPlan::evaluate with the node and the reason, instead of taking the
  plan evaluator's normal, which can be made up where the derivatives do not evaluate.
- The poles' range check takes max then min: f64::clamp panics on a NaN bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:09:36 +00:00
CaRLymx 949db4e59a Spanish (es-ES): translate 127 messages (#14300)
Catch-up with `en.json` at `42918fe51`.

Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
2026-11-09 22:07:42 +00:00
PaddleStroke b7c77226f1 Sketch: every slot and the rounded rectangle add a middle point (#7908) (#8118)
* claim: #7908

* Sketch: every slot adds a middle point (#7908)

Straight slots (centre to centre, overall, centre point) add a construction
line between the arc centres and a point held at its midpoint. Arc slots add
a point on the construction centre arc with equal construction chords to the
two cap centres, so it stays halfway along the arc. The middle takes no
freedom away and adds none; commands return it as `middle` (and
`centre_line` for straight slots).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch: the slot previews show the middle's construction geometry

While a slot is placed, the preview draws what the command will make: the
centre line and middle of a straight slot, and the middle's chords on an
arc slot's centre arc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch: count an arc slot's freedom right with its middle; update slot scenario

Make the arc slot's middle point after the centre-line ends and before the
side ends, so the rank analysis takes the middle's two constraints on its own
coordinates before the cap tangencies (which hold only to the solve's
accuracy). Made last, a radius edit left the slot reported with one degree of
freedom too few; made right after the centre, `locate` placed the middle
before the start and fixed the start's x where it barely moves, so the slot
shifted. The slot_preview scenario counts the middle's construction curves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch: the rounded rectangle adds a centre point (#7908)

The rounded rectangle now gets a construction diagonal between its
bottom-left and top-right corner arc centres and a point held at its
midpoint, so its centre stays in the middle when it is edited or
dragged. It adds no freedom. The slot tests now take Vec2 from
solidcore_record, where main moved it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch shapes: preserve typed replies for construction centers

Preserve current upstream behavior while applying the reviewed correction. Central native builds and strict workspace linting pass; remaining runtime and screenshot checks are recorded in the PR.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:07:39 +00:00
bherbruck 46067f304d Offset: a whole torus face offsets again (#13541) (#13897)
* claim: #13536 and #13541, offset reach refused by the certified-reach gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Offset: a face with no neighbour is not refused for a reach it doesn't need (#13541)

A whole torus offset as one face meets only itself across its seams, so the
local reach has nothing to bound and returns None, as its docs say. It asked
the face's exact class first, at its seam vertex and for its move, and a torus
has none, so the move was refused as ReachUncertified. The neighbours are now
found before any bound is asked.

The torus face offsets again (offset_torus_face passes; its known-failing line
goes). Kernel test: a torus given a coaxial torus 0.5 wider moves to it, which
fails without the change with ReachUncertified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 22:07:03 +00:00
0rdnungsamt c58781c7cd Sketch: Trim shows the piece a click removes on splines (#14227) (#14237)
* claim: #14227

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch: Trim shows the piece a click removes on splines (#14227)

The hover keeps a free-form curve's cuts once per sketch state and shares
the code that splits the curve with sketch.trim, so both name the same piece.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:50:56 +00:00
0rdnungsamt 720e2edd62 Agents and scripts: Surface Extend and Surface Trim describe their exact parameters and reply (part of #2829) (#14234)
* claim: part of #2829 (surface.extend and surface.trim schemas)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give surface.extend and surface.trim input and output schemas

Part of #2829.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:50:53 +00:00
dudedagger42 7e2c0b35c1 Move: clicking an axis arrow without dragging shows that axis in the value box (#13101) (#13167)
* Move: tests for a click on an arrow and for the total from the start (#GABE)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Move: test a click whose press and release arrive in one frame (#13101)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Move: a click on a triad handle shows the value it drives (#13101)

gizmo::show reports nothing until a drag starts, so a press and release that
arrive in one frame never reached dialogs_move::drag and the box stayed on X.
Record a press on a handle that starts no drag, and show its value in the box;
a click moves nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Review: doc comment back on show(); drop the unrelated total test (#13101)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gizmo: take the clicked handle from the press position

A press and release in one frame can end with the pointer elsewhere, so
the handle under the press event itself is the one clicked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gizmo: take the pressed handle even when the pointer ends off the triad

The press hit-test now runs before the early returns for a pointer off
every handle, and only for a press in the gizmo's own layer and view, as
hover already required. The arrow click test gains a one-frame case that
moves the pointer off the triad after the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 21:50:51 +00:00
dudedagger42 0fcd87da9f Undo: a command built from other commands is one undo step (#12393) (#13027)
Only the outermost command saves an undo step and clears redo, for
itself or for an undoable command it ran. A command run inside another
saves none, so a combine into a new component is one step and a failed
command leaves the undo and redo lists as they were. A command that
replaces the session (opening a design) keeps its fresh history.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 21:50:48 +00:00
dudedagger42 2fc53a2b16 STEP export: a spline curve is written with closed_curve and self_intersect where it knows them (#12147) (#12926)
* STEP export: test that a spline curve's closed_curve and self_intersect are written when known (#12147)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP export: write closed_curve and self_intersect where the curve knows them (#12147)

A clamped spline now says whether its ends meet (.T./.F.), and a straight
two-point pcurve says it does not cross itself. Anything else stays .U.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 21:50:46 +00:00
dudedagger42 de210ea55a MCP export: accept iges, igs and stp as the format argument (#11806) (#12923)
* MCP export: accept iges, igs and stp as the format argument (#11806)

The export tool's format enum omitted iges, igs and stp although
file.export reads all three from the extension. Adds them to the enum
and the description, with a test that exports each.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* MCP docs: export table lists stp, iges and igs (#11806)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 21:50:44 +00:00
dudedagger42 71bace072d Docs: scx, a script-first MCP server prototype, and what it saves against the plain MCP (#12522)
* docs: scx, a script-first MCP server for SolidCraft, with its benchmark results

Adds docs/scx-hybrid: a prototype MCP server (scx.py) that lets a model drive
SolidCraft with short Python scripts instead of single tool calls, the task
files it was measured on, and a write-up of the results (23-task benchmark on
Haiku 5.5, difficulty ladder L1-L9, L8 also on Sonnet 5 and Opus 5.5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(scx): say what the check covers, the real stopping sequence and the code alias; scx.py reports bridge errors

- The 69/69 row is "passing the check": volume and bounding box per body, not each feature's place.
- `code` is the schema's name; the other names work only for clients that do not check the schema.
- The stopping rule paragraph lists the versions kept under the 5% bar (v10, v11) and where tuning stopped.
- scx.py: a JSON-RPC `error` from the SolidCraft bridge is raised as SCError, for scx's own tools
  and for passed-through calls, instead of an empty reply that is not marked as an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(scx): say which comparison the 5% bar is read on (v10's -6% is against a mixed baseline)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:50:41 +00:00
N3MI-DG db361fa06e Face right-click menu: hide actions that cannot act on the face, show Press Pull for curved faces (#11795) (#11796)
* Faces: Context Menu Cleanup (#11795)

A curved face, or several selected faces, offer Press Pull in place of
Create Sketch, Extrude and Offset Plane. The face menu leaves out items
that cannot act on the selection instead of greying them out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Faces: test that the face menu lists only what acts on the faces (#11795)

A curved face, or several faces, offer Press Pull in place of Create
Sketch, Extrude and Offset Plane. Tests that expected greyed-out items
now expect them left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:50:38 +00:00
raykholo c63d2a136d Quitting with several unsaved designs asks once, in one list (#11680) (#11683)
* claim: #11680

* Quitting with several unsaved designs asks once, in one list (#11680)

Quitting, quitting to install an update, and closing several tabs at once
now show one "Save changes to N designs?" window listing every unsaved design
in this window's tabs, each with a tick box, instead of one prompt per design.
Save Selected saves the ticked designs in tab order and closes nothing if a
location picker is cancelled or a save fails. One unsaved design keeps the
existing prompt, and a General preference keeps the old one-at-a-time prompts.

The control channel lists the rows and takes save_indices through ui.documents.
The headless test helpers now clear texture updates and use the test clock.

* Save changes to N designs: each path sits under its name, and a long list scrolls (#11680)

* Control protocol: say that an empty save_indices list still closes and discards (#11680)

* Save changes to N designs: each row has its own Save and Discard (#11680)

Each row now has Save and Discard buttons beside its tick box. Answering a row
closes only that tab; a cancelled picker or failed save keeps the row. The
window stays until the last row is answered, then the close or quit carries on.
The control channel takes save_row and discard_row.

* Control protocol: list save_prompt_one_at_a_time with the other ui.prefs settings (#11680)

* Save changes to N designs: follow main's theme tokens and preferences window (#11680)

The quit-one-dialog-per-tab test turns on the preference that keeps that flow,
since several unsaved designs are now asked about once, in one list.
2026-11-09 21:50:35 +00:00
Mateusz Sych fc3c3741ac Agent chat in the Snap says why no agent is found (#1458) (#11670)
* claim: #1458

* Agent chat: in the Snap, say why no agent is found instead of listing installs (#1458)

The strictly confined Snap can neither see nor start the coding agents
installed on the computer, so the picker came up empty with only install
commands that cannot help. Like the Flatpak, the panel and Preferences now
explain the sandbox, pointing to the AppImage/.deb/.rpm or an
OpenAI-compatible API agent, and the Snap shows no install list. The Snap
check is shared with the updater's (update::is_snap).

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-09 21:50:32 +00:00
Seth LaForge aede2ae66a solidcore: the trim-interval regression test runs in 43 s unoptimised, and the new refusal is documented (#13968)
Review follow-ups to #13555 (issue #13629, from tangency-labs/solidcore#3217).

The test's time was in its three booleans (cut in turn 34 s, union of the
sweeps 46 s, cut by the union 37 s unoptimised), not the volume. The default
test keeps the cut in turn, the sequence SolidCraft runs, the corpus case
records and the kernel rig replays; it still fails without #13555 (NotSolid).
The cut by the union moves to its own #[ignore]d test, as kernel_cases.rs does
for its slow unions; it passes with --ignored. One helper grades both: one
solid, valid at CheckLevel::All, within the case's 1e-6 of its closed form,
the volume measured to 1e-7.

create_proto_edge now says at the trim_unsnapped call when it refuses (a
joined curve's own trim snaps further than the common interval allows, or it
is no B-spline to reparametrise) and why: the old code built an edge running
past its 3D curve there.

No change to what the kernel computes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:50:30 +00:00
PaddleStroke bc18e7c15a Sketch: a dimension's value sits beside the line it measures, not over it (#12482) (#12703)
* claim: #12482

* Sketch: an unplaced dimension's value clears the line it measures (#12482)

The default place of a linear dimension's value was a fixed 22 px across from
the measured points. The value is drawn level on screen, so on a vertical or
slanted line a wide value reached back over the line. The default place now
moves out by the value box's extent across the line plus 8 px, and never less
than the old 22 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:50:27 +00:00
austin62jake 9599752771 Web app: the model is antialiased, and the Antialiasing setting takes effect (#11622) (#11624)
* claim: #11622

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Web app: the 3D view takes the Anti-aliasing setting, 4x by default (#11622)

The web app drew the view straight into egui's window pass, which has one
sample, so the view was 1x whatever Graphics said, and supersampling did
nothing either. It now installs the view the desktop's way
(install_viewport -> set_wgpu_split): offscreen, with its own 32-bit depth
buffer and the saved anti-aliasing, then copied into egui's pass, which has
no depth buffer now. WebGL draws 1x, as desktop OpenGL does, and Graphics
shows 2x-8x as not available there.

The desktop's sample-count rule moves to ui-egui's gpu::scene_samples so both
apps share it; gpu::scene_sample_choices leaves out the counts a backend
would draw at 1x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-09 21:50:25 +00:00
bherbruck 4017dab32c defeature: a feature whose base patch is not built is kept and named, never left open (tangency-labs/solidcore#3093) (#13680)
* defeature: a feature whose base patch is not built is kept and named, never left open (#3093)

Moved from tangency-labs/solidcore#3179.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* smlib: drop the retired defeature_refuses_open_base fix (tangency-labs/solidcore#3179)

The carried change retires UpstreamFixes::defeature_refuses_open_base: a feature whose base patch is not built is kept and named on every kernel, so the app no longer sets the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Port issue_3093_extrusion_defeature to main's certified volume sign

Soundness::volume is a VolumeSign enclosure on main (#13593), not an f64; the
test compares the enclosures (overlap across reaches, containment of the
measured value) instead of the numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:34:03 +00:00
Mateusz Sych 1fd9ad4b13 solidcore: SolidCraft #13519 case (a prism joined with its side leaning a hair off a wall) (#13951)
* solidcore: SolidCraft #13519 case (a prism joined with its side leaning a hair off a wall)

Six cases for tangency-labs/solidcraft#13519: a box and a prism extruded
from XY whose side starts on the box's face and leans 0 to 1e-3 mm off it
at its far end, joined (leon-vv's reduction of the report). On SolidCraft
main the 7e-5 mm gap fails in make_faceuse and 3e-5 is refused by the
check. Volumes are exact: 4200 - 25 g by the shoelace formula.

* solidcore: #13519 case notes give the replay on SolidCraft main and kernel

solidcraft-cli built from main 0d490ffb and kernel 8f13e948 gives the same
results; at 1e-4 and 1e-3 the join comes out as two bodies.

* solidcore: #13519 control case's profile corner is 0.0, not -0.0

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-09 21:14:27 +00:00
dudedagger42 8fc1bad502 Insert Design: a round tube read back through STEP measures as built (#12760) (#13535)
* test: a whole turn's form from the quarter line its seam is a rounding short of; a tube measures the same after a STEP round trip (#12760)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a whole turn's form starts on the quarter line its seam is within reach of (#12760)

A cylinder, cone, sphere or torus face of one whole turn had its NURBS form from the quarter
its seam's angle lies in, without the reach the partial-turn path ends on quarter lines with. A
STEP file's seam on its frame's x axis reads back a rounding short of the quarter line
(2pi - 2e-12), so the form started a quarter early: its four quarters ended a quarter short of
the seam's second use, the face's last quarter was off its form, and mass properties
integrated past the form's end (a tube read back measured 14 % over).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the read-back centre at the measure's accuracy (#12760)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:14:24 +00:00
dudedagger42 fc53757f63 Sweep: the twist turns the profile about the path, not the sketch plane (#12337) (#13223)
* test(kernel): a sweep's twist turns the profile about the path whatever plane it was drawn on (#12337)

A line along Y swept 40 along X with a quarter turn is the same helicoid
strip drawn on the XY plane (the path's own plane) as on the YZ plane (square
to the path): its far end stands along Z and its area is the strip's, 875.9.
Fails before the fix: on the XY plane the twist turned the line about the
sketch plane's normal, leaving a flat sheet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(kernel): a sweep's twist turns the profile about the path, not the sketch plane (#12337)

`controlled_stations` twisted (and scaled) the profile about the profile
plane's normal, pointing the way the path leaves it. For a profile square
to its path (every solid sweep) that is the path's direction; for a surface
sweep of a curve drawn on the path's own plane it is not: the issue's line
along Y on the XY plane, swept along X with a 90 degree twist, spun in its
plane and gave a flat sheet (rc.2 refuses it), while the same line on the
YZ plane twisted about the path.

The axis is now the path's start tangent, as `SweepControl` describes the
twist ("about the path"); `direction_offset` measures its correction after a
corner about the run's direction too, as its own comment says. Profiles
square to their path sweep as before (`twisted_sweep_turns_in_proportion`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 21:14:21 +00:00
dudedagger42 9563634fe3 STEP import: skip a header record that does not parse (#11650) (#12928)
* STEP header: test that a malformed header record does not stop the import (#11650)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP header: skip a header record that does not parse (#11650)

The header loop failed the whole import on the first malformed record. The
header only describes the file, so skip to the next line that starts with a
keyword; with none left, the error stands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP header: stop skipping bad records at DATA, count lines once (#11650)

A header missing its ENDSEC; no longer reads the DATA section as header
records to skip, and line numbers for errors met while skipping are
counted from the last error instead of the start of the file.
Also cargo fmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 21:14:18 +00:00
ashxddaga e17eae23ed fix(ui): permit body edge chains as sections in surface loft dialog (#12850)
Fixes #11838

Changes a default: no

The surface loft dialog restricted section picks to PROFILES | CURVES and required sections to come from two or more sketches. Users could not loft between surface body boundaries, open sheet edges, or between a body edge and a sketch curve. Furthermore, when re-editing an existing surface loft feature created with body edge sections, the dialog failed to restore the edge selections.

1. Update SelInput for sections in Surf::Loft to accept PROFILES | CURVES | EDGES.
2. In commands(), chain picked edges alongside sketch curves and map Sel::Edge into {"body": body, "edges": [points]}.
3. Replace the strict sketch-count check with a check for >= 2 total valid sections.
4. In for_feature(), resolve section body edges back to Sel::Edge when re-opening the dialog.
5. Add unit regression test surface_loft_with_body_edges_and_sketch_sections in loft_rails_dialog_tests.rs.
2026-11-09 21:14:02 +00:00
PaddleStroke a6a55b2569 Sketch: Project accepts construction points and makes projected points from them (#12075) (#12701)
* claim: #12075

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* wip: Project accepts construction points (#12075)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* wip: count construction-point links in the read set (#12075)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch Project: a construction point is brought into the sketch's frame and its component's placement is read (#12075)

Review: a point owned by a moved component projected in the root's frame, and moving its component did not refresh a root sketch's projection. The sketch's frame is now passed to the link resolver, and the point's component is in the sketch's placement reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Project construction point test: unground the part before moving it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch Project: carry the construction-point frame through main's lost-name handling and the point reference form

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch projection: keep the author occurrence with its frame

Preserve current upstream behavior while applying the reviewed correction. Central native builds and strict workspace linting pass; remaining runtime and screenshot checks are recorded in the PR.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brennen Herbruck <brennen.herbruck@gmail.com>
2026-11-09 21:13:59 +00:00
Elias-Ta 2dd980fdf0 Mesh convert: a closed mesh of any shape converts as a faceted solid (#12264) (#12421)
* Mesh convert: a closed mesh of any shape converts as a faceted solid (#358)

Convert Mesh rebuilt only constant-section prisms and refused every other
closed mesh ("this mesh is not a supported constant-section prism"), which
the prismatic axes fail the mesh is rebuilt as the faceted solid its
triangles describe: each run of coplanar neighbour triangles one planar
face (within 1e-7 mm of the run's seed plane, under the 1e-6 mm sewing
attaches a plane to a wire at), the boundary walked around shared vertices
through the group's own triangles, every boundary side exactly one wire
edge so the faces that share a rim always pair, the faces sewn with
sew_touching and checked for validity and the mesh's own volume. Nothing
is fitted: a rounded wall stays flat triangles rather than an uncertified
cylinder. The faces are at most the triangles, so the 50,000-triangle
budget alone bounds the cost (#346).

Tested: cargo test -p solidcraft-kernel mesh_convert (10 tests, incl. a
chamfered box rebuilding as exactly its 7 flats at volume 4000 +- 1e-6 with
a STEP round trip, a subdivided box as 6 flats, a sphere as per-triangle
facets); a 39,468-triangle f32 part converts to 4,105 planar faces in 2.6 s
over the CLI (closed, one shell, volume kept); full kernel suite unchanged
(10 blend failures reproduce on clean main under this toolchain); xtask
layers clean; fmt clean.

* Tests: drop an unneeded mut on the subdivided-box flat closure (CodeRabbit)
2026-11-09 21:13:56 +00:00
Thijs Damsma 7c4156c0d4 Sketch: keep line inputs clear of the preview (#12180) (#12197)
* claim: keep line dimension inputs clear of the preview (#12180)

Co-Authored-By: Codex <noreply@openai.com>

* fix: keep sketch line inputs clear of their preview

Co-Authored-By: Codex <noreply@openai.com>

* claim: keep line dimension inputs clear of the preview (#12180)

Co-Authored-By: Codex <noreply@openai.com>

* fix: keep sketch line inputs clear of their preview

Co-Authored-By: Codex <noreply@openai.com>

* fix: retry line input placements in tight viewports

Retry the first field when the second cannot fit, then try bounded repairs while preserving the original clearance predicate and successful placements. Cover constructive small-viewport regressions.

Co-Authored-By: Codex <noreply@openai.com>

* test: use egui unique IDs for line-input regression

Keep the new Line placement regression on the egui 0.37 ID API used by current main.

Co-Authored-By: Codex <noreply@openai.com>

---------

Co-authored-by: Codex <noreply@openai.com>
2026-11-09 21:13:53 +00:00
Jesper We 9c2b9af196 Loft with guide rails: a section drawn on a plane facing the other way no longer fails (#12031) (#12032)
* claim: #12031

* Loft rails: section normals face along the loft (#12031)

* Loft rails: keep the short turn for sections within 60°
2026-11-09 21:13:50 +00:00
PeppaWutz c82b318881 Forms: selected text reads in SolidCraft 95 and CGA (#11804) (#11899)
* Forms: selected text reads in SolidCraft 95 and CGA (#11804)

* Forms: name the selection ink threshold (#11804)

* Forms: take the contrast ratio and tokens from the theme crate (#11804)

main moved contrast_ratio and Tokens from ui-egui's theme module into
solidcraft_theme, so the selection ink and its tests use them from there.

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).

* Forms: keep selection_ink private to the layout module (#11804)

main's gallery test now asks every crate-visible ui_kit function for a
gallery entry. selection_ink only picks a colour for layout.rs, so it is
private now.

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
2026-11-09 21:13:48 +00:00
Ahmad Khan 4424506633 Browser tree: a body keeps a faint bar while one of its faces, edges or vertices is selected (#11547) (#11560)
* claim: #11547

* Browser: a body keeps a faint bar while one of its faces, edges or vertices is selected (#11547)

With a face selected and the pointer off the model, the browser tree showed nothing: a selected
face lights only the feature that made it, inside the Features folder, which is usually folded.
The body holding the face, edge or vertex now keeps the faint bar of a row with something lit
inside it, and so do the rows leading to it. The body is not drawn as selected.

* test: the placement holding the body keeps its faint bar while a face is selected, and clears with the selection

* test: name Vec3 from solidcore_record, as main's tests now do

* Browser: a body that holds the selected face and is under the pointer marks its placement once

---------

Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
Co-authored-by: begsaquib <saquibbeg4444@gmail.com>
2026-11-09 21:13:41 +00:00
PaddleStroke a44e3882e3 Rectangular Pattern: each direction's pick, quantity and spacing grouped together (#9634) (#9667)
* claim: #9634

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rectangular Pattern: each direction's pick, quantity and spacing grouped together (#9634)

The dialog listed both direction picks, then both quantities, then both spacings, so a direction's settings were split apart. Each direction is now its own titled group (Direction 1, Direction 2) holding its pick, Quantity, Spacing and, for features, Alignment, in the same order for both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rectangular Pattern: submit the regrouped dialog explicitly in the hole scenario

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:38 +00:00
sascha2101 6f56942703 About: Attributions read as formatted text, not raw Markdown (#7776) (#7789)
* claim: #7776

* About: the attributions read as formatted text, not raw Markdown (#7776)

The About dialog showed ATTRIBUTION.md through a plain label, so its
headings, bold text and pipe tables appeared as written. It now goes
through the Markdown renderer of the release notes, parsed once.

That renderer learns pipe tables: a row reads as one entry, its first
cell in bold and the other cells between dots, so the thousand file
rows wrap to the narrow About window instead of widening it. The row
above a |---| line is a header, shown smaller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq

---------

Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:35 +00:00
SamD98 1b85624d3e crash reports: a caught kernel panic on a Rayon worker is not an immediate crash (#7402) (#7446)
* Crash reports: hold a Rayon worker panic until the command ends (#7402)

A pool thread does not see the command scope or the guard flag, so the
hook wrote a report at once, without the design the command started
from, before guard turned the panic into an internal error. While a
command is running, keep that panic and report it with the command and
design when the command ends. Mark it recovered when the worker was
inside guard or the kernel caught an internal error for the command. A
worker panic outside a command is still written at once.

Kernel internal errors reach the engine as EngineError::Internal, as a
command's own panic does, so Session::execute tells the crash module
when the error came from the kernel (crash::kernel_caught) instead of
command_done matching on the message. diagnostics.crash_test gains the
worker and worker_raw modes in its typed contract.

Tests cover a panic inside guard on a kernel pool worker during a
command (map_with_local_pools, the kernel's own parallel path), a worker
panic resumed into guard, one outside guard, and one outside any
command. docs/crash-reports.md notes two limits: plain threads are not
held, and the open-command count is process-wide.

Fixes #7402

Co-authored-by: SamD98 <SamD98@users.noreply.github.com>

* docs(engine): document worker crash-report hold-back API

Doc comments only, for the items #7402 touched: the crash_test command
handler, CrashTestMode's deserializer, PoolPanics' fields and its static,
with_pool_panics, PoolHold, Scope::enter and its Drop, unclean_exit,
and the four worker crash tests.

Co-authored-by: SamD98 <SamD98@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: SamD98 <SamD98@users.noreply.github.com>
2026-11-09 21:13:32 +00:00
yhamil04 3aa2c63b35 Sheet metal: an edge flange runs along the edge a cut left (#2009) (#7278)
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 21:13:29 +00:00
bherbruck 10026213be test: the rim chamfer's gain is the flux over the faces that differ, cross-checked by sections, not a pin (#3098) (#14224)
The hull test pinned 80.0551, the difference of two 5.1e6 volumes each asked at 1e-6: integrator noise, not the gain. The gain is now the flux over the faces the chamfer changed, taken about a point of the chain: 80.12092 at 1e-11 and 80.12081 at 1e-9, asserted to agree to 1e-3. Whole-body volumes about the box's middle miss their 1e-11 by 1.5e-9 relative on this body (tangency-labs/solidcraft#14170), so the gain is not their difference.

It is cross-checked against an independent integration of the chamfer's sections along the chain, from the faces before and the rails after, with the chain's ends sectioned through their end faces: 79.82 +- 0.53, the stations near the ends with no section the remaining term. A U groove chamfered 0.5 and 1 removes its closed form L (0.5^2 + 1^2) / 2 (from w2905). The test guards b456f34f0a (#2907): with its band condition `(p - e).length() < width` removed, both tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:26 +00:00
bherbruck 319aa915a1 Kernel PRs go to main; the kernel branch is retired (#14223)
* Retire the kernel branch: kernel PRs go to main; big work on its own feature branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Big features go as stacked PRs into main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:24 +00:00
bherbruck cfd4f444af implicit, torus: evaluates_as's bound derived term by term; the recognised torus's sweep frame from its form (#3185, #3199) (#13743) (#14215)
Moved from tangency-labs/solidcore#3224.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:21 +00:00
bherbruck 5d80b69729 Kernel rig: merge groups run only the areas their batch touches (#14208)
A batch of UI or docs PRs ran every rig area in the merge queue. The plan now maps the
batch's diff (merge_group base to head) the same way it maps a PR's, so a batch that
touches no kernel path starts no rig shard. Pushes to main still run every area, so
anything a batch misses shows up right after it lands.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:18 +00:00
bherbruck 26d9bee64d query + ops: continuity across an edge (G0 to G4) and curvature combs (#3142) (#13925)
The kernel measures, at a parameter of an edge between two face uses, the gap between the feet, the normal angle, and the jumps of the normal sections' curvature and its first two arc-length derivatives, from exact partials. It also gives an edge curve's κN and a surface's normal curvature along a parameter line, at even steps for combs. Ops runs the continuity report on record order with the caller's tolerances: the order met at the stations, plus the kernel's proved tangent_all_along as the whole-edge G1 verdict. Ops does no float arithmetic. Tests are against closed forms: box edges at pi/2, a round's rails tangent with a jump of 1/r, cylinder circles with κN = 1/R pointing at the axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:16 +00:00
bherbruck bdaf2c11c5 sweep: the section blend keeps its definition, and the review's fixes (tangency-labs/solidcore#3140, tangency-labs/solidcore#3194) (#13901)
* sweep: a multi-section blend through ordered cross-sections, G0 to G3 at its ends (#3140)

section_blend builds SolidCraft's loft_blend in the kernel: one B-spline surface through every station of ordered cross-sections. At the first and last section it holds the frames' conditions, G0 to G3. Interior sections hold the run's finite differences, C3 with four or more sections and C2 with three. Every span is one Bezier of Hermite rows, elevated exactly. That is SolidCraft's definition of the blend, documented as such, not an interpolant with an error against anything. end_rows, the Hermite-row builder, is public for the bridge to share. Tests against closed forms: two sections at G0 are the ruled surface; G1 ends leave at the definition's speed exactly; five sections of a parabolic cylinder with its own frames reproduce it; interior joints are C3 or C2; the refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sweep: section_blend grows its vectors as it fills them (panic-site inventory)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sweep: section blend review: the backward sign, the cubic at u = 0.75 (#3194)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sweep: the section blend keeps its definition, and the review's fixes (#3140, #3194)

The multi-section blend's surface now keeps its definition (KeptDefinition::SectionBlend, BlendRef::SectionBlend): the sections, end orders, crowns and the motions its net took. Re-certification rebuilds the net and replays each motion through the same BSplineSurface::transform, then compares exactly (net_distance 0, any orientation of the parameter square). A record (SectionBlendRecord) round-trips it, and a tampered net is refused.

From jyviko's review of #3194:
- the backward one-sided third difference had its sign turned (SolidCraft's too); it is now the backward difference;
- G3 is tested against a circle's and a helix's frames;
- 5-station v interpolation, the bounds by message, and every interior joint are tested;
- end_rows and third_derivative are crate-private;
- the floors are documented as exact tests that refuse.


Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:13 +00:00
bherbruck 15d1cc1890 Preferences search tests: put the right-to-left test's doc comment back on its test (#11787)
The comment describing the right-to-left label search (#10471) had ended up above the layout test added in #10638. Test-only comment move, nothing the user sees changes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 21:13:11 +00:00
Alex Lytvynenko c314b6d26c Fix offset splitting and preserve parallel sheet boundaries (#13912)
* offset: the four-face vertex split is the only path; the three split flags retire

Addresses solidcore#2981.

The three `UpstreamFixes` fields (`offset_closes_loop_gaps`,
`offset_splits_corner_vertices`, `offset_four_face_vertex_split`) delete with
their off paths: every extended offset closes its loop gaps and splits its
corner vertices (`step_gaps.rs`'s `split_corner_vertices` unconditional), the
geometry creator's four-face split always builds (its own helper is gone; the
tangency tolerance is the fork's half a degree unconditionally,
`default_tangency_tol_radians()`; `use_creator_tangency_tol` applies it), and
an offset or a shell of a manifold solid that is no closed manifold solid is
refused (`SM_ERR`, 1001) rather than returned, as WR-578/WR-657 ask.

Goldens blessed with the change's own justification: `offset.offset_executive_p2`
594 PASS (61 changed-cases: the eight shell variants of a closed box round whose
offset was no closed manifold solid is now refused 1000 -> 1001 — the default
path's own refusal — plus two cylinder offsets whose surface geometry moves by
ulp-scale rounding from the always-half-degree tangency tolerance);
`offset.offset_geometry_creation` 926 PASS (2: the executor's own state caches
shrink with the split's changed solve plan, the results are the same);
`topology.brep_local_operation` 165 PASS (83 changed: the built bodies now carry
the split's extra edge — graph 146 -> 249 nodes, edge owners +2, uv owners +2 —
whereas before the split was off and the merge either refused or left the
four-faces' corner as one grown vertex). `offset.offset_executive_p1` 1060 PASS,
no result change.

Tests move to the default path: `wr_578_offset_closes_loop_gaps.rs` and
`wr_657_offset_splits_corner_vertices.rs` lose their own flag bools;
`fork_2e1bb8c_four_face_vertex_split.rs`'s four off-path expectations delete
(the off state no longer exists) and the two closed-forms tests run on the
default path; `issue_2020`, `wr650`, `issue_1945`, `issue_1818`, `issue_2260`
drop their enable flags (`kernel_cases/fixes.json` 300 -> 297). The whole-crate
cargo test keeps one failure, `fillet_round_failure_notes_status::

the_rollover_failure_notes_its_status`, which reproduces on this base with the
change stashed (pre-existing on main, unrelated to the flags' paths).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* findings: goldens checksum blocks move across platforms (aarch64 darwin vs CI's x86_64)

The full goldens sweep on this machine fails 84/283 units whose changed rows
are checksum-block-only movements, with identical value rows and identical
op/crash/state hashes; reproduced on the clean base 7efbd5090 in a scratch
worktree while the same code is green on CI's x86_64 (run 42399108643 at
8d7a0bf07's tip family). Records what a later agent must check before
attributing such red units to their own lane (run the clean base first), and
what holds: value rows agree within the 1e-9 basis; the block tolerances
absorb aarch64-vs-x86_64 value noise.

Evidence thread for #2981's re-verification turns (the PR's own four units
PASS locally with within-gate values; the sweep red is the platform baseline).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* review: bound the verification claims and remove retired flag scaffolding

Only one goldens unit was compared with a clean base; the 84-unit sweep is not an established platform baseline. Main run 42399108643 skipped goldens. Correct the unmerged finding instead of treating those failures as cleared.

Remove the constant-true four_face variable and its dead conditions without changing the enabled construction. Drop the retired flag documentation accidentally attached to the unrelated cone flag, and name the now-unused kernel argument explicitly.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: migrate remaining offset regressions to the default split path

Remove obsolete flag-off refusal assertions and duplicate constant-boolean helpers. Preserve the plate topology check, closed-form volume tolerances and loop-gap checks. The fully chamfered inset now names the vanishing edge (1043).

The square-to-circle loft on default fixes now runs the existing complete assert_sound oracle: all check levels, outward manifoldness and independent cavity/volume enclosure.

Focused validation: 42 tests pass across nine binaries with four pre-existing ignored. Workspace clippy -D warnings, fmt and wasm pass. The optimized whole-crate no-fail-fast run is pending, as are the moved-case soundness evidence and canonical exact-head CI.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: require valid topology for the top-open drilled plate

Update only the top-open #1795 shell regression to the measured 16/36/24 topology and require clean validity levels 0, 1 and 2. The old extra straight-edge split shared a vertex one millimetre off the curve and hid it behind a 4.166 mm tolerance. Keep the existing volume reference and 1e-9 relative tolerance unchanged, and retain the unresolved side-open expectation byte for byte.

Focused optimized test passes: intact manifold solid, volume 9655.150691018822. Workspace fmt, focused clippy -D warnings and diff check pass. No kernel or golden change. Upstream advanced to 7a593124c with substantial overlapping changes; integration and fresh verification remain outstanding before publication/readiness.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: verify the default cavity refusal retains its input

The ribbed lid without crossing-loop resolution leaves an open cavity. The default shell now refuses the non-manifold result with shell_brep status 1001 rather than returning it, so the old successful-result unwrap is obsolete.

Probe the actual ShellOutcome before updating the expectation: ownership is not transferred; the retained 11/24/16 input keeps its key, topology ids, vertex positions and tolerances; integrity and validity levels 0-2 are clean, it is manifold, and volume 2143.9999999999995 matches the closed form 2144. Make these checks permanent without refreshing the returned caches.

Share the unchanged shell options with the successful controls, whose 587.5 volume and 1e-9 tolerance remain byte-identical. All three optimized tests, focused clippy -D warnings, fmt and diff check pass. No kernel or golden change; other PR failures and canonical CI remain unresolved.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: verify the sliver cavity refusal retains its frame

Without cavity-region selection, the crossing floor offsets keep a sliver and leave no manifold cavity. The default shell refuses that result with shell_brep status 1001, so the old successful-result unwrap is obsolete.

Before changing the expectation, probe the actual ShellOutcome: ownership is not transferred; the retained 21/48/32 frame keeps its key, body tolerance, topology ids, vertex positions and tolerances; integrity and validity levels 0-2 pass, it remains manifold, and volume 1249.9999999999995 agrees with the closed form 1250. Make these assertions permanent without refreshing the returned caches.

All four optimized tests, focused clippy -D warnings, fmt and diff check pass. The three successful controls are byte-identical, including the 638.75 volume at 1e-9 relative and the thicker-floor bitwise comparison. No kernel, golden or failure-registry change; other PR failures and canonical CI remain unresolved.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: verify an open vase shell refusal retains its input

With WR-343 enabled and WR-342 disabled, the vase bottom gets no offset. The default shell guard refuses the resulting open offset with shell_brep status 1001 instead of returning it. Replace the obsolete open-body success expectation, not the construction.

Probe both rotational sweep directions first: ownership is retained; the original 3/4/3 vase keeps its key, body tolerance, topology ids, vertex positions and tolerances; integrity and validity levels 0-2 pass and it remains manifold. Retained volumes agree with the original cone/cylinder closed form 18849.55592153876 within 8e-10 relative. Make these assertions permanent without refreshing retained caches, at 1e-9 relative volume tolerance.

Both optimized WR-342 tests, focused clippy -D warnings, fmt and diff check pass. The successful-shell path and oracle are byte-identical, including the 1e-5 relative tolerance. No kernel, golden or failure-registry change; other PR failures and canonical CI remain unresolved.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: verify the drilled-tip refusal preserves the input

Without the apex sphere cap, the cone offset remains open. The default shell guard refuses it with shell_brep status 1001, regardless of the legacy empty-cavity refusal flag, instead of returning a non-manifold body.

Probe all four side/top and refusal-flag combinations first: ownership is retained; the 8/16/11 input keeps its body key and tolerance, topology ids, vertex positions and tolerances; integrity and validity levels 0-2 pass and it remains manifold. Retained volume 31700.26772804645 agrees with the block-minus-drill closed form 31700.2677274178 within 2e-11 relative. Make these assertions permanent at 1e-9 relative, without refreshing retained caches.

Both optimized tests, focused clippy -D warnings, fmt and diff check pass. The sphere-cap success helper and four-way control are byte-identical. No kernel, fixture, golden or failure-registry change. Other PR failures and canonical CI remain unresolved.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* test: run the pyramid split regression without retired flag setup

Delete only the three stale assignments to removed split fields and describe the default path. The geometry, assertions and local-operation soundness gate are unchanged.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* offset: request only the second-order jet for crossing classification

Continue #3056. The #1818 rim-mating failures request a full (2,2) rectangle from an implicit offset, exceeding its total-order-three limit. passes_through consumes only second-order partials. Request the triangular jet without changing its formula, derivative limits, pole handling or tolerances.

Tests first: the existing plane control passed and the new implicit-offset regression failed with status 1001. Both pass with the caller correction. The regression preserves the unsupported full-rectangle refusal and independently checks a touch, a lifted quadratic crossing and a shallow straight crossing.

Primary verification: seven creator tests and all four active #1818 tests pass, including strict validity and independent cavity-volume checks. Fmt, workspace/all-target clippy, wasm and panic inventory pass. Integration selection improves 53/8/9 to 56/5/9 (pass/fail/ignore). The seven local-operation failures remain; no assertion or gate is relaxed.

All 2745 affected golden cases remain selected. Exactly one ordinary output changes from the prior head: #1818 now returns the manifold 11-face shell instead of 1001. All 57 existing unapproved differences remain visible; no golden or known failure is blessed. Attach same-camera native-edge before/after views and the successful shaded shell, with provenance and limitations in the finding.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: certify the repaired square-to-circle shell result

Continue #3056: update only issue1818_square_to_circle_loft_api_shell_1_top after the second-order caller correction. Status 1001/no body becomes 1000, ownership transferred, result counts [2,2,11,12,24,96,16] and bbox [-20,-20,0,20,20,60]. Both input-purity flags, selected faces and all source geometry fields are retained; the boolean context is restored.

At e6ddf2c31, the actual fixture-runner result has a clean Level-2 check, manifoldness, 11 faces / 24 edges / 16 vertices and volume 8784.12356008852. All four direct issue regressions pass unchanged Level-0/1/2 validity, outward orientation and independently bounded cavity-slab volume checks. Same-camera native-edge before/after and shaded fixed-shell views are in the preceding finding. Fixture SHA-256: 0e7b3fa3d97e8f856fa6be9a838f496233b98990cce0cd5c8b844079664a1b5f.

The complete replacement row equals the actual profile-matched collector; all other rows are byte-unchanged, no failure entry or tolerance changes, and all 2745 affected cases remain selected. The other 56 unapproved golden differences and all unrelated construction failures remain visible.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* test: grade the default pyramid split in both selection orders

Continue #3056: replace the obsolete flag-off refusal with the unconditional four-face split contract. Both replacement selection orders must give the independent five-plane solid: all six analytic plane-intersection vertices, unchanged 1e-9 volume comparison, manifoldness, topology 5/9/6 and clean Level-0/1/2 validity.

Only test code changes. Production local-operation prefix, failure matching, soundness gate, refusal helper and rollback assertions stay byte-unchanged; #3073/#3084/#3124 scopes are untouched.

Measured at parent 0dcc5df1a00ec86e0ffe54ec98e8ba99e00ec692 plus test SHA-256 305d5a9cd6c3e2214abad1b63702a0c9941e56336638ad7aadcb047e74155c50: all three pyramid controls and both crossing controls pass; local operations 29 pass / 6 fail / 2 ignore, the 17 integration binaries 56 pass / 5 fail / 9 ignore. Fmt, workspace/all-target clippy, wasm and panic inventory pass. All 2745 captured outputs including 121 known failures are byte-identical to the pre-refresh capture. The actual gate retains 56 ordinary differences; no further expectation or failure entry is changed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: grade the two generated pyramid splits against their five planes

Continue #3056 / #2981 without production, case-input or tolerance changes.
gen0040 and gen0068 have the independently enumerated six plane-intersection
vertices and manifold 5/9/6 topology, with clean Level-0/1/2 checks.
Kernel volumes 992044.1995192543 / 1093044.507611554 versus independent
992044.19951951645359 / 1093044.50761159413764; relative errors 2.643e-13 /
3.662e-14 at the unchanged 1e-9 rule. All six oracle controls pass.

Only these two complete runner encodings change; full old/new encodings,
mathematical construction, kept-input plane residuals, controls and native
before/after wireframes are in the finding. The rounded nonplanar sides in
these cases are replaced by caller planes, not continued through their poles.
No excluded pole construction or #3073/#3084/#3124 scope is implemented.

The other eleven local-operation differences, six local-operation test
failures and all other units' unapproved expectations remain visible. No
failure registry, gate, floor or assertion changes. No canonical pass or
readiness is claimed. Actual new gate counts follow verification.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* docs: retain the disk check-order and boundary-preservation hold

Continue #3056 without blessing any disk or modifying a gate. At ec0f65581,
52 fresh existing results / 156 checks show nine disks pass once then fail
Loop Orientation on the second/third call. soundness, direct check, standing
and an arena-rebound control all agree. The first call makes four UV trims;
counts and tolerances stay unchanged. Both pyramids and two tilted disks
stay clean. This refutes a naming-only explanation, not a geometry hold.

Separately, gen0001's parallel-plane move changes the tangential boundary:
the original positive-weight curve control hull has y in [0,5], while a fresh
unchecked result has a vertex at y=7.5, exactly 2.5 outside. Exact input/raw
bits, four hull controls, native input/result views and the verified 27-member
archive are retained in the finding. No invented before-result or schematic.

Production, all cases, goldens, all failure entries, tolerances and assertions
remain untouched. The 54 ordinary differences and six local-operation test
failures remain open. No #3084 matching/gate or excluded pole construction.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* offset: preserve typed shell refusals after retiring split flags

Use the shared lost_manifold reason at the existing early shell refusal,
before the difference can consume the caller's input. Keep the manifold
predicate and comparison status 1001, and keep the empty-cavity opt-in's
1007 refusal first. Remove its now-redundant generic manifold check.

The cavity and sliver retention regressions fail on the rebased parent
because its early guard returns a generic error instead of main's typed
Unsound(NotManifoldSolid). Align the vase and drill-tip matrix assertions
with that typed contract without weakening their retained-input, validity,
or independent-volume checks.

Migrate the newly merged curved-seam tests off the retired loop-gap field;
their geometry, checks and unrelated analytic options stay unchanged.

No input, tolerance, golden, known-failure disposition, final manifold
gate, or excluded construction changes. Exact-head verification follows
this commit; this message does not claim a passing full gate.

Co-Authored-By: OpenCode (DeepSeek V4.1 Flash) <noreply@opencode.ai>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* Fix parallel offset sheets and complete migrated split-path regressions

Preserve the source UV frame for exact parallel bare B-spline lamina moves, retain procedural definitions, report typed loop-gap refusal with the input face, and refresh static shell topology at hand-back. Keep the original conservative manifold-solid gate and sheet return path; do not add the experimental cavity-region construction.

Golden changes: 13 parallel disk/circle rows now preserve their translated boundary; 14 shell cases including gen0000 refuse unsupported construction with status 1001 and retain the input instead of returning invalid/unlabelled results. Thirteen issue1528/1795 shell records follow repaired topology caches and restored Boolean context. gen0166 retains its input on failure; the large cylinder splits its seam (3 to 5 edges, 2 to 4 vertices), and the two PE cylinder/box cases have 2 regions/2 shells and 21 edges instead of 1/1/23, with existing geometric observations retained. Four tilted disks remain visible as issue-backed known failures, not passes. No input, tolerance, floor, or unrelated expectation changes.

Bounded validation: 116 focused tests passed plus both plate-shell regressions; the added extra-scope thin-box experiment was removed, not counted as passing. Final actual goldens: p1 1060 cases unchanged; local operation 165 cases unchanged with 4 known failures; p2 594 cases has only 9 inherited local differences after the data-only gen0000 correction; geometry creation 926 cases has 1 inherited difference. Those ten inherited differences remain unblessed. Formatting passed; kernel all-target Clippy and wasm checks passed before removal of the experimental code. No full local sweep or canonical CI pass is claimed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* Remove retired offset fix assignments from the SolidCraft wrapper

The wrapper already enabled these three fixes. Their kernel paths are unconditional, so delete the obsolete field assignments that fail the migrated app compile. No product preference or default changes.

Verification: wrapper formatting check; canonical Quick compile is the targeted compile gate. No duplicate local kernel suite run for these assignment deletions.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* Keep historical review images outside the replacement PR diff

Remove the eight inherited generated review PNGs and preserve every historical finding's image through an immutable link to the already-published 954fe0d45 bytes. Current gen0001 was exported from the existing kernel rlibs and rendered with the same view; its PNG is byte-identical to the published input outline, so the PR can show the current corrected result without committing another review asset.

Documentation and review artifacts only: no Rust, test, fixture, golden, tolerance or geometry changes. The current after render and historical before were inspected; no kernel/app rebuild or broad test sweep was run for this cleanup.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* Fix offset refusal and fixture regression checks

Preserve the typed edge-vanishing cause assertion, keep upstream fixtures optional locally and required in strict mode, and reconcile only the two manifold-refusal goldens. Publish review-safe image links and independent numerical evidence without committing generated PNGs. Focused regressions, formatting, targeted Clippy, and both repaired golden cases pass. The full root CI run timed out after reporting corpus/pleat and GPU failures; no full-gate pass is claimed.

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: GPT-6.1 Sol <noreply@openai.com>
Co-authored-by: OpenCode (DeepSeek V4.1 Flash) <noreply@opencode.ai>
2026-11-09 20:56:18 +00:00
Brennen Herbruck 3ba860af7a Themes: a file's unknown colour keys are ignored with a warning instead of refusing the file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:51:48 -05:00
Brennen Herbruck 443af2e92a Navigator: reword the navigator strings in every catalog; test that a saved theme with unknown colour keys loads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:24:15 -05:00
Brennen Herbruck 3cbca9c470 Navigator: remove the extra navigator style; the axes navigator stays
The extra navigator style, its drawing and picking code, its Preferences and
right-click menu choices and its view.navigator value are removed. The
signed-axes and custom-body navigators are unchanged (Custom shows the signed
axes until a body is chosen). A navigator style this version does not know
loads as Axes; ui.prefs and view.navigator refuse one with an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:09:48 -05:00
jyviko 5c99792a8b Viewport: three shared crates (viewport, viewport-gpu, viewport-egui) over solidcore-tess plans (#14125)
* solidcore-tess: re-export the evaluator types and vector conversions its plans expose

TessPlan's public `surfaces` are solidcore::eval::EvalSurface values built with
solidcore_convert's vector conversions, so a client that builds or reads a plan had to name the
kernel crate and solidcore-convert, which the solidcore seam check refuses outside the public
crates. solidcore-tess now re-exports EvalCurve, EvalSurface, vec3 and record_vec3; a test
builds and reads a plan's surfaces through those names alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Viewport: the background is painted from two colours, not the theme (#12943)

paint_background takes the top and bottom colours instead of Tokens, so the
renderer that will move into a shared crate needs no theme. SolidCraft's
callers pass the viewport tokens as before; a bare-context test pins the
gradient.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: the renderer's own vector maths, not geom's prelude (#12943)

The camera, CPU rasterizer, shading models, section hatch and GPU pipelines
use solidcraft_render::vec (Vec3Ops, Vec2Ops: the methods they call, with
geom's formulas and threshold) instead of solidcraft_geom::prelude, one step
toward a renderer with no geometry crate. The GPU crate's test skip lines
print as its other tests do. Part of #12943.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Geometry: Mesh and simplify::Level are solidcore-tess's (#12943)

solidcraft_geom's Mesh and Level were field-for-field copies of
solidcore-tess's. geom now re-exports them, so the kernel's tessellation,
the model and the viewport hold one type and nothing is copied or converted
between them. geom's own mesh queries (bounds, measure, mass_properties,
contains, raycast, crosses, inside_index, the edge chains and patches)
are the MeshGeom trait in its prelude; the five methods solidcore-tess
already has are dropped. Callers that did not import the prelude get the
trait. solidcore-tess is a workspace dependency at solidcore's pin.

Tests pin the identity (geom's types are solidcore-tess's) and that a
headless picture draws the body's own mesh allocation. Refs #13399.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Render: the job tests import MeshGeom only where they use it (#12943)

retained_request_tests calls Mesh::bounds only in its raytrace-cpu tests, so
the import is gated with them; without the feature it was an unused import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: the renderer names solidcore-tess's Mesh and Level (#12943)

The renderer's scene, CPU rasterizer, GPU pipelines and offscreen captures
take solidcore_tess::Mesh and simplify::Level, the types SolidCraft's model
already holds (geom re-exports them), so they no longer name geom for
meshes. render and gpu depend on solidcore-tess directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: the camera and ground occlusion take the renderer's Bounds (#12943)

Camera::fit, fit_aspect, clip_bounds and view_proj_bounds, GroundAo and
Frame::preview_bounds take solidcraft_render::vec::Bounds (plain min/max with
the box queries they use) instead of geom's Aabb3, so the viewport API names
no geometry crate. A model's Aabb3 becomes a Bounds in one place,
solidcraft_render::view_bounds, on the app side of the renderer; every
caller in engine, ui-egui, mcp, the CLI and the render job code converts
through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* GPU tessellation: what the shader is given is pinned byte for byte (#12943)

A digest of the packed surface headers, data, vertices and pinned nodes, for
the GPU tests' kernel bodies and for synthetic plans that use every packed
field (a pinned node, a flipped rational NURBS closed along u, a revolved
face swapped under a mirroring transform), alone and packed together. The
upload moves to solidcore-tess's plans next; this holds it to the same bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: the GPU evaluates solidcore-tess's plans, made once per plan cell (#12943)

TessGpu packs and evaluates solidcore_tess::TessPlan (solidcore's surface
forms, read through their accessors and solidcore-convert), so the GPU
tessellation names no geom type. The kernel still plans in geom's form:
doc converts each display plan once, in cells beside its plan cells
(ModelBody::gpu_plan_at, shared by clones and placements like the plan), so
the viewport's uploads, keyed by each plan's allocation, stay put from frame
to frame. A refined plan is converted once when it is made and kept in that
form by the viewport. The conversion (doc::gpu_plan) is temporary until the
kernel plans in solidcore-tess's form (#13399).

A plan with pole nodes is refused as GpuError::PoleNodes and drawn from its
CPU mesh like any refused plan. The packed bytes match the base digests for
the kernel and synthetic plans.

Extra memory, live malloc bytes (macOS heap) for each design's display plans
at the current quality, geom plans then GPU plans:
  SELENE-functional (2116 bodies): 189 MiB, then 157 MiB more (0.83x)
  modern-sheet-metal: 213 KiB, then 61 KiB (0.29x)
  modern-plastic: 95 KiB, then 77 KiB (0.81x)
  joints-hinge (4 bodies): 110 KiB, then 104 KiB (0.95x)
  components-arm (3 bodies): 99 KiB, then 85 KiB (0.86x)
  m0-revolve-primitives: 80 KiB, then 69 KiB (0.86x)
  m0-box-fillet-hole: 42 KiB, then 39 KiB (0.94x)
The geom figure includes what planning keeps beside the plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: a Shared solidcraft-viewport crate with the camera, pictures and shading (#12943)

The orbit camera, the description of a picture and its CPU rasterizer, the
opaque and clear-coat shading models, the section hatches and the renderer's
vector and box maths move from solidcraft-render into a new Shared crate,
solidcraft-viewport (xtask layers: shared). It depends on solidcore-record,
solidcore-tess and solidcraft-color only, and names no geometry or app type.
Every consumer (render's jobs and path tracer, doc, engine, gpu, mcp, ui-egui,
the apps and their tests) uses solidcraft_viewport paths; render and engine
re-export none of it. The path tracer's use of the shading model
(OpaqueSurface::with_authored_finish, specular_fresnel) and of the CPU shade
is now public API. The GPU pipelines join the crate next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* GPU: the host passes the adapter policy and CPU tessellation; the viewport code reads no app variable (#12943)

The GPU code that moves to the shared viewport crate no longer reads
SOLIDCRAFT_GPU or SOLIDCRAFT_CPU_TESS: open_headless, TessGpu::headless,
Capturer::headless*, capture::{shared, headless, headless_rgba} take the
Policy, and view::tessellation and TessLater::start take force_cpu.
capture::headless returns the GpuError, and drawing is Headless::rgba.
NoAdapter names only the policy; device labels and thread names are neutral,
and the docs name no product.

SolidCraft reads its variables in solidcraft_gpu::env (app side; it stays in
crates/gpu): policy(), force_cpu_tessellation(), and headless entry points
that add how to choose another adapter (SOLIDCRAFT_GPU, `solidcraft-cli gpu`)
to a missing adapter. Every caller (startup, CLI, MCP, ui-egui, path tracer,
tests) passes them. Tests: the app's parsers and hint, force_cpu leaving the
evaluation out on a device that would run it, and a scan that the moving
files' code names no SolidCraft variable, product or CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: its shaders live in crates/viewport/shaders (#12943)

viewport.wgsl (from assets/shaders) and tess.wgsl (from crates/gpu/src) move
beside the crate that will compile them, so a Shared crate includes nothing
from outside its own directory. The GPU code includes them from there until
it moves too; the HLSL check, the colour-token check, the ATTRIBUTION.md row
and assets/README.md point at the new place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* GPU tessellation: PackedPlans::of, the shader's input as public data (#12943)

What prepare_many uploads (surface headers, their f32 data, vertex words, the
pinned nodes) is a public PackedPlans, made by PackedPlans::of, with
SURFACE_WORDS and VERTEX_WORDS public beside it. The byte-parity digests of
the kernel and synthetic plans and the pole refusal read it there, so they
can stay app-side once the GPU code moves into the viewport crate. Same
digests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: the GPU pipelines, pictures and tessellation join the shared crate (#12943)

crates/gpu's viewport code (view, view/*, pbr, capture, concurrent, select,
and its root: TessGpu, PackedPlans, devices, depth formats, errors) moves to
solidcraft_viewport::gpu. crates/gpu keeps the app side: the path tracer
(path), SolidCraft's GPU environment (env) and the tests built on kernel
bodies. Every caller uses solidcraft_viewport::gpu paths; nothing is
re-exported from solidcraft_gpu.

The shared crate's tests read their own VIEWPORT_TEST_GPU and
VIEWPORT_TEST_REQUIRE_GPU (gpu::test_env; with REQUIRE set a missing adapter
fails, pinned), and ci.yml sets them beside the SOLIDCRAFT_* ones on the
Linux, GPU path and Windows FXC jobs. The shader, HLSL and empty-dispatch
tests move with the code. TessGpu gains device()/queue() and open_headless
and Policy::admits are public for the app's tests and path tracer. The app
test that the crate names no SolidCraft variable or product now scans all of
crates/viewport. README and docs/render-path.md describe the variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: the egui glue is the shared crate's solidcraft_viewport::egui (#12943)

The paint callbacks (ViewportCallback, ProductHighlightCallback), install,
TessReady, paint_background and log_uncaptured_errors move from ui-egui's
gpu.rs to solidcraft_viewport::egui. install takes force_cpu and a reporter
instead of reading SolidCraft's environment and writing its stderr line;
ui-egui passes solidcraft_gpu::env::force_cpu_tessellation() and its own
report_gpu_error. The background test moves with paint_background. ui-egui
keeps the app glue: tick_tessellation, the picture renderers, the background
picture preference and the offscreen test rigs. Every caller uses the new
paths; the viewport crate builds for wasm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: base, -gpu and -egui crates, so egui and wgpu stay above the engine (#12943)

solidcraft-viewport had grown the GPU code and the egui glue, so doc, io,
render and engine, which use it for the camera, pictures and shading, built
wgpu, egui and egui-wgpu: against "nothing below ui-egui depends on egui".
It is now three Shared crates: solidcraft-viewport (camera, pictures, CPU
rasterizer, shading, hatches; solidcore and color only),
solidcraft-viewport-gpu (the former gpu module, its shaders and test
variables) and solidcraft-viewport-egui (the former egui module). Every
caller uses solidcraft_viewport_gpu / solidcraft_viewport_egui paths; each
crate has its README. cargo tree shows no egui, egui-wgpu or wgpu under
engine, doc, render or io.

xtask layers now follows workspace dependencies: a crate below L5 that
reaches a UI crate (egui, egui-wgpu, wgpu, ...) through any workspace crate,
Shared ones included, is a violation naming the crate it comes through. On
the graph before this change it reported 12 (doc, io, render and engine
reaching egui, egui-wgpu and wgpu through solidcraft-viewport); it passes now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: the shared viewport crates in the architecture, and their egui-wgpu patch (#12943)

docs/architecture.md lists viewport, viewport-gpu and viewport-egui among the
Shared crates, with what each holds and the layers rule that keeps UI
toolkits and wgpu above L5 through them; render and gpu describe what stays
app-side. The book's architecture page names the crates that draw headless
pictures. vendor/README.md says that solidcraft-viewport-egui relies on the
vendored egui-wgpu and that another workspace using it patches to the same
copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Workspace: solidcore-tess and solidcore-convert from the vendored solidcore/ (#12943)

Since the viewport split was branched, main vendored solidcore at solidcore/
and takes its crates as path dependencies. The split still pinned
solidcore-tess, solidcore-convert and solidcore-record from git; they now
come from the vendored tree, the lock has no git source for solidcore left,
and the architecture doc's doubled L1 row is merged into one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Viewport: the evaluator types and vector conversions through solidcore-tess (#12943)

Since the split was branched, main's solidcore seam check (cargo xtask
layers, xtask/solidcore-public.txt) lets SolidCraft use only solidcore-ops,
solidcore-tess and solidcore-record. The doc's GPU plan, the GPU tests and
the shared viewport-gpu crate named solidcore::eval's EvalCurve and
EvalSurface and solidcore-convert's vec3 and record_vec3 directly; they now
take solidcore-tess's re-exports of the same items, and drop their
solidcore and solidcore-convert dependencies. No crate uses
solidcore-convert any more, so its workspace entry goes too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Render workspace tests: the product preview light test on the split viewport (#12943)

Main's newer test product_preview_lights_decode_simple_colours_as_the_cpu_image_does
took StandardView from solidcraft_engine::render and fitted the camera to the
scene's Aabb3. The split moved StandardView to the shared viewport crate and
the camera now fits the renderer's Bounds, so the test names
solidcraft_viewport::StandardView and fits through render::view_bounds, as
its sibling tests do. What it asserts is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* xtask shared: a shared crate builds on its own with the vendored solidcore (#12943)

cargo xtask shared builds each shared crate in a scratch workspace that
keeps only external dependencies and the copied shared crates. Since the
viewport split was branched, main vendored solidcore at solidcore/ and takes
its crates as path dependencies, which the scratch workspace dropped; the
shared viewport crate is the first shared crate to depend on solidcore
(which shared crates may), so it failed with solidcore-record missing from
workspace.dependencies. Workspace dependencies whose path lies under the
vendored solidcore/ tree now stay, with the path made absolute as patches
already are; the module comment says so and the scratch-manifest test
covers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* xtask mesh-decisions: the GPU display plan is display work (#12943)

cargo xtask mesh-decisions on main flags any function that reads a display
plan or a mesh's triangles outside display, export, previews and mesh
bodies. The viewport split added crates/doc/src/gpu_plan.rs, which copies a
body's display plan into the form the viewport evaluates on the GPU, and
ModelBody::gpu_plan_at, which caches that copy per display quality. Both
are the display plan itself, so they join the skipped modules and functions
beside display_quality.rs and ModelBody::display_plan; mesh-decisions.txt
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Viewport: main's render update on the split crates (#12943)

Main's render quality update (#12546) landed after the split was made and still named the
render crate's moved surface model, camera and standard views. Its texture, denoise,
indirect-light and image code and tests now name them in solidcraft_viewport, and the GPU path
tests take srgb8_to_linear from there. The GGX visible-normal sampler it added to glass_model
is shared by the clear coat's test in the viewport crate and by render's glass and indirect
light, so it moves to solidcraft_viewport::surface::visible_normal (render keeps its own error
mapping). with_metal_energy_compensation and metal_energy_compensation are public, since
render calls them from another crate now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Saved models: the viewport crate is in the cache key (#12943)

doc and render compute models with the shared viewport crate now (its camera bounds and
surface model), so a change there must invalidate the cached models, as the io test asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* GPU: pin plans_pack_to_the_same_bytes to main's own packing

The expected digests were made on the older solidcore (a445bdaac). The
values here are main's own packer run on current main (the export-main
run: main plus only this pin test), not a re-bless of the split's output.

The split already packs those bytes: geom's plan through gpu_plan and
PackedPlans gives surface headers, data, vertices, count and fixed nodes
equal field for field to main's packer over the same geom plan, for all
five bodies. No code change is needed; only the stale expectations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Cargo.lock: the viewport dependency in cargo's order (#12943)

A hand-merged entry listed sysinfo before solidcraft-viewport; cargo sorts them by name and
rewrites the file, which a --locked build refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* GPU: pin packing byte for byte on the literal plans; kernel-made plans round per platform (#12943)

CI on x86-64 Linux and a run on arm64 macOS pack the same three kernel-made bodies (a box
with a hole, a cross hole, a mirrored pin) to different bytes: the kernel's plans round
differently per platform, and both main's packer and the split's agree on each platform. The
pin keeps the plans built from literal numbers (one of each surface kind), alone and packed
together, which pack identically everywhere; the real bodies must pack, and pack the same way
twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Viewport GPU: main's resolve-clear test opens its capturer with the tests' adapter policy (#12943)

The test (#14090) landed on main after the split and called Capturer::headless() without the
policy the host now passes; it takes the tests' VIEWPORT_TEST_GPU policy, as the crate's other
tests do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 12:28:03 -05:00
bherbruck b0270f41dc Tests: G2 corners measure smoothness away from both corners, and a 3.5 setback is refused (#13539, #13522) (#13896)
* claim: #13539 and #13522, the G2 corner's tests after solidcore #2988

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* kernel tests: the G2 corner's seams and the set-back 3.5 case follow solidcore #2988's Setback (#13539, #13522)

Fixes tangency-labs/solidcraft#13539
Fixes tangency-labs/solidcraft#13522

Both regressed in the solidcore bump a445bdaac6 -> c0d3cc0124 for one cause: tangency-labs/solidcore#2988 (e9a5ded95c, "a vertex marked set back between planes is the blend corner's Setback"). The same cause as tangency-labs/solidcraft#13895 (#13540 and #13542). solidcore's change is deliberate and certified: the corner's fill meets the rounds tangentially (4.71 degrees -> 0.00 on the golden box), where the N x N patch it replaces was uncertified. So the tests that encoded the old corner change, not the kernel.

**#13539: corner_g2 `a_concave_corner_takes_the_g2_patch_too` and `set_back_8_the_g2_corner_lies_between_the_hull_and_the_prisms`.**
Both failed only at their curvature-jump-clear-of-the-corners assert (0.0611 and 0.0554 against CURVATURE_SHARE / 5 = 1e-3); faces, volumes and validity were right.

The cause is the test helper `corner_seams`, not the fill:
- Its doc says a station counts when it is at least `share` of its side from the side's end at a corner. Its code took one corner end per seam and counted stations at least 2 x share of the edge from it, assuming "SMLib splits each side in two".
- Since #2988 each round's end section is one edge with a corner at both ends. The code excluded only the first, so stations right at the far corner, where no patch is G2 beside G1 rounds, counted as clear. The 405 stations counted are what that predicts: 6 face-curve halves and 3 whole sections.
- The helper now counts a station when it is at least `share` of its side from each end of the side at a corner. The side is the seam plus the seam continuing it smoothly at an end that doesn't turn.

This is the same rule on the old topology. Take a half-side of length L whose start is the corner, continued by a half of length L. A station at fraction f is fL from the corner and L + (1 - f)L >= L >= 0.15 x 2L from the side's other end, so it counts iff fL >= 0.15 x 2L, i.e. f >= 0.3: the old `|f - 0| >= 2 x share`, and the same from the other end. `CURVATURE_SHARE` and `share` (0.15) are unchanged. The set-back-8 test now prints its `clear`, as the concave one does.

Run (sc-build, solidcore c0d3cc0124 + batch):
- set back 8: clear 2.954e-4 per mm (405 stations), pass
- concave corner: clear 4.136e-4 per mm (the ball's 0.2), pass

**#13522: fillets/every-box-corner-three-edges-r3-curvature-setback-3-5** (r3, set back 3.5).
The case expected the corner to build. Since #2988, solidcore refuses it: at 3.5 the Setback's fill doesn't stay convex, and the least setback that builds is 3.5595. The N x N patch built there before was uncertified. The case now expects that refusal, as its sibling `...-setback-3-refused` expects its own.

SolidCraft's refusal doesn't name the size yet: "the fillet with open setback corners is larger than the faces beside its edges allow". solidcore reports the least setback only in its error comment, and `blend_failure` sizes only radius, scale and chord fits. Carrying it as typed data and naming it: size named in tangency-labs/solidcraft#13913.

KNOWN_FAILING: the two corner_g2 lines and the case's fillets line come out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:03:59 +00:00
dudedagger42 a42eeeb15c STEP import: a curve style does not colour the solid (#12291) (#12984)
* STEP import: test that a curve style does not colour the solid (#12291)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP import: a curve style does not colour the solid (#12291)

find_colour took the first COLOUR_RGB reachable from a style list, searching
from the end of the queue, so a CURVE_STYLE (edge colour) listed beside a
surface style painted the whole solid. Skip curve, point, symbol and text
styles, and read the styles in the order the file lists them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP import: keep the old style order; boundary lines do not colour the solid (#12291)

The skip of curve and point styles alone fixes #12291 whichever order the styles are listed in, so
the style list is read in its old order again (the colour and transparency of the same style win, as
before). A surface style's boundary, silhouette, segmentation, control-grid and parameter lines
(and CURVE_STYLE_RENDERING under them) are curves drawn on the surface: their colour no longer
paints the solid either. Test: a surface style whose boundary lines are blue is still its fill's
orange.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:03:56 +00:00
dudedagger42 fd233bfde0 Boundary Surface: two curves meeting at both ends make a lens (#11878) (#13057)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:02:53 +00:00
dudedagger42 3f64bc3376 STEP export and Insert Design: a body with a slot cut a hair off a sloped bar is kept (#12365) (#13089)
* test(smlib): a UV trim with a dead span past its edge's end maps to a valid pcurve (#12365)

SMLib hands over trims whose domain runs a rounding past the edge record's
interval with a dead span there (knots `5 x4, 5.00000000003 x5` on an edge
ending at 5), or whose last interior knots lie one ulp past the end knots.
`mapped` moved the end knots alone, leaving a run of 9 or an unordered vector,
which the record refuses ("knot multiplicity", "unordered knots"); the body was
dropped on STEP import and `component.insert_design`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(smlib): a UV trim a rounding past its edge is cut to the edge (#12365)

SMLib can hand `mapped` a trim whose domain runs a rounding past the edge
record's interval with a dead span there (knots `5 x4, 5.00000000003 x5` on an
edge ending at 5: the slot of the sloped bar in the issue), or whose last
interior knots sit one ulp past its end knots. Moving the two end values alone
made a run of 9 knots at 5, or an unordered knot vector, which the record
refuses as "knot multiplicity" / "unordered knots"; the body was dropped on
STEP export and `component.insert_design`.

Now every knot within the slack past an end is that end, and the end spans
that leaves without length are dropped together with their control points,
never below degree + 1. A basis function whose support has no length is zero
on the domain, so the curve on the edge is unchanged.

Tests: `export::tests::mapped_tests` (committed first, failing as reported).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(smlib): a reversed UV trim with a dead span maps onto the edge's start (#12365)

The dead span of the issue on an edgeuse running against its edge: `mapped`
reverses the trim about its own ends, so the span sits at the start as a knot
run a rounding inside the edge's start. Distinct control points in the dead
span, so a drop at the wrong end would show.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(smlib): snap a UV trim's knots within the slack of either edge end (#12365)

On an edgeuse running against its edge `mapped` reverses the trim about its
own ends first, so the issue's dead span sits inside the start as a knot run
a rounding after it (`3 x4, 3.00000000003 x4`). Snapping only the knots at or
past the ends left that run, and the trim kept the dead span's control points
at its start: the record took it (a run of degree + 1 inside), but the pcurve
began at the dead span's point, not the trim's.

A knot within the slack of either end of the edge's interval, inside or past
it, is now that end; the drop of dead end spans then runs on both sides. The
two earlier cases map as before.

Test: `a_reversed_trim_with_a_dead_span_is_cut_at_the_edges_start`
(committed first, failing: 9 points kept).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 15:02:29 +00:00
dudedagger42 c136ca6fea Cut: say when the cut removes no material (#9969) (#13191)
* Cut: say when the cut removes no material (#9969)

A cut whose tool lies clear of the body finished with "operation complete" and no
warning, though nothing changed. It now warns, as a hole does (#10701).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Cut: check for a cut that removes nothing after a sheet-metal cut too (#9969)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:01:49 +00:00
bherbruck a104ac1049 macOS packaging: retry stapler validate as staple does (#13195)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:01:33 +00:00
dudedagger42 659faf1012 Send to Slicer: find QIDI Studio, CHITUBOX, CreatWare and slicers one folder down on Windows (#13171) (#13204)
* Send to Slicer: test that Windows installs of Lychee, QIDI Studio, CHITUBOX and CreatWare are found (#13171)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Send to Slicer: find QIDI Studio, CHITUBOX and CreatWare, Lychee's real program name, and slicers one folder below their maker's on Windows (#13171)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Send to Slicer: the new Windows test builds its expected paths with the platform's separator (#13171)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Send to Slicer: rustfmt the QIDI Studio row (#13171)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:01:18 +00:00
Igor Bronovskyi 9420dbb128 Ukrainian: translate 100 new interface strings (#14061) 2026-11-09 15:00:45 +00:00
dudedagger42 2ee3881f30 Offset: say when a parameter distance drives nothing (#12294) (#13209)
* Offset: say when a parameter distance drives nothing (#12294)

A spline offset together with another curve is trimmed and unlinked, so a distance
that names a parameter drives nothing, and the reply only showed "param": null.
The reply now carries a warning saying so.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Offset: keep the unlinked-offset warning in a const (fmt width)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:00:34 +00:00
dudedagger42 3bd7acc3a0 Extrude: faces in different planes extrude each from the body as picked (#11492) (#13228)
The dialog recorded one Extrude per face when the faces were not coplanar, so a later face was the grown face. One command now takes the faces of a body in any planes: every profile sketch is made from the body as picked, then each group extrudes. Faces in different planes were refused by solid.extrude before; the atomicity test keeps its error cases that remain errors.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 14:59:58 +00:00
bherbruck 66a54ed55e Kernel: a far-moved disjoint sphere and box union, ignored until solidcore#3131 (#13235)
Found by translation_invariant at PROPTEST_CASES=200 on solidcore a445bdaac6.

Co-authored-by: bherbruck <bherbruck1@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:59:47 +00:00
dudedagger42 48f5c3de9f Loft: a curved section lofts to a point on ruled walls (#11216) (#13245)
* test(kernel): a circle and a rounded square loft to a point (#11216)

The cone from a circle of radius 20 to an apex 30 above (pi r^2 h / 3, at
most three faces) and the pyramid over a 20 x 20 square with 5 mm corner
rounds (area x h / 3; the base, four triangles, four cone pieces). Both are
refused before the fix: "not supported yet: lofting curved sections to a
point". The square's `pyramid` test stays as it is.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(smlib): a curved section lofts to a point on ruled walls (#11216)

`smlib_loft_to_point` built the pyramid from lines only: the base from new
lines between the corners and a planar triangle per line. A circle, a
rounded rectangle or a fit spline has no corners to build triangles from, so
`helix.rs` refused them ("not supported yet: lofting curved sections to a
point").

The base is now built on the section's own curves, each line still gets its
planar triangle, and each arc or spline gets the ruled surface from its
B-spline to a zero-length curve at the apex (`create_ruled_surface`): the
surface's v = 1 side is the apex, a pole, and its u sides are the rulings
from the segment's ends, so the wall is one face over the surface's whole
box (`create_face_from_surface`). `StitchAndOrient` joins them as before.
The refusal in `helix.rs` goes, and a section of one closed curve (a circle)
is a section: the three-curve minimum goes, the loop check stays.

Routes rejected: the skin loft with a degenerate apex section (1001 for
every apex-curve degree); a taper extrude to the apex (closes only where
the inward offset collapses to a point).

Tests: `helix::tests::cone_from_a_circle_to_a_point` and
`a_rounded_square_lofts_to_a_point` (committed first, failing).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(smlib): a circle lofted to a point above it is a cone, not refused (#11216)

The hostile-argument tests still expected Error::Argument for a circle
lofted to an apex above its plane, which the fix now builds. Both now
check the cone's volume (pi r^2 h / 3) and that an apex in the circle's
plane is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 14:59:30 +00:00
dudedagger42 0aee215381 Patterns: many cut copies are joined in groups, each cut in turn (#8765) (#13254)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:59:17 +00:00
dudedagger42 51935cc109 Plane, sketch and other face references: a numbered piece of a round face saved by 0.30.1 is found again (#13252) (#13263)
* Test: a tangent plane on a numbered piece of a round face is found again (#13252)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* A numbered piece of a round face is found again by the other face references (#13252)

locate_face falls back on the whole-face recovery Sketch Project has had since #11707,
with its guards, when a numbered name (F2:side:c1#2) finds nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 14:59:04 +00:00
dudedagger42 3249b670c3 STEP export: state the loosest edge and vertex tolerance as the file's uncertainty (#11997) (#13264)
STEP keeps no tolerance per edge: the representation context's uncertainty is the
only statement of how far an edge may lie off its faces. The export wrote the
loosest record tolerance, so a body whose intersection edges lie 5.6e-4 mm off a
face was written as good to 1e-6. Read back, those edges need tolerances SMLib's
check refuses (more than 1000 times the body's).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:58:50 +00:00
0rdnungsamt c62e398778 Agents and scripts: Motion Trace, Keep Motion Trace and Edit Motion Trace describe their exact parameters and replies (#13297)
* claim: #2829 (motion trace schemas)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Agents and scripts: Motion Trace, Keep Motion Trace and Edit Motion Trace describe their exact parameters and replies

Part of #2829. Adds input and output schemas for motion.trace,
motion.trace.keep and motion.trace.edit, builds their replies from the
reply types, and removes the three names from unconverted.txt. The Trace
dialog's preview no longer sends point_ref to motion.trace, which never
read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Motion Trace: test a trace stopped by contact

A trace that stops at a contact gives its stopped_at and
stopped_by_contact through the typed reply, for trace and keep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:58:25 +00:00
0rdnungsamt 864270d190 Sketch patterns: editing a circular or rectangular pattern can add or remove the entities it copies (#10649) (#13298)
* claim: #10649

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch patterns: editing a pattern can add or remove the entities it copies (#10649)

A changed seed list on an existing circular or rectangular sketch pattern
now removes the old copies and rebuilds the pattern from the new list,
instead of being refused. A copy still cannot seed its own pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch patterns: a changed seed list keeps a line drawn from a copy's point

Also says in the schema descriptions and the guide that the seed list can change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sketch patterns: a changed seed list survives losing an earlier pattern

Removing the old copies can retire an earlier pattern seeded by one of
them, which left the stored index past the end of the list and refused
the edit. The index is now capped once and used for both steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:58:15 +00:00
dudedagger42 daabe0fb59 Cancel stops the kernel work of the feature it is on (#10577) (#13299)
A dialog's Cancel set doc::progress::Progress, which the evaluation checks
only between features; a running boolean ran to its end. Progress now
carries a kernel Stop, stopped on Cancel, and a Stop entered while another
is current runs the work under a token that stops when either does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:58:04 +00:00
Mek 716f22d491 Linux X11: the title bar moves the window on every drag, not every other one (#13334) (#13335)
* claim: #13334

* Linux X11: release the press a window move or resize hands to the window manager

The title bar, a design tab and the edge zones start an OS move or
resize with the press egui is handling. On X11 the window manager then
receives the button's release, so egui kept the left button down and
the next press only ended that drag: the bar moved the window every
other time, and the edges resized it every other time.

window_chrome::hand_press_to_os remembers the press it hands over, and
the desktop app's raw-input hook calls release_press_taken_by_os first
thing on the next frame. Where the OS does send the release later, egui
ignores it (no button is down, no click).

Fixes #13334.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document the desktop raw-input hook and the title bar test frame

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mek <mf@linescode.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:57:39 +00:00
dudedagger42 62b36c176d Sketch: a construction plane of another component keeps its place (#11734) (#13365)
* Sketch: a construction plane of another component keeps its place (#11734)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch: deref the mapped plane (#11734)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch: find a plane's component without a counted feature search (#11734)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Review 13365: placed_plane keeps its doc comment, the test pins axes, normal and line end (#11734)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Review 13365: bring the geometry traits into the test (#11734)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 14:57:27 +00:00
bherbruck 2c5ddbeef9 CI: regenerate generated files nightly, not on each push; give the checks job 25 minutes (#14255)
The regeneration pushed straight to main after merges, and each push outside the
merge queue throws away every batch being tested (about 30 minutes lost each time,
four times today). It now runs once a night at 08:17 UTC (3:17 AM ET). The
Format and repository checks job took 13-14 minutes under load and was cancelled
at 15, failing its batch; it now has 25.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:15:52 -05:00
bherbruck 0bb7cce47c Move SolidCraft's mesher into solidcore-tess and its evaluator into the kernel, one mesher (RFC 0003 item 1.1) (#14003)
* Move SolidCraft's mesher and its evaluator into solidcore (RFC 0003, item 1.1)

A move only, so history follows the files: SolidCraft's planner
(crates/kernel/src/tess/) becomes solidcore-tess's src/mesher/, and its plan
evaluator (crates/geom/src/tess.rs and tess/) becomes the kernel's
src/eval/plan.rs and src/eval/plan/, next to solidcore::eval. No content
changes; the next commits wire them in and remove solidcore-tess's own
planner, so one mesher remains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: remove its own planner and plan, replaced by SolidCraft's

solidcore-tess's planner (src/planner/) and plan (src/plan.rs) were a port
of SolidCraft's; SolidCraft's own moved in with the previous commit, so one
mesher remains (RFC 0003, item 1.1). The next commit wires the moved code in
behind solidcore-tess's public API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Wire in SolidCraft's mesher and evaluator; SolidCraft meshes through solidcore-tess

The moved plan evaluator is the kernel's `solidcore::eval::plan`, in the
kernel's vectors, beside `solidcore::eval` (where the two overlap, they are
unified in a later step). The moved planner is solidcore-tess's `mesher`,
behind its public API: `plan`, `TessPlan` and `FacePlan` (SolidCraft's plan
types, now in solidcore-tess's `plan`, with its `poles` field), `evaluate`
returning the mesh and the faces left out, `PlanStats` and its records,
`MeshTolerance`, `preview` and `simplify`.

- The mesher evaluates surfaces with the kernel's plan evaluator and edge
  curves and pcurves with the kernel through solidcore-convert; it works in
  the kernel's vectors and hands the plan the record's. It keeps SolidCraft's
  text (rustfmt skips it) and allows the lints it does not meet yet, with the
  reason; a panic in planning is caught at `plan` until the next step brings
  checked access.
- SolidCraft's geom re-exports the evaluator as `geom::tess`, through one line
  in xtask/solidcore-seam.txt (until RFC 0003 item 1.8 moves model-evaluation
  callers to ops and tess queries), with converters between its vectors and
  the kernel's and its partials in its own vectors; the B-spline basis
  functions it shares with geom's splines moved into `eval::plan::basis`.
- The kernel crate meshes with `solidcore_tess::plan`; its `MeshTolerance` and
  `DISPLAY_ANGLE` are solidcore-tess's, and its worker map that only the
  mesher used is gone.
- Tests move with what they test: the planner's tests that build bodies with
  SolidCraft's kernel are crates/kernel/src/display_plan_tests.rs, the plan
  types' are in solidcore-tess, the evaluator's cross-check with geom's NURBS
  surface is in geom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Kernel rig: the shared core names the mesher's new home

tests/kernel-cases/PATHS-ALL still listed crates/kernel/src/tess/**, which the
move emptied, and the rig plan's check fails a glob that matches no file. The
mesher and its plan evaluator are now solidcore-tess's src/mesher/ and the
kernel's src/eval/plan.rs and plan/; they are inside solidcore/** already, and
are named there so the move stays visible in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: ask the moved mesher's pole question once per node

The sag refinement and the flips ask, at every triangle corner, side and
round, whether a node is on a pole, and the self-crossing check asked it twice
per node, once for each chord the node ends. Whether a node is on a pole is a
property of its parameters: a `sag::Poles` memo, shared by refinement and the
flips of a face, asks it once per node, and `tangle::crossing` keeps each
node's answer for both its chords.

This is #13559's fix for solidcore-tess's own planner, which the move replaced,
carried over to the moved mesher with its test
(`the_pole_question_is_asked_once_per_node`: 27,452 questions for 349 nodes
without the memo, at most two per node with it). Triangle counts are unchanged
on the 1,107 kernel-case bodies and primitives, and the coil's test
(`the_spline_coil_keeps_its_triangles`) still passes.

Ported from #13559 by @bherbruck (co-authored by jyviko).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore: no panic-prone calls in the moved plan evaluator

f64::clamp panics on NaN bounds, so Domain::clamp and wrap_into take max then min. The
derivatives and starting-grid vectors grow as they are filled instead of preallocating a
product that could overflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:15:37 -05:00
bherbruck 343a5b8dce Display: a G2 corner's side to its round is drawn again, and a fillet chain pick stops there (#13542) (#14226)
* kernel: smooth_edges measures each edge's ends too, so a G2 corner's side to its round is drawn (#13542)

Fixes tangency-labs/solidcraft#13542

Since tangency-labs/solidcore#2988 the G2 corner patch's side to each round is one edge, no longer two halves split at a vertex. Along that edge the patch is G2 to the round over the middle but only G1 near both ends, where the side ends on a face and the round is only G1 to the plane. smooth_edges decided G2 from 8 stations at the middles of 8 equal parameter spans, the nearest 1/16 of the edge from each end. On the 20 mm box's r3 corner those fall just inside the G2 part, so all 8 pass and the side was hidden in the shaded view.

Measured along the three sides (a local probe, not committed): the relative curvature jump across the edge, against BEND 8.73e-3, is 0.19 to 0.23 at the ends and up to 0.64 within 4% of them. From f 0.0625 to 0.95 it is 1e-5 to 8e-3 (edge 8: 7.97e-3 at f 1/16). The normals agree everywhere (1e-7 to 4.6e-4 rad).

**Change.** `join` also measures at the edge's two exact end parameters. An end where the normal is undefined (a pole, an apex) or that does not evaluate is skipped, as the module's doc requires; the 8 middles keep their rule. This is still sampling: a join that leaves G2 only between stations reads smooth. The certified decision is a whole-edge G2 enclosure, tangency-labs/solidcraft#13914. Moving these stations onto solidcore's continuity measure is tangency-labs/solidcraft#14171.

**Tests.**
- `a_g2_corner_s_side_to_its_round_is_drawn_whole` passes; its KNOWN_FAILING_TESTS line comes out.
- New negative test `measuring_the_ends_keeps_a_g2_join_hidden`: the 8- and 16-piece rings' wall joins (pieces of one cubic spline, G2 ends included) stay smooth and hidden.
- smooth_edges: 12/12.
- solidcraft-kernel: 811 passed, 8 failed, every failure on KNOWN_FAILING_TESTS.
- solidcraft-engine: every failure on KNOWN_FAILING_TESTS; no listed test now passes.

**What changes for a user.** A G2 corner's sides to its rounds are drawn again in the shaded view, as before #2988. The smooth flags also steer which edges a fillet chain pick passes over, so a chain pick now stops at those sides, also as before #2988. No engine test moves.

Changes a default: no

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* smooth_edges: rustfmt the ring test's filter (#13542)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:15:20 -05:00
Hainlein4711 42918fe51b Components: reject ambiguous names in command targets (#13388) 2026-11-09 14:56:50 +00:00
github-actions[bot] 47bfaf734c Regenerate generated files and fold pending files [skip ci] 2026-11-09 17:53:30 +00:00
phobicdotno ca171fe920 Parameters: resize the window and drag column widths (#13422)
* Parameters: resizable window and draggable column dividers (#5097)

The Parameters window can be resized from its free side edge, its bottom
edge and the corner between them; a double-click on an edge returns that
edge's axis to its automatic size. Width and body height are stored on
their own, so an axis the user never dragged keeps following its content.
Every column heading has a divider that drags the column to a chosen
width, kept for every section so they stay lined up; a double-click on a
divider returns that column to its automatic width. Sizes and widths are
kept in egui's persisted memory. Defaults are unchanged until the user
drags.

* Parameters: the side resize grip stays clear of the body scrollbar (#13422 review)

When the Parameters window is docked on the left, its free edge is the
right one, where the body's vertical scrollbar also lives. The side grip
was registered after the scrollbar and so won every drag on that strip,
making the scrollbar undraggable whenever the parameter list overflowed.

The panel now records the body's scroll rect when its content overflows,
and the side grip starts below that rect. The footer strip and the
corner grip still resize the width, so nothing is lost when the list
is long; when it fits, the grip covers the whole edge as before.
2026-11-09 14:56:15 +00:00
MarkusRe fe789b2669 Insert Design: read the saved design beside the window, with progress and Cancel (#11593) (#13445)
* claim: #11593

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Insert Design: a test that the saved design is read beside the window (#11593)

The test fails on main: Insert Design runs component.insert_design on the
window thread, so the file load is never busy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Insert Design: read the saved design beside the window, with progress and Cancel (#11593)

Insert Design ran component.insert_design on the window thread. A large
design froze the window until its features were rebuilt, and two crash
reports end there (#11593, #12108).

A saved design picked in the file dialog, or chosen from the list of
designs open in other tabs, now goes through the background file load
that Insert STEP and Update Links use (a new kind, InsertDesign). The
window keeps drawing, the card shows the rebuild's progress, and Cancel
leaves the design as it was. The design-is-open-elsewhere question stays
for Open only: Insert Design only reads the file, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:56:03 +00:00
dudedagger42 b1c2edf0f7 STEP import: a face's parameter box is at least 1e-3 across where its surface allows (#12757) (#13457)
* STEP import: a face's parameter box is at least 1e-3 across where its surface allows (#12757)

A plane is unbounded, so the reader chooses each face's parameter box: its
pcurves' box, padded 1e-6. A sliver face 1e-5 mm wide then had a plane whose
two sides lay within SMLib's tolerance of each other, and SMLib refused the
body (SmSurface test #6, "partially coincident boundaries"). Along any
direction not closed on itself, the box is now at least 1e-3 across, within
the surface's natural range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* STEP import: a sliver face's box keeps its full span at a natural bound; pcurves outside the range stay an empty domain (#12757)

The widening about the middle was clipped by the surface's natural range,
so a sliver face against a bound got half of OPEN_DOMAIN_SPAN or less;
it now shifts off the bound. A box clipped to nothing (pcurves wholly
outside the natural range) is no longer widened into a non-empty one and
is refused as an empty face domain again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:55:46 +00:00
Alex Lytvynenko 6c85bb76f6 fix: correct offset-surface third derivatives (WR-160/161) (#13916)
* fix: retain the four unconditional offset third-derivative corrections

Flatten the archived PR #1914 net change before rebasing onto current goldens main. The original head, complete history and source/evidence are preserved by the verified archive. Keep all original evidence, not retired runtime classifications.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* evidence: measure the offset derivative goldens migration without blessing

3182 same-input unbroken cases at d9f774290 on 1483513:443 encoded changes,442 caused by the four terms plus inherited gen1558. Preserve raw old/new outputs/input hashes and distinguish raw component counts from the actual gate.405 of443 transfer historical input+bits evidence; no blanket correctness claim. Keep draft and every unexplained difference visible; no golden or known-failure change.

Temporary observer and all measurements archived/readback-verified (24 members) under pr1914-goldens-measurement-20261106T195012Z, then observer removed. Requested-grid fix belongs to standalone2970 after2765closed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: migrate independently checked offset derivatives and named alias defects

Keep the four numerical corrections and all original evidence unchanged. Update only the 45 input-oracle-supported expectations; list 79 exact-input/exact-bit named-slot defects against #2717 and remove their wrong golden lines. Every one of the 3182 cases remains selected.

Independent 80/160-digit Cox-de Boor/Taylor jets start from the input control nets, not kernel base partials; 199 moved components meet the unchanged 1e-9 relative rule. Five closed-form/degeneracy/knot controls pass. Case-specific old/new blocks and measured errors belong in the PR evidence. No tolerance, floor, fixture, runner or requested-grid fix changes.

Supported cases: api_offset_m12_state_evals, gen0000, gen0007, gen0068, gen0196, gen0203, gen0241, gen0255, gen0430, gen0453, gen0458, gen0506, gen0587, gen0659, gen0660, gen0716, gen0741, gen0745, gen0794, gen0951, gen0959, gen0974, gen0985, gen1080, gen1188, gen1312, gen1416, gen1423, gen1425, gen1540, gen1846, gen1977, gen1979, gen1998, gen2053, gen2324, gen2456, gen2559, gen2601, gen2689, gen2761, gen2839, gen2864, gen2882, third_derivative_outputs.

The pre-migration gate failed with 443 changes. Expected remaining ordinary changes: 319; these are not blessed or hidden. Keep draft; no full-suite, downstream or canonical-success claim.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: identify six more offset named-slot failures from input geometry

List gen0033, gen0351, gen0690, gen1110, gen1310 and gen1643 against #2717 and remove only their wrong expected lines. Each has a successful unequal upper-half request whose named D(2,0) slot is overwritten by D(1,1) or D(1,2).

Independent 80/160-digit input-control-net jets establish 21 wrong scalar components; nine oracle controls pass, including a correct-slot negative control and zero-offset/failed-step exclusions. The fresh profile-matched collector reproduces all 3182 inputs and raw outputs byte for byte. No new golden value is blessed.

All cases remain selected: 3097 golden rows and 85 known failures. The other 313 ordinary changed cases stay red. No numerical code, fixture, runner, tolerance, floor or previous failure entry changes.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: verify thirteen pass-through offset cases from their input nets

Continue #1914 with 13 wrapped B-spline cases: gen0110, gen0756, gen0799, gen0824, gen1240, gen1275, gen1469, gen1748, gen1756, gen2231, gen2423, gen2662, gen2762.

CurveBoundedSurface forwards evaluation to its basis; StepSurface of a B-spline preserves the input parameters. Independent 80/160-digit input-control-net jets establish all 54 moved scalar components within the unchanged 1e-9 relative rule. Thirteen numerical and scope controls pass, including wrong-result, nested-offset and edited-script rejection; the whole-script named-slot alias audit finds no problems in these cases.

Replace only the 13 complete encoded rows, each identical to the preserved profile-matched current collector and CLI. Numerical source, all 3182 case identities, all 85 known-failure entries, and every unrelated golden stay unchanged. This makes 58 independently supported replacements (253 moved components); 300 other ordinary differences remain unresolved.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove twenty-six non-aliasing offset requests from input geometry

Continue #1914: gen0080, gen0236, gen0252, gen0379, gen0420, gen0466, gen0700, gen0811, gen0835, gen1052, gen1129, gen1535, gen1676, gen1691, gen1743, gen1804, gen1905, gen2058, gen2101, gen2225, gen2242, gen2402, gen2586, gen2721, gen2888, gen2913.

Unequal upper-half request shape alone does not make a named slot wrong. For each successful request in these read-only scripts, verify that every serialized slot has its named partial as its final writer. Exclude actual aliases, then independently evaluate every moved component from the input net at 80/160 decimal digits. All 162 components satisfy the unchanged 1e-9 relative rule and precision stability test. Seventeen numerical/scope controls pass, including rejection of an aliasing grid, a wrong returned value and an undecoded changed component.

Only 26 complete encoded rows change, each equal to the preserved profile-matched collector. No numerical source, input, runner, tolerance, mask/refusal contract or known failure changes. All 3182 cases remain, including 85 known wrong cases; 274 ordinary differences stay unresolved. Total supported replacements: 84 cases / 415 moved components.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove four zero-nested-offset cases from input nets

Continue #1914 with gen0302, gen0957, gen2311 and gen2766. One of each pair of nested offsets has exactly zero distance, so it is the identity without assuming orientation or a fold rule for two nonzero offsets. Independently evaluate all 17 moved components of the remaining offset from the original B-spline net at 80/160 decimal digits. Maximum relative error 1.012e-12; unchanged limit 1e-9. Twenty numerical/scope controls pass, including refusing two nonzero nested offsets and detecting a wrong returned value.

Only these four complete encoded rows change. All 3182 cases, all 85 known failures, source, tolerances, runners and unrelated expectations remain unchanged. Total supported: 88 cases / 432 moved components. 270 ordinary differences remain unresolved.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove eight regular nested offsets from their input normals

Continue #1914: gen0231, gen0693, gen1737, gen1831, gen1961, gen2461, gen2680, gen2938.

At every moved evaluation, independently differentiate the original input net and its first offset. Its tangent cross product is a positive multiple J of the base normal, so locally the second offset equals S+(d+e)n. Prove J and collinearity independently at 80/160 decimal digits; J ranges from 0.7951 to 2.5695. This is a local derivative statement, not a global fold or body-validity certificate. Reject negative or vanishing orientation, clamp/extension, mutation and named-slot aliases. All 36 moved components meet the unchanged 1e-9 relative rule (maximum 2.285e-11) with precision stability below 1e-50. Twenty-four numerical/scope controls pass.

Change only these eight complete collector-encoded rows. All 3182 cases, all 85 known failures, numerical source, tolerances and unrelated expectations stay unchanged. Total supported: 96 cases / 468 moved components. 262 ordinary differences remain unresolved.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove twenty copy/setter scripts from their receiver state

Continue #1914 without changing its four numerical terms or #2717's contract.
The runner's create_offset and split return separate surfaces; neither assigns
to its receiver. Explicit set_dist/set_allow steps are tracked independently.
Every one of the 89 moved receiver-derivative components is evaluated from its
original input B-spline net and active scalar distance at 80/160 decimal digits.
Maximum relative error 2.256e-12, unchanged limit 1e-9, maximum precision drift
2.468e-76. All 32 numerical/scope controls pass. True named-slot aliases, changed
returned-copy metadata, uncovered outputs, other edits and extended/clamped,
singular or unsupported-order evaluations fail closed.

Only these twenty complete collector-encoded rows change. All 3182 cases, 3097
ordinary rows and 85 known failures remain; unrelated rows are byte-unchanged.
Total supported: 116 cases / 557 moved components. 242 ordinary differences
remain unresolved. No new full-suite or canonical-CI success is claimed.

| Case | Components / maximum relative error | Old → new checksum blocks |
|---|---|---|
| `gen0032` | 3 / `4.4236e-16` | `[[92670.95430254005, 92715.26944807055, 5394495.4598471755]]` → `[[92693.03707327033, 92694.87458551113, 5394777.774092088]]` |
| `gen0162` | 3 / `1.0244e-15` | `[[46871.64465480175, 46912.45744508984, 641284.698235866]]` → `[[46871.953361038046, 46912.76615132614, 641305.966600447]]` |
| `gen0475` | 3 / `1.5724e-14` | `[[27151.82918504171, 131932.52549897382, -939979.6007762165]]` → `[[31185.4680385726, 127549.24613271712, -558222.3126147436]]` |
| `gen0572` | 6 / `1.7067e-15` | `[[167849.9025395879, 167890.1078119872, 21300050.663649388]]` → `[[167850.06785832535, 167890.11503477694, 21300062.927538052]]` |
| `gen0880` | 3 / `5.5494e-14` | `[[60920.154703662876, 61176.964056868645, 3906415.661581074]]` → `[[60920.704177028034, 61177.49230659628, 3906458.4145365255]]` |
| `gen0994` | 3 / `1.1467e-14` | `[[106650.05878460688, 106650.18920233288, 8439346.19902911]]` → `[[106650.05725603321, 106650.19177550825, 8439345.982613547]]` |
| `gen1224` | 3 / `1.8937e-13` | `[[30411.369709868013, 30459.526237990547, 1232554.694631206]]` → `[[30411.448242488477, 30459.51607033539, 1232556.181057504]]` |
| `gen1315` | 8 / `4.5609e-13` | `[[30383.545788392545, 30601.565436381683, 500105.67712378607]]` → `[[30810.583620881953, 31221.276639732067, 528537.8746195738]]` |
| `gen1409` | 3 / `6.742e-15` | `[[152531.7026919565, 152595.2574882943, 15973296.61425218]]` → `[[152487.8116847785, 152551.36648111625, 15964854.977971999]]` |
| `gen1736` | 5 / `5.803e-13` | `[[30381.85489376341, 30463.342937108937, 501217.8127281958]]` → `[[30381.84341680052, 30463.32683485667, 501216.735673139]]` |
| `gen1832` | 8 / `3.0573e-14` | `[[76195.08638962446, 76280.17415270921, 4698801.86238598]]` → `[[76194.26386539408, 76280.91280592125, 4698729.4083463205]]` |
| `gen1868` | 3 / `3.4819e-14` | `[[106585.78353762635, 106753.77622991134, 10570997.703791479]]` → `[[106603.2684371318, 106777.40382001479, 10572110.759543618]]` |
| `gen2179` | 3 / `4.8025e-13` | `[[106611.43744989557, 106748.63148533777, 10203459.959847251]]` → `[[106611.42625550646, 106748.64315685986, 10203459.145313958]]` |
| `gen2517` | 3 / `2.7329e-14` | `[[45834.828639702326, 45858.81352402091, 1450934.8174728234]]` → `[[45834.921610059566, 45858.92964139639, 1450936.507029497]]` |
| `gen2687` | 8 / `1.2649e-13` | `[[91548.27224064688, 91893.13238284142, 6649038.304278976]]` → `[[91548.21958135064, 91893.04473901891, 6649037.327577968]]` |
| `gen2688` | 3 / `2.255e-12` | `[[152371.15474998739, 152542.02290568707, 16971914.402893644]]` → `[[152371.10683083418, 152541.96646537865, 16971911.22996498]]` |
| `gen2732` | 6 / `2.4426e-15` | `[[107607.11219702837, 107831.01576927245, 8908709.776030885]]` → `[[107623.6670791933, 107824.74647024467, 8910818.62771597]]` |
| `gen2740` | 3 / `1.8201e-13` | `[[61009.98164865395, 61027.88924260481, 4365938.865820019]]` → `[[61009.990955444446, 61027.92015518564, 4365939.5726180915]]` |
| `gen2785` | 9 / `2.399e-14` | `[[91152.17943308967, 92306.83440740129, 7652415.540723781]]` → `[[91076.78844778116, 92375.2087779946, 7650622.669702106]]` |
| `gen2836` | 3 / `4.0678e-14` | `[[91224.74312733122, 91314.04492096876, 5636908.971653164]]` → `[[91224.72905059176, 91314.07339008275, 5636907.712885878]]` |

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove eighteen clamped boundary jets from input nets

Continue #1914. With extension disabled throughout, select_surface clamps the evaluation point to the original domain before differentiating. Independently grade the jet at that point, not derivatives of a constant extension. Every one of 102 moved components agrees with the original-net Taylor series at 80/160 digits; maximum relative discrepancy 4.364e-13, unchanged rule 1e-9, maximum precision drift 3.453e-77. All 37 numerical/scope controls pass.

Only these eighteen complete collector-encoded rows change. gen1146, gen2298 and gen2832 remain numerical disagreements, not blessed or automatically attributed to #2717. Actual extensions, wrappers with different domains, singularities, unsupported edits/orders and named-slot aliases remain outside this proof. All 3182 cases and 85 known failures remain. Total supported: 134 cases / 659 moved components; 224 ordinary differences remain unresolved.

Preserve the preceding twenty copy/setter rows and their already-published old/new proof verbatim in a finding to keep the PR body within its size limit. No earlier conclusion is changed by that relocation.

| Case | Components / maximum relative error | Old → new checksum blocks |
|---|---|---|
| `gen0096` | 3 / `5.2088e-14` | `[[-6773770209.875433, 64424015402.72274, -378749722047.2987]]` → `[[-6870563819.263274, 64495408259.89502, -386553674290.37585]]` |
| `gen0106` | 9 / `7.5415e-14` | `[[31626.719296583855, 33363.51465493481, 2099118.839468814]]` → `[[31510.658539520075, 33182.16476097925, 2093273.1365587397]]` |
| `gen0117` | 3 / `3.7514e-14` | `[[30393.92240119993, 30444.3317490507, 501900.592222631]]` → `[[30393.952077675916, 30444.158329310143, 501902.39235053613]]` |
| `gen0134` | 6 / `4.3632e-13` | `[[37.832510687021795, 104.14284964357064, 1054.742957704554]]` → `[[39.06487390981253, 102.07201454768582, 1103.5111544935114]]` |
| `gen0439` | 9 / `1.588e-14` | `[[-13493691.441181729, 13717537.718196781, -1869604804.9283178]]` → `[[-5556688.228706748, 5780534.505721803, -802954287.5887265]]` |
| `gen0470` | 6 / `3.6124e-14` | `[[30411.56311392412, 30538.255424358227, 2722225.4981703223]]` → `[[30411.02807801994, 30538.749829700955, 2722215.508914114]]` |
| `gen0520` | 6 / `2.0478e-14` | `[[2603.181323610652, 101149.6432820234, -150409.28755995663]]` → `[[4478.212077142718, 91793.59915509017, -110231.78941596425]]` |
| `gen0975` | 3 / `2.4293e-14` | `[[-0.07499960999706787, 0.28311173511413507, -1.263414230268762]]` → `[[0.0019536212055459258, 0.26423357801083436, -0.039611592564807424]]` |
| `gen1005` | 9 / `9.107e-14` | `[[28217.743974127192, 38093.608383810904, 1863000.1361175987]]` → `[[28256.151890684785, 38207.22373499281, 1865588.3408999464]]` |
| `gen1026` | 3 / `9.0952e-15` | `[[45771.34220991706, 45864.49375112448, 577600.5831079823]]` → `[[45772.14833280894, 45865.584040146976, 577658.7064678086]]` |
| `gen1047` | 3 / `8.4307e-14` | `[[31208.26635863553, 32223.47216717072, 562497.5647403828]]` → `[[31221.666358372844, 32308.728980748914, 563452.3343034461]]` |
| `gen1501` | 6 / `4.6519e-15` | `[[76938.6903388469, 77235.62454754181, 6127941.733940306]]` → `[[76034.63657112679, 77163.63088496617, 6061737.160377767]]` |
| `gen1815` | 3 / `7.9991e-14` | `[[30455.168428658406, 30493.41594569247, 1691412.366477158]]` → `[[30459.764928504002, 30490.46183200792, 1691573.0880891723]]` |
| `gen2309` | 3 / `2.8838e-13` | `[[-2130345613.6145353, 50165393450.44275, -376803058672.42773]]` → `[[-2129529154.1441603, 50151508983.73155, -376729189548.8684]]` |
| `gen2448` | 9 / `1.1976e-14` | `[[35233.48643230344, 35764.10016249043, 1427262.1225083272]]` → `[[19456.819917104032, 54845.74218580647, 1214690.186811323]]` |
| `gen2507` | 6 / `1.6393e-14` | `[[60864.74652840865, 60955.2526814626, 3623994.1771435984]]` → `[[60872.637104188514, 60959.125270976685, 3624559.095594456]]` |
| `gen2629` | 3 / `9.9247e-15` | `[[-0.7952289704799329, 21.961465706434275, -1.3129666919095415]]` → `[[-0.3803237369067746, 22.131710411144315, 10.81145447121247]]` |
| `gen2869` | 12 / `5.2903e-15` | `[[43578.856643134844, 50713.272901409575, 2669836.6176526896]]` → `[[45556.57516403698, 48850.43526261595, 2728328.359954016]]` |

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove canonical revolution jets and retain a named-slot defect

Continue #1914 without changing production, input selection or the #2717 contract.
Derive the rational full turn from original B-spline generator inputs, weights,
knots, axis and origin, never a kernel-generated control net. All 62 moved
components in thirteen cases pass the strict unchanged 1e-9 relative rule:
maximum error 3.689e-12, separate-net precision drift below 2.11e-76.
All 48 numerical/scope controls pass, including rational axial derivatives,
axis scale/orientation, explicit clamp and wrong-value rejection.

The unchanged gen1603 three-step/35-scalar prefix demonstrates #2717: step 2
names D(2,0), later overwritten by D(1,2), at out[27..28]. Independent expected
98.17705633871186 / -62.64369053230619 versus observed -682.320875639607 /
-274.2373622424093. Remove its wrong golden and add its issue-backed known
failure; the entire case still runs, and no alias implementation is taken.

All 3182 cases remain as 3096 golden rows plus 86 known failures. Every other
golden row and failure entry is unchanged. Total supported 147 cases / 721
moved components; projected remaining ordinary differences 210, pending the
actual gate. Preserve old/new encodings, mathematical construction, controls,
exact input hashes/bits, exclusions and the verified archive in the new finding.

| Case | Components / max relative error | Old → new checksum blocks |
|---|---|---|
| `gen0078` | 9 / `1.56099e-14` | `[[60992.32232773774, 62007.82172754136, 3573365.621681268]]` → `[[60955.3429329313, 61630.4439708014, 3570310.42044418]]` |
| `gen0171` | 3 / `2.40474e-14` | `[[30460.067354949344, 30789.234141913264, 1571002.9046867639]]` → `[[30170.97766234359, 31175.217379802565, 1563841.1838079793]]` |
| `gen0510` | 2 / `3.49776e-15` | `[[136783.91504548964, 136940.61322075027, 12115341.037492398]]` → `[[136755.1149473944, 136912.07291654628, 12111572.593217228]]` |
| `gen0688` | 3 / `1.9788e-15` | `[[31346.461684132, 32403.592700681234, 1772295.8567843311]]` → `[[30781.309826793167, 32510.58038801976, 1753301.5773981945]]` |
| `gen0913` | 3 / `1.51772e-13` | `[[61750.73067260197, 61840.21030969888, 3698631.549175109]]` → `[[61633.6609800661, 61723.14061716303, 3688650.729457595]]` |
| `gen0965` | 6 / `3.92942e-16` | `[[31437.46049255547, 31844.361634149758, 3185201.893145722]]` → `[[31020.49497003035, 33310.32052049644, 3179826.550622901]]` |
| `gen1021` | 3 / `1.32677e-14` | `[[32022.521603079706, 32221.489579301622, 622051.0166188008]]` → `[[32040.91340095389, 32239.881377175807, 623490.583575049]]` |
| `gen1336` | 3 / `1.80575e-15` | `[[30508.18586005638, 30627.932843864983, 508130.084364765]]` → `[[30493.239429126246, 30634.94737160066, 507062.21452469326]]` |
| `gen1523` | 3 / `3.02139e-15` | `[[106762.79310347415, 107249.71947489904, 8025630.370844599]]` → `[[108515.03086142626, 109001.95723285116, 8245925.889540591]]` |
| `gen2083` | 3 / `9.18182e-14` | `[[76180.35956883941, 76708.58602231681, 3861956.0360428616]]` → `[[76207.07582306274, 76693.32645472689, 3865576.97943913]]` |
| `gen2174` | 9 / `3.68895e-12` | `[[30567.472013004335, 30885.59102480571, 513955.18919101433]]` → `[[30404.688016921584, 30612.68300826498, 501435.5076105441]]` |
| `gen2176` | 6 / `9.06304e-14` | `[[30807.70523767886, 31289.921984959317, 534969.6617323706]]` → `[[30716.53174207574, 31302.716083319912, 528245.5167390216]]` |
| `gen2792` | 9 / `5.18444e-15` | `[[61142.0736249404, 61506.22354610884, 3704443.051684439]]` → `[[60914.84419372687, 61285.2585890741, 3688211.2636018256]]` |

Removed wrong gen1603 encoding: n=46,
blocks=[[3650.1057913563527,6286.665825084098,118136.81376043486]].
No tolerance/floor, compatibility flag, source, runner or topology change.
No full-suite, canonical-CI success, review resolution or readiness claimed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* docs: preserve the published clamped-jet evidence before body compaction

Retain the eighteen-row old/new section from #1914 at 2ef53c83e verbatim in a dedicated finding. The historical exact head and 85-failure count are explicitly scoped to that publication. No source, expectation, input, assertion, outcome or evidence conclusion changes.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove five receiver jets across returned offsets and inversion queries

Continue #1914 without changing formulas, inputs, tolerances or #2717's contract.
base_offset returns an owning copy for a direct B-spline; it does not assign the
receiver. With extension disabled throughout, step_inv leaves that receiver's
geometry/distance intact; its unchanged three-scalar payload is decoded but is
never graded as a derivative. Other kinds/nullable results remain excluded.

All 26 moved components in gen0136, gen0287, gen1623, gen1782 and gen2425 pass
strict relative 1e-9 against the independent 80/160-digit input-net Taylor
calculation of S+d(Su x Sv)/|Su x Sv|. Max error 1.17606e-12, precision drift
1.69242e-76. The last three use explicit clamping from the original knots.
All 52 controls pass. gen2558 independently disagrees and is not blessed.

Only these five complete retained actual runner encodings change. All other
3091 rows and every one of the 86 known failures remain byte-identical; all
3182 cases are retained. Total supported 152 cases / 747 moved components;
projected 205 ordinary differences, pending the real gate. Preserve exact
inputs/bits, full observations, disagreement, executable model/controls and
old/new encodings in the hash-verified archive and committed finding.

| Case | Components / max relative error | Input SHA-256 | Old → new checksum blocks |
|---|---|---|---|
| `gen0136` | 3 / `5.79439e-15` | `4515a282fe90fd6359b6858d6d7c8301dc3f80f89eb2f6e707b2f2a1d8695052` | `[[91876.89527025135, 92224.64165636596, 3709608.8064747667]]` → `[[91973.71856665419, 92226.07023677968, 3722151.3675662098]]` |
| `gen0287` | 3 / `1.17606e-12` | `e51e3bd2ec6e6c620c88445e04bb4153986ad684128ca811a218da31284c462f` | `[[106623.85073001102, 106758.36789985334, 8192383.812744899]]` → `[[106623.09246265274, 106758.03749782688, 8192286.540092921]]` |
| `gen1623` | 8 / `1.22953e-14` | `857b21820fa862beba1580ea3dd53eb5debdd8553e0b86d9c5790abee8e31467` | `[[937.572243299023, 1479.9961428738736, 24633.179569469605]]` → `[[898.8254877713206, 1424.9679762169394, 23984.775109513852]]` |
| `gen1782` | 3 / `1.9561e-14` | `015844bf6a82125fa52f3b3cbbd7dcf3753397ed5a8a9e725be22de887cb8fff` | `[[77236.60030788192, 87804.80267997787, 4353949.4496201]]` → `[[76718.60999413418, 86553.13132175046, 4319698.866069493]]` |
| `gen2425` | 9 / `3.87624e-14` | `3e08eda805173750cd4cb0f3a6bcf45b221e3853587034ab96ed05a4060e286e` | `[[994.256720147682, 1013.7863198590826, 32902.28326575608]]` → `[[995.7214865508853, 1015.1216311978034, 32929.3849376887]]` |

out.n stays 134 / 191 / 45 / 133 / 37. All statuses/topology/other fields stay
unchanged. No new absolute floor, construction, failure entry or gate change.
No full-suite or canonical-CI success, review resolution or readiness claimed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove five parameter-edit scripts from their input nets

Continue #1914 with no production, input, tolerance, floor or failure change.
All 24 moved components satisfy strict relative 1e-9 against independent
80/160-digit input-net Taylor jets: max error 1.99955e-12, drift 1.13002e-76.
All 59 numerical/scope controls pass, including affine-plane closed forms,
nonuniform-knot reflection, wrong-value/status rejection and the real case.

Reverse reflects the clamped knots/net and keeps d; swap transposes it and
negates d. Extension, failed edits, aliases and ambiguous knot sides stay
excluded. gen0686/gen0803/gen2531 changed jets precede their edit suffix;
gen0909 and gen2960 actually grade post-swap/post-reverse derivatives.
gen0304 and gen0672 independently disagree and stay unapproved.

Only the following five complete actual collector encodings change. All
other rows and all 86 known failures remain byte-identical; all 3182 cases
remain selected. Projected 157 supported cases / 771 components, 200 ordinary
differences pending the actual gate. Full observations, original input hashes
and bits, controls and verified archive are in the committed finding.

| Case | Components / max relative error | Input SHA-256 | Old → new checksum blocks |
|---|---|---|---|
| `gen0686` | 6 / `1.99955e-12` | `9d08376d9c36e5392c5d09ca19e7a42d951006834726e090c52d2cd7fdb10dd9` | `[[121930.35080477943, 122286.7144167371, 8701121.039546246]]` → `[[121962.3389298341, 122224.82699030975, 8705193.75695359]]` |
| `gen0803` | 9 / `2.23887e-15` | `1c9b9b7c8465da58ee4de50b4a92ba6086af384f7d7967df546742c35243d4a8` | `[[30433.220442247508, 30463.821946298885, 503538.46555445157]]` → `[[30433.06458783185, 30463.6612749778, 503524.44087460113]]` |
| `gen0909` | 3 / `5.29685e-13` | `4bb845ec023ff9aa5ed7af2f2175f1dc52125b80adbb1a3f9a708a8c9fb7c57a` | `[[76256.28041470288, 76301.39173228155, 4336610.869009565]]` → `[[76255.8695864608, 76301.01981243513, 4336553.392578878]]` |
| `gen2531` | 3 / `1.30999e-14` | `be2221978e98e31d856b00abc7753267057b708f92496c105d0271c8ae329689` | `[[45825.10682569999, 45897.16292421347, 1451228.6467945287]]` → `[[45826.31522262935, 45897.31002906505, 1451250.566334015]]` |
| `gen2960` | 3 / `4.3237e-14` | `0cad4c75b7b482c04b0a69061ef20d3065743f94ab5eef5696ea3b4903b70865` | `[[30413.486952040126, 30468.854227191732, 1445253.6014927593]]` → `[[30413.479977829935, 30468.838392777052, 1445253.4163972281]]` |

out.n stays 204 / 108 / 147 / 87 / 86. No status/topology changes.
No full-suite or canonical-CI pass, review resolution or readiness claimed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove seven offset scripts without cropping production cases

Continue #1914 with 29 independently graded moved components. Input-only
80/160-digit Cox-de Boor/Taylor jets satisfy strict relative 1e-9, with
max error 7.941402674945828e-14 and precision drift 9.382196956836735e-78.
All 66 numerical/scope controls pass. No tolerance or floor is changed.

gen0294 and gen1956 use complete-script proofs. The other five use decoded
prefixes containing every moved bit and verify every full suffix bit against
the four-term counterfactual. No suffix correctness or post-transform claim
follows. Production still runs each complete original case. Default evidence
decoding stays strict; no production decoder or execution skip is introduced.

Only these seven complete retained collector encodings change. The finding
records all 29 observations, original input identities, exact boundaries and
suffix digests, exclusions and the readback-verified 44-member archive.

| Case | Proof steps / raw scalars; unchanged suffix | Components / max relative error | Input SHA-256 | Old → new checksum blocks |
|---|---|---|---|---|
| `gen0046` | `4/6 steps, 131/187 scalars; 56` | 3 / `1.37231e-15` | `dbd153151a9ece86d7374df7e75c3b8fd643697463040a2c9b0aca8d7078246c` | `[[77121.34624082278, 78371.89588151565, 7204413.02628398]]` → `[[77127.56162394208, 78413.58757987301, 7204937.948856801]]` |
| `gen0294` | `7/7 steps, 139/139 scalars; 0` | 3 / `6.07478e-15` | `8e631a42bc095bbf10e999e257af4b43316e25bff9aa5b110989c4598f8075d1` | `[[131571.70347178396, 142646.29819385172, 8950770.303338341]]` → `[[131585.2632002976, 142668.59266738236, 8951797.094917087]]` |
| `gen0715` | `4/6 steps, 140/146 scalars; 6` | 8 / `7.9414e-14` | `3095b349ba413411105bf66000545935862075cf6b359fe85ebb31e65a068ad5` | `[[77220.71046083543, 77311.38053866001, 3702379.153818082]]` → `[[77227.07637822005, 77297.60213367597, 3703184.8790371185]]` |
| `gen1107` | `4/5 steps, 101/106 scalars; 5` | 6 / `6.85615e-15` | `9ea3bc28cade8ee485e49065c45b30dbad3f665b0e575a3b437e41e607b4185f` | `[[46154.69065168403, 46690.47230266395, 1392869.5927645955]]` → `[[46145.861751782475, 46681.36711117941, 1391635.70220002]]` |
| `gen1871` | `4/5 steps, 98/103 scalars; 5` | 3 / `3.18996e-15` | `b1f76deb657f9c21511623f227d889bc3682961414b54382f3dc3db102abd84d` | `[[30452.32264385231, 31097.267325821787, 505666.83287312003]]` → `[[30457.86551369243, 31094.125835654002, 506083.6793920865]]` |
| `gen1956` | `2/2 steps, 68/68 scalars; 0` | 3 / `9.69707e-15` | `025de42ef6f9465e60ab68ca104811b32a4bc5d9bd59e3289a451edb3d5fd2ff` | `[[31752.321348733272, 33425.56012012197, 595083.5858024094]]` → `[[31972.454180961, 33392.31834752314, 609529.5325767717]]` |
| `gen2606` | `2/5 steps, 22/41 scalars; 19` | 3 / `2.37756e-15` | `90597c56d3e9efbe5c336f44a7b90af5dcbe4059a4667c1a442afa29d7ea3c79` | `[[3.6721180340205315, 9.99814166857267, 125.20427053028689]]` → `[[3.8326148961392716, 10.356535523557282, 128.5298394660693]]` |

Common fields remain deferred=null, error=null, ints={}, topology=null;
out.n stays 187 / 139 / 146 / 106 / 103 / 68 / 41. Every other golden row
and all 86 known failures are unchanged. All 3182 cases remain selected.
Projected 164 supported cases / 800 components / 193 ordinary differences
pending the actual gate. Raw collector evidence retains its measured head,
not the refreshed branch. No canonical-CI/full-suite pass or readiness claim.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: record two independently wrong named-slot query prefixes

Continue #1914 without changing #2717's implementation or design. gen0757
and gen2243 return D(2,0) slots overwritten by the later D(1,1) branch.
An input-only 80/160-digit Taylor oracle independently proves all six named
components wrong by 0.9128 to 1.6373 relative, with precision drift <=3.931e-77.
All 71 numerical/scope controls pass. No tolerance or floor is changed.

gen0757 retains its receiver distance after the returned base_offset and
uses the explicit clamp-to-domain jet; gen2243's proof is before its swap.
Original complete inputs/outputs, exact bits, prefix boundaries, expected
values and the 49-member verified archive are in the committed finding.
This certifies neither the overwriting expression nor any suffix.

Remove these two wrong complete goldens, replacing neither with another
wrong result. Both full cases keep executing under case-specific #2717
known-failure entries. Every other golden and existing failure is unchanged.

| Case | Full original input SHA-256 | Prefix steps / scalars | Removed old golden block / full n |
|---|---|---|---|
| `gen0757` | `a7fff71f532ef0b76a0f9aace5c2d210fa393ff426cb68f470377a44d426a08b` | `2/5 steps, 66/108 scalars` | `[[45835.32261187165, 45951.766035862245, 580801.8107888201]]` / `108` |
| `gen2243` | `b5c1b0a415107215e7fdcd7fc301a26bf021c40add31c08ae54074e3f9e07f4d` | `3/6 steps, 63/137 scalars` | `[[30474.99107836607, 30522.408270029144, 3002299.3746962883]]` / `137` |

Common removed-row fields: deferred=null, error=null, ints={}, topology=null.
All 3182 cases remain as 3094 golden rows and 88 known failures. Supported
replacements stay 164 cases / 800 components; projected ordinary differences
191 pending the actual gate. No production crop, skip, guard or geometry edit.
No full-suite, canonical-CI pass, review resolution or readiness claim.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove two prefixes where extension is enabled but unused

Continue #1914 with 12 input-only 80/160-digit Taylor jet observations.
OffsetSurface starts without an extended surface; every audited prefix
point is within the original direct B-spline domain, so select_surface
reaches neither extension nor clamping. Actual extension remains unapproved.
All 78 numerical/scope controls pass; max relative error 8.805332234651928e-14
under unchanged 1e-9, precision drift <=4.472617148231448e-77, no new floor.

gen2007 proves step 0 before swap/equality, 32/36 scalars and four unchanged
suffix scalars. gen2248 proves its state/eval prefix before split/trim/reverse,
74/308 scalars and all 234 suffix scalars bit-identical. No suffix correctness,
post-edit derivative or actual extension claim. Every complete case still runs.

| Case | Components / max relative error | Original input SHA-256 | Old → new checksum blocks |
|---|---|---|---|
| `gen2007` | 9 / `1.08547e-14` | `40979f91a5c8143e5b0b2e560902302e5bc30d21b034c5bc5e5bee592e922ac6` | `[[8385.129761675184, 10208.861909515392, 240531.36113004576]]` → `[[7433.183654517214, 8963.545179952707, 224246.08082010786]]` |
| `gen2248` | 3 / `8.80533e-14` | `a8b8b0afe101dabe69c50ee63ee7897ba829c8c8873bd9089fde1faf5ce3d839` | `[[156372.75134309038, 252429.00380531867, 19263689.92973941], [40312.70837088957, 63077.20645060704, 274696.5340730896]]` → `[[155970.61055021366, 252143.7086417839, 19234501.757323023], [40312.70837088957, 63077.20645060704, 274696.5340730896]]` |

Common fields remain deferred=null, error=null, ints={}, topology=null;
out.n stays 36 / 308, including gen2248's unchanged full second block.
Every other golden and all 88 known failures are unchanged. All 3182 cases
remain as 3094 golden rows and 88 known failures. Projected 166 supported
cases / 812 components and 189 ordinary differences pending the actual gate.
gen2690 independently disagrees and is not blessed. No numerical source,
input, guard, production decoder, tolerance, floor, crop or skip changes.
The finding retains all observations, inputs, bits, boundaries, source proof
and the readback-verified 56-member archive. No readiness or CI pass claimed.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* docs: retain six published named-slot proofs verbatim

Preserve #1914's complete six-case/21-component section from the published
beb1eb184 description before replacing its inline duplicate with a link.
Verify exact text equality, including input hashes, observations, bounds
and original collector/control/archive provenance. No new execution or
reblessing is implied; all numerical source, goldens and failures stay intact.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove the explicit VMax clamp in gen1332

Continue #1914 with one complete row / three moved components. Extension
stays enabled in the original input, but the explicit original side mask
11 includes VMax=8 and clamps the sole outside request before extension.
Independent 80/160-digit original-net jets establish both nonperiodicity
position tests (separation/threshold >6.632e12, regular first samples) and
the D(0,3) at the clamped point, not a constant continuation's derivative.

All three components pass unchanged relative 1e-9: max error 8.063531413806036e-14,
precision drift <=1.8686238951724364e-77. All 85 numerical/scope controls pass.
No tolerance, floor, flag, original input, operation, crop or skip changes.

| Case | Components / max relative error | Full original input SHA-256 | Old → new checksum blocks / unchanged n |
|---|---|---|---|
| `gen1332` | 3 / `8.06353e-14` | `cb46fb52544fb4c84e3a64deca2346d55fc1ad7f94adec553f4e41652db65660` | `[[68487.9127286635, 73586.6520814705, 1550175.2367883257]]` → `[[30395.004642320324, 31489.622915038668, 1034470.1990414166]]` / `72` |

Common fields remain deferred=null, error=null, ints={}, topology=null;
out.n remains 72. Every other golden and all 88 known failures are unchanged.
All 3182 cases remain as 3094 golden rows and 88 known failures. Projected
167 supported cases / 815 components and 188 ordinary differences, pending
the actual gate. The source, full inputs/bits, values, state proof, exclusions
and readback-verified 64-member archive are in the committed finding.

gen0590's first periodicity sample reaches a normal fallback; gen0962's
swap does not remap its stored mask. Neither numeric-only candidate is
approved. No actual-extension, full-suite, CI success or readiness claim.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove gen1320's polynomial linear end-span continuation

Continue #1914 with one complete row / nine moved components. The original
unit-weight q=1 terminal span uniquely continues through the inspected G1R
endpoint formula, independent of extension chunks or exact join-knot removal.
No source-order replay or kernel-generated extended net supplies expected jets.
Exact-rational separated boundary hulls establish nonclosure; explicit mask
zero and the in-span u value establish the selected extension direction.

All nine 80/160-digit input-only Taylor components pass unchanged relative
1e-9, max error 9.324089962188992e-14, drift <=6.182732331065606e-78.
All 92 numerical/scope controls pass, including every previous model after
the explicit opt-in temporary evaluator hooks. Default helpers stay strict.
The independent affine-plane and saddle controls retain exact closed forms.
Rational/curved extensions, ambiguous boundaries and edits remain excluded.

| Case | Components / max relative error | Full original input SHA-256 | Old → new checksum blocks / unchanged n |
|---|---|---|---|
| `gen1320` | 9 / `9.32409e-14` | `01537720249ce9b50614cc1147ba7646e678602cb601657b2468bd837ce52399` | `[[8.083516148205456, 338.29768636282773, 410.2475809158631]]` → `[[8.083506601475959, 338.297647168599, 410.2473283613418]]` / `56` |

Common fields remain deferred=null, error=null, ints={}, topology=null;
out.n stays 56. The full three-step script executes; no suffix crop or skip.
Every other golden and all 88 known failures are unchanged. All 3182 cases
remain as 3094 goldens + 88 known failures. Projected 168 supported cases /
824 components / 187 ordinary differences, pending the real unit gate.

The finding retains old/new values, all observations, exact input/bit identity,
state proof, controls, initial Decimal-equality control failures and the
readback-verified 76-member archive. No production formula, flag, tolerance,
floor or decoder changes. No body validity/volume or full-suite/CI pass claim.
The correctness, attribution-review and approval-blocked CI holds remain.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* goldens: prove gen1851's receiver is unchanged by its outward trim

Continue #1914 with one complete six-step/147-scalar row, six moved values.
Extension is disabled throughout, no extended surface exists, and the trim
requests a bigger domain. Its gets_bigger branch therefore trims neither
an extension nor the owned original base. Grade the later jets on the
original net, not a purported trimmed or kernel-generated net.

All six 80/160-digit input-only Taylor observations pass unchanged relative
1e-9: max discrepancy 9.048670857263963e-13, drift <=1.3065385952206204e-76.
All 98 numerical/scope controls pass. The private evidence decoder uses the
known status/four-scalar query shape only after proving the trim is a no-op;
the default decoder still refuses trim. No production operation is replaced.
Actual net trims, enabled extension, failed trims and base edits stay excluded.
gen2214 independently disagrees and is not blessed.

| Case | Components / max relative error | Full original input SHA-256 | Old → new checksum blocks / unchanged n |
|---|---|---|---|
| `gen1851` | 6 / `9.04867e-13` | `71955d6e58899aa90a8f3e07a5fd12adce0d1f9b008a5af7cf74c613054d7532` | `[[61829.67557510773, 61888.01368940549, 3859448.1427769545]]` → `[[61831.18435404368, 61889.99122955711, 3859566.2962446106]]` / `147` |

Common fields remain deferred=null, error=null, ints={}, topology=null;
out.n remains 147. Every other golden and all 88 known failures are unchanged.
All 3182 cases remain as 3094 goldens + 88 known failures. Projected 169
supported cases / 830 components / 186 ordinary differences pending the gate.

The finding retains exact old/new values, all observations, original inputs
and bits, source branch proof and the readback-verified 71-member archive.
No construction, flag, tolerance, floor, production decoder, crop or skip
changes. No full kernel-suite, canonical-CI pass or readiness claim follows.

Co-Authored-By: OpenAI GPT-6.1 Sol <noreply@openai.com>

* docs: preserve the published offset migration evidence verbatim

Archive the complete ceadc86fe PR body in a per-entry finding before making
the description shorter. Preserve every old/new value, case, proof boundary,
archive hash and historical measured revision without relabelling it.

The new gen1320/gen1851 evidence remains in its own committed findings.
No source, expectation, input, tolerance or case-selection changes.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* fix: finish vendored offset derivative golden dispositions

Retain all cases, record demonstrated wrong slots against existing issues, and refresh the justified WR-160/161 expectations. Leave unrelated local approximation differences untouched.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

* docs: keep offset review image out of the PR diff

Use the already-published immutable geometry image and disclose the unavailable attachment route. No kernel code, golden expectation or validation scope changes.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>

---------

Co-authored-by: OpenAI GPT-6.1 Sol <noreply@openai.com>
2026-11-09 14:53:40 +00:00
Brazz 15e70f80fc reach: a corner of two kept faces on a straight edge and a moved plane is met on the edge's line (#13932)
`local_extension_reach` refused every corner whose three surfaces hold two or more curved
quadrics. That includes the vertex where a moved plane ends on the split ruling of a round
drawn as two faces: a boss whose wall is two half cylinders, its top drafted, is refused
as ReachUncertified although it builds sound.

Faces that keep their surfaces keep their edges, so where two kept faces at the vertex
share a straight edge, the three new surfaces meet on that edge's line, where it crosses
the third's new plane: x0 + tau u, tau = (o - x0).nu / (u.nu), at sqrt(|x0 - p|^2 + tau^2)
from the vertex, raised by the curve's distance from its line over the sine of their
angle. The line is proved on the edge's Bezier control points (each within the B-rep's
tolerance of the line through the first and the last), and the meeting is enclosed in the
reach's interval arithmetic. A line within the tangency tolerance of parallel to the
plane meets it nowhere near, as two planes' line does. Every other corner with two or more
quadrics still refuses.

Tests: the meeting against its closed form; a split boss (alone and on a block) drafted
+-3 degrees, refused before, now valid at CheckLevel::All with the closed-form volume
pi r^2 5 tan 3 degrees within the B-rep tolerance over the top's area. The same boss drawn
as one seamed face is the control and lands on the closed form to rounding.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:53:16 +00:00
SS-EN 71d11d41e4 occurrence.move: place an occurrence at an absolute pose with to / to_matrix (#12150) (#13933)
* occurrence.move: absolute placement with `to` / `to_matrix` (#12150)

`occurrence.move` can now take a target pose instead of a move:
`to: {position, axis?, angle?}` or `to_matrix` (the 3x4/4x4 form `matrix`
takes), in `space: local` (the parent, default) or `world`. It is refused
together with translate/axis/angle/origin/matrix/base_transform. Grounding,
rigid groups, capture and joints behave as for a relative move, which stays
the default.

Refs #12150

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Format: rustfmt the occurrence.move changes (#12150)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:53:02 +00:00
bherbruck 17699be744 RFC 0003: plan for moving SolidCraft's geometry into solidcore, one implementation per job (#13935)
* RFC 0003: solidcore consolidation, one implementation per job

An inventory of the geometry SolidCraft computes or duplicates from
solidcore, grouped by job, and the order of the work that leaves each job
in one layer (solidcore docs/layers.md), with the gate each PR passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: place the jobs with no solidcore counterpart by layers.md

IGES, surface construction, planar builds and sewing, local surface
commands, sheet metal, and hidden lines and sections each get their layer
and a phase entry. Mesh-body tools stay in the app for now. Picking
follows the rule that hover may use display meshes while the pick decision
is a kernel ray/body call. One open question remains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: the goal, solidcore as a general-purpose kernel, and Phase 6

ops becomes the complete public modelling API with nothing
SolidCraft-specific in it; semver, docs, examples and a second consumer
in CI; bindings later. The remaining-geometry phase becomes Phase 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: the placement test, and mesh bodies in solidcore-mesh

Anything another application on the same kernel could use goes in
solidcore. Mesh-body tools move to a new solidcore-mesh crate (PR 1.10).
Curve offsets join sections and projections in the kernel. The app keeps
only state, UI and workflow specific to SolidCraft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: every job against the placement test

A table of each geometry job in SolidCraft with its line count and where
it goes: solidcore when CAM, simulation or another app on the kernel would
use it, otherwise the app with the reason. About 161k lines move, about
42k stay. Phase entries 2.14-2.19 and 6.6 cover the newly placed jobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: the placement test covers geometry only

solidcore holds the geometry any app would ask the kernel for. Solvers,
history, naming and document state stay in the app: the 2D constraint
solver, multibody dynamics, thread standards tables and point-cloud
handling move back. About 150k lines move and about 54k stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Revert "RFC 0003: the placement test covers geometry only"

This reverts commit 8b0486bcf1.

* Revert "RFC 0003: every job against the placement test"

This reverts commit 244b053c78.

* Revert "RFC 0003: the placement test, and mesh bodies in solidcore-mesh"

This reverts commit 3e6ef956c8.

* RFC 0003: solidcore's scope is OpenCASCADE's published module list

One rule in section 3, and the four rows whose placement would change
under it, left unchanged until the owner decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: scope is what moves into the vendored solidcore/; mesh formats go to record + ops

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: drop the vendoring date

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: restore the vendoring date

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: no relative dates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: SolidCraft's code is the base; add solidcore's measured wins

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: when and on which commits the bake-offs ran

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: git mv SolidCraft's code into solidcore/, one crate per job

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: PRs target main; kernel branch for multi-step work only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RFC 0003: 0.4, solidcore stays self-contained

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:52:51 +00:00
github-actions[bot] b0fba036bd Regenerate generated files and fold pending files [skip ci] 2026-11-09 16:13:14 +00:00
Brett Lawson 23e835a67e solidcore: retire fillet_trace_rejects_short_step (WR-503) (#13982)
* solidcore: claim sc#13833, retire fillet_trace_rejects_short_step (WR-503)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: retire fillet_trace_rejects_short_step, WR-503 guard is unconditional

The short-step guard in FilletIntersector::compute_next_point no longer sits
behind a kernel fix; the field, its settings in tests, fixes.json and
crates/smlib/src/core.rs go. Adds a unit test of the predicate and an addendum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: rustfmt the WR-503 guard

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:51:32 +00:00
ancient_astronaut_theorist 556b1aec6a Drop a PNG or JPEG on the window to place it as a Canvas (#13926) (#13992)
* claim: #13926

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* Dropping a PNG or JPEG on the window places it as a canvas (#13926)

A dropped picture went down the design-file path and failed with
"design file is not UTF-8 JSON". drop_path now recognises PNG and JPEG
(by extension, any case) and starts the Canvas tool with that image
chosen, exactly as picking it in the tool's file dialog does; the next
click on a plane, a face or the sketch plane places it.

The picker callback and the drop share sketch_tools::start_image_placement.
Regression test: file_picker::tests::a_picture_dropped_on_the_window_is_placed_as_a_canvas.

Fixes #13926

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* A picture dropped on Home closes Home so it can be placed (#13926)

Starting image placement left Home open, and cad_ui returns before the
viewport while Home is shown, so there was nothing to click to place the
canvas. start_image_placement now closes Home, as running a command
does. The drop test now starts on Home and checks that it closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:50:53 +00:00
Brett Lawson f6813e3766 solidcore: nurbs failures carry a typed reason instead of NLib error numbers (part of #13790) (#14004)
* claim: #13790 nurbs slice

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: add NurbsFailure, the typed reason for a NURBS routine failure

Reason::Nurbs carries a NurbsFailure per NLib error kind, with the degree,
derivative order, knot index, weight, parameter and domain, or index and
length where a routine has them. The status the Sm callers report stays
SM_ERR, and kind() stays NotConverged for convergence and Failed otherwise,
except a parameter outside its domain, which is OutsideDomain.
error::nurbs::nlib_number is the comparison hook the goldens runners use for
the NL number they compare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: NURBS routines return NurbsFailure instead of SolidError::Nlib

Every SolidError::nlib / SolidError::Nlib site in src/nurbs, except the
constructors in crv_common.rs (held by another open change), now returns
SolidError::Failed with a NurbsFailure and the same operation path. The
typed variants carry the values at the knot, basis, curve and surface
checks. The callers that ignore an NLib failure (heal_data, edgeuse gaps,
shell bounds, the span-maxima searches) drop a NurbsFailure exactly where
they dropped Nlib.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: goldens runners read the compared NL number through nlib_number

The NURBS goldens units matched SolidError::Nlib { code } for the number they
compare. They now read error::nurbs::nlib_number, so the compared values do
not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: NURBS failures pass clippy, and a source test keeps src/nurbs free of NLib errors

Test: no file of src/nurbs except crv_common.rs names SolidError::Nlib, NlibCode
or SolidError::nlib. Also drops the SolidError imports the goldens runners no
longer use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: fuzz corpus expects the NURBS failures' new message text

Fourteen corpus cases pinned the old 'NLib error Memory (15)', 'NLib error
Curve (1)' and 'NLib error Knot (4)' text. They now pin the solidcore
wording; the outcomes (an error on the same input at the same operation) are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

* solidcore: an empty output slice in crv_derivs_into keeps its NL number

It is NurbsFailure::NoDerivativeRows (compared as the derivative-limit
number, 20) rather than Input, so this change moves no compared value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:50:27 +00:00
0rdnungsamt 2c1ca4c75d macOS menu bar: a submenu that starts empty stays a submenu, so it can be filled later (part of #13367) (#14015)
* claim: part of #13367 (native menu keeps an empty submenu a submenu)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* macOS menu bar: a submenu that starts empty stays a submenu

MenuTable treated an item with no children as a clickable entry, so a
submenu empty when the bar was built became an entry and a later
replace(id) failed with "no submenu". An item with a menu.* id now stays
a submenu, which also keeps a nested empty one from being dispatched.

Part of #13367.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:50:12 +00:00
ChrisNo 3ca9421aee Kernel: directed distance queries keep valid answers far from the start point (#13960) (#14023)
* fix: cull directed tree boxes by the admissible directions

TopologySolver::may_contain_answer culled DirectedMinimize,
SignedDirectedMinimize and DirectedMaximize branches by the in-plane
offset between box centres instead of their distance to the direction
plane. Every branch whose directed answer lay farther than the two
bounding radii was culled, so directed queries lost far answers.

Enclose the view projection Q = N x d x N over every pair of box points
with outward-rounded intervals. Cull only when no pair can be within the
local solver's 2-degree direction test, or when every admitted value is
more than the query tolerance worse than the best. The signed bound is
-|Q| at most, because the sign also follows the operand order.

directed_culling_keeps_admissible_answers_and_culls_the_rest fails on
main (DirectedMinimize culls an answer at distance 10) and passes with
the fix. The C++ has the same arm; recorded as WR-772.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: bless directed culling goldens moved by the admissible-direction fix

Against tangency-labs/solidcore#3161, 2 of 283 units change, with nothing added, removed or broken.

- intersect.topology_solver_p1: 48 may_contain_answer records, 130
  changed decisions. Each was checked by clipping the box pair's projected
  polygon with the 2-degree direction wedge (60-digit decimals). No new
  cull removes an admissible answer. 38 decisions that main culled hold
  real directed answers. 21 are kept conservatively by the interval
  bound.
- intersect.topology_solver_p2: 18 directed static_operation_matrix
  records (entries 0 and 2, operations 11 to 13). Main returned no
  answer; each now returns the single admissible directed answer, checked
  by exhaustive enumeration in value and sign.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: show the directed cull's mechanism and its literature, correct WR-772

Review findings on #14023 (CodeRabbit):
- WR-772 said the old cull removes every answer farther than the two
  radii. It compares the box centres' offset in the plane of N and V with
  the radii; that offset includes motion along N and can be smaller than
  the answer (Y ranges [-1, 1] and [0.5, 2.5]: offset 1.5, radii 2,
  answer 3.5). The record now says so.
- A schematic compares the old centre test with the difference box against
  the 2-degree wedge, with its source script.
- The record names the method: interval branch and bound's feasibility
  and cut-off tests (Hansen and Walster 2004; Ratschek and Rokne 1988),
  with outward-rounded interval arithmetic (Moore 1966).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: ChrisNourse <16234527+ChrisNourse@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:49:30 +00:00
Frank cf6caa37a0 Insert SVG and DXF: a dense drawing goes in many times faster, because new sketch ids no longer search the whole sketch (#14049)
* claim: fast sketch ids for dense drawings

* Sketch: Insert SVG and DXF add points and curves without searching for each id

Each new point and curve searched every id in the sketch (`id_taken`)
before taking one, so inserting a dense drawing took time in proportion
to the square of its size. `Sketch::batch()` keeps the sketch's ids in a
set while it borrows the sketch; its `add_point` and `add_curve` give the
same ids, checks and refusals as the sketch's own. The import's
`add_geometry` adds through a batch: 9900 DXF lines go in in 203 ms
instead of 960 ms (ci profile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:48:55 +00:00
Mek 6e6968d45a Italian: translate the 945 messages main gained after batch 12 (batch 13) (#14052)
* claim: Italian batch 13

* Italian: translate the 945 messages main gained after batch 12 (batch 13)

One pending file, assets/locales/pending/italian-batch-13.json, with the
Italian for every message the catalog lacked at f9ed9a90b: Preferences
options and notes, interface messages, the Render workspace, command
labels and help, sketch, import, thread, joint and panel messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Italian: name the Exact radius option as the dialog shows it

Review on #14052: the G2 corner message asked for "Adattamento esatto",
but the Face limits option it means is labelled "Raggio esatto".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mek <mf@linescode.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:48:41 +00:00
Frank eb0ccd36a1 Preferences search: a labelled number row such as Window steps is found, not "No matching preferences" (#14080)
* claim: Preferences search finds labelled setting rows

* Preferences search: a labelled row with a plain number field is a result

Searching Preferences for the label of a row built with settings_block
whose control reports no match of its own (a plain number field, such as
Sketch > Window steps) drew the row but never counted it: the page and
section headings stayed away and the window said "No matching
preferences". A block that matches now reveals its headings and counts
as a hit, and takes one stop for the arrow keys unless a control inside
already has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences search: no gap above a match below the Sketch drawing checkboxes

While searching, the Drawing section's two checkbox columns kept the
spacing of every hidden row, so a match further down (Window steps)
showed some 170 points below its section heading. The columns now draw
into a detached child and keep their space only when one of their
checkboxes matched, as sections and pages already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences search: a row found by its hint is a stop, and Enter outlines it

A block whose hint alone matched the search was shown but had no stop for
the arrow keys; it now has one, named by its label. After Enter opens a
block's page from a search, the block scrolls into view and is outlined,
as other rows are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:48:07 +00:00
PaddleStroke 8dc9e845f1 Pictures: clear the multisampled picture's resolve texture first, so ui.render and MCP screenshots are not black on AMD Direct3D 12 (#14090)
* claim: #10140

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pictures: clear the multisampled picture's resolve texture first, so ui.render is not black on AMD Direct3D 12 (#10140)

Headless pictures (ui.render, the MCP screenshot tool, thumbnails) draw with 4x MSAA on the
app's device and resolve into a new texture that only the resolve writes. Some AMD Radeon
Direct3D 12 drivers then read it back black or as garbage, the same fault the viewport had
before #7399 cleared its resolve targets (#6828). The capturer now clears that texture with
the viewport's clear_resolve_targets before the pass, and a test counts the clears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:47:42 +00:00
github-actions[bot] cf34b56f01 Regenerate generated files and fold pending files [skip ci] 2026-11-09 15:23:28 +00:00
MarkusRe 9b43ed518b Setback beside curved faces: the named size is the spring curve's limit, the rails' directions tested (#13832) (#14096)
* test: the named setback lies within 1e-3 of the spring curve's limit (#2343 (d))

jyviko's review: the size named is the last point of a grid of
thousandths (7.803 for asked 8.5), 3.8e-3 short of the closed-form
limit 7.8068. The test now asks for a setback whose spring curve stays
clear and that lies within 1e-3 of the limit (the check's gap, the
tolerances, costs about 6e-4). Red at 3748959be: 7.803.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the named setback is the spring curve's limit to the tolerance, not a grid point (#2343 (d))

jyviko's review: after the bisection on the grid of thousandths, the
last grid cell is halved (at most 30 times) until it is shorter than
the corner's tolerance along every edge. The size named is still one
the check itself found clear: the bisection's hi - 1 is lo or an index
it cleared, and each halving keeps its cleared end, so it is sound even
where the assumed monotonicity fails. Asked 8.5 on the bored prism now
names a setback within 1e-3 of the closed-form limit 7.8068 (was 7.803).
docs/generic-blend.md section 3.2 says what the search assumes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the rails' directions in closed form, per face kind and at a concave corner (#2343 (d))

jyviko's review: the closed-form rail directions had only a one-off
comparison behind them.
- setback::tests::a_rails_direction_in_closed_form_is_its_derivative:
  rail_direction (split out of rail_toward_vertex) against the rail's
  derivative by central differences, on a convex cylinder, a cone, a
  sphere and a concave corner beside a bore, the other face a tilted
  plane so that normal_turn turns the direction (oblique to the
  principal directions). A wrong sign in the cylinder arm is caught
  (off by 0.076), so is a wrong cone half-angle (2.7e-3).
- The corner keeps the spring curves it checked (SetbackCorner::springs),
  and the cylinder, concave cylinder, cone and sphere corner tests assert
  they are the fill's own spring edges: within 1e-11 (measured at most
  9.2e-13), their rail-end tangents within 1e-9 rad (at most 5.5e-14).
  Those corners' rounds run along principal directions, where the turn
  only scales the direction, so the unit test is what pins normal_turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(findings): addendum, the named setback as the spring curve's limit (#2343 (d))

The merged record names 7.803 for asked 8.5, the last point of the grid
of thousandths. Measured now: 7.806221 (0.918379 x 8.5), 5.8e-4 below
the closed-form limit 7.806805, its spring curve 5.1e-5 clear of the
bore; 19 probes of the check. The one-off comparison of the rails'
directions is replaced by the committed tests. A new file: a merged
record is never edited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the named setback is the largest to the corner's tolerance (#2343 (d))

The bored D prism's test checked the named setback only within 1e-3 of
the closed-form limit, which a halving that stops a few steps early
still meets. Asked a tolerance (the corner's approx_tol_3d) more than
the size it names, the corner is now refused as SetbackTooLong too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: what the spring-curve refusal names where no setback keeps the curves on (#2343 (d))

generic-blend.md section 3.2 said the refusal always names the largest
common scale; where none at or above the rails' crossing keeps every
spring curve on its face, no setback builds. SetbackCorner::springs is
also empty where the curves are not made, and only tests read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:47:15 +00:00
Werner1303 2fb7c1f88a Multi-section blend: the last-but-one section of four or more bends the right way (#13447) (#14120)
* claim: #13447

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Multi-section blend: tests that the last-but-one section holds the sections' third difference (#13447)

A closed form on the cubic cylinder (2v, t, t^3) through five sections:
d3/du3 is (0, 0, 6) at every interior section. The backward stencil at the
last-but-one gives (0, 0, -6.002). four_sections_join_at_c3 expected that
wrong sign at its second joint; both joints read the same four sections
and now expect the same difference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Multi-section blend: the backward third difference has the right sign (#13447)

Blend::third_difference's "only behind" arm took
f(i-2) - 3 f(i-1) + 3 f(i) - f(i+1), the negative of the change of
curvature it estimates, so the last-but-one section of a blend through
four or more sections bent the last two spans the wrong way. It now takes
f(i+1) - 3 f(i) + 3 f(i-1) - f(i-2), as the forward arm does for the
same four stations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:46:39 +00:00
Mateusz Sych daa82fa1e1 solidcore: SolidCraft #14000 case (a round at the foot of a slightly tilted boss) (#14133)
* solidcore: SolidCraft #14000 case (a round at the foot of a slightly tilted boss)

Six cases from the report's script: a block, a boss of radius 5 joined along an axis tilted 0, 0.5, 1, 2,
3 and 5 degrees, and a 1 mm round on its foot. Volumes are analytic: the block, the boss above it, and the
round integrated in tube coordinates about its elliptical spine (closed form at 0).

* solidcore: #14000 cases run with the two fixes that reach the regression, at nine tilts

The round is refused only with fillet_exact_round_classes and fillet_round_as_definition
(both on in SolidCraft's fixes()); the cases now carry them, add the tilts 0.25, 1.5 and 2.5
degrees, and note the CLI replay on main and on 4890cebd3.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-09 14:45:57 +00:00
PaddleStroke 47a30eaf89 Translation checks: fail when the command registry cannot be read (#14141) (#14142)
* claim: #14141

* Translation checks: propagate command registry errors

Fail before reporting success or writing a catalog when command discovery fails. Cover errors in every mode and preserve successful label checking and repair. Fixes #14141.
2026-11-09 14:45:30 +00:00
PaddleStroke 204d8b47a4 Windows tests: validate the slicer reply schema on every platform (#14143) (#14144)
* claim: #14143

* Windows tests: validate empty slicer replies against their schema

Exercise the shared assertion from the existing platform-independent serialization test. Keep Unix discovery coverage and strict warning checks. Fixes #14143.
2026-11-09 14:45:18 +00:00
Werner1303 3d2c51b874 Measure: a line or curve drawn in a 3D sketch measures like a 2D sketch curve (#14051) (#14155)
* claim: #14051

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Measure: a test that curves drawn in a 3D sketch measure like sketch curves (#14051)

A line from (0, 0, 0) to (24, 0, 0) drawn with sketch.line3d is refused
with "an item can't be measured": sketch_curve looks the id up in the
sketch's 2D curves only, and 3D curves are its wires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Measure: a line or curve drawn in a 3D sketch measures like a sketch curve (#14051)

sketch_curve falls back to the sketch's wires when the id is not one of
its 2D curves: a 3D line measures its length and direction, an exact 3D
arc its radius, angle and arc length, and any other wire its length
along its points. A pick without a sketch finds the sketch holding the
wire too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:45:04 +00:00
bherbruck 3a7278cdfe step-import: CMCRSG is COMPOSITE_CURVE_SEGMENT, not COMPOSITE_CURVE (#14159)
step-import's short-name table mapped CMCRSG to COMPOSITE_CURVE. ISO's short-names file
(http://standards.iso.org/iso/10303/tech/short_names/short-names.txt) gives CMCRSG as
COMPOSITE_CURVE_SEGMENT and COMPOSITE_CURVE as CMPCRV, so a file written with short names
had its curve segments read as curves. Both now read as ISO gives them, the source cited beside
the table. Test: the two short names read as their long names.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:44:51 +00:00
Mek b477198def Viewport: a picked sketch no longer re-meshes every body on hover (#14166)
A sketch makes no bodies or faces, so its highlight falls back to
changed_faces, which compared the states either side of the feature by
calling Body::faces on every body, twice. Body::faces meshes the body each
time, and the highlight is rebuilt on every hover change: on a design of
about 250 bodies each hover change took 1-2 s while a sketch stayed picked.

changed_faces now skips a body the feature left as it was (same record,
same face names) before asking for its faces. Such a body has no face the
feature changed, so the lit faces are the same.

Fixes #14147

Co-authored-by: Mek <mf@linescode.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:44:38 +00:00
Simon Appelt 559e6e70fd Sweep: a profile drawn partway along the path, or beside it, follows the whole path (#11772)
* Sweep: a profile drawn partway along the path, or beside it, follows the whole path

SMLib sweeps a profile from the path's start only. A profile drawn partway along
an open path (its plane crossing the path, not at its start) failed with "SMLib
failed (status 1001)" or, on the current solidcore pin, built a body along a copy
of the path moved onto the profile. A profile whose plane misses the path did the
same.

The profile is now carried back to the path's start the way the sweep carries it
forward (moved along lines, turned about arcs and corners, turned step by step
along free-form curves; only moved when the profile keeps its orientation), from
where its plane crosses the path nearest it, or else from the path's point
nearest it. The body follows the whole path and passes through the profile where
it was drawn, square to the path, keeping its distance and side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Sweep: twisted, scaled and guided sweeps also start from a profile drawn partway along the path

A sweep with a twist, an end scale, guide rails or an orientation direction took
the profile as drawn for its start section, wherever it lay. A profile drawn
partway along the path or beside it then gave a body that did not follow the
whole path. Such a sweep now carries the profile to the path's start once, as the
plain sweep does, before building its stations: the drawn profile is the start
section, and the twist and the scale run from there. The circle-about-the-axis
fallback sweeps the aligned profile without aligning it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:07:24 +00:00
dudedagger42 94b088979e STEP import: a planar face's slit inner bound is left out of the face, not the face (#13096) (#13130)
* STEP import: test that a planar face with a slit as an inner bound keeps the solid closed

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP import: a slit that is an inner bound of a face is left out of it (#13096)

A face bound that runs one or more edges out and straight back encloses no
area. Where the face has another bound and no other face uses those edges,
the slit is left out of the face (and noted), so the face and the solid stay.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP import: rustfmt the slit test

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 14:06:37 +00:00
Andrzej Błasik a1b692c75f Speed: a body's exact bounding box is measured once, not again for every boolean that meets it (#13512)
* Speed: a body's exact bounding box is measured once, not again for every boolean that meets it

* Speed: callers on other threads wait for a body's first box measurement instead of measuring it again
2026-11-09 14:06:23 +00:00
jyviko 097ea3bb07 Docs: layers.md, how the layers, the shared crates and the solidcore seam fit together (#14139)
A schematic (docs/layers/layers.svg, attributed in attribution/pending/docs-layers.md, in solidcore's diagram style) follows one click on Fillet
through the layers: the host runs the command the ribbon component returned, the engine and the
document build it through solidcore-ops, the document plans the display mesh through
solidcore-tess, and the shared viewport crates evaluate and draw it. The page lists each rule
with what enforces it (downward only, shared crates standing alone, UI toolkits only from L5 at
any depth, the solidcore seam, test tooling, wasm), what makes a piece a shared component, and
how to add a crate. architecture.md and AGENTS.md point to it; the page links solidcore's own layers.md.


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

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:05:08 +00:00
github-actions[bot] a77b7ab2e4 Regenerate generated files and fold pending files [skip ci] 2026-11-09 13:00:08 +00:00
selune ed2bafe7e3 Render: quality controls, progressive previews and RAM refresh (#12546)
Expose 16,384 spp, 8K UHD with X2 supersampling, CPU workers and persistent
denoising controls. Preserve complete-pass previews and bounded paged GPU
storage, with compatibility choices for changed rendering defaults.

Refresh native RAM observations after active allocation work ends while
retained images and metadata keep their software leases. Replay the complete
render changes onto current main and retain its integration contracts.

Give the existing GPU coat test a separate cold-pipeline preparation budget
while keeping a fixed 60-second deadline after preparation. Label all five
variants and report full job status on failure; keep their fixtures and
image/history assertions unchanged. Production timeouts and CI policy stay
unchanged.
2026-11-09 12:15:07 +00:00
jyviko ef966d46a1 Agent chat panel: a component over plain state that records what the user asked (#14124)
* Agent chat: the panel publishes into the widget registry directly (#12948)

panel.rs, settings.rs and the tests call solidcraft_ui_registry for
handles and rects instead of the crate::scenario re-exports, so the
panel does not reach the app's scenario module; tests keep the Harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the panel's place and fold are its own AgentPanelPrefs (#12948)

The panel draws from AgentPanelPrefs (side, width, folded, in its own
window, the window's place) instead of reading SolidCraft's Preferences
fields: show copies them in, the panel changes the copy, and show
stores what changed (a side it did not move keeps its stored spelling).
toggle_panel shows, unfolds and hides on the panel's state alone; toggle
keeps the shared panel host's layout path and falls back to it. The
shared host's content reads the same copy, and owns_keyboard_input,
left_inset and tool_space read it too. In its own window the toggle key
works on the copy being drawn from, so it is not overwritten when the
copy is stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the panel records what was asked as PanelActions (#12948)

Sending, connecting, new chat, mode and model choices, picking,
attaching, answering a permission and the rest no longer run while the
panel draws: the panel pushes a PanelAction onto the chat's queue and
the host runs the queue (panel::run) after the panel: show after the
frame's panel, shared_content after the shared host's body, and
prepare_shared right after the New chat shortcut. Test: the send button
records the message and sends nothing itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the chat's own queries are AgentChat methods (#12948)

Whether New chat needs confirming, the models and their providers, the
provider the picker opens on, the grouped model list and whether a typed
model is accepted read only the conversation, so AgentChat answers them;
the functions taking the app delegate. The panel can then ask the chat
without the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the panel draws from the chat and a PanelView, with no app (#12948)

panel::draw (the panel with its header) and panel::draw_content (the
controls and conversation in the shared panel host's body) take the
conversation (AgentChat), the panel's own prefs and a PanelView: the
plain data SolidCraft reads before drawing (layout, history bar,
document name and units, agents and the permission mode, selection
count, dialog open, turn limit, model warning, toggle keys, reading
direction, whether the shared host is measuring the content and the
composer height it measured). The flow stays an argument.

What the panel changes goes back as PanelActions, now also the
permission mode and a pasted picture (paste::handle returns the
action). What the shared host measures (the composer's height, the
height the content needs, the text fields that take the keyboard) is
kept in a ContentReport and handed to the host after drawing, in the
order it was drawn and only for the host's own layer, as before. show()
and shared_content() build the view, draw, store the prefs, hand over
the report and run the actions.

The conversation's items still hold the engine's Arc<Document> (marks
which messages changed the design); that is a follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the desktop-only panel actions build only on the desktop (#12948)

Refresh, Connect, PastePicture and AttachPicture run discovery, agents
and the clipboard reader, which the web build does not have; their
variants and match arms are gated as those calls were, and every place
that records them already is. The wasm32 build compiles again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the host looks for agents before the panel's first frame (#12948)

Opening the chat ran discovery before drawing, so the first frame said
the agents were being looked for; recording it for after the frame drew
one frame of "None found" (or the setup card) first. show() starts
discovery before drawing again, as prepare_shared does for the shared
host, and the panel no longer records a Refresh of its own. Test: the
first frame after opening shows the detecting state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: the host reads the panel's view only when the panel is drawn (#12948)

PanelView::of (selection count, agent list, keys, tooltip, model
warning) ran every frame, also while the chat was off or closed. The
start of the panel's frame (forgetting what is not drawn, the New chat
shortcut) is now panel::begin, which takes whether AI features are on
and says whether the panel is drawn; show() reads the view only then,
and shared_content() only for a shown, open chat, as main read them
only while drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Agent chat: main's preset test reads the widget registry directly (#12948)

The Gemini preset test (#12427) landed on main after the batch and reached the registry
through `scenario::`, which the registry commit replaced in this module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 11:55:22 +00:00
jyviko 8babea5c63 solidcore-tess: re-export the evaluator types and vector conversions its plans expose (#14119)
* solidcore-tess: re-export the evaluator types and vector conversions its plans expose

TessPlan's public `surfaces` are solidcore::eval::EvalSurface values built with
solidcore_convert's vector conversions, so a client that builds or reads a plan had to name the
kernel crate and solidcore-convert, which the solidcore seam check refuses outside the public
crates. solidcore-tess now re-exports EvalCurve, EvalSurface, vec3 and record_vec3; a test
builds and reads a plan's surfaces through those names alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: format the re-export test with solidcore's rustfmt settings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 06:47:16 -05:00
bherbruck f9ed9a90b4 Kernel sync (#13985)
Merges the kernel branch at 8ffb97d541.
2026-11-09 02:51:10 -05:00
Ahmad Khan b653486d10 Assistant: Add Agent offers a Google Gemini preset for OpenAI-compatible endpoints (#11816) (#12427)
* claim: #11816

* Test: Add Agent offers a Google Gemini OpenAI-compatible preset (#11816)

* Assistant: Add Agent offers a Google Gemini preset for OpenAI-compatible endpoints (#11816)

Google's OpenAI-compatible endpoint is
https://generativelanguage.googleapis.com/v1beta/openai; requests go to
{base}/chat/completions as for the other presets. DeepSeek, also asked for
in the issue, has been a preset since #5063.

* Test: the Add Agent dialog test scrolls the preset list to reach OpenRouter (#11816)

With the Google Gemini preset the list is taller than the popup, so OpenRouter sat below
its visible end and the click landed on nothing. The test now scrolls the list first.

---------

Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
2026-11-09 06:36:09 +00:00
jyviko 40ca4ce08c Ribbon and app bar: components over plain data that return what the user chose (#14006)
* Ribbon: the panel row's fit and label layout move to a ribbon module (#12939)

fit, Plan, PanelSize and ButtonSize, the row's dimensions and the button
label layout (lines, widths, CJK breaks, galleys) move unchanged from
toolbar.rs to the new pub module ribbon, with their tests; toolbar.rs uses
them from there. No behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ribbon: the panel row is a component over plain group data (#12939)

ribbon::show draws groups of buttons, gallery cells, collapsed groups,
group labels and the » menu from plain data (Row, Group, Button, MoreRow)
and returns what was chosen (Run with its place, Menu, Section, Open) with
each drawn button's response for the app's own part. Handles come from the
app (Handles), so SolidCraft's names are unchanged. SolidCraft builds the
data from its panels and keeps tooltips, drags, persistent paint, family
menus and the panel drop-downs, now run from the returned actions; its own
copies of the row's text, chevron and reveal painters are gone.
Tested with no app: a click returns its command and rect, a disabled
button and a disabled » row return nothing, a label asks for its menu,
and a group that does not fit is in the » menu, whose row runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ribbon: the tab row is a component over plain tab data (#12939)

ribbon::tabs draws the tabs (each with its own accent and contextual
flag), the + button and the buttons after the search field from plain
data, and returns Select (with whether the tab was already selected),
Menu, Add and Run; it gives back where the search field goes. Handles
and tab aliases come from the app, so SolidCraft's names are unchanged;
the visible-tab state is keyed by the component's scope. SolidCraft
builds the tabs and its Render job buttons and runs the actions; its tab
style moves to ribbon::tab_style.
Tested with no app: a tab click (selected or not), a right-click, the +
button, a ready and a not-ready button, and an alias.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ribbon: a group's menu rows and icon grid are components over plain entries (#12939)

ribbon::menu_row, ribbon::grid and ribbon::grid_cell draw a group menu's
rows and its grid of icons from plain data (MenuEntry, MenuStyle) and
return what was chosen (Run, Menu, TogglePin) with each entry's response
for the app's own part. The grid layout, arrow keys, pin delay, tips beside
the menu, headings, footer and row width move with them; focus ids come
from the style the app gives. SolidCraft builds the entries from its
commands and keeps drags, reveal and ribbon tooltips, persistent paint and
the sketch family menus.
Tested with no app: a row and a grid icon are hot under the pointer, run
when clicked, open their menu on a right-click, run nothing when disabled
and toggle their pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ribbon: customising drags and persistent paint are the component's, as returned actions (#12939)

The ribbon now starts and marks drags of its row buttons, gallery cells,
menu rows and grid icons, and returns a drop as Action::Drop (before a
button, or at a group's end); drag_end draws the ghost (the app gives the
icon) and returns a command dragged off the row. paint_deferred paints the
buttons the app paints after its commands ran, from the look the app
resolves (DeferredLook), and publishes what is pressed and focused.
SolidCraft keeps its store (toolbar_custom), runs Drop and the removal,
resolves each persistent button's look and its tooltip; its own drag
payload, ghost and paint code are gone.
Tested with no app: drops before a button and at a group's end, a button
dragged off the row removed with its ghost crossed out, and a deferred
button painted pressed and counted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* App bar: the application bar is a component over plain bar data (#12939)

app_bar::show draws the app's mark and name, Home, the File button, the
file actions, Help, Settings, Report a bug and the version link inside the
title bar's content rect, from plain data (AppBar, BarButton, Report,
Version) with names from Handles, and returns what was chosen (Home, File,
Button, Help, Report, Version) with the places it leaves to the app: its
quick-access buttons, an offer pill, the document tabs, the assistant and
the search field. Its layout (report and version room, the search rect)
moves with it. SolidCraft gives its mark, name, version and tips, runs the
actions and draws its quick access, Update pill, tabs and assistant there.
Tested with no app: each control's click, a disabled button, the places
returned, and a compact bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ribbon: measure button labels in a theme colour, and keep toolbar.rs fully scanned (#12939)

The label measure moved from toolbar.rs to ribbon.rs with a literal white,
which the colour scan counts against ribbon.rs; the colour plays no part
in a width, so it is the theme's text colour. toolbar.rs has no colour
literal left, so its allowance goes. The scan reads a file up to its first
#[cfg(test)]: toolbar.rs's test-only helpers move down to its tests, so
none of its code is skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* App bar: the document tab strip is a component over plain tab data (#12939)

app_bar::tabs draws the document tabs from plain data (TabStrip, DocTab,
TabMenuRow, a TabRename over the text being typed) and returns what was
chosen in the order it happened (Select, Close, Menu, Reorder,
StartRename, RenameDone/Cancelled, Maximize, New). Dragging to reorder,
the tab menu, the » list and the rename field stay in the component; its
drag, menu and list state are keyed by the id the app gives. SolidCraft
builds the tabs from its documents and runs the actions with its usual
precedence; its own strip drawing is gone. Its test-only helpers sit with
its tests, so the colour scan still reads all of documents.rs.
Tested with no app: a tab click, a close, the + button, a tab-menu row and
a drag that reorders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* App bar: the File menu and the workspace switcher are components (#12939)

app_bar::file_menu draws the in-window File menu from plain rows
(FileRow: items, button rows with a tip, the Open Recent submenu,
separators) with ids from the app, and returns the row chosen and
whether the menu closes. app_bar::switcher draws the workspace switcher
(name, caption, icon, accent, folded and staged looks) and returns its
click; SolidCraft opens its own Workspace menu from it. SolidCraft builds
the File rows from its actions and runs the id chosen.
Tested with no app: a File row returns its id and closes the menu, a
disabled row returns nothing and keeps it open, a click elsewhere closes
it, and the switcher returns its click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* App bar: the search field's width takes the palette shortcut, as main's command search does (#12939)

Main's CommandSearch::closed_width now takes the shortcut the closed field shows
(3af51db55, the command search can move off S). The ribbon's tab row already gets its width
from the host; the app bar's search_rect computed its own, so AppBar gets the shortcut as
data (search_shortcut) and SolidCraft passes keymap::display_for(app, "ui.palette").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Ribbon: the panel lookup is panel_button, not a raw egui button to the UI-kit guard (#12939)

PanelButtons::button returned the host's data for a button the ribbon drew; the UI-kit
guard reads any `.button(` call as a raw egui widget and failed toolbar.rs on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* App bar: the tab strip says whether its list of every tab is open (#12939)

The tab strip keeps its list's open flag under the host's strip id, so main's icon audit,
which read the old sc_doc_more memory id, found the list closed. app_bar::tab_list_open
answers for a strip (the component reads it too) and documents::tab_list_open for
SolidCraft's; the audit asks that instead of a memory id. Its expectation is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Ribbon: the menu-tip test hosts its drop-down in the kit's Container (#12939)

cargo xtask ui-containers allows outer hosts only through ui_kit::Container; the test moved
out of toolbar.rs into ribbon_tests.rs and kept a bare egui::Area.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 05:50:19 +00:00
github-actions[bot] dfd9512e2e Regenerate generated files and fold pending files [skip ci] 2026-11-09 06:08:50 +00:00
Jannis Ruellmann 291d01f351 Insert Part: countersunk socket head screws (ISO 10642) join the standard parts (#13361) (#13362)
Adds the `countersunk_socket_screw` family, M3 to M20, from the ISO 10642
table (head diameter dk, head height k, socket size s and depth t). The head
is a 90 degree cone sunk below the seating plane, so the top of the head is
z = 0, level with the face, and the length counts the head. A length no longer
than the head is refused.

Seated at a hole, the foot of the head's cone goes on the rim picked (where
the hole's bore meets its countersink), moved along the axis by the bore's
radius over the shank's, so the cone lies on the countersink's and the top is
level with the face. The difference is signed: a clearance hole sits the cone
deeper, a tap drill higher. With no bore known there is no move. A countersink
of another angle is not detected.

M2 and M2.5 are left out: only one of the tables compared lists them. The key
engagement depth t is the newer edition's, which differs from the 2013 edition
at M4, M5, M8 and M10.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 05:23:08 +00:00
Dan Willis 34e6e4b342 Crash tests: build on Windows with warnings denied (#13193)
The clean_read_only case of crash_tests (#12891) declared `sessions` outside
its #[cfg(unix)] block, so on Windows it was unused and
`cargo clippy -p solidcraft-engine --all-targets -- -D warnings` failed to
build the engine's tests. Declare it inside the Unix block, where it is used.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 05:17:13 +00:00
bherbruck 17daee946c Keep solidcore/ self-contained: layers rule and an export-and-check CI job (#13995)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 04:51:54 +00:00
jyviko 11a799b565 Preferences window: a component over the host's pages; SolidCraft's Preferences is that window (#13987)
* Preferences window: the window shell as a component over host pages (#12942)

prefs_window draws the page list, the search over every page with its
outline of the current match, Restore Defaults for the page shown, and
OK / Cancel / Apply over a snapshot the host takes, compares and puts
back (Host). Pages are HostPage { icon, label, group, handle, scroll,
draw, defaults }. The kit's page list takes closures and a host Icon so
the window can draw through it. Tested from two fake pages, no app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences: SolidCraft's Preferences is the window component with its pages (#12942)

prefs::show draws through prefs_window::show; SolidCraft's twelve pages
are HostPages with their bodies unchanged, and SolveApp is the Host: its
Snapshot, the panel-layout-first restore, shortcut capture and the
Shortcuts/Plugins search hooks. The ui.prefs buttons press the same
window buttons. The window's open, section and filter move to
prefs_window.shell (path change only in the other files); the old
search, page loop, land_on and close code is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences window: the names it publishes and its search id are the host's (#12942)

Options takes a prefix for every registry name the window and its search
field publish (defaults, search, search:frame, search:clear, page_body,
window, footer, ok, apply, cancel, actions), and the search field's id
comes from the window's id, so two windows never share one. SolidCraft
passes "prefs", so its names stay as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Settings search: the current match and the revealed row are published under the window's prefix (#12942)

settings_search::begin takes the prefix of the window it searches, so
the current match is `{prefix}:search:current`; a Reveal carries the
prefix of the window that opened it, so the outlined row is
`{prefix}:revealed` wherever it draws. The Preferences window passes its
own prefix; SolidCraft's is "prefs", so its names are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Preferences: main's newer callers read open and filter through the window's shell (#12942)

The panel runtime, the toolbar's modal guard and four tests written on main after the
batch base still named the fields the window component took over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* Preferences window: Apply runs the host's apply step; SolidCraft's switches the shared panel layout (#12942)

Main's Apply now enables or disables the shared panel layout before it takes the new
snapshot (eaf8f27a7). The window component gets Host::apply for that step, run before the
snapshot; press returns its error, so a command reports it, and the footer keeps it as the
window's restore error, as main did (Apply waits, a close reverts).
production_setting_cancel_preserves_classic_and_apply_enables_the_real_host fails with the
step left out; a_failed_apply_keeps_the_last_snapshot_and_the_footer_shows_it_until_a_close_reverts
pins both paths on the component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
2026-11-09 04:34:14 +00:00
bherbruck ee00ae925f Kernel PRs go to main; the kernel branch is for multi-step work only (#13986)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 04:26:51 +00:00
PeppaWutz f7ae792b69 Agents and scripts: New Design describes its exact parameters and reply (#13305) (#13322)
* Agents and scripts: New Design describes its exact parameters and reply (#13305)

file.new now publishes typed input and output schemas, as the linked-component,
canvas and Browser folder commands do. The parameter type reads `name` as the
handler does, and the reply is built from a typed struct that serializes to the
same JSON as before. The call is checked in warn and strict mode, so a
misspelt key is reported. Behaviour, messages and replies are unchanged with the check off; with it on,
a `name` that is not text is now reported in warn and refused in strict.
file.new is removed from the list of commands without schemas.

Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.

* Fix unconverted.txt after merge
2026-11-09 03:44:31 +00:00
bherbruck 5217d48a79 CODEOWNERS: solidcore/ owned by the solidcore-owners team, one reviewer per PR (#13973)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 03:31:34 +00:00
jyviko f96b5e067d Fonts: the kit's type API is ui_kit::type_face, the font data stays with the host (#13421)
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)

A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the widget registry is a shared crate, ui-registry (#12936)

The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)

TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: Preferences search is a kit module, settings_search (#13256)

prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the kit owns the menu entry and the list width (#13257)

Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)

parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)

The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)

LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)

continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: length fields take unit scaling and length parsing from the host (#13270)

The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)

geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Material editor: the Remove buttons are translated (material.editor.remove)

The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)

bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)

no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Theme: the icon palette leaves Tokens; the icon renderer owns it (#13304)

A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: the token contract as a shared crate, solidcraft-theme (#13304)

Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.

The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the icon lint and the docs read palettes.rs at crates/theme (#13304)

lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: format the field contrast test (#13304)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the server machinery is a shared crate, solidcraft-mcp-server (#12947)

JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the shared server tests what it owns, on its own (#12947)

Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: the architecture table lists the shared crates and the MCP split (#12947)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the token files and Effect are a shared crate, solidcraft-control (#12946)

The per-launch token, its files, the discovery file and the client's token
lookup move from the engine to crates/control, with no engine dependency.
Which app they belong to (name, data folder, environment, UTC format,
default port) is an `App` value; SolidCraft's is engine::control_auth::APP.
`Effect` moves with them; which requests do what stays in the engine's
control_pending. Callers switch paths; files and messages are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the loopback control server is shared; SolidCraft gives it its Protocol (#12946)

The token-admitted JSON-lines server (framing and line limit, connection
cap, control.running/pending/capabilities, busy and timeout replies,
control.subscribe events), ControlRequest and the design watch move to
solidcraft-control as `server::Hub`. What a request means (its name in
messages, its effect, command id, step count, the batch wait, the
capabilities) is the app's `server::Protocol`; SolidCraft's is in
apps/solidcraft/src/control_server.rs with its listeners and token files.
The wire format is unchanged and the server's tests move with it.

Also from review: Effect::when_done and the timeout text take the app's
word for its document ("design" for SolidCraft), the Listen docs name no
SolidCraft preference, and bench_assistant.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the shared server speaks the app's words and listens on loopback only (#12946)

The change event, the revision method, the docs path in the unauthorized
reply and the document noun come from the app's Protocol; SolidCraft's
replies are byte-identical. The design watch is the DocumentWatch.
Hub::listen refuses a listener off loopback, so no app can put the
channel on the network; SolidCraft binds 127.0.0.1 as before.
control_pending.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: ui.screenshot and ui.resize are shared over egui (#12946)

The window's screenshots in flight (the frame waits, the AO-settled
shot, the hidden-window and chat-turn refusals, the 8 s deadline) move
from SolveApp to solidcraft_control::screenshot::Shots, with the PNG
encoding; ui.resize and the hidden-window test go to window. SolidCraft
passes its AO settle, its issue report's frames and its vfs write, so a
second app can serve ui.screenshot with the shared crate alone. The issue
report and tests reach the queue as app.shots; replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: ui.screenshot and ui.resize are their own shared crate, solidcraft-control-egui (#12946)

d8d35d454 made egui and image dependencies of solidcraft-control, so the
headless engine, the CLI and the MCP bridge compiled egui. The screenshot
and window modules move unchanged to solidcraft-control-egui; ui-egui
uses them from there. cargo tree finds no egui under engine, mcp or control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the app gives the screenshot reply for no frame (#12946)

The shared text named SolidCraft's ui.render. Shots::new takes the app's
words; SolidCraft passes its reply unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: tests for the screenshot gate, a hidden window and frames nobody waits for (#12946)

Shots::issue takes a frame on the third frame and 150 ms after the request,
or 500 ms later for a view that keeps moving; a hidden window refuses a
request and answers one waiting; with nothing pending the frames that came
are dropped. Each fails on a one-line mutation of screenshot.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the title bar and window settings from plain inputs (#12940)

window_chrome holds what any Craft app's window needs without SolveApp: viewport(title, app_id,
custom_titlebar) with SolidCraft's window settings, TitleBar (traffic-light inset, drag area,
caption buttons) that returns a WindowAction for the app to run, and the resize zones and hit
test. titlebar.rs is SolidCraft's thin use of it: Close asks about unsaved designs first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the macOS menu bar from the kit's menu items (#12940)

window_chrome::native_menu builds the menu bar from ui_kit::Item trees (the app menu's system
layout, the system's Window entries) and returns the chosen entry as (id, params); the app-menu
title comes from the app. SolidCraft's native_menu.rs builds its Items from FileAction with
command ids or ui.* ids and maps the ids back, with the same entries, shortcuts and labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the application bar is laid out by TitleBar (#12940)

app_bar drew its own drag area, traffic-light inset and caption buttons beside the shared
TitleBar; it now runs TitleBar::show and answers the returned WindowAction through
titlebar::run, so the title bar tests pin what SolidCraft draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the menu bar's layout is pure and tested on every system (#12940)

MenuTable lays out the whole bar (the app menu's system entries, the Window menu's extras,
submenus) and replaces a submenu's entries itself; NativeMenu only turns rows into AppKit
items. Nothing is dead code off macOS, and the layout and replace tests run everywhere.
The Window menu's id is menu.window; menu ids are never dispatched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: entries use the control channel's ids, tested on every system (#12940)

Quit is app.quit, Settings and Keyboard Shortcuts are ui.prefs {open}, Agent Chat is
ui.agent_chat and Help's items are ui.help {item}; the menus are menu.file, menu.file.recent,
menu.edit, menu.window and menu.help. The id round trip and the shortcut table are tested
without a window on every system; the module builds there for its tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: its control ids do the same over the control channel (#12940)

Agent Chat sends ui.agent_chat {toggle}, which the control channel now takes (show or hide as
the panel's button does). ui.help runs every item the Help menu sends (docs, messages and
install_cli too) and answers an error for an unknown item instead of a silent ok. A test
runs the menu's control ids through the control channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: a menu of the bar that is off stays off; replace is for submenus (#12940)

The bar's own menus take their item's enabled flag. replace() refuses a menu of the bar
(it keeps the system entries the layout gave it), now documented and tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: the actions with a fixed id are a slice, not a sized array (#12940)

The list grows with the menu, so it follows the repo's rule for growing lists (cargo xtask lists).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the bar is a component that returns the slot or menu row chosen (#12944)

nav_bar draws the bar, More, its context menu and the open menu from plain data
(SlotView, Menu, BarLabels) and returns a NavAction; it keeps which menu is open
and reads nothing of the app. navigation_bar_view now describes SolidCraft's
slots and its Visual style and View menus as that data and runs the action it
gets back with the same calls as before. The Preferences editor keeps using
paint_row, which now paints through the component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* View compass: the signed-axis compass is a component that returns the view chosen (#12944)

view_compass draws the six signed axes from the camera's view axes (plain right,
up and back vectors), the Home chip and the hover tip, and returns a ViewAction:
Snap to an axis, Home, or Orbit by this frame's drag delta. Its drag tracker
(edge warp included) is shared by the cube and the custom body, which keep
their own drawing. SolidCraft turns the camera with the same calls as before
(animate_turn with the reverse-on-repeat rule, animate_view, orbit_drag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Navigation bar: a slot's second registry name is published where it is drawn (#12944)

SlotView gains an alias the bar publishes at the slot's handle, on the bar and in
More alike. SolidCraft gives the View menu's slot its older name `nav:view` there,
instead of looking the slot's handle up after the bar has drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: repaint after running the chosen action (#12944)

A menu that stays open after a pick (View) shows its rows' new state on the next
frame instead of waiting for the next input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the component's returned actions are tested from plain data (#12944)

Driven with no app: a slot and a row of its menu are returned and a closing menu
closes; More returns a folded slot and closes, opens a folded menu in its place,
and a menu that does not close on a pick stays open; a submenu row is returned by
its own id; the bar's context menu returns Customise, Hide and Reset, and a slot's
(on the bar or in More) returns Remove with the slot's stored index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: plain [f64; 3] axes in and out, no geometry type (#12944)

ViewAxes, Tip and ViewAction::Snap carry plain arrays, with the dot product a
local function, so the component needs neither solidcore_record::Vec3 nor the
engine's geometry trait. The compass tip painter takes the axis as an array too
(it only reads which axis it is, for the colour); SolidCraft converts at its edge
(view_axes, the Snap arm), and the Preferences navigator figure is posed by the
same ViewAxes::pose and AXES list as the live compass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the tip reach is defined once, by the compass (#12944)

The Preferences navigator figure placed its tips with its own 38.0 * 0.84; it now
scales view_compass::TIP_REACH, so the figure and the live compass cannot drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: SolidCraft runs only the actions the compass can return (#12944)

show() returns a tip or Home, never Orbit (a drag comes from orbit()); the docs
say so and signed_axis_view no longer carries a dead Orbit arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the module list in rustfmt's order (#12944)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: one function places a tip, for the compass and its figure (#12944)

tip_offset maps a posed screen direction to where its tip is drawn, applying the
reach once at f64; tips() and the Preferences navigator figure both call it, so
TIP_REACH goes. The figure's tips move by about one f32 ulp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: show() returns only a tip or Home; a drag is an OrbitStep (#12944)

ViewAction is now Snap or Home, so a drag cannot be represented as something the
compass returns; orbit() returns its motion as an OrbitStep. signed_axis_view's
match is exhaustive over what the compass can return, with no catch-all arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: SolidCraft's View slot is `nav:view` on the bar and in More (#12944)

A harness test: with the View slot on the bar, `nav:view` is its handle; folded
into More in a narrow view it is not shown until More opens, then `nav:view` is
the folded row's handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Start page: the page is a component that returns the entry chosen (#12941)

start_page::show draws the rail, the title and one body (action tiles,
recent files and samples; a tile grid; a review list) from plain data:
labels, icon names, host thumbnails, and what each entry stands for.
It returns the entry clicked; it holds no app, design or engine type.
home.rs is SolidCraft's use of it: it builds the page from the app's
state and runs the chosen FileAction. Recent, menu and sample clicks now
go through dispatch after the frame instead of running mid-draw.
Every published home rect and handle is unchanged (dumped before/after).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Start page: tiles and samples take a host Icon (#12941)

ui_kit::Icon is the picture a host gives a kit surface: a kit icon
name, a texture it made, or its own painter. FileView's action tiles and
sample cards draw it, so the start page shows another app's icons.
start_page is a public module like view_compass; Body::Start is boxed
(it is far larger than the other bodies). Tests: a tile draws the host's
texture icon, a recent row's context menu returns the entry picked, a
disabled rail entry returns nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Home: start-page entries name a recovery entry or recent file by index (#12941)

FileAction::Recovery, OpenRecent, ForgetRecent and ShowInFolder carry
an index into the app's lists instead of a cloned entry or path, and
the page borrows titles and file names from the app, so drawing the
start page no longer clones an entry or a path per frame. dispatch looks
the index up once, when the entry is chosen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Start page: a menu entry's icon is a kit name until MenuItem takes an Icon (#12941)

Recent rows' context-menu entries are drawn by ui_kit::MenuItem, whose
icon is a kit icon name; #13379 gives it a host Icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the type API moves to ui_kit::type_face (#13327)

The weights (medium, semibold, bold, RichTextExt), the family and fallback
names, the script checks and the CJK font loader state move out of fonts.rs
into ui_kit::type_face, which holds no font data and no engine reference.
fonts.rs keeps the host part: the embedded Plex faces, ui_font over the
engine's font lookup, the system font search and the install functions.
with_weight_families lets a host that installs no faces draw the kit's
weights with egui's default faces. Call sites use the new path; no
re-export at the old one. The font-family guard names the new file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: which faces the web shell fetches is the host's choice (#13327)

web_script_font_kind and needs_chinese_font decide which on-demand faces
SolidCraft's web shell downloads, so they move from the kit's type API back
to fonts.rs, with their assertions in a host test. The Arabic fallback's doc
link points at solidcraft_ui_text::bidi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the CJK font loader tests install the catalogs they read (#13327)

CjkFontLoader resolves the language through the installed catalogs, so its
tests passed only when another test in the process had installed them first
(they failed under `-- cjk` alone). Each now installs them itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: a host with no faces of its own calls type_face::install (#13327)

The kit's weights draw only once the context binds the weight families. The
host contract is now stated on the module and in docs/ui-kit.md: a host with
faces binds them in its definitions (with_weight_families binds any it leaves
out), a host with none calls type_face::install once. A test draws the
semibold name of a kit status header on a bare context after only that call;
without it egui has no face for the family and draws no glyphs. Also puts
`pub mod type_face` in rustfmt's order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: the Thai, Hebrew and Arabic names are imported only off the web (#13327)

Only the desktop's system font search uses them, so the wasm build warned
that the imports were unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: the wasm test build imports the Thai, Hebrew and Arabic names (#13327)

The font tests use them on every target, so the import is gated on
test or a desktop target, not on the desktop alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: type_face::install is install_weight_families (#13327)

The kit's entry point had the same name as the host's private
fonts::install, which installs SolidCraft's faces. The new name says what
the kit call does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: install_weight_families says it replaces the font definitions (#13327)

It sets egui's default definitions. It cannot merge into the context's own:
egui keeps the definitions set this frame private until the next one, and
reading the current fonts before the first frame panics. The doc and
docs/ui-kit.md say so; a host with faces passes its definitions through
with_weight_families instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the bare-context test also checks a context without the binding (#13327)

The control half: a bare context that never called install_weight_families
has no semibold family and no face that draws the name, which is why the
call is the host contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the egui-faces test sets up through install_weight_families (#13327)

It inlined the entry point's body; it now calls the call a host makes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 02:56:55 +00:00
jyviko cc55f59bf2 History bar: a component over plain step data that returns what the user chose (#13429)
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)

A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the widget registry is a shared crate, ui-registry (#12936)

The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)

TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: Preferences search is a kit module, settings_search (#13256)

prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the kit owns the menu entry and the list width (#13257)

Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)

parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)

The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)

LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)

continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: length fields take unit scaling and length parsing from the host (#13270)

The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)

geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Material editor: the Remove buttons are translated (material.editor.remove)

The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)

bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)

no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Theme: the icon palette leaves Tokens; the icon renderer owns it (#13304)

A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: the token contract as a shared crate, solidcraft-theme (#13304)

Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.

The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the icon lint and the docs read palettes.rs at crates/theme (#13304)

lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: format the field contrast test (#13304)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the server machinery is a shared crate, solidcraft-mcp-server (#12947)

JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the shared server tests what it owns, on its own (#12947)

Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: the architecture table lists the shared crates and the MCP split (#12947)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the token files and Effect are a shared crate, solidcraft-control (#12946)

The per-launch token, its files, the discovery file and the client's token
lookup move from the engine to crates/control, with no engine dependency.
Which app they belong to (name, data folder, environment, UTC format,
default port) is an `App` value; SolidCraft's is engine::control_auth::APP.
`Effect` moves with them; which requests do what stays in the engine's
control_pending. Callers switch paths; files and messages are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the loopback control server is shared; SolidCraft gives it its Protocol (#12946)

The token-admitted JSON-lines server (framing and line limit, connection
cap, control.running/pending/capabilities, busy and timeout replies,
control.subscribe events), ControlRequest and the design watch move to
solidcraft-control as `server::Hub`. What a request means (its name in
messages, its effect, command id, step count, the batch wait, the
capabilities) is the app's `server::Protocol`; SolidCraft's is in
apps/solidcraft/src/control_server.rs with its listeners and token files.
The wire format is unchanged and the server's tests move with it.

Also from review: Effect::when_done and the timeout text take the app's
word for its document ("design" for SolidCraft), the Listen docs name no
SolidCraft preference, and bench_assistant.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the shared server speaks the app's words and listens on loopback only (#12946)

The change event, the revision method, the docs path in the unauthorized
reply and the document noun come from the app's Protocol; SolidCraft's
replies are byte-identical. The design watch is the DocumentWatch.
Hub::listen refuses a listener off loopback, so no app can put the
channel on the network; SolidCraft binds 127.0.0.1 as before.
control_pending.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: ui.screenshot and ui.resize are shared over egui (#12946)

The window's screenshots in flight (the frame waits, the AO-settled
shot, the hidden-window and chat-turn refusals, the 8 s deadline) move
from SolveApp to solidcraft_control::screenshot::Shots, with the PNG
encoding; ui.resize and the hidden-window test go to window. SolidCraft
passes its AO settle, its issue report's frames and its vfs write, so a
second app can serve ui.screenshot with the shared crate alone. The issue
report and tests reach the queue as app.shots; replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: ui.screenshot and ui.resize are their own shared crate, solidcraft-control-egui (#12946)

d8d35d454 made egui and image dependencies of solidcraft-control, so the
headless engine, the CLI and the MCP bridge compiled egui. The screenshot
and window modules move unchanged to solidcraft-control-egui; ui-egui
uses them from there. cargo tree finds no egui under engine, mcp or control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the app gives the screenshot reply for no frame (#12946)

The shared text named SolidCraft's ui.render. Shots::new takes the app's
words; SolidCraft passes its reply unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: tests for the screenshot gate, a hidden window and frames nobody waits for (#12946)

Shots::issue takes a frame on the third frame and 150 ms after the request,
or 500 ms later for a view that keeps moving; a hidden window refuses a
request and answers one waiting; with nothing pending the frames that came
are dropped. Each fails on a one-line mutation of screenshot.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the title bar and window settings from plain inputs (#12940)

window_chrome holds what any Craft app's window needs without SolveApp: viewport(title, app_id,
custom_titlebar) with SolidCraft's window settings, TitleBar (traffic-light inset, drag area,
caption buttons) that returns a WindowAction for the app to run, and the resize zones and hit
test. titlebar.rs is SolidCraft's thin use of it: Close asks about unsaved designs first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the macOS menu bar from the kit's menu items (#12940)

window_chrome::native_menu builds the menu bar from ui_kit::Item trees (the app menu's system
layout, the system's Window entries) and returns the chosen entry as (id, params); the app-menu
title comes from the app. SolidCraft's native_menu.rs builds its Items from FileAction with
command ids or ui.* ids and maps the ids back, with the same entries, shortcuts and labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the application bar is laid out by TitleBar (#12940)

app_bar drew its own drag area, traffic-light inset and caption buttons beside the shared
TitleBar; it now runs TitleBar::show and answers the returned WindowAction through
titlebar::run, so the title bar tests pin what SolidCraft draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the menu bar's layout is pure and tested on every system (#12940)

MenuTable lays out the whole bar (the app menu's system entries, the Window menu's extras,
submenus) and replaces a submenu's entries itself; NativeMenu only turns rows into AppKit
items. Nothing is dead code off macOS, and the layout and replace tests run everywhere.
The Window menu's id is menu.window; menu ids are never dispatched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: entries use the control channel's ids, tested on every system (#12940)

Quit is app.quit, Settings and Keyboard Shortcuts are ui.prefs {open}, Agent Chat is
ui.agent_chat and Help's items are ui.help {item}; the menus are menu.file, menu.file.recent,
menu.edit, menu.window and menu.help. The id round trip and the shortcut table are tested
without a window on every system; the module builds there for its tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: its control ids do the same over the control channel (#12940)

Agent Chat sends ui.agent_chat {toggle}, which the control channel now takes (show or hide as
the panel's button does). ui.help runs every item the Help menu sends (docs, messages and
install_cli too) and answers an error for an unknown item instead of a silent ok. A test
runs the menu's control ids through the control channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: a menu of the bar that is off stays off; replace is for submenus (#12940)

The bar's own menus take their item's enabled flag. replace() refuses a menu of the bar
(it keeps the system entries the layout gave it), now documented and tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: the actions with a fixed id are a slice, not a sized array (#12940)

The list grows with the menu, so it follows the repo's rule for growing lists (cargo xtask lists).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the bar is a component that returns the slot or menu row chosen (#12944)

nav_bar draws the bar, More, its context menu and the open menu from plain data
(SlotView, Menu, BarLabels) and returns a NavAction; it keeps which menu is open
and reads nothing of the app. navigation_bar_view now describes SolidCraft's
slots and its Visual style and View menus as that data and runs the action it
gets back with the same calls as before. The Preferences editor keeps using
paint_row, which now paints through the component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* View compass: the signed-axis compass is a component that returns the view chosen (#12944)

view_compass draws the six signed axes from the camera's view axes (plain right,
up and back vectors), the Home chip and the hover tip, and returns a ViewAction:
Snap to an axis, Home, or Orbit by this frame's drag delta. Its drag tracker
(edge warp included) is shared by the cube and the custom body, which keep
their own drawing. SolidCraft turns the camera with the same calls as before
(animate_turn with the reverse-on-repeat rule, animate_view, orbit_drag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Navigation bar: a slot's second registry name is published where it is drawn (#12944)

SlotView gains an alias the bar publishes at the slot's handle, on the bar and in
More alike. SolidCraft gives the View menu's slot its older name `nav:view` there,
instead of looking the slot's handle up after the bar has drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: repaint after running the chosen action (#12944)

A menu that stays open after a pick (View) shows its rows' new state on the next
frame instead of waiting for the next input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the component's returned actions are tested from plain data (#12944)

Driven with no app: a slot and a row of its menu are returned and a closing menu
closes; More returns a folded slot and closes, opens a folded menu in its place,
and a menu that does not close on a pick stays open; a submenu row is returned by
its own id; the bar's context menu returns Customise, Hide and Reset, and a slot's
(on the bar or in More) returns Remove with the slot's stored index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: plain [f64; 3] axes in and out, no geometry type (#12944)

ViewAxes, Tip and ViewAction::Snap carry plain arrays, with the dot product a
local function, so the component needs neither solidcore_record::Vec3 nor the
engine's geometry trait. The compass tip painter takes the axis as an array too
(it only reads which axis it is, for the colour); SolidCraft converts at its edge
(view_axes, the Snap arm), and the Preferences navigator figure is posed by the
same ViewAxes::pose and AXES list as the live compass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the tip reach is defined once, by the compass (#12944)

The Preferences navigator figure placed its tips with its own 38.0 * 0.84; it now
scales view_compass::TIP_REACH, so the figure and the live compass cannot drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: SolidCraft runs only the actions the compass can return (#12944)

show() returns a tip or Home, never Orbit (a drag comes from orbit()); the docs
say so and signed_axis_view no longer carries a dead Orbit arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the module list in rustfmt's order (#12944)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: one function places a tip, for the compass and its figure (#12944)

tip_offset maps a posed screen direction to where its tip is drawn, applying the
reach once at f64; tips() and the Preferences navigator figure both call it, so
TIP_REACH goes. The figure's tips move by about one f32 ulp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: show() returns only a tip or Home; a drag is an OrbitStep (#12944)

ViewAction is now Snap or Home, so a drag cannot be represented as something the
compass returns; orbit() returns its motion as an OrbitStep. signed_axis_view's
match is exhaustive over what the compass can return, with no catch-all arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: SolidCraft's View slot is `nav:view` on the bar and in More (#12944)

A harness test: with the View slot on the bar, `nav:view` is its handle; folded
into More in a narrow view it is not shown until More opens, then `nav:view` is
the folded row's handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Start page: the page is a component that returns the entry chosen (#12941)

start_page::show draws the rail, the title and one body (action tiles,
recent files and samples; a tile grid; a review list) from plain data:
labels, icon names, host thumbnails, and what each entry stands for.
It returns the entry clicked; it holds no app, design or engine type.
home.rs is SolidCraft's use of it: it builds the page from the app's
state and runs the chosen FileAction. Recent, menu and sample clicks now
go through dispatch after the frame instead of running mid-draw.
Every published home rect and handle is unchanged (dumped before/after).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Start page: tiles and samples take a host Icon (#12941)

ui_kit::Icon is the picture a host gives a kit surface: a kit icon
name, a texture it made, or its own painter. FileView's action tiles and
sample cards draw it, so the start page shows another app's icons.
start_page is a public module like view_compass; Body::Start is boxed
(it is far larger than the other bodies). Tests: a tile draws the host's
texture icon, a recent row's context menu returns the entry picked, a
disabled rail entry returns nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Home: start-page entries name a recovery entry or recent file by index (#12941)

FileAction::Recovery, OpenRecent, ForgetRecent and ShowInFolder carry
an index into the app's lists instead of a cloned entry or path, and
the page borrows titles and file names from the app, so drawing the
start page no longer clones an entry or a path per frame. dispatch looks
the index up once, when the entry is chosen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Start page: a menu entry's icon is a kit name until MenuItem takes an Icon (#12941)

Recent rows' context-menu entries are drawn by ui_kit::MenuItem, whose
icon is a kit icon name; #13379 gives it a host Icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the type API moves to ui_kit::type_face (#13327)

The weights (medium, semibold, bold, RichTextExt), the family and fallback
names, the script checks and the CJK font loader state move out of fonts.rs
into ui_kit::type_face, which holds no font data and no engine reference.
fonts.rs keeps the host part: the embedded Plex faces, ui_font over the
engine's font lookup, the system font search and the install functions.
with_weight_families lets a host that installs no faces draw the kit's
weights with egui's default faces. Call sites use the new path; no
re-export at the old one. The font-family guard names the new file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: which faces the web shell fetches is the host's choice (#13327)

web_script_font_kind and needs_chinese_font decide which on-demand faces
SolidCraft's web shell downloads, so they move from the kit's type API back
to fonts.rs, with their assertions in a host test. The Arabic fallback's doc
link points at solidcraft_ui_text::bidi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the CJK font loader tests install the catalogs they read (#13327)

CjkFontLoader resolves the language through the installed catalogs, so its
tests passed only when another test in the process had installed them first
(they failed under `-- cjk` alone). Each now installs them itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: a host with no faces of its own calls type_face::install (#13327)

The kit's weights draw only once the context binds the weight families. The
host contract is now stated on the module and in docs/ui-kit.md: a host with
faces binds them in its definitions (with_weight_families binds any it leaves
out), a host with none calls type_face::install once. A test draws the
semibold name of a kit status header on a bare context after only that call;
without it egui has no face for the family and draws no glyphs. Also puts
`pub mod type_face` in rustfmt's order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: the Thai, Hebrew and Arabic names are imported only off the web (#13327)

Only the desktop's system font search uses them, so the wasm build warned
that the imports were unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: the wasm test build imports the Thai, Hebrew and Arabic names (#13327)

The font tests use them on every target, so the import is gated on
test or a desktop target, not on the desktop alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: type_face::install is install_weight_families (#13327)

The kit's entry point had the same name as the host's private
fonts::install, which installs SolidCraft's faces. The new name says what
the kit call does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: install_weight_families says it replaces the font definitions (#13327)

It sets egui's default definitions. It cannot merge into the context's own:
egui keeps the definitions set this frame private until the next one, and
reading the current fonts before the first frame panics. The doc and
docs/ui-kit.md say so; a host with faces passes its definitions through
with_weight_families instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the bare-context test also checks a context without the binding (#13327)

The control half: a bare context that never called install_weight_families
has no semibold family and no face that draws the name, which is why the
call is the host contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* UI kit: the egui-faces test sets up through install_weight_families (#13327)

It inlined the entry point's body; it now calls the call a host makes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* History bar: the bottom History bar as a component over plain step data (#12945)

history_bar draws the grip and title, the History controls and a strip of step
chips with the marker, from plain data (Bar, Step, StepState), and returns
Actions: Seek (a control or the dropped marker, as a slot and its side of the
marker), Play, ControlsMenu, Select with its modifiers, Edit, Menu (the app shows
its own items) and Reorder. The app draws its own chips, trays and inline rows
through the strip (Contents). It keeps only the drag and reads nothing of the app.
Tested with no app: clicks, a double click, right clicks, step and marker drags on
both sides of the marker, a folded chip of the app's, the controls with and
without Play, a narrow bar, an empty bar and a release outside the window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* History bar: SolidCraft's bottom History bar is the component with its features (#12945)

timeline_contents describes the features as Steps and draws its folded runs,
folders, joint rows, trays and the trailing joints through the component's
strip; it runs the returned actions with the same calls as before (select or
pick into the dialog, Edit Feature, the one context menu, roll_to at the
dropped slot's design position, drop_feature). The History controls' actions
run right after the header, as before; a chip's click and menu now run once
the strip is drawn. transport_response (the left History) routes through the
same transport actions. The bar's own drag and slot code leaves timeline.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* History bar: the grip and the History controls publish under the bar's Handles (#12945)

The grip's id comes from the bar's scope and its grip handle instead of one
global id, so two bars never share a drag. The History controls are published
as Handles::controls followed by their id through HistoryTransport::show_as;
show keeps "history:" for its other callers. SolidCraft passes "history:", so
its names are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* History bar: a step carries its icon's accent; the bar reads no kind names (#12945)

Step.kind (matched against SolidCraft's feature type names for the icon's
accent) becomes Step.icon_accent, an icon palette name or none, painted
through HistoryChip::paint_with_accent. SolidCraft passes what its kinds give
(feature_icon_accent of the type name and icon), so its chips are unchanged;
another app gives its own accents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* History bar: tests for a drop at the marker from after it and for the chip's marks (#12945)

A step from after the marker dropped at the marker's own slot returns the side
it lands on; highlighted, rolled back and failed steps reach their chips (lit,
faded, the error fill); only the floating card dots the last step before the
marker, and only that step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 02:48:47 +00:00
PeppaWutz 38598a7860 Agents and scripts: Installed Slicers describes its exact parameters and reply (#13306) (#13340)
* Agents and scripts: Installed Slicers describes its exact parameters and reply (#13306)

file.slicers now publishes typed input and output schemas. It takes no
parameters, so a call with a key is reported in warn and refused in strict,
and a call without one runs as before. The reply is built from a typed
value and serializes to the same JSON as before: each slicer has id, name,
program and icon, the last two null when missing. Its line leaves
unconverted.txt.

Two test helpers in tests/mesh_convert.rs become crate-visible so the new
reply test takes turns with the send tests that fake the slicer list.

Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Format the imports after merging main

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 02:33:43 +00:00
jyviko 92af38e470 Start page: a component that takes the host's entries and icons and returns the entry chosen (#13410)
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)

A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the widget registry is a shared crate, ui-registry (#12936)

The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)

TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: Preferences search is a kit module, settings_search (#13256)

prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the kit owns the menu entry and the list width (#13257)

Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)

parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)

The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)

LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)

continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: length fields take unit scaling and length parsing from the host (#13270)

The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)

geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Material editor: the Remove buttons are translated (material.editor.remove)

The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)

bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)

no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Theme: the icon palette leaves Tokens; the icon renderer owns it (#13304)

A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: the token contract as a shared crate, solidcraft-theme (#13304)

Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.

The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the icon lint and the docs read palettes.rs at crates/theme (#13304)

lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: format the field contrast test (#13304)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the server machinery is a shared crate, solidcraft-mcp-server (#12947)

JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the shared server tests what it owns, on its own (#12947)

Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: the architecture table lists the shared crates and the MCP split (#12947)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the token files and Effect are a shared crate, solidcraft-control (#12946)

The per-launch token, its files, the discovery file and the client's token
lookup move from the engine to crates/control, with no engine dependency.
Which app they belong to (name, data folder, environment, UTC format,
default port) is an `App` value; SolidCraft's is engine::control_auth::APP.
`Effect` moves with them; which requests do what stays in the engine's
control_pending. Callers switch paths; files and messages are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the loopback control server is shared; SolidCraft gives it its Protocol (#12946)

The token-admitted JSON-lines server (framing and line limit, connection
cap, control.running/pending/capabilities, busy and timeout replies,
control.subscribe events), ControlRequest and the design watch move to
solidcraft-control as `server::Hub`. What a request means (its name in
messages, its effect, command id, step count, the batch wait, the
capabilities) is the app's `server::Protocol`; SolidCraft's is in
apps/solidcraft/src/control_server.rs with its listeners and token files.
The wire format is unchanged and the server's tests move with it.

Also from review: Effect::when_done and the timeout text take the app's
word for its document ("design" for SolidCraft), the Listen docs name no
SolidCraft preference, and bench_assistant.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the shared server speaks the app's words and listens on loopback only (#12946)

The change event, the revision method, the docs path in the unauthorized
reply and the document noun come from the app's Protocol; SolidCraft's
replies are byte-identical. The design watch is the DocumentWatch.
Hub::listen refuses a listener off loopback, so no app can put the
channel on the network; SolidCraft binds 127.0.0.1 as before.
control_pending.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: ui.screenshot and ui.resize are shared over egui (#12946)

The window's screenshots in flight (the frame waits, the AO-settled
shot, the hidden-window and chat-turn refusals, the 8 s deadline) move
from SolveApp to solidcraft_control::screenshot::Shots, with the PNG
encoding; ui.resize and the hidden-window test go to window. SolidCraft
passes its AO settle, its issue report's frames and its vfs write, so a
second app can serve ui.screenshot with the shared crate alone. The issue
report and tests reach the queue as app.shots; replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: ui.screenshot and ui.resize are their own shared crate, solidcraft-control-egui (#12946)

d8d35d454 made egui and image dependencies of solidcraft-control, so the
headless engine, the CLI and the MCP bridge compiled egui. The screenshot
and window modules move unchanged to solidcraft-control-egui; ui-egui
uses them from there. cargo tree finds no egui under engine, mcp or control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the app gives the screenshot reply for no frame (#12946)

The shared text named SolidCraft's ui.render. Shots::new takes the app's
words; SolidCraft passes its reply unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: tests for the screenshot gate, a hidden window and frames nobody waits for (#12946)

Shots::issue takes a frame on the third frame and 150 ms after the request,
or 500 ms later for a view that keeps moving; a hidden window refuses a
request and answers one waiting; with nothing pending the frames that came
are dropped. Each fails on a one-line mutation of screenshot.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the title bar and window settings from plain inputs (#12940)

window_chrome holds what any Craft app's window needs without SolveApp: viewport(title, app_id,
custom_titlebar) with SolidCraft's window settings, TitleBar (traffic-light inset, drag area,
caption buttons) that returns a WindowAction for the app to run, and the resize zones and hit
test. titlebar.rs is SolidCraft's thin use of it: Close asks about unsaved designs first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the macOS menu bar from the kit's menu items (#12940)

window_chrome::native_menu builds the menu bar from ui_kit::Item trees (the app menu's system
layout, the system's Window entries) and returns the chosen entry as (id, params); the app-menu
title comes from the app. SolidCraft's native_menu.rs builds its Items from FileAction with
command ids or ui.* ids and maps the ids back, with the same entries, shortcuts and labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the application bar is laid out by TitleBar (#12940)

app_bar drew its own drag area, traffic-light inset and caption buttons beside the shared
TitleBar; it now runs TitleBar::show and answers the returned WindowAction through
titlebar::run, so the title bar tests pin what SolidCraft draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the menu bar's layout is pure and tested on every system (#12940)

MenuTable lays out the whole bar (the app menu's system entries, the Window menu's extras,
submenus) and replaces a submenu's entries itself; NativeMenu only turns rows into AppKit
items. Nothing is dead code off macOS, and the layout and replace tests run everywhere.
The Window menu's id is menu.window; menu ids are never dispatched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: entries use the control channel's ids, tested on every system (#12940)

Quit is app.quit, Settings and Keyboard Shortcuts are ui.prefs {open}, Agent Chat is
ui.agent_chat and Help's items are ui.help {item}; the menus are menu.file, menu.file.recent,
menu.edit, menu.window and menu.help. The id round trip and the shortcut table are tested
without a window on every system; the module builds there for its tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: its control ids do the same over the control channel (#12940)

Agent Chat sends ui.agent_chat {toggle}, which the control channel now takes (show or hide as
the panel's button does). ui.help runs every item the Help menu sends (docs, messages and
install_cli too) and answers an error for an unknown item instead of a silent ok. A test
runs the menu's control ids through the control channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: a menu of the bar that is off stays off; replace is for submenus (#12940)

The bar's own menus take their item's enabled flag. replace() refuses a menu of the bar
(it keeps the system entries the layout gave it), now documented and tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: the actions with a fixed id are a slice, not a sized array (#12940)

The list grows with the menu, so it follows the repo's rule for growing lists (cargo xtask lists).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the bar is a component that returns the slot or menu row chosen (#12944)

nav_bar draws the bar, More, its context menu and the open menu from plain data
(SlotView, Menu, BarLabels) and returns a NavAction; it keeps which menu is open
and reads nothing of the app. navigation_bar_view now describes SolidCraft's
slots and its Visual style and View menus as that data and runs the action it
gets back with the same calls as before. The Preferences editor keeps using
paint_row, which now paints through the component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* View compass: the signed-axis compass is a component that returns the view chosen (#12944)

view_compass draws the six signed axes from the camera's view axes (plain right,
up and back vectors), the Home chip and the hover tip, and returns a ViewAction:
Snap to an axis, Home, or Orbit by this frame's drag delta. Its drag tracker
(edge warp included) is shared by the cube and the custom body, which keep
their own drawing. SolidCraft turns the camera with the same calls as before
(animate_turn with the reverse-on-repeat rule, animate_view, orbit_drag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Navigation bar: a slot's second registry name is published where it is drawn (#12944)

SlotView gains an alias the bar publishes at the slot's handle, on the bar and in
More alike. SolidCraft gives the View menu's slot its older name `nav:view` there,
instead of looking the slot's handle up after the bar has drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: repaint after running the chosen action (#12944)

A menu that stays open after a pick (View) shows its rows' new state on the next
frame instead of waiting for the next input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the component's returned actions are tested from plain data (#12944)

Driven with no app: a slot and a row of its menu are returned and a closing menu
closes; More returns a folded slot and closes, opens a folded menu in its place,
and a menu that does not close on a pick stays open; a submenu row is returned by
its own id; the bar's context menu returns Customise, Hide and Reset, and a slot's
(on the bar or in More) returns Remove with the slot's stored index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: plain [f64; 3] axes in and out, no geometry type (#12944)

ViewAxes, Tip and ViewAction::Snap carry plain arrays, with the dot product a
local function, so the component needs neither solidcore_record::Vec3 nor the
engine's geometry trait. The compass tip painter takes the axis as an array too
(it only reads which axis it is, for the colour); SolidCraft converts at its edge
(view_axes, the Snap arm), and the Preferences navigator figure is posed by the
same ViewAxes::pose and AXES list as the live compass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the tip reach is defined once, by the compass (#12944)

The Preferences navigator figure placed its tips with its own 38.0 * 0.84; it now
scales view_compass::TIP_REACH, so the figure and the live compass cannot drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: SolidCraft runs only the actions the compass can return (#12944)

show() returns a tip or Home, never Orbit (a drag comes from orbit()); the docs
say so and signed_axis_view no longer carries a dead Orbit arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the module list in rustfmt's order (#12944)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: one function places a tip, for the compass and its figure (#12944)

tip_offset maps a posed screen direction to where its tip is drawn, applying the
reach once at f64; tips() and the Preferences navigator figure both call it, so
TIP_REACH goes. The figure's tips move by about one f32 ulp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: show() returns only a tip or Home; a drag is an OrbitStep (#12944)

ViewAction is now Snap or Home, so a drag cannot be represented as something the
compass returns; orbit() returns its motion as an OrbitStep. signed_axis_view's
match is exhaustive over what the compass can return, with no catch-all arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: SolidCraft's View slot is `nav:view` on the bar and in More (#12944)

A harness test: with the View slot on the bar, `nav:view` is its handle; folded
into More in a narrow view it is not shown until More opens, then `nav:view` is
the folded row's handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Start page: the page is a component that returns the entry chosen (#12941)

start_page::show draws the rail, the title and one body (action tiles,
recent files and samples; a tile grid; a review list) from plain data:
labels, icon names, host thumbnails, and what each entry stands for.
It returns the entry clicked; it holds no app, design or engine type.
home.rs is SolidCraft's use of it: it builds the page from the app's
state and runs the chosen FileAction. Recent, menu and sample clicks now
go through dispatch after the frame instead of running mid-draw.
Every published home rect and handle is unchanged (dumped before/after).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Start page: tiles and samples take a host Icon (#12941)

ui_kit::Icon is the picture a host gives a kit surface: a kit icon
name, a texture it made, or its own painter. FileView's action tiles and
sample cards draw it, so the start page shows another app's icons.
start_page is a public module like view_compass; Body::Start is boxed
(it is far larger than the other bodies). Tests: a tile draws the host's
texture icon, a recent row's context menu returns the entry picked, a
disabled rail entry returns nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Home: start-page entries name a recovery entry or recent file by index (#12941)

FileAction::Recovery, OpenRecent, ForgetRecent and ShowInFolder carry
an index into the app's lists instead of a cloned entry or path, and
the page borrows titles and file names from the app, so drawing the
start page no longer clones an entry or a path per frame. dispatch looks
the index up once, when the entry is chosen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Start page: a menu entry's icon is a kit name until MenuItem takes an Icon (#12941)

Recent rows' context-menu entries are drawn by ui_kit::MenuItem, whose
icon is a kit icon name; #13379 gives it a host Icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 01:53:04 +00:00
PeppaWutz 0c4f55b60f Agents and scripts: Insert Mesh describes its exact parameters and reply (#13309) (#13341)
* Agents and scripts: Insert Mesh describes its exact parameters and reply (#13309)

file.insert_mesh now publishes typed input and output schemas. Each
parameter is read the way the handler reads it today and refused with the
handler's own message, and the reply is built from the same type its output
schema describes, so the JSON it returns does not change. Keys the handler
reads only in some cases (up only for a glTF file, names and folder only when
up is given for a glTF file, everything but path only for a mesh file) are
read by the check in the same cases only. Its line leaves unconverted.txt.

Part of #2829.

Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.

* Insert Mesh schema: leave name unread for a file that is not a mesh

The handler refuses the kind of file before it reads name, so Strict now
does too.

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
2026-11-09 01:47:26 +00:00
github-actions[bot] 0d490ffb35 Regenerate generated files and fold pending files [skip ci] 2026-11-09 01:50:34 +00:00
bherbruck b5716d0fb8 Dial CodeRabbit down: chill profile, no draft or per-push reviews, no extras (#13889)
* Dial CodeRabbit down: chill profile, no draft or per-push reviews, no extras

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CodeRabbit: the defaults rule is about UX defaults only, not kernel results

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CodeRabbit: define a changed default with the owner's examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:58:54 +00:00
bherbruck dab1f296bb Docs: Changes a default means UX defaults only (#13927)
* Docs: Changes a default means UX defaults only, not kernel results

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: sharper definition of a changed default

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AGENTS.md: back under the 5,000-character cap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:58:46 +00:00
PeppaWutz d946550019 Agents and scripts: Align describes its exact parameters and reply (#13314) (#13342)
* Agents and scripts: Align describes its exact parameters and reply (#13314)

solid.align now publishes typed input and output schemas, so agents and
scripts can read exactly which parameters it takes and what it returns,
and the warn and strict parameter checks cover it.

Each parameter is read the way the handler reads it today, with the same
error messages, and only in the cases where the handler reads it: a side
that has a plane or a frame, or a face given as a point, does not check the
keys the handler skips. The reply is the same feature result as before. Its
line leaves unconverted.txt. Part of #2829.

Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.

* Align schema: leave a frame unread beside the side's other keys

The handler refuses a frame given with the side's point, face or plane as
"not both" before it reads the frame, so Strict now does too.

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix unconverted.txt after merge

* Format the imports after merging main

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 00:50:54 +00:00
bherbruck 47121d5287 Agent chat: a PDF's text goes to agents that take no documents (#2812) (#2860)
The PDF is read with lopdf (MIT, native builds only) on the worker that reads the file: the first
300 pages and 200,000 characters, with a cap on what a page's content may inflate to and a catch
for a reader panic. Text-less (scanned) or unreadable PDFs are pointed to by path.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 00:35:48 +00:00
DuBearte be12f10eab MCP: a compact sketch reply counts spline poles instead of listing them (#9180) (#9232)
With verbose: false, a sketch command that creates more points than its
new curves have ends (the poles of an offset's or a fitted spline)
replies created.point_count instead of every point id. Agents refer to
those curves by id, so the ids only filled their context: an offset of a
16-curve outline listed 117 to 144 of them. Rectangles, lines and arcs
keep their point ids.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:35:32 +00:00
HiroKenEz 39e4100773 Appearance: the theme row opens on the theme in use each time Preferences shows it (#11445) (#12026)
* claim: #11445

* Appearance: the theme row opens on the theme in use each time it comes into view (#11445)

The theme row already brought the theme in use into view when the theme
or a filter changed. It now does so as well each time the row comes back
into view: Preferences opens, its Appearance page is shown again, or a
search is cleared. Only then: the arrows, the wheel and a drag move the
row freely while it stays in view.

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>
2026-11-09 00:35:12 +00:00
thocsmith 6ca597b857 Browser: Send to Slicer and Save As Mesh take every selected body (#12334) (#12368)
* claim: #12334

* Browser: Send to Slicer and Save As Mesh take every selected body (#12334)

Right-clicking a Browser body that is one of several selected bodies opened the mesh dialogs with only that body. They now start with every selected body, as the Appearance item on the same menu already does; a body outside the selection still opens them with itself alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-09 00:34:59 +00:00
P'tite Tête 543314c191 fix: A revolve axis without an origin passes through the world origin in a moved component (#12615)
- the frame table's axis mapping gives an {origin, dir} axis without origin the world's [0, 0, 0] before mapping it
- test: solid and surface revolves, made and edited, against origin [0, 0, 0]

Co-authored-by: P'tite Tête <asso@ptitetete.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-09 00:34:33 +00:00
CamWight 8fb4a1d22a CSV exports: a field that starts with =, +, - or @ stays text in a spreadsheet (#12728) (#12729)
* CSV exports: a field that starts with =, +, - or @ stays text in a spreadsheet (#12728)

A spreadsheet reads a CSV field that starts with =, +, - or @ as a formula, so a part numbered
=1+1 opened as 2 and "-5 mm shim" as #NAME?. Every CSV writer now writes such a field with a '
first (OWASP's CSV Injection mitigation), through one module, solidcraft_doc::csv:

- field() quotes only when a field needs it, now also for ; and a tab (a spreadsheet set to ;
  splits there); quoted() always quotes; unguard() takes the ' off again on import.
- A whole plain number (-5, +5, 1e-9) is left as it is: a spreadsheet reads it as that number.
  inf, NaN and a number with spaces around it are not plain numbers.
- Spaces and apostrophes before a formula start count. A field that already starts with ' and
  then a formula gets one more, so parameters.import reads back exactly what was exported.
- The parameter export, the BOM, the bend table and the motion export use it. Each keeps its
  quoting style, so files with plain fields are byte for byte the same, except one: the motion
  export used to turn a comma in a placement's name into a space and broke the row on a quote
  or a line break; the name is now quoted and kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Guides: how a script reads a guarded BOM CSV, and older parameter CSVs with a typed '

From @theironchef's review of #12729. The assemblies guide says a script
reading a BOM CSV sees `'=1+1`, and gets the value back by taking off one
leading `'` before a formula start (as `solidcraft_doc::csv::unguard`
does) or by reading the JSON export. The parameters guide says a CSV from
an earlier version whose value really started with a typed `'` before a
formula character (`'=x`) now imports as `=x`. Both pages now name every
formula start the guard uses (the full-width forms, a tab, a line break,
after spaces) and say a plain number such as `-5` is left as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Guides: a guarded CSV field after several apostrophes, and a spreadsheet that saves the file again

A formula field is one that starts as a formula after any spaces and
apostrophes, as `solidcraft_doc::csv` checks it: `'=1+1` is written as
`''=1+1`, and taking off one leading `'` gives it back. Both guides also
say the `'` holds only while the file is as SolidCraft wrote it: a
spreadsheet that saves the file again as CSV can drop it (Excel does), so
the formula runs the next time that file opens, and the advice is to
export again instead of passing on such a file (OWASP's CSV Injection
page says the same of these guards).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:34:13 +00:00
ancient_astronaut_theorist 0fc1ef8ca7 Fillet and Chamfer: when only one of several picked edges is left between two faces, ask for the edges again instead of rounding just one (#13332) (#13423)
* claim: #13332

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

* Ask for reselection when numbered edges' faces now bound one edge elsewhere (#13332)

Several edges between the same two faces share one name and are told
apart by a number (`~k`) given by where they lie. When the faces change
so that only one edge is left between them, every numbered name used to
find that edge: a chamfer of nine edges chamfered one, with no error.

A numbered name now takes the one edge left between its faces only where
its reference's point lies on it. Otherwise the name finds nothing and
its feature asks for its edges to be picked again.

Fixes #13332

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gr84VuQWHKtTwJ7cC3CP3W

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:32:55 +00:00
bherbruck 5fe92e88ec Tapped hole: thread every piece of a wall split by a pocket or slot (#13505) (#13520)
A tapped hole drilled across a pocket or slot leaves its cylindrical wall
in separate faces along the axis. The thread was put on only the piece
nearest the entry, so cosmetic and modelled threads stopped at the first
gap. The hole now threads every piece of the wall within its depth, and a
thread depth runs on through the pieces from where the first one starts.

Fixes #13505

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:32:42 +00:00
github-actions[bot] 1c8fdde4ba Regenerate generated files and fold pending files [skip ci] 2026-11-09 00:54:57 +00:00
PeppaWutz 3ce8b875b1 Agents and scripts: Save As Mesh describes its exact parameters and reply (#13308) (#13353)
* Agents and scripts: Save As Mesh describes its exact parameters and reply (#13308)

file.save_mesh now publishes typed input and output schemas. Each parameter
is read the way the handler reads it today and refused with the handler's own
message, and the reply is built from the same typed values the output schema
describes, so the JSON it returns does not change. `ascii` is read by the
check only with `format: stl`, and `single_instance` only with a `slicer`,
as the handler does. Its line leaves unconverted.txt.

Part of #2829.

Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.

* Fix unconverted.txt after merge
2026-11-09 00:23:39 +00:00
bherbruck bf78850423 Tests: an open setback corner now leaves a 9-edge hole (#13540) (#13895)
* claim: #13540 and #13542, the setback corner's tests after solidcore #2988

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the marked setback corner is the Setback; its open hole is nine edges (#13540)

Since tangency-labs/solidcore#2988 (e9a5ded95c) a vertex marked set back between planes is the
blend corner's Setback with its certified fill, not the N x N patch. Its sections and face
curves are one edge each, so the box corner left open has 9 faces, 27 edges and 19 vertices
and a hole of nine edges (the N x N patch split each in two: 12 edges, 30 and 22).

The G2 corner's side to its round is likewise one edge (w2949's smooth_edges change, the test
renamed); it is not drawn whole, so that test stays known-failing with its new cause (#13542).

Fixes tangency-labs/solidcraft#13540

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:15:14 +00:00
0rdnungsamt 3af51db557 Shortcuts: command search can move off S, and S can go to another command (#12190) (#13163)
* claim: #12190

* Shortcuts: command search can move off S, and S can go to another command (#12190)

Command search becomes the ordinary command ui.palette with S as its default
key, so Preferences > Shortcuts lists it and can move it; S leaves the
reserved keys and the hard-wired S branch in the toolbar goes. The search
field's key cap shows command search's current key, or none when it has
none. Out of the box nothing changes: S still opens command search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 00:14:25 +00:00
Loohi 956fe0070b Revolve: input and output schemas (part of #2829) (#13319)
* claim: part of #2829, revolve schema

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Yz6dHZiXcgTgrxjVNC6hx

* Revolve: input and output schemas (part of #2829)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Yz6dHZiXcgTgrxjVNC6hx

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:55:09 +00:00
PeppaWutz 53ce0e47b1 Agents and scripts: Draw Dimensioned Sketch Geometry describes its exact parameters and reply (#13313) (#13354)
* Agents and scripts: Draw Dimensioned Sketch Geometry describes its exact parameters and reply (#13313)

sketch.draw.dimensioned now publishes typed input and output schemas. Each
parameter is read as the handler reads it today, with the same error
messages, and the reply is built from the typed value and serializes to the
same JSON as before. Its line leaves unconverted.txt.

Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.

* Keep a nested command's parameter warnings in warn mode

A checked command that runs another checked command, as Draw Dimensioned
runs its creator, now adds its own warnings to the inner ones instead of
replacing them.

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).

* Fix unconverted.txt after merge

* Format the imports after merging main

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
2026-11-08 23:38:29 +00:00
SS-EN f46eae28c7 Joint drive: only features that read a pose-dependent mirror's copies recompute (#12139) (#12177)
* Joint drive: only features that read a pose-dependent mirror's copies recompute (#12139)

A mirror of placed bodies hashed its placement into the fingerprint chain, so a
joint drive changed the fingerprint of every later feature and they all
recomputed. Its placement is now checked on its own (Model::poses); when only
its pose changed, the features after it that read none of its copies keep their
results with its new copies in place. A feature that reads them (or might:
picked faces, projections, To Object, ...) recomputes, and everything after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Adopted model: don't seed pose-only mirror results into the cache (#12139)

A pose-only mirror's fingerprint leaves its pose out, so a saved result can be
from another pose than the design's. Model::adopt put it in the cache under the
design's placement key, and the next evaluation took it instead of mirroring
again. Those results are no longer seeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Adopted model: evaluate a pose-only mirror's saved result again (#12139)

When the saved file kept every state, Model::adopt returned without evaluating,
so a pose-only mirror saved at another pose stayed in the results the design
opens with. A design with one is now evaluated on adoption: the mirror again,
the features that read none of its copies kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Recompute a revolve about a model axis after a pose-only mirror (#12139)

A revolve's model axis is a pick of the geometry, not a body by name, so
the read set of its drawn targets misses a mirrored copy it turns about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:21:21 +00:00
austin62jake ea4164dabb Agents and scripts: Split Body and Split Face describe their exact parameters and reply (#13127) (#13128)
* Claim #13127: Split Body and Split Face schemas

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Split Body and Split Face: input and output schemas (#13127)

solid.split_body gets SplitBodyParams and solid.split_face SplitFaceParams in a new
schema/split.rs; both reply with FeatureResult. The cutting tool keys (tool, sketch,
line, plane) are checked for their form whenever given; which one the handler uses,
and a call with none, stay the handler's.

The tool face gets ToolFace in schema/types.rs, with the handlers' messages for
tool.body and tool.point; surface.trim reads tool the same way and can share it.
Split Body's sketch is a feature id only (SketchFeatureId), as its handler reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-08 23:05:26 +00:00
0rdnungsamt af16e55217 Mirror: mirroring a hole with its Thread feature builds the thread on the mirrored hole (#13278) (#13381)
* claim: #13278

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Mirror: a Thread feature is cut again on the mirrored hole (#13278)

Mirror and Pattern refused a Thread feature, so a hole with its thread
could not be mirrored. Replay now moves the thread's face pick by each
copy's transform and evaluates the thread there. A copy keeps the
thread's hand. A thread that ends at a plane is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:54:06 +00:00
1993eg1 c5fd9bc518 Add Chamfer command guide (machinist perspective) (#13521)
* Add Chamfer command guide (machinist perspective)

User guide for the Chamfer command in v0.30.1: Equal Distance, Two Distances, and Distance and Angle, picking edges vs a whole face, Flip, Tangent chain, and why a chamfer next to an existing small chamfer can fail.

* Chamfer guide: Equal Distance is 45° only on a square corner

Per review: Equal Distance gives a 45° chamfer only where the faces meet at 90°. Recommends Distance and Angle whenever the print gives an angle and for chamfers on sloped faces.
2026-11-08 22:49:35 +00:00
1993eg1 7cd4bc3b2c Add Thread command guide (machinist perspective) (#13507)
* Add Thread command guide (machinist perspective)

User guide for the Thread command in v0.30.1: Automatic size, coarse vs fine pitch, modeled vs callout threads, and thread length.

* Update Thread guide for the v0.40 dialog

Rewrites the guide for the new Thread dialog per review: Extent (Full / Length / Up to) replaces Full Length, Automatic size is on by default, the Form list adds Trapezoidal, Acme, Buttress and pipe threads, Ends (Start chamfer, End square or fade), and a tip to click the face near the end to thread or pick Start end. Notes that UNC/UNF are only on Hole tapped holes for now. Tested on v0.40.0-rc.3.
2026-11-08 22:48:21 +00:00
1993eg1 0d0a7327d9 Update Hole guide for the v0.40 dialog (#13547)
* Update Hole guide for the v0.40 dialog

Hand replaces Thread direction; adds the new Modeled checkbox and End (Square / Fade); Tap drill diameter can now be typed, with the accepted range (minor diameter to 5% over the tap maker's drill). Presets carry over to every design. Tested on v0.40.0-rc.3.

* Hole guide: clarify tap drill range and custom metric default

Per review: the lowest accepted drill is the smaller of the minor diameter and the tap maker's drill (pipe taps such as Rp 1/4 run under the minor diameter). For custom metric sizes not in the table, the default drill is major diameter minus pitch, so the guide now says to check the tap maker's chart.

* Hole guide: standard tap drill wording and tap drill table fallback

Says "standard tap drill" instead of "tap maker's drill" (noting the app's messages use the latter), and per review clarifies that the major-minus-pitch default only applies to custom metric sizes with no entry in the app's tap drill table.
2026-11-08 22:47:26 +00:00
Nathan Dane ff6ca84915 Assistant: adapt shared panels to wide layouts (#11311) (#13514)
* claim: #11311 Assistant responsive panel completion

* Assistant: reuse content regions in wide shared panels (#11311)

* Update the retained Assistant composer test for explicit flow

* Panels: retain editor focus through floating sizing passes

* Panels: preserve eligible editor focus across metadata refreshes
2026-11-08 22:44:56 +00:00
bherbruck eabbbc4c3d Retire the kernel pause: kernel work goes in solidcore/ (#13824)
* kernel-paused: don't pause solidcore/

Kernel work now happens in solidcore/ (vendored in #13173), behind its code
owners' review; the pause only covers SolidCraft's own crates/kernel and
crates/geom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Retire the kernel pause: kernel work goes in solidcore/

Delete the kernel-paused bot and its CI step, replace the CONTRIBUTING
section, and update the skills and docs that said kernel work is paused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:38:05 +00:00
bherbruck 0917a5dec2 Docs: how to update a PR moved from solidcore, and agent cost tips (#13888)
* docs: how to update a PR moved from solidcore, and agent cost tips

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: fetch the moved branch into a local branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:37:55 +00:00
PeppaWutz c7fb4f7cab Agents and scripts: Export describes its exact parameters and reply (#13315) (#13321)
file.export gets a typed contract (ExportParams) and an output schema
(FileExportResult), so describe_command and engine.describe give its
exact parameters and reply, and warn and strict can check its calls.

The contract reads each key only for the format the handler reads it
for (the URDF keys for URDF, quality and objects for the others), with
the handler's own messages, so a call that works today is not refused.
A format or mesh_prefix that is not text, which the handler reads as
left out, is now reported in warn and refused in strict.

Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
2026-11-08 21:54:38 +00:00
jyviko 089d09e40a Navigation bar and view compass: components that return the action chosen (#13409)
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)

A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the widget registry is a shared crate, ui-registry (#12936)

The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)

TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: Preferences search is a kit module, settings_search (#13256)

prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: the kit owns the menu entry and the list width (#13257)

Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)

parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)

The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)

LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)

continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* UI kit: length fields take unit scaling and length parsing from the host (#13270)

The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)

geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Material editor: the Remove buttons are translated (material.editor.remove)

The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)

bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)

no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Theme: the icon palette leaves Tokens; the icon renderer owns it (#13304)

A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Shared UI crates: the token contract as a shared crate, solidcraft-theme (#13304)

Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.

The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: the icon lint and the docs read palettes.rs at crates/theme (#13304)

lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Theme: format the field contrast test (#13304)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the server machinery is a shared crate, solidcraft-mcp-server (#12947)

JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* MCP: the shared server tests what it owns, on its own (#12947)

Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: the architecture table lists the shared crates and the MCP split (#12947)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the token files and Effect are a shared crate, solidcraft-control (#12946)

The per-launch token, its files, the discovery file and the client's token
lookup move from the engine to crates/control, with no engine dependency.
Which app they belong to (name, data folder, environment, UTC format,
default port) is an `App` value; SolidCraft's is engine::control_auth::APP.
`Effect` moves with them; which requests do what stays in the engine's
control_pending. Callers switch paths; files and messages are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the loopback control server is shared; SolidCraft gives it its Protocol (#12946)

The token-admitted JSON-lines server (framing and line limit, connection
cap, control.running/pending/capabilities, busy and timeout replies,
control.subscribe events), ControlRequest and the design watch move to
solidcraft-control as `server::Hub`. What a request means (its name in
messages, its effect, command id, step count, the batch wait, the
capabilities) is the app's `server::Protocol`; SolidCraft's is in
apps/solidcraft/src/control_server.rs with its listeners and token files.
The wire format is unchanged and the server's tests move with it.

Also from review: Effect::when_done and the timeout text take the app's
word for its document ("design" for SolidCraft), the Listen docs name no
SolidCraft preference, and bench_assistant.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: the shared server speaks the app's words and listens on loopback only (#12946)

The change event, the revision method, the docs path in the unauthorized
reply and the document noun come from the app's Protocol; SolidCraft's
replies are byte-identical. The design watch is the DocumentWatch.
Hub::listen refuses a listener off loopback, so no app can put the
channel on the network; SolidCraft binds 127.0.0.1 as before.
control_pending.rs is formatted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Control: ui.screenshot and ui.resize are shared over egui (#12946)

The window's screenshots in flight (the frame waits, the AO-settled
shot, the hidden-window and chat-turn refusals, the 8 s deadline) move
from SolveApp to solidcraft_control::screenshot::Shots, with the PNG
encoding; ui.resize and the hidden-window test go to window. SolidCraft
passes its AO settle, its issue report's frames and its vfs write, so a
second app can serve ui.screenshot with the shared crate alone. The issue
report and tests reach the queue as app.shots; replies are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: ui.screenshot and ui.resize are their own shared crate, solidcraft-control-egui (#12946)

d8d35d454 made egui and image dependencies of solidcraft-control, so the
headless engine, the CLI and the MCP bridge compiled egui. The screenshot
and window modules move unchanged to solidcraft-control-egui; ui-egui
uses them from there. cargo tree finds no egui under engine, mcp or control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: the app gives the screenshot reply for no frame (#12946)

The shared text named SolidCraft's ui.render. Shots::new takes the app's
words; SolidCraft passes its reply unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Control: tests for the screenshot gate, a hidden window and frames nobody waits for (#12946)

Shots::issue takes a frame on the third frame and 150 ms after the request,
or 500 ms later for a view that keeps moving; a hidden window refuses a
request and answers one waiting; with nothing pending the frames that came
are dropped. Each fails on a one-line mutation of screenshot.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the title bar and window settings from plain inputs (#12940)

window_chrome holds what any Craft app's window needs without SolveApp: viewport(title, app_id,
custom_titlebar) with SolidCraft's window settings, TitleBar (traffic-light inset, drag area,
caption buttons) that returns a WindowAction for the app to run, and the resize zones and hit
test. titlebar.rs is SolidCraft's thin use of it: Close asks about unsaved designs first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the macOS menu bar from the kit's menu items (#12940)

window_chrome::native_menu builds the menu bar from ui_kit::Item trees (the app menu's system
layout, the system's Window entries) and returns the chosen entry as (id, params); the app-menu
title comes from the app. SolidCraft's native_menu.rs builds its Items from FileAction with
command ids or ui.* ids and maps the ids back, with the same entries, shortcuts and labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Window chrome: the application bar is laid out by TitleBar (#12940)

app_bar drew its own drag area, traffic-light inset and caption buttons beside the shared
TitleBar; it now runs TitleBar::show and answers the returned WindowAction through
titlebar::run, so the title bar tests pin what SolidCraft draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: the menu bar's layout is pure and tested on every system (#12940)

MenuTable lays out the whole bar (the app menu's system entries, the Window menu's extras,
submenus) and replaces a submenu's entries itself; NativeMenu only turns rows into AppKit
items. Nothing is dead code off macOS, and the layout and replace tests run everywhere.
The Window menu's id is menu.window; menu ids are never dispatched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: entries use the control channel's ids, tested on every system (#12940)

Quit is app.quit, Settings and Keyboard Shortcuts are ui.prefs {open}, Agent Chat is
ui.agent_chat and Help's items are ui.help {item}; the menus are menu.file, menu.file.recent,
menu.edit, menu.window and menu.help. The id round trip and the shortcut table are tested
without a window on every system; the module builds there for its tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: its control ids do the same over the control channel (#12940)

Agent Chat sends ui.agent_chat {toggle}, which the control channel now takes (show or hide as
the panel's button does). ui.help runs every item the Help menu sends (docs, messages and
install_cli too) and answers an error for an unknown item instead of a silent ok. A test
runs the menu's control ids through the control channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Window chrome: a menu of the bar that is off stays off; replace is for submenus (#12940)

The bar's own menus take their item's enabled flag. replace() refuses a menu of the bar
(it keeps the system entries the layout gave it), now documented and tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Native menu: the actions with a fixed id are a slice, not a sized array (#12940)

The list grows with the menu, so it follows the repo's rule for growing lists (cargo xtask lists).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the bar is a component that returns the slot or menu row chosen (#12944)

nav_bar draws the bar, More, its context menu and the open menu from plain data
(SlotView, Menu, BarLabels) and returns a NavAction; it keeps which menu is open
and reads nothing of the app. navigation_bar_view now describes SolidCraft's
slots and its Visual style and View menus as that data and runs the action it
gets back with the same calls as before. The Preferences editor keeps using
paint_row, which now paints through the component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* View compass: the signed-axis compass is a component that returns the view chosen (#12944)

view_compass draws the six signed axes from the camera's view axes (plain right,
up and back vectors), the Home chip and the hover tip, and returns a ViewAction:
Snap to an axis, Home, or Orbit by this frame's drag delta. Its drag tracker
(edge warp included) is shared by the cube and the custom body, which keep
their own drawing. SolidCraft turns the camera with the same calls as before
(animate_turn with the reverse-on-repeat rule, animate_view, orbit_drag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW

* Navigation bar: a slot's second registry name is published where it is drawn (#12944)

SlotView gains an alias the bar publishes at the slot's handle, on the bar and in
More alike. SolidCraft gives the View menu's slot its older name `nav:view` there,
instead of looking the slot's handle up after the bar has drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: repaint after running the chosen action (#12944)

A menu that stays open after a pick (View) shows its rows' new state on the next
frame instead of waiting for the next input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: the component's returned actions are tested from plain data (#12944)

Driven with no app: a slot and a row of its menu are returned and a closing menu
closes; More returns a folded slot and closes, opens a folded menu in its place,
and a menu that does not close on a pick stays open; a submenu row is returned by
its own id; the bar's context menu returns Customise, Hide and Reset, and a slot's
(on the bar or in More) returns Remove with the slot's stored index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: plain [f64; 3] axes in and out, no geometry type (#12944)

ViewAxes, Tip and ViewAction::Snap carry plain arrays, with the dot product a
local function, so the component needs neither solidcore_record::Vec3 nor the
engine's geometry trait. The compass tip painter takes the axis as an array too
(it only reads which axis it is, for the colour); SolidCraft converts at its edge
(view_axes, the Snap arm), and the Preferences navigator figure is posed by the
same ViewAxes::pose and AXES list as the live compass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the tip reach is defined once, by the compass (#12944)

The Preferences navigator figure placed its tips with its own 38.0 * 0.84; it now
scales view_compass::TIP_REACH, so the figure and the live compass cannot drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: SolidCraft runs only the actions the compass can return (#12944)

show() returns a tip or Home, never Orbit (a drag comes from orbit()); the docs
say so and signed_axis_view no longer carries a dead Orbit arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: the module list in rustfmt's order (#12944)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: one function places a tip, for the compass and its figure (#12944)

tip_offset maps a posed screen direction to where its tip is drawn, applying the
reach once at f64; tips() and the Preferences navigator figure both call it, so
TIP_REACH goes. The figure's tips move by about one f32 ulp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* View compass: show() returns only a tip or Home; a drag is an OrbitStep (#12944)

ViewAction is now Snap or Home, so a drag cannot be represented as something the
compass returns; orbit() returns its motion as an OrbitStep. signed_axis_view's
match is exhaustive over what the compass can return, with no catch-all arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Navigation bar: SolidCraft's View slot is `nav:view` on the bar and in More (#12944)

A harness test: with the View slot on the bar, `nav:view` is its handle; folded
into More in a narrow view it is not shown until More opens, then `nav:view` is
the folded row's handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 21:44:57 +00:00
1609 changed files with 137363 additions and 45998 deletions
+17 -5
View File
@@ -1,15 +1,27 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
reviews:
profile: chill
poem: false
sequence_diagrams: false
related_issues: false
related_prs: false
suggested_labels: false
suggested_reviewers: false
in_progress_fortune: false
auto_review:
# Review when a PR is opened or marked ready, or on `@coderabbitai review`; not on every push.
drafts: false
auto_incremental_review: false
# Dependabot bumps are checked by CI; skip CodeRabbit reviews on them.
ignore_usernames:
- "dependabot[bot]"
path_instructions:
- path: "**"
instructions: |
Project rule: don't change anyone's existing defaults unless the change is purely additive and belongs in the application.
Flag any change to something a user already relies on: a default setting or preference value, what a command or tool does when used the usual way, the UI's default layout or behaviour, a keyboard or mouse shortcut, or the result an existing saved design rebuilds to.
Don't flag purely additive changes: a new command, a new setting whose default keeps today's behaviour, or a new option that appears only when someone chooses it. Don't flag bug fixes that only turn a crash or an error into the result the feature always promised.
Project rule: don't change anyone's existing UX defaults unless the change is purely additive and belongs in the application.
A changed default is when something people already rely on in the UI changes for everyone who didn't opt in: an existing control, interaction or setting value. Examples the owner declined or reverted: right click becoming select while left click orbits (make it opt-in instead); a PR that removed all camera animation when only orbit lag was to be fixed. Flag any change to something a user already relies on: a default setting or preference value, what a command or tool does when used the usual way, the UI's default layout or behaviour, a keyboard or mouse shortcut.
A changed kernel or geometry result (different faces or volumes, a case that now builds or is now refused), including anything under solidcore/, is not a changed default; don't flag it as one, and expect `Changes a default: no` for it.
Not defaults: bug fixes and more correct results, kernel or geometry result changes, new options that are off by default, and CI or tooling config. Don't flag purely additive changes: a new command, a new setting whose default keeps today's behaviour, or a new option that appears only when someone chooses it. Don't flag bug fixes that only turn a crash or an error into the result the feature always promised.
When you flag one, start the comment with "Changes a default:", say what a user sees before and after, whether a setting keeps the old behaviour, and whether the PR description's `Changes a default:` line says so.
# Where code goes, SolidCraft or SolidCore: solidcore/docs/layers.md.
@@ -177,5 +189,5 @@ reviews:
- name: "Existing defaults unchanged"
mode: "warning"
instructions: |
Pass if the pull request changes no default a user already relies on (default setting or preference values, what a command or tool does when used the usual way, the UI's default layout or behaviour, shortcuts, or the result an existing saved design rebuilds to), or if its changes are purely additive.
Fail otherwise, naming each changed default and what a user sees before and after. Also fail if the description says `Changes a default: no` while the code changes one.
Pass if the pull request changes no UX default a user already relies on (default setting or preference values, what a command or tool does when used the usual way, the UI's default layout or behaviour, shortcuts), or if its changes are purely additive. UX defaults only: a changed kernel or geometry result (different faces or volumes, a case that now builds or is now refused), including anything under solidcore/, is not a changed default and passes.
Fail otherwise, naming each changed default and what a user sees before and after. Also fail if the description says `Changes a default: no` while the code changes a UX default.
+28 -2
View File
@@ -48,9 +48,16 @@ filter = "binary(action_matrix) & test(=action_matrix_sample)"
threads-required = 12
priority = 100
# The longest test by far (a median of 950 s on the 32 vCPU runner, #14362) starts at once, beside
# the action matrix sample. In the list below it would wait behind the tests that sort before it,
# and the run ends when it does.
[[profile.ci.overrides]]
filter = "package(solidcraft-kernel) & test(=thread_tests::a_long_thread_cuts_without_a_deep_tree)"
priority = 100
# The slowest tests start first, so a shard does not end with one of them running alone on a
# core while the others sit idle. Medians from the CI test timings (the junit artifacts), slowest
# first; a test over ~100 s belongs here.
# first; a test over ~100 s belongs here (the medians of 24 full runs added the last ones, #14362).
[[profile.ci.overrides]]
filter = """
(package(solidcraft-engine) & (
@@ -62,6 +69,11 @@ filter = """
| test(=tests::flush_join_loft::a_flange_flush_with_the_end_of_a_bent_tapered_tube_joins)
| test(=cmd::sheet::tests::perforated_sheet_takes_a_thousand_round_holes)
| test(=boolean_regression_tests::joined_loft_frames_plan_their_display_at_every_quality)
| test(=tests::join_onto_a_finely_faceted_import)
| test(=tests::chord_and_variable_fillets)
| test(=cmd::joints::tests::motion_sweep::find_limits_sees_arms_meet_behind_pins_already_in_each_other)
| test(=thread_tests::a_multi_start_thread_takes_the_volume_of_one_start)
| test(=thread_tests::a_thread_reaches_a_flat_shoulder_at_every_angle_and_leaves_it_whole)
))
| (package(solidcraft-engine) & (binary(atomic_commands) | binary(step_export_regressions)))
| (package(solidcraft-kernel) & (
@@ -70,9 +82,23 @@ filter = """
| test(=tspline::tests::hostile_tsplines_never_panic)
| test(=fill::tests::regular_rims_of_three_to_eight_sides_fill_with_one_face_and_close_the_cup)
| test(/^issue_788_tests::equal_bores_crossing_/)
| test(=blend_variable::tests::variable_radius_on_a_box_edge)
| test(=tests::measure_of_a_body_with_many_faces)
| test(=modify_history_tests::fillet_variable_and_chord)
| test(=sweep_rails_tests::a_parallel_rail_along_a_curved_path_keeps_the_section)
| test(=corner_g2::tests::a_box_with_every_edge_rounded_has_eight_g2_corners)
| test(=corner_g2::tests::a_long_setback_keeps_the_g2_patch_inside_the_box)
| test(=blend_corner::tests::issue1557_connected_rounds_keep_their_radii)
))
| (package(solidcraft-cli) & binary(naming_corpus) & test(=corpus_designs_name_alike_every_build_and_fail_where_a_reference_is_gone))
| (package(solidcraft-ui-egui) & (
test(=render_workspace::tests::progressive_image_controls_route_to_the_registered_host)
| test(=preselect_tests::variable_fillet_round_trip)
| test(=dialogs::dense::tests::a_saved_smooth_patch_reopens_with_its_setback)
| test(=dialogs::dense::tests::no_reachable_fillet_option_state_is_refused_for_its_options)
))
"""
priority = 100
priority = 99
# Each nextest test has its own process/device. Concurrent cold lavapipe path
# compiles contend for CPU and can exhaust the 600 s pipeline setup budget.
+2 -1
View File
@@ -4,4 +4,5 @@
# The vendored SolidCore kernel, owned as in SolidCore's own CODEOWNERS
# (solidcore/.github/CODEOWNERS, which GitHub reads only at a repository's root).
/solidcore/ @jyviko @bherbruck
# The team (jyviko and bherbruck) assigns one of them per pull request, so either one approves.
/solidcore/ @tangency-labs/solidcore-owners
+1 -1
View File
@@ -6,7 +6,7 @@ Start with what this is, in plain words someone new to the project would underst
Changes a default: no
<!-- Required line, replace "no" if this changes what a user gets out of the box (a default value, key binding, setting, tool behaviour):
<!-- Required line, replace "no" if this changes what a user gets out of the box (a default value, key binding, setting, tool behaviour; a UX default only, a kernel result change is 'no'):
Changes a default: yes — <what>, old behaviour kept behind preference <name>
Docs-only and bot PRs are exempt. -->
+65 -5
View File
@@ -402,7 +402,7 @@ jobs:
name: Format and repository checks
if: (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci')
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
@@ -430,8 +430,11 @@ jobs:
- name: Sorted lists
run: cargo xtask lists
# New UI text must reach every complete catalog; report it here, before review (#8542).
# Catalog coverage only: the command labels need the app's command table, which this job
# never builds (a release build of the CLI took 9-15 minutes). The `tests` job checks them
# against the CLI it builds anyway.
- name: Translations in the complete catalogs
run: cargo xtask i18n --check
run: cargo xtask i18n --check --no-registry
- name: Model decisions use exact geometry, not meshes
run: |
cargo xtask mesh-decisions
@@ -448,8 +451,8 @@ jobs:
run: python3 -I scripts/ci/test_affected.py
- name: Merge-of-main detection self-test
run: python3 -I scripts/ci/test_main_merge.py
- name: Claim, triage and kernel-paused bot tests
run: node --test scripts/claim/*.test.js scripts/triage/*.test.js scripts/kernel-paused/*.test.js scripts/pr-checks/*.test.js
- name: Claim, triage and PR-check bot tests
run: node --test scripts/claim/*.test.js scripts/triage/*.test.js scripts/pr-checks/*.test.js
# Fast feedback for a pull request that has not been approved for the full CI yet.
quick-compile:
@@ -577,6 +580,9 @@ jobs:
# would skip for want of an adapter fails instead.
SOLIDCRAFT_GPU: software
SOLIDCRAFT_REQUIRE_GPU: "1"
# The same for the shared viewport crate's own GPU tests, which read no SolidCraft variable.
VIEWPORT_TEST_GPU: software
VIEWPORT_TEST_REQUIRE_GPU: "1"
ACTION_MATRIX_JOBS: "12"
CARGO_INCREMENTAL: "0"
RUSTC_WRAPPER: sccache
@@ -654,6 +660,20 @@ jobs:
--ignore-default-filter -E "($base) & not (package(=solidcraft-gpu) & test(/^path::/))" || status=$?
if [ "$status" -ne 0 ] && [ "$status" -ne 100 ]; then exit "$status"; fi
cargo xtask known-failing --junit "$junit"
# Every command label needs an English catalog entry (#8542). The label table is the CLI's
# command registry, so this builds the CLI in the profile the tests just used (the shared
# crates are cached) and reads it. Skipped when no selected package can change the table.
- name: Command labels in the translation catalogs
env:
FULL: ${{ needs.changes.outputs.full }}
PACKAGES: ${{ needs.changes.outputs.packages }}
run: |
case "$FULL $PACKAGES " in
true*|*" solidcraft-cli "*|*" solidcraft-ui-egui "*|*" xtask "*) ;;
*) echo "No package that feeds the command table changed."; exit 0 ;;
esac
cargo build -p solidcraft-cli --profile ci --locked
cargo xtask i18n --check --cli "${CARGO_TARGET_DIR:-target}/ci/solidcraft-cli"
- name: Compiler cache statistics
if: always()
run: if command -v sccache >/dev/null; then sccache --show-stats; else echo "sccache was not set up; built without it"; fi
@@ -686,6 +706,9 @@ jobs:
# As in Native Linux tests: lavapipe draws, and a test without an adapter fails.
SOLIDCRAFT_GPU: software
SOLIDCRAFT_REQUIRE_GPU: "1"
# The same for the shared viewport crate's own GPU tests, which read no SolidCraft variable.
VIEWPORT_TEST_GPU: software
VIEWPORT_TEST_REQUIRE_GPU: "1"
# lavapipe's own shader cache (below).
MESA_SHADER_CACHE_DIR: /home/runner/.cache/mesa-gpu
CARGO_INCREMENTAL: "0"
@@ -992,6 +1015,9 @@ jobs:
# WARP is a software (Cpu) adapter.
SOLIDCRAFT_GPU: software
SOLIDCRAFT_REQUIRE_GPU: "1"
# The same for the shared viewport crate's own GPU tests, which read no SolidCraft variable.
VIEWPORT_TEST_GPU: software
VIEWPORT_TEST_REQUIRE_GPU: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
@@ -1040,7 +1066,7 @@ jobs:
# commit's real run instead: the merge queue reads `ci` from the newest run of a commit, so a
# run without a passing `ci` would block a pull request that passed (#1850, #3182, #3252).
name: ci
needs: [changes, checks, quick-compile, clippy, build-tests, tests, gpu-path-tests, doc-tests, oracle, windows-fxc, windows-startup, dependency-advisories, wasm, solidcore]
needs: [changes, checks, quick-compile, clippy, build-tests, tests, gpu-path-tests, doc-tests, oracle, windows-fxc, windows-startup, dependency-advisories, wasm, solidcore, solidcore-standalone]
if: always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft)
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
@@ -1100,6 +1126,7 @@ jobs:
WASM: ${{ needs.wasm.result }}
SOLIDCORE_RUN: ${{ needs.changes.outputs.solidcore }}
SOLIDCORE: ${{ needs.solidcore.result }}
SOLIDCORE_STANDALONE: ${{ needs.solidcore-standalone.result }}
run: |
test "$CHANGES" = success
test "$CHECKS" = success
@@ -1140,6 +1167,7 @@ jobs:
# solidcore/** changed on an approved pull request, in a merge group or on main.
if [ "$SOLIDCORE_RUN" = true ]; then
test "$SOLIDCORE" = success
test "$SOLIDCORE_STANDALONE" = success
fi
- name: Note a carried result
if: needs.changes.outputs.carried != ''
@@ -1175,6 +1203,35 @@ jobs:
# solidcore/** (Select checks' `solidcore`), where `ci` requires it; skipped otherwise. Its build,
# kernel-rig and goldens jobs are one entry here, so the rig and goldens binaries are built once
# without an artifact between jobs.
# solidcore/ exported alone (as the mirror to tangency-labs/solidcore carries it) must still check: the
# `layers` rule reads the manifests, this builds them without the rest of the repository around.
solidcore-standalone:
name: SolidCore builds on its own
needs: changes
if: needs.changes.outputs.solidcore == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 30
env:
CARGO_INCREMENTAL: 0
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Export solidcore/ alone
run: |
mkdir -p "$RUNNER_TEMP/sc"
git archive HEAD solidcore | tar -x -C "$RUNNER_TEMP/sc"
- name: Toolchain (solidcore/rust-toolchain.toml)
working-directory: ${{ runner.temp }}/sc/solidcore
run: rustup show active-toolchain || rustup toolchain install
# Main saves the cache; every other run restores it, read-only.
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: "${{ runner.temp }}/sc/solidcore -> bin"
key: solidcore-standalone
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
- name: Check the exported tree
working-directory: ${{ runner.temp }}/sc/solidcore
run: cargo check --workspace --all-targets --locked
solidcore:
name: SolidCore checks (${{ matrix.check }})
needs: changes
@@ -1330,6 +1387,9 @@ jobs:
if: matrix.check == 'kernel rig and goldens'
env:
KERNEL_CASES_REPORT: ${{ github.workspace }}/solidcore/reports/kernel-rig/table.md
# each case in a process of its own, one per vCPU of the 16-vCPU, 64 GB runner, with a
# CPU-time hang limit (#14277)
KERNEL_CASES_JOBS: "16"
run: |
mkdir -p reports/kernel-rig
cd crates/solidcore-ops
+5 -21
View File
@@ -1,4 +1,4 @@
# Files generated from the code are rewritten here, on main, after each merge, so pull requests
# Files generated from the code are rewritten here, on main, once a night, so pull requests
# never edit them (two pull requests that each regenerated one would always conflict):
# THIRD-PARTY-LICENSES.txt (`cargo xtask licences`) and docs/features.md (`cargo xtask features`).
# Pending files (assets/locales/pending, attribution/pending) are folded into the catalogs and
@@ -6,26 +6,10 @@
name: Generated files
on:
push:
branches: [main]
# THIRD-PARTY-LICENSES.txt follows the manifests, the lockfile, the vendored crates' licence
# files and the fonts' licences; docs/features.md follows the command registry (crates/engine),
# read through solidcraft-cli. Other pushes regenerate nothing.
paths:
- "Cargo.lock"
- "**/Cargo.toml"
- "vendor/**"
# solidcore's licence files are embedded in THIRD-PARTY-LICENSES.txt.
- "solidcore/LICENSE"
- "solidcore/NOTICE"
- "solidcore/ATTRIBUTION.md"
- "assets/fonts/**"
- "crates/engine/**"
- "apps/solidcraft-cli/**"
- "xtask/**"
- "assets/locales/pending/*.json"
- "attribution/pending/*.md"
- ".github/workflows/generated.yml"
# Nightly, outside merge hours. A push straight to main resets every merge-queue batch, so the
# regeneration no longer runs on each push.
schedule:
- cron: "17 8 * * *"
workflow_dispatch:
permissions:
-43
View File
@@ -1,43 +0,0 @@
# While a new geometry kernel is evaluated, new issues and discussions about kernel work, and new
# pull requests that change the kernel directories, are labelled `kernel-paused` for a maintainer to
# answer (CONTRIBUTING.md, "Kernel work is paused"). The logic is in scripts/kernel-paused/ (tested
# in ci.yml).
name: Kernel paused
on:
issues:
types: [opened]
# pull_request_target so PRs from forks can be labelled. It runs the base branch's
# scripts/kernel-paused and only lists the PR's changed paths; the PR's code is never checked out.
pull_request_target:
types: [opened]
discussion:
types: [created]
permissions:
contents: read # to check out scripts/kernel-paused
issues: write
pull-requests: write # to list a PR's changed paths and add its label
discussions: write
concurrency:
group: kernel-paused-${{ github.event.issue.number || github.event.pull_request.number || github.event.discussion.number }}
cancel-in-progress: false
jobs:
paused:
name: Kernel paused bot
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: scripts/kernel-paused
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const paused = require('./scripts/kernel-paused/kernel-paused.js');
const done = context.eventName === 'discussion'
? await paused.handleDiscussion({ github, context })
: await paused.handleOpened({ github, context });
core.info(done);
+8 -8
View File
@@ -17,8 +17,8 @@
# The trigger paths are whole folders that hold every area's PATHS (a self-test,
# scripts/ci/test_kernel_rig_plan.py, checks they do), so a pull request that touches none of
# them (the UI, docs, packaging) starts no runner at all, and a new area needs no edit here: the
# plan finds the areas from the folders. Pushes to main and develop on the same paths, merge groups
# (once the queue is on), the release gate (release-gate.yml, workflow_call) and a run by hand run
# plan finds the areas from the folders. Merge groups run the areas their batch's diff reaches, like a
# pull request. Pushes to main and develop on the same paths, the release gate (release-gate.yml, workflow_call) and a run by hand run
# every area; those pushes also commit the compiler cache (a Blacksmith sticky disk) that every
# other run mounts read-only.
name: Kernel rig
@@ -96,19 +96,19 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# A pull request's diff needs its base's history.
fetch-depth: ${{ github.event_name == 'pull_request' && '0' || '1' }}
fetch-depth: ${{ (github.event_name == 'pull_request' || github.event_name == 'merge_group') && '0' || '1' }}
- name: Selection self-test
run: python3 -I scripts/ci/test_kernel_rig_plan.py
- id: plan
name: Map the changed files to areas
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
run: |
# Pull requests run the areas their diff reaches; pushes, merge groups, the release
# gate and a run by hand run every area.
if [ "$EVENT_NAME" = pull_request ]; then
# Pull requests and merge groups run the areas their diff reaches (a batch that touches no
# kernel path runs none); pushes to main, the release gate and a run by hand run every area.
if [ "$EVENT_NAME" = pull_request ] || [ "$EVENT_NAME" = merge_group ]; then
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/rig-paths"
python3 -I scripts/ci/kernel_rig_plan.py --paths "$RUNNER_TEMP/rig-paths" \
--github-output "$GITHUB_OUTPUT"
+5 -4
View File
@@ -3,7 +3,7 @@
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
## Start here
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Local `plan/` notes are optional.
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`.
## Hard rules (what enforces each)
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
@@ -12,15 +12,15 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
- Never crash: no unwrap/expect/panic/unsafe in non-test code, errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
- Layering; only `crates/kernel` uses truck; solidcore only via `solidcore-ops`/`-tess`/`-record` (`xtask/solidcore-seam.txt`): `cargo xtask layers`; see `solidcore/docs/layers.md`.
- Layering; only `crates/kernel` uses truck; solidcore only via `solidcore-ops`/`-tess`/`-record` (`xtask/solidcore-seam.txt`): `cargo xtask layers`; see `docs/layers.md`.
- One outer UI container, one camera path (`navigation::handle`), colours from theme tokens: `cargo xtask ui-containers`.
- Application code is Rust only and builds for wasm: `cargo xtask wasm`, `cargo xtask web-offline-test`.
- The product name is SolidCraft; the old name lives only in `docs/compatibility.md`.
- The product name is SolidCraft; the old name lives in `docs/compatibility.md`.
- Sorted lists (message catalogs, `mod` lines, languages) stay sorted: `cargo xtask lists`. Never edit generated files (`docs/features.md`, `THIRD-PARTY-LICENSES.txt`) in a PR.
- A design-format change is ONE new file in `crates/doc/src/format/pending/`: `scripts/test-format-pending.sh`.
- A command has input and output schemas (will become required; `schema/unconverted.txt` only shrinks): `skills/solidcraft-commands/SKILL.md`.
- New UI strings go in `assets/locales/pending/<slug>.json`, ATTRIBUTION rows in `attribution/pending/<slug>.md`: `cargo xtask lists`.
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line: `pr-checks.yml`.
- A changed default (existing UI, not kernel results) keeps the old behaviour behind a preference; fill the PR's `Changes a default:` line.
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR: `skills/solidcraft-contributing/SKILL.md`.
- A PR's title and first sentence make sense to someone who never saw your chat or the issue: `skills/solidcraft-contributing/SKILL.md`.
@@ -37,6 +37,7 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
| A command, a crate boundary | `skills/solidcraft-commands/SKILL.md` |
| SolidCraft or solidcore, kernel, geometry | `skills/solidcraft-layers/SKILL.md` |
| A dialog, panel, camera, icon, colour | `skills/solidcraft-ui/SKILL.md` |
| Sheet metal | `skills/solidcraft-sheetmetal/SKILL.md` |
| Labels, messages, translations | `skills/solidcraft-ui-text/SKILL.md` |
| Saving something new in a design, import, export | `skills/solidcraft-file-formats/SKILL.md` |
| Preferences, a changed default | `skills/solidcraft-preferences/SKILL.md` |
+2 -3
View File
@@ -200,7 +200,7 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/platforms/macos/README.txt | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/windows/solidcraft.en-us.wxl | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/windows/solidcraft.wxs | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/shaders/viewport.wgsl | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| crates/viewport-gpu/shaders/viewport.wgsl | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-dark.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-light.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/solidcraft-95.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
@@ -706,8 +706,6 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
| docs/design/preferences/figures/mouse-mapping.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-axes.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-axes.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-cube.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-cube.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-centre.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-centre.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-cursor.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
@@ -843,6 +841,7 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
| assets/locales/pending/drag-to-zoom.json | SolidCraft contributors | Original drag-to-zoom labels and translations | MIT OR Apache-2.0 |
| `assets/locales/pending/hole-group-folder.json` | SolidCraft contributors | Original Hole grouping preference labels and translations | MIT OR Apache-2.0 |
| assets/locales/pending/press-pull-dialog-title.json | SolidCraft contributors | Original Press Pull dialog title translations for SolidCraft's English catalog; no external text used | MIT OR Apache-2.0 |
| docs/layers/layers.svg | SolidCraft contributors | original diagram of SolidCraft's crate layers for docs/layers.md, written as SVG in solidcore's docs/layers diagram style | MIT OR Apache-2.0 |
## Code adapted from sibling projects
+27 -9
View File
@@ -17,15 +17,29 @@ Before choosing a task, check the [command inventory](docs/features.md) and
Questions, ideas or want to say what you're working on? Join the [Discord](https://discord.gg/FtU4NQNVDH).
## Kernel work is paused
## Kernel work goes in solidcore/
We're moving to a new geometry kernel. Until that's done, please don't open pull requests that
change `crates/kernel/`, `crates/geom/` or the vendored kernel in `solidcore/`, or issues and discussions about kernel
work: booleans, fillets, chamfers, shell, offsets, sweeps and lofts, threads and holes, STEP/IGES
import and export, or tessellation. The pinned issue on the tracker explains it. New ones get the
`kernel-paused` label (`.github/workflows/kernel-paused.yml`) and a maintainer answers them. Crashes
and data loss are still taken as issues. Pull requests outside those directories carry on as normal,
including the dialogs and tools for these features.
Kernel and geometry work is welcome: booleans, fillets, chamfers, shell, offsets, sweeps and lofts,
threads and holes, STEP/IGES import and export, tessellation. It goes in `solidcore/`, the vendored
solidcore kernel, following [`solidcore/docs/layers.md`](solidcore/docs/layers.md) and
`skills/solidcraft-layers/SKILL.md`. SolidCraft's own `crates/kernel`, `crates/smlib` and
`crates/geom` only shrink toward the seam (ops, tess, record); please don't grow them.
A kernel pull request targets `main` like any other; a change that touches the kernel and the app is one
pull request. A big feature in several steps goes as stacked pull requests into `main`, each one landing in order
on top of the one before.
Crashes and data loss are still taken first.
### Updating a PR that was moved from solidcore
solidcore's open PRs were re-opened here on `moved/solidcore-N` branches in tangency-labs, so
contributors without write access can't push to them. To update one:
1. `git fetch https://github.com/tangency-labs/solidcraft moved/solidcore-N:moved/solidcore-N`, then
`git switch moved/solidcore-N`.
2. Commit your changes on it and push it to your fork: `git push <your-fork> moved/solidcore-N`.
3. Open a PR against `main` that says "Replaces #M" (M is the moved PR's number). The moved one
gets closed.
## Get it running (about 10 minutes on a fast machine)
@@ -429,9 +443,13 @@ owning one area, with a human setting direction and reporting bugs. Their workin
[AGENTS.md](AGENTS.md). Human contributors are just as welcome. Use whatever tools you like, as
long as the change meets the rules above.
If you run an agent, keep one long-running session rather than restarting it, don't `--resume` one
whose prompt cache has gone cold (it re-reads the whole history at full price), and act on CodeRabbit
comments only when they're about crashes, wrong results, data loss or security.
Two quick checks run on every pull request (`.github/workflows/pr-checks.yml`, not required): it
fails when a lower-numbered open PR by someone else already closes the same issue (the label
`not-a-duplicate` skips it), or when the description lacks the `Changes a default: no` / `Changes a default: yes — <what>, old behaviour kept behind preference <name>` line (docs-only and bot PRs are exempt).
`not-a-duplicate` skips it), or when the description lacks the `Changes a default: no` / `Changes a default: yes — <what>, old behaviour kept behind preference <name>` line (docs-only and bot PRs are exempt). A default here is a UX default only: a control, setting, preference or tool behaviour a user sees by default; a changed kernel or geometry result is `Changes a default: no`.
## Labels and triage
Generated
+329 -12
View File
@@ -125,10 +125,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cipher 0.4.4",
"cpufeatures 0.2.17",
]
[[package]]
name = "aes"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fc36d7440d015b45468ba08a21e80e056ebbd014c611c6298bb7503e04bd1"
dependencies = [
"cipher 0.5.2",
"cpubits",
"cpufeatures 0.3.1",
]
[[package]]
name = "ahash"
version = "0.8.12"
@@ -143,6 +154,21 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -485,6 +511,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
dependencies = [
"hybrid-array",
]
[[package]]
name = "block2"
version = "0.5.1"
@@ -516,6 +551,16 @@ dependencies = [
"piper",
]
[[package]]
name = "brotli-decompressor"
version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -703,7 +748,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
"cipher 0.4.4",
]
[[package]]
name = "cbc"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
dependencies = [
"cipher 0.5.2",
]
[[package]]
@@ -768,7 +822,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common 0.1.7",
"inout",
"inout 0.1.4",
]
[[package]]
name = "cipher"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
dependencies = [
"crypto-common 0.2.2",
"inout 0.2.6",
]
[[package]]
@@ -887,6 +951,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
[[package]]
name = "cpubits"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -996,9 +1066,9 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
dependencies = [
"aes",
"block-padding",
"cbc",
"aes 0.8.4",
"block-padding 0.3.3",
"cbc 0.1.2",
"dbus",
"fastrand",
"hkdf",
@@ -1095,6 +1165,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "ecb"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26f2a8b3e564eba0877223dc343703ad0385794e882e6d13f3a4dd5c6b1f41ac"
dependencies = [
"cipher 0.5.2",
]
[[package]]
name = "ecolor"
version = "0.37.0"
@@ -1625,7 +1704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
"weezl",
"weezl 0.1.12",
]
[[package]]
@@ -2068,10 +2147,20 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"block-padding",
"block-padding 0.3.3",
"generic-array",
]
[[package]]
name = "inout"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a580c13f0ffc335ef42dd3f6b1a41b726051c5e1656d9a3aa6fddb3c5dfbbf9"
dependencies = [
"block-padding 0.4.2",
"hybrid-array",
]
[[package]]
name = "itertools"
version = "0.15.0"
@@ -2316,6 +2405,43 @@ version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "lopdf"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfffda0fe1ab0157e1a13c14bebd3f28671f2fccb7922f0722ec53926e6922d3"
dependencies = [
"aes 0.9.4",
"bitflags 2.13.2",
"brotli-decompressor",
"cbc 0.2.1",
"ecb",
"encoding_rs",
"flate2",
"getrandom 0.4.3",
"indexmap",
"itoa",
"log",
"md-5",
"nom",
"rand 0.10.3",
"rangemap",
"sha2 0.11.1",
"stringprep",
"thiserror 2.0.21",
"weezl 0.2.1",
]
[[package]]
name = "md-5"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
dependencies = [
"cfg-if",
"digest 0.11.3",
]
[[package]]
name = "memchr"
version = "2.8.3"
@@ -2508,6 +2634,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ntapi"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.4.3"
@@ -2773,6 +2908,16 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
@@ -3311,6 +3456,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
dependencies = [
"chacha20",
"getrandom 0.4.3",
"rand_core 0.10.1",
]
@@ -3355,6 +3501,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
[[package]]
name = "rangemap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d"
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@@ -3730,8 +3882,8 @@ version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
dependencies = [
"aes",
"cbc",
"aes 0.8.4",
"cbc 0.1.2",
"futures-util",
"generic-array",
"hkdf",
@@ -4056,6 +4208,23 @@ dependencies = [
"thiserror 2.0.21",
]
[[package]]
name = "solidcore-convert"
version = "0.1.0"
dependencies = [
"solidcore",
"solidcore-record",
]
[[package]]
name = "solidcore-ops"
version = "0.1.0"
dependencies = [
"solidcore",
"solidcore-convert",
"solidcore-record",
]
[[package]]
name = "solidcore-record"
version = "0.1.0"
@@ -4065,6 +4234,19 @@ dependencies = [
"thiserror 2.0.21",
]
[[package]]
name = "solidcore-tess"
version = "0.1.0"
dependencies = [
"rayon",
"serde",
"solidcore",
"solidcore-convert",
"solidcore-record",
"spade",
"thiserror 2.0.21",
]
[[package]]
name = "solidcraft"
version = "0.40.0-rc.3"
@@ -4097,6 +4279,8 @@ dependencies = [
"solidcraft-theme",
"solidcraft-ui-egui",
"solidcraft-ui-text",
"solidcraft-viewport",
"solidcraft-viewport-gpu",
"ureq",
"windows-sys 0.61.2",
"winit",
@@ -4121,6 +4305,8 @@ dependencies = [
"solidcraft-gpu",
"solidcraft-kernel",
"solidcraft-mcp",
"solidcraft-viewport",
"solidcraft-viewport-gpu",
]
[[package]]
@@ -4164,6 +4350,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"solidcore-record",
"solidcore-tess",
"solidcraft-color",
"solidcraft-derive",
"solidcraft-geom",
@@ -4171,6 +4358,7 @@ dependencies = [
"solidcraft-profile",
"solidcraft-render",
"solidcraft-sketch",
"solidcraft-viewport",
"thiserror 2.0.21",
"web-time",
]
@@ -4206,6 +4394,7 @@ dependencies = [
"solidcraft-profile",
"solidcraft-render",
"solidcraft-sketch",
"solidcraft-viewport",
"thiserror 2.0.21",
"toml_edit",
"web-time",
@@ -4217,7 +4406,10 @@ version = "0.40.0-rc.3"
dependencies = [
"serde",
"serde_json",
"solidcore",
"solidcore-ops",
"solidcore-record",
"solidcore-tess",
"thiserror 2.0.21",
]
@@ -4233,12 +4425,15 @@ dependencies = [
"serde",
"serde_json",
"solidcore-record",
"solidcore-tess",
"solidcraft-color",
"solidcraft-engine",
"solidcraft-geom",
"solidcraft-io",
"solidcraft-kernel",
"solidcraft-render",
"solidcraft-viewport",
"solidcraft-viewport-gpu",
"thiserror 2.0.21",
"wgpu",
]
@@ -4280,7 +4475,9 @@ dependencies = [
"rayon",
"serde",
"serde_json",
"solidcore-ops",
"solidcore-record",
"solidcore-tess",
"solidcraft-geom",
"solidcraft-profile",
"solidcraft-smlib",
@@ -4300,6 +4497,8 @@ dependencies = [
"solidcraft-engine",
"solidcraft-gpu",
"solidcraft-mcp-server",
"solidcraft-viewport",
"solidcraft-viewport-gpu",
]
[[package]]
@@ -4325,15 +4524,19 @@ dependencies = [
"image",
"moxcms",
"png",
"rayon",
"robust",
"serde",
"serde_json",
"sha2 0.10.9",
"solidcore-record",
"solidcore-tess",
"solidcraft-color",
"solidcraft-geom",
"solidcraft-viewport",
"sysinfo",
"web-time",
"weezl",
"weezl 0.1.12",
]
[[package]]
@@ -4358,6 +4561,7 @@ dependencies = [
"log",
"serde_json",
"solidcore",
"solidcore-ops",
"solidcore-record",
"solidcraft-geom",
"solidcraft-profile",
@@ -4374,6 +4578,7 @@ dependencies = [
name = "solidcraft-step"
version = "0.40.0-rc.3"
dependencies = [
"solidcore-ops",
"solidcore-record",
"solidcraft-geom",
"solidcraft-profile",
@@ -4402,6 +4607,7 @@ dependencies = [
"egui-wgpu",
"image",
"log",
"lopdf",
"minisign-verify",
"muda",
"nix 0.31.3",
@@ -4412,6 +4618,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"solidcore-record",
"solidcore-tess",
"solidcraft-color",
"solidcraft-control",
"solidcraft-control-egui",
@@ -4421,6 +4628,9 @@ dependencies = [
"solidcraft-theme",
"solidcraft-ui-registry",
"solidcraft-ui-text",
"solidcraft-viewport",
"solidcraft-viewport-egui",
"solidcraft-viewport-gpu",
"syn 2.0.119",
"tiny-skia",
"toml",
@@ -4447,6 +4657,45 @@ dependencies = [
"unicode-bidi",
]
[[package]]
name = "solidcraft-viewport"
version = "0.40.0-rc.3"
dependencies = [
"image",
"serde",
"serde_json",
"solidcore-record",
"solidcore-tess",
"solidcraft-color",
]
[[package]]
name = "solidcraft-viewport-egui"
version = "0.40.0-rc.3"
dependencies = [
"egui",
"egui-wgpu",
"log",
"solidcraft-viewport-gpu",
"wgpu",
]
[[package]]
name = "solidcraft-viewport-gpu"
version = "0.40.0-rc.3"
dependencies = [
"log",
"naga",
"pollster 0.4.0",
"serde",
"solidcore-record",
"solidcore-tess",
"solidcraft-color",
"solidcraft-viewport",
"thiserror 2.0.21",
"wgpu",
]
[[package]]
name = "solidcraft-web"
version = "0.40.0-rc.3"
@@ -4506,6 +4755,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]]
name = "subtle"
version = "2.6.1"
@@ -4557,6 +4817,20 @@ dependencies = [
"web-sys",
]
[[package]]
name = "sysinfo"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e8a3edbe0394ce115f3917a00349dd242be09eb5a091ed509eaf67b55cfb8a8"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows",
]
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -4629,7 +4903,7 @@ dependencies = [
"flate2",
"half",
"quick-error",
"weezl",
"weezl 0.1.12",
"zune-jpeg",
]
@@ -4668,6 +4942,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
version = "1.53.2"
@@ -4845,6 +5125,15 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "unicode-normalization"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
version = "0.1.5"
@@ -5226,6 +5515,12 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "weezl"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2"
[[package]]
name = "wgpu"
version = "30.0.1"
@@ -5409,6 +5704,22 @@ dependencies = [
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -5418,6 +5729,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.62.2"
+12
View File
@@ -33,6 +33,9 @@ solidcraft-ui-registry = { path = "crates/ui-registry" }
solidcraft-color = { path = "crates/color" }
solidcraft-ui-text = { path = "crates/ui-text" }
solidcraft-theme = { path = "crates/theme" }
solidcraft-viewport = { path = "crates/viewport" }
solidcraft-viewport-egui = { path = "crates/viewport-egui" }
solidcraft-viewport-gpu = { path = "crates/viewport-gpu" }
solidcraft-space-mouse = { path = "crates/space-mouse" }
solidcraft-mcp = { path = "crates/mcp" }
solidcraft-mcp-server = { path = "crates/mcp-server" }
@@ -47,6 +50,13 @@ solidcore = { path = "solidcore/crates/solidcore" }
# Geometry as data (solidcore's layers §1, "Record"): the B-rep record, its structural validation
# and the native body file. Vendored with solidcore, in the same subtree.
solidcore-record = { path = "solidcore/crates/solidcore-record" }
# The modelling layer (solidcore's layers §1, "Ops"): the record-level calls SolidCraft makes into the
# kernel, converting through solidcore-convert.
solidcore-ops = { path = "solidcore/crates/solidcore-ops" }
# Geometry made drawable (solidcore's layers §3): the one mesher, SolidCraft's, moved into the
# subtree (RFC 0003, item 1.1), with the display mesh and its levels of detail, the types
# SolidCraft's meshes are. Vendored with solidcore, in the same subtree.
solidcore-tess = { path = "solidcore/crates/solidcore-tess" }
# test tooling: a dev-dependency only (xtask/src/layers.rs)
solidcraft-testkit = { path = "crates/testkit" }
@@ -69,6 +79,8 @@ semver = "1"
# constrained Delaunay + Ruppert refinement for trimmed faces (MIT OR Apache-2.0).
spade = { version = "2.15", default-features = false, features = ["std"] }
rayon = "1.12"
# Native render-memory admission; RAM-only queries through a safe MIT-licensed API.
sysinfo = { version = "0.40.1", default-features = false, features = ["system"] }
# packages (3MF)
zip = { version = "9", default-features = false, features = ["deflate"] }
+658 -113
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -28,6 +28,8 @@ raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
profile = ["solidcraft-engine/profile"]
[dependencies]
solidcraft-viewport-gpu = { workspace = true }
solidcraft-viewport = { workspace = true }
solidcore-record = { workspace = true }
solidcraft-engine = { workspace = true }
solidcraft-gpu = { workspace = true }
@@ -85,9 +85,9 @@ fn run() -> Result<(), String> {
// Vulkan device, parse GPU policy or initialize a GPU host.
let (adapter_report, gpu_host_seconds) = if needs_gpu(&session, &params)? {
let gpu_host_start = Instant::now();
let policy = solidcraft_gpu::select::Policy::from_env().map_err(|e| e.to_string())?;
let policy = solidcraft_gpu::env::policy().map_err(|e| e.to_string())?;
let (adapter, report) =
solidcraft_gpu::select::pick_path(policy, |_| true).ok_or("no Vulkan or Metal adapter satisfies the requested GPU policy")?;
solidcraft_viewport_gpu::select::pick_path(policy, |_| true).ok_or("no Vulkan or Metal adapter satisfies the requested GPU policy")?;
session.register_render_job_host(solidcraft_gpu::path::host_on_adapter(&adapter).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
(Some(report), Some(gpu_host_start.elapsed().as_secs_f64()))
} else {
+9 -8
View File
@@ -76,7 +76,7 @@ const USAGE_BENCH_ASSISTANT: &str = " solidcraft-cli bench-assistant --connect
/// `snapshot`'s lines in the usage.
const USAGE_SNAPSHOT: &str = " solidcraft-cli snapshot <script|design> [--in design.sc|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27] [--quality coarse|normal|fine|ultra]\n solidcraft-cli snapshot --in <design.sc|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON;\n --quality: the display quality the bodies are meshed at, as the app's setting (default normal))\n";
/// `render`'s lines in the usage.
const USAGE_RENDER: &str = " solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]\n [--progressive] [--samples N] [--max-depth N] [--seed N] [--exposure EV] [--lighting FILE.json] [--environment FILE.json] [--tone-map reinhard|aces_fitted]\n";
const USAGE_RENDER: &str = " solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]\n [--progressive] [--samples N] [--max-depth N] [--seed N] [--exposure EV] [--lighting FILE.json] [--environment FILE.json] [--tone-map reinhard|aces_fitted] [--legacy-memory-admission]\n --legacy-memory-admission skips available-RAM checks; fixed shared/host budgets and allocation limits still apply.\n";
/// `exec`'s lines in the usage.
const USAGE_EXEC: &str = " solidcraft-cli exec <command> [json-params]\n";
/// `commands`'s lines in the usage.
@@ -653,9 +653,9 @@ fn cmd_script(args: &[String]) -> Result<(), String> {
/// `gpu`: the headless adapter survey and the policy's pick.
fn cmd_gpu(args: &[String]) -> Result<(), String> {
use solidcraft_gpu::select::{Policy, survey};
use solidcraft_viewport_gpu::select::{Policy, survey};
let policy = match args {
[] => Policy::from_env()?,
[] => solidcraft_gpu::env::policy()?,
[flag, v] if flag == "--policy" => Policy::parse(v).ok_or_else(|| format!("--policy must be auto, hardware, software or cpu, not `{v}`"))?,
_ => return Err("usage: solidcraft-cli gpu [--policy auto|hardware|software|cpu]".into()),
};
@@ -675,7 +675,7 @@ fn cmd_gpu(args: &[String]) -> Result<(), String> {
}))
);
match s.lands_on {
Some(solidcraft_gpu::select::Landing::Cpu) => {
Some(solidcraft_viewport_gpu::select::Landing::Cpu) => {
solidcraft_engine::note!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
Ok(())
}
@@ -691,7 +691,7 @@ fn cmd_gpu(args: &[String]) -> Result<(), String> {
/// error that names the policy.
fn install_renderer() {
solidcraft_engine::capture::install(std::sync::Arc::new(|scene, cam, w, h| {
let (rgba, renderer) = solidcraft_gpu::capture::headless_rgba(scene, cam, w, h)?;
let (rgba, renderer) = solidcraft_gpu::env::headless_rgba(scene, cam, w, h)?;
Ok(solidcraft_engine::capture::Picture { rgba, renderer })
}));
}
@@ -778,7 +778,7 @@ fn cmd_icon(args: &[String]) -> Result<(), String> {
}
fn icon_png(size: usize) -> Result<Vec<u8>, String> {
solidcraft_engine::render::png(size, size, app_icon::rgba(size)?).ok_or_else(|| "icon: PNG encoding failed".to_string())
solidcraft_viewport::png(size, size, app_icon::rgba(size)?).ok_or_else(|| "icon: PNG encoding failed".to_string())
}
/// A Windows icon file holding PNG images (Vista and later read these).
@@ -908,9 +908,10 @@ fn serve_mcp(
scope: Option<solidcraft_engine::io::vfs::PathScope>,
options: solidcraft_mcp::Options,
) -> Result<(), String> {
let stdin = std::io::stdin();
// Not the locked handles: the server reads stdin on a thread of its own (#14007).
let stdin = std::io::BufReader::new(std::io::stdin());
let stdout = std::io::stdout();
solidcraft_mcp::Server::new(backend).with_path_scope(scope).with_options(options).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
solidcraft_mcp::Server::new(backend).with_path_scope(scope).with_options(options).serve(stdin, stdout).map_err(|e| e.to_string())
}
/// The port of the running desktop app: the one its discovery file names, if something listens
+14 -4
View File
@@ -3,7 +3,7 @@ use serde_json::{Value, json};
use solidcraft_engine::{
Session,
doc::render_settings::{RenderJob, RenderSetup},
render::job::{CpuHost, JobId, Phase, ToneMap},
render::job::{CpuHost, JobId, NativeMemoryPolicy, Phase, ToneMap},
};
/// Only explicit runtime CLI flags are stored here. A typed setup owns these
@@ -74,6 +74,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
let mut height = None;
let mut exposure = None;
let mut runtime = RuntimeOptions::default();
let mut memory_policy = NativeMemoryPolicy::AvailableRam;
let mut overwrite = false;
let mut timeout = 120u64;
let mut i = 1;
@@ -82,6 +83,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
"--progressive" => runtime.progressive = true,
"--transparent" => runtime.transparent = true,
"--overwrite" => overwrite = true,
"--legacy-memory-admission" => memory_policy = NativeMemoryPolicy::SoftwarePoolOnly,
"--out" | "--setup" | "--width" | "--height" | "--timeout" | "--samples" | "--max-depth" | "--seed" | "--exposure" | "--lighting"
| "--tone-map" | "--environment" => {
i += 1;
@@ -134,9 +136,15 @@ pub fn run(args: &[String]) -> Result<(), String> {
// Resolve saved intent before opening a GPU or explicitly settling geometry.
// Width/height/exposure only change this validated inline settings clone.
let (mut params, needs_gpu) = runtime.request(&session, settings)?;
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
// The machine admission policy may accompany any saved setup; it does not
// replace the setup's image-quality options or alter the saved document.
let cpu_host = CpuHost::for_platform().map_err(|e| e.to_string())?;
cpu_host.set_native_memory_policy(memory_policy);
session.set_render_job_host(Some(cpu_host)).map_err(|e| e.to_string())?;
if needs_gpu {
session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
let gpu_host = solidcraft_gpu::path::host().map_err(|e| e.to_string())?;
gpu_host.set_native_memory_policy(memory_policy);
session.register_render_job_host(gpu_host).map_err(|e| e.to_string())?;
}
session.execute("render.scene", &json!({})).map_err(|e| e.to_string())?;
if setup.is_none() {
@@ -144,7 +152,9 @@ pub fn run(args: &[String]) -> Result<(), String> {
camera
} else {
let mut camera = RenderSetup::default().camera;
camera.fit(&solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?.bounds);
camera.fit(&solidcraft_engine::render::view_bounds(
&solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?.bounds,
));
camera
};
params["settings"]["camera"] = json!(camera);
@@ -1,6 +1,7 @@
//! The bracket example must cut two through holes, rather than successfully cutting empty
//! space beside the body. Check the shipped script and rebuild its saved design from scratch.
use solidcraft_engine::geom::MeshGeom;
use std::f64::consts::PI;
use serde_json::Value;
+11 -1
View File
@@ -152,7 +152,17 @@ fn render_help_keeps_the_registered_gpu_request_controls() {
assert!(output.status.success(), "{args:?}: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.stderr.is_empty());
let stdout = String::from_utf8_lossy(&output.stdout);
for flag in ["--progressive", "--samples", "--max-depth", "--seed", "--exposure", "--lighting", "--environment", "--tone-map"] {
for flag in [
"--progressive",
"--samples",
"--max-depth",
"--seed",
"--exposure",
"--lighting",
"--environment",
"--tone-map",
"--legacy-memory-admission",
] {
assert!(stdout.contains(flag), "{args:?} lost {flag}: {stdout}");
}
}
+12 -4
View File
@@ -507,7 +507,7 @@ fn snapshot_runs_its_script_on_the_in_design() {
std::fs::read(&png).unwrap()
};
// Pictures are drawn on the GPU: without an adapter only the option checks below run.
match solidcraft_gpu::capture::shared() {
match solidcraft_viewport_gpu::capture::shared(solidcraft_gpu::env::policy().unwrap_or_else(|e| panic!("{e}"))) {
Ok(_) => {
let alone = shot("alone.png", &[shifted.as_os_str()]);
let on_base = shot("on_base.png", &[shifted.as_os_str(), "--in".as_ref(), base.as_os_str()]);
@@ -515,7 +515,7 @@ fn snapshot_runs_its_script_on_the_in_design() {
assert_ne!(alone, on_base, "the script ran on the --in design");
assert_ne!(base_only, on_base);
}
Err(e @ solidcraft_gpu::GpuError::NoAdapter(_)) if std::env::var_os("SOLIDCRAFT_REQUIRE_GPU").is_none() => {
Err(e @ solidcraft_viewport_gpu::GpuError::NoAdapter(_)) if std::env::var_os("SOLIDCRAFT_REQUIRE_GPU").is_none() => {
eprintln!("pictures left out: {e}")
}
Err(e) => panic!("{e}"),
@@ -673,6 +673,7 @@ fn render_uses_typed_cpu_setups_from_scripts_and_saved_designs() {
.unwrap(),
);
assert_eq!(result["preflight"]["resolved"]["executor"], "cpu-raytrace");
assert_eq!(result["preflight"]["admission"]["host_memory"]["policy"], "available_ram");
assert_eq!(result["export"]["frame"]["executor"], "cpu-raytrace");
assert_eq!(result["preflight"]["request"]["integrator"], "raytrace");
assert_eq!(result["preflight"]["request"]["raytrace"].get("progressive"), options.get("progressive"));
@@ -689,11 +690,12 @@ fn render_uses_typed_cpu_setups_from_scripts_and_saved_designs() {
.arg(&saved)
.args(["--setup", "Portable setup", "--out"])
.arg(&overridden)
.args(["--width", "12", "--height", "8", "--exposure", "1.25"])
.args(["--width", "12", "--height", "8", "--exposure", "1.25", "--legacy-memory-admission"])
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-cpu")
.output()
.unwrap(),
);
assert_eq!(result["preflight"]["admission"]["host_memory"]["policy"], "software_pool_only");
assert_eq!(result["preflight"]["request"]["width"], 12);
assert_eq!(result["preflight"]["request"]["height"], 8);
assert_eq!(result["preflight"]["request"]["raytrace"]["exposure_ev"], 1.25);
@@ -773,7 +775,13 @@ fn typed_gpu_render_selects_its_host_without_a_progressive_runtime_flag() {
let script = render_setup_script(&dir, job.clone());
let out = dir.0.join("gpu.png");
let mut command = cli();
command.arg("render").arg(&script).args(["--setup", "Portable setup", "--out"]).arg(&out).env("SOLIDCRAFT_GPU", "not-a-gpu-policy");
command
.arg("render")
.arg(&script)
.args(["--setup", "Portable setup", "--out"])
.arg(&out)
.arg("--legacy-memory-admission")
.env("SOLIDCRAFT_GPU", "not-a-gpu-policy");
if job.is_null() {
command.arg("--progressive");
}
+5 -5
View File
@@ -17,14 +17,14 @@ static NEXT: AtomicUsize = AtomicUsize::new(0);
/// Whether pictures can be drawn here; installs the renderer the CLI installs, so the MCP
/// `screenshot` in this process draws as `solidcraft-cli mcp` does.
fn gpu() -> bool {
match solidcraft_gpu::capture::shared() {
match solidcraft_viewport_gpu::capture::shared(solidcraft_gpu::env::policy().unwrap_or_else(|e| panic!("{e}"))) {
Ok(c) => {
solidcraft_engine::capture::install(std::sync::Arc::new(move |scene, cam, w, h| {
c.rgba(scene, cam, w, h).map(|rgba| solidcraft_engine::capture::Picture { rgba, renderer: c.renderer() }).map_err(|e| e.to_string())
}));
true
}
Err(e @ solidcraft_gpu::GpuError::NoAdapter(_)) if std::env::var_os("SOLIDCRAFT_REQUIRE_GPU").is_none() => {
Err(e @ solidcraft_viewport_gpu::GpuError::NoAdapter(_)) if std::env::var_os("SOLIDCRAFT_REQUIRE_GPU").is_none() => {
eprintln!("skipping: {e}");
false
}
@@ -172,7 +172,7 @@ fn a_saved_view_is_its_camera_and_a_standard_view_is_unchanged() {
let mut s = Session::default();
s.run_script(&serde_json::from_slice(&std::fs::read(&script).unwrap()).unwrap()).unwrap();
let mut cam = solidcraft_engine::view::home_camera(&s).unwrap();
cam.set_view(solidcraft_engine::render::StandardView::Top);
cam.set_view(solidcraft_viewport::StandardView::Top);
let scene = solidcraft_engine::view::scene(&s, &cam).unwrap();
assert_same(&top, &solidcraft_engine::capture::png(&scene, &cam, SIZE.0, SIZE.1).unwrap().bytes, "the home camera turned to the top");
assert_same(&top, &dir.mcp(&script, "mcp-top.png", json!({"view": "top"})), "the MCP top view");
@@ -336,9 +336,9 @@ fn a_forced_cpu_snapshot_is_the_cpu_rasterizers_and_says_so() {
let mut s = Session::default();
s.run_script(&serde_json::from_slice(&std::fs::read(&script).unwrap()).unwrap()).unwrap();
let mut cam = solidcraft_engine::view::home_camera(&s).unwrap();
cam.set_view(solidcraft_engine::render::StandardView::Top);
cam.set_view(solidcraft_viewport::StandardView::Top);
let scene = solidcraft_engine::view::scene(&s, &cam).unwrap();
let cpu = solidcraft_engine::render::render_png(&scene, &cam, SIZE.0 as usize, SIZE.1 as usize).unwrap();
let cpu = solidcraft_viewport::render_png(&scene, &cam, SIZE.0 as usize, SIZE.1 as usize).unwrap();
assert_eq!(std::fs::read(dir.0.join("cpu.png")).unwrap(), cpu);
}
+32 -8
View File
@@ -191,8 +191,9 @@ mod web {
const CANVAS_ID: &str = "solidcraft_canvas";
const LOADING_ID: &str = "solidcraft_loading";
/// A 32-bit float depth buffer (no stencil): WebGPU and WebGL2 both have it, and the viewport
/// draws it with reversed depth, whose precision follows the distance from the eye (#1434).
/// The viewport scene's depth buffer, a 32-bit float one (no stencil): WebGPU and WebGL2 both
/// have it, and the viewport draws it with reversed depth, whose precision follows the
/// distance from the eye (#1434). egui's own pass has none: the scene is drawn offscreen.
const DEPTH_BITS: u8 = 32;
/// The loading animation (assets/styles/web/loading-logo.svg) settles into the finished mark
@@ -360,7 +361,7 @@ mod web {
/// The start page's recent designs were filled from the stored ones.
recents_synced: bool,
/// On-demand script fonts: fetched only when a CJK language is selected.
font: solidcraft_ui_egui::fonts::CjkFontLoader,
font: solidcraft_ui_egui::ui_kit::type_face::CjkFontLoader,
font_result: FontLoadResult,
/// The fetched Japanese font's bytes: kept so a language change can re-order the fallbacks.
cjk_bytes: Option<Vec<u8>>,
@@ -1059,7 +1060,7 @@ mod web {
}
};
let effective = solidcraft_ui_text::effective_language(&self.app.preferences.language);
if !installed && solidcraft_ui_egui::fonts::needs_cjk_font(effective) {
if !installed && solidcraft_ui_egui::ui_kit::type_face::needs_cjk_font(effective) {
log::warn!("script font unavailable; showing English for this session");
}
self.font.finished(installed, &mut self.app.preferences.language);
@@ -1093,7 +1094,7 @@ mod web {
/// CJK text on screen with no Japanese font yet (an English UI showing a Japanese name):
/// fetch the Japanese font, so the text does not stay tofu (#720).
fn watch_text_for_font(&mut self, ctx: &egui::Context) {
if self.font.watching_text() && solidcraft_ui_egui::fonts::painted_cjk(ctx) && self.font.text_needs_font() {
if self.font.watching_text() && solidcraft_ui_egui::ui_kit::type_face::painted_cjk(ctx) && self.font.text_needs_font() {
self.fetch_font(ctx, "ja");
}
}
@@ -1210,6 +1211,25 @@ mod web {
storage.and_then(|s| s.get_item(&crate::channel::key(WEBGL_KEY)).ok().flatten()).is_some()
}
/// Install the GPU viewport the desktop's way: the scene is drawn offscreen with its own depth
/// buffer and the saved anti-aliasing, then copied into egui's pass, which has neither. Drawn
/// straight into egui's pass, the view was 1x whatever Graphics said (#11622).
///
/// The sample count follows the desktop's rule (`gpu::scene_samples`): the saved choice where
/// the browser's device draws it, else the supported default. WebGL draws 1x, as desktop
/// OpenGL does. eframe's web painter has no stencil buffer.
fn install_viewport(app: &mut SolveApp, rs: &eframe::egui_wgpu::RenderState) {
let backend = rs.adapter.get_info().backend;
let depth = eframe::egui_wgpu::depth_format_from_bits(DEPTH_BITS, 0);
let supported = eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth);
let requested = solidcraft_ui_egui::gpu::scene_samples(backend, app.preferences.msaa, &supported);
let drawn = app.set_wgpu_split(rs, DEPTH_BITS, 0, requested);
// After the install, which sets egui's own counts: those leave out the scene's depth
// buffer, and they include counts WebGL would draw at 1x.
app.msaa_supported = solidcraft_ui_egui::gpu::scene_sample_choices(backend, supported);
log::info!("SolidCraft graphics: {backend:?}, MSAA {drawn}x (saved {}x)", app.preferences.msaa);
}
/// Fetch font bytes asynchronously through the browser.
async fn fetch_font_bytes(url: &str) -> Result<Vec<u8>, String> {
let window = web_sys::window().ok_or("no window")?;
@@ -1226,6 +1246,9 @@ mod web {
pub fn start() {
eframe::WebLogger::init(log::LevelFilter::Info).ok();
solidcraft_ui_egui::i18n::install();
solidcraft_ui_egui::icon_svg::install();
solidcraft_ui_egui::workspace::install_tabs();
solidcraft_ui_egui::navigation_slots::install();
wasm_bindgen_futures::spawn_local(async {
let Some(window) = web_sys::window() else { return };
let launch = crate::Launch::parse(&query());
@@ -1239,7 +1262,9 @@ mod web {
log::error!("missing <canvas id=\"{CANVAS_ID}\">");
return;
};
let mut options = eframe::WebOptions { depth_buffer: DEPTH_BITS, ..Default::default() };
// The window's pass (egui's) has no depth buffer: the scene has its own
// (`install_viewport`).
let mut options = eframe::WebOptions { depth_buffer: 0, ..Default::default() };
if webgl()
&& let eframe::egui_wgpu::WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup
{
@@ -1313,8 +1338,7 @@ mod web {
app.load_prefs(&p);
}
if let Some(rs) = &cc.wgpu_render_state {
// eframe's web painter has no stencil buffer.
app.set_wgpu(rs, DEPTH_BITS, 0, 1);
install_viewport(&mut app, rs);
}
if launch.open.is_none() && crate::query_flag(&query(), "sample") {
if let Err(e) = app.session.run_script(&solidcraft_engine::sample::script()) {
+2
View File
@@ -30,6 +30,8 @@ type_complexity = "allow"
neg_cmp_op_on_partial_ord = "allow"
[dependencies]
solidcraft-viewport-gpu = { workspace = true }
solidcraft-viewport = { workspace = true }
solidcraft-engine = { workspace = true }
solidcraft-control = { workspace = true }
# The D3D12 shader compiler the window starts with (#6756).
+14 -13
View File
@@ -261,7 +261,11 @@ impl eframe::App for App {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
}
/// The frame's input before egui reads it: the release of a press handed to a window move
/// or resize, the macOS menu and full-screen fixes, the 3D mouse, then the app's own hook.
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
// First: the release of a press the last frame handed to a window move or resize (#13334).
solidcraft_ui_egui::window_chrome::release_press_taken_by_os(ctx, raw);
#[cfg(target_os = "macos")]
if let Some(menu) = &mut self.native_menu {
menu.raw_input_hook(&mut self.solve, ctx, raw);
@@ -844,16 +848,10 @@ fn backend_preference() -> Option<String> {
graphics().and_then(|v| v.get("backend").and_then(serde_json::Value::as_str).map(str::to_string))
}
/// The viewport scene's sample count: the app's anti-aliasing (the window's pass is 1x), clamped
/// by the backend's policy (OpenGL renders 1x, see `effective_msaa_samples`). A count the device
/// cannot render with falls back to the supported default.
/// The viewport scene's sample count, by the rule the web app shares
/// (`solidcraft_ui_egui::gpu::scene_samples`).
fn scene_samples(backend: eframe::wgpu::Backend, samples: u32, supported: &[u32]) -> u32 {
let requested = eframe::egui_wgpu::effective_msaa_samples(backend, samples.max(1));
if requested <= 1 || supported.is_empty() || supported.contains(&requested) {
requested
} else {
supported.iter().copied().filter(|&n| n <= solidcraft_ui_egui::prefs::Prefs::default().msaa).max().unwrap_or(1)
}
solidcraft_ui_egui::gpu::scene_samples(backend, samples, supported)
}
/// Keep the default until the installed renderer's startup check proves a fallback is needed.
@@ -936,6 +934,9 @@ fn main() -> eframe::Result {
objc2_foundation::NSProcessInfo::processInfo().setProcessName(objc2_foundation::ns_string!("SolidCraft"));
mark("main");
solidcraft_ui_egui::i18n::install();
solidcraft_ui_egui::icon_svg::install();
solidcraft_ui_egui::workspace::install_tabs();
solidcraft_ui_egui::navigation_slots::install();
// On OCLP-patched Macs, wgpu picks a Metal Shading Language standard that the system's
// compiler refuses (`invalid value 'metal3.2'`), because the patched OS reports a newer
// macOS version than the GPU's Metal compiler supports. Re-launching with
@@ -1229,7 +1230,7 @@ fn main() -> eframe::Result {
app.session.undo.clear();
// Built straight into the session, so nothing asked for a fit: frame it at the
// home view as any opened design is (#9195). The fit waits for the first layout.
app.cam.set_view(solidcraft_engine::render::StandardView::Iso);
app.cam.set_view(solidcraft_viewport::StandardView::Iso);
app.fit_view();
}
// Lock files next to open designs: a design open on another computer says so (#5467),
@@ -1360,7 +1361,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
let indirect = std::env::var(startup::INDIRECT_VALIDATION_ENV).ok();
setup.instance_descriptor.flags = startup::window_instance_flags(setup.instance_descriptor.flags, intel_mac, indirect.as_deref());
// Never a `dxcompiler.dll` another program put on PATH (#6756).
setup.instance_descriptor.backend_options.dx12.shader_compiler = solidcraft_gpu::dx12_compiler();
setup.instance_descriptor.backend_options.dx12.shader_compiler = solidcraft_viewport_gpu::dx12_compiler();
let environment = std::env::var("WGPU_ADAPTER_NAME").ok();
let adapter = graphics().and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str).map(str::to_string));
startup::configure_adapter(setup, cpu, environment, adapter);
@@ -1630,7 +1631,7 @@ mod prefs_writer_tests {
let now = Instant::now();
assert_eq!(writer.store(&mut disk, app.prefs(), now), None);
assert_eq!(disk.flushes, 0, "the settings read at start are not written again");
app.preferences.navigator_style = NavigatorStyle::Cube;
app.preferences.navigator_style = NavigatorStyle::Custom;
app.preferences.display_quality = "fine".into();
assert!(writer.due(true, now), "a frame with input is checked");
assert_eq!(writer.store(&mut disk, app.prefs(), now), None);
@@ -1642,7 +1643,7 @@ mod prefs_writer_tests {
let mut next = new_app();
let stored = solidcraft_ui_egui::prefs::stored(|k| disk.flushed.get(k).cloned()).expect("settings on disk");
next.load_prefs(&stored);
assert_eq!(next.preferences.navigator_style, NavigatorStyle::Cube);
assert_eq!(next.preferences.navigator_style, NavigatorStyle::Custom);
assert_eq!(next.preferences.display_quality, "fine");
assert!(disk.flushed.contains_key(STORAGE_KEY));
}
+3 -3
View File
@@ -260,7 +260,7 @@ pub const FLOAT_DEPTH_STENCIL: eframe::wgpu::Features = eframe::wgpu::Features::
/// asks for a float one only when whichever it picks has it.
pub fn float_depth_everywhere(options: &eframe::egui_wgpu::WgpuConfiguration) -> bool {
let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &options.wgpu_setup else { return false };
let mut desc = solidcraft_gpu::instance_descriptor_from_env();
let mut desc = solidcraft_viewport_gpu::instance_descriptor_from_env();
desc.backends = setup.instance_descriptor.backends;
let instance = eframe::wgpu::Instance::new(desc);
let adapters = pollster::block_on(instance.enumerate_adapters(setup.instance_descriptor.backends));
@@ -277,7 +277,7 @@ pub fn has_hardware_adapter(backends: eframe::wgpu::Backends) -> bool {
let mut desc = eframe::wgpu::InstanceDescriptor::new_without_display_handle();
desc.backends = backends;
desc.flags = eframe::wgpu::InstanceFlags::from_build_config().with_env();
desc.backend_options.dx12.shader_compiler = solidcraft_gpu::dx12_compiler();
desc.backend_options.dx12.shader_compiler = solidcraft_viewport_gpu::dx12_compiler();
let instance = eframe::wgpu::Instance::new(desc);
let adapters = pollster::block_on(instance.enumerate_adapters(backends));
let found: Vec<String> = adapters.iter().map(|a| format!("{} ({:?})", a.get_info().name, a.get_info().device_type)).collect();
@@ -698,7 +698,7 @@ mod tests {
if cfg!(target_os = "windows") && std::env::var_os(WGPU_BACKEND_ENV).is_none() {
assert_eq!(backends, eframe::wgpu::Backends::DX12);
}
let mut desc = solidcraft_gpu::instance_descriptor_from_env();
let mut desc = solidcraft_viewport_gpu::instance_descriptor_from_env();
desc.backends = backends;
let instance = eframe::wgpu::Instance::new(desc);
let adapters = pollster::block_on(instance.enumerate_adapters(backends));
-1
View File
@@ -21,7 +21,6 @@ assets/
styles/web/loading.css # web loading/canvas styles
appearances/library.rs # built-in visual presets
materials/library.rs # physical defaults and colours
shaders/viewport.wgsl # original viewport shader
fonts/biz-udp-gothic/ # unmodified font and OFL licence
locales/{en,de,ja,ko}.json # shared offline catalogs
platforms/{macos,linux,windows}/ # bundle, freedesktop and WiX templates
+10 -136
View File
@@ -1,135 +1,12 @@
//! Navigation drawing definitions. Camera projection and picking remain in the UI widgets.
use egui::{Color32, FontId, Pos2, Rect, Shape, Stroke, pos2, vec2};
use solidcore_record::Vec3;
use solidcraft_engine::geom::Ring;
use egui::{Pos2, Shape, Stroke, pos2, vec2};
use solidcraft_theme::Tokens;
pub(crate) struct Facet {
pub(crate) direction: Vec3,
pub(crate) points: Ring<Pos2>,
pub(crate) outer_edges: Vec<bool>,
pub(crate) centre: Pos2,
pub(crate) label: String,
pub(crate) text_axes: Option<(egui::Vec2, egui::Vec2)>,
pub(crate) depth: f64,
}
// Private marker for label meshes with atlas-pixel UVs. The output hook replaces
// it with the font texture before any renderer sees the frame.
pub(crate) const LABEL_PIXELS: egui::TextureId = egui::TextureId::User(u64::from_le_bytes(*b"cubetext"));
struct LabelAtlas;
impl egui::plugin::Plugin for LabelAtlas {
fn debug_name(&self) -> &'static str {
"navigation cube label atlas"
}
fn output_hook(&mut self, ctx: &egui::Context, output: &mut egui::FullOutput) {
let size = ctx.fonts(|f| f.font_image_size());
let normalize = vec2(1.0 / size[0].max(1) as f32, 1.0 / size[1].max(1) as f32);
for shape in &mut output.shapes {
if let Shape::Mesh(mesh) = &mut shape.shape
&& mesh.texture_id == LABEL_PIXELS
{
let mesh = std::sync::Arc::make_mut(mesh);
mesh.texture_id = egui::TextureId::default();
for v in &mut mesh.vertices {
v.uv = (v.uv.to_vec2() * normalize).to_pos2();
}
}
}
}
}
/// Warp the font mesh into the projected face, so labels turn with the model.
pub(crate) fn face_text(painter: &egui::Painter, face: &Facet, ink: Color32, font_size: f32) {
let Some((r, down)) = face.text_axes else { return };
let opacity = ((face.depth - 0.18) / 0.17).clamp(0.0, 1.0) as f32;
if opacity <= 0.0 {
return;
}
let ink = ink.gamma_multiply(opacity);
let galley = painter.layout_no_wrap(face.label.clone(), FontId::proportional(font_size), ink);
let centre = galley.rect.center();
// Keep atlas-pixel UVs until the frame is complete: another label or widget
// can grow the atlas after this mesh is created, invalidating early UVs.
painter.ctx().add_plugin(LabelAtlas);
let text = Shape::Text(egui::epaint::TextShape::new(Pos2::ZERO, galley, ink));
let atlas = painter.ctx().fonts(|f| f.font_image_size());
let primitives = painter.ctx().tessellate(vec![egui::epaint::ClippedShape::new(Rect::EVERYTHING, text)], painter.ctx().pixels_per_point());
for primitive in primitives {
if let egui::epaint::Primitive::Mesh(mut mesh) = primitive.primitive {
mesh.texture_id = LABEL_PIXELS;
for v in &mut mesh.vertices {
v.uv = pos2(v.uv.x * atlas[0] as f32, v.uv.y * atlas[1] as f32);
let local = v.pos - centre;
v.pos = face.centre + r * local.x + down * (local.y * 1.35);
}
painter.add(Shape::mesh(mesh));
}
}
}
pub(crate) fn paint_cube(painter: &egui::Painter, faces: &[Facet], hovered: Option<usize>, t: Tokens, labels: bool, scale: f32) {
// A shared, opaque triangle mesh has no antialiasing fringes between facets.
// Per-polygon fringes and miter joins overlap as a face becomes edge-on.
let mut fill_mesh = egui::epaint::Mesh::default();
let mut edges: Vec<(Pos2, Pos2, usize, bool)> = Vec::new();
let hovered_direction = hovered.and_then(|i| faces.get(i)).map(|f| f.direction);
for face in faces {
let base = t.cube_face;
let shade = (0.72 + 0.28 * face.depth.min(1.0)) as f32;
let highlighted = hovered_direction == Some(face.direction);
let fill = if highlighted {
t.cube_hover
} else {
Color32::from_rgb((f32::from(base.r()) * shade) as u8, (f32::from(base.g()) * shade) as u8, (f32::from(base.b()) * shade) as u8)
};
let centre = fill_mesh.vertices.len() as u32;
fill_mesh.colored_vertex(face.centre, fill);
for &p in &face.points {
fill_mesh.colored_vertex(p, fill);
}
for (j, (&a, &b)) in face.points.edges().enumerate() {
fill_mesh.add_triangle(centre, centre + 1 + j as u32, centre + 1 + ((j + 1) % face.points.len()) as u32);
// Coplanar picking regions have no visible seams. Only actual cube
// edges and the hovered target's boundary receive an outline.
let outer = face.outer_edges.get(j).copied().unwrap_or(false);
if !highlighted && !outer {
continue;
}
if let Some(edge) = edges
.iter_mut()
.find(|(p, q, _, _)| (p.distance_sq(a) < 1e-6 && q.distance_sq(b) < 1e-6) || (p.distance_sq(b) < 1e-6 && q.distance_sq(a) < 1e-6))
{
edge.2 += usize::from(highlighted);
edge.3 |= outer;
} else {
edges.push((a, b, usize::from(highlighted), outer));
}
}
}
painter.add(Shape::mesh(fill_mesh));
for (a, b, highlights, outer) in edges {
// Two highlighted coplanar patches share an internal subdivision, not
// a target boundary. Actual cube edges remain visible across faces.
if !outer && highlights != 1 {
continue;
}
let highlighted = highlights > 0;
painter.line_segment([a, b], Stroke::new(1.0, if highlighted { t.accent } else { t.cube_border }));
}
if labels {
for face in faces {
face_text(painter, face, t.cube_text, t.font_heading * scale);
}
}
}
pub(crate) fn arrow(painter: &egui::Painter, at: Pos2, direction: egui::Vec2, hover: bool, t: Tokens) {
let side = vec2(-direction.y, direction.x);
painter.add(Shape::convex_polygon(
vec![at + direction * 7.0, at - direction * 5.0 + side * 7.0, at - direction * 5.0 - side * 7.0],
if hover { t.accent_soft } else { t.cube_face },
Stroke::new(1.0, if hover { t.accent } else { t.cube_border }),
if hover { t.accent_soft } else { t.navigator_face },
Stroke::new(1.0, if hover { t.accent } else { t.navigator_border }),
));
}
pub(crate) fn curved_arrow(painter: &egui::Painter, at: Pos2, clockwise: bool, hover: bool, t: Tokens, scale: f32) {
@@ -141,8 +18,8 @@ pub(crate) fn curved_arrow(painter: &egui::Painter, at: Pos2, clockwise: bool, h
at + vec2(angle.cos(), angle.sin()) * (32.0 * scale)
})
.collect();
painter.add(Shape::line(points.clone(), Stroke::new(5.0 * scale, if hover { t.accent } else { t.cube_border })));
painter.add(Shape::line(points.clone(), Stroke::new(3.0 * scale, if hover { t.accent_soft } else { t.cube_face })));
painter.add(Shape::line(points.clone(), Stroke::new(5.0 * scale, if hover { t.accent } else { t.navigator_border })));
painter.add(Shape::line(points.clone(), Stroke::new(3.0 * scale, if hover { t.accent_soft } else { t.navigator_face })));
if let Some(tip) = points.last() {
let sign = if clockwise { 1.0 } else { -1.0 };
arrow(painter, *tip, vec2(-end.sin(), end.cos()) * (sign * scale), hover, t);
@@ -150,16 +27,13 @@ pub(crate) fn curved_arrow(painter: &egui::Painter, at: Pos2, clockwise: bool, h
}
/// The pointer target around a compass end: larger than the drawn disc, so it is easy to hit.
pub(crate) const AXIS_RADIUS: f32 = 15.0;
pub(crate) fn axis_tip(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, at: Pos2, depth: f64, hovered: bool, t: Tokens) {
axis_tip_scaled(painter, c, name, d, at, depth, hovered, t, 1.0);
}
/// The same compass artwork at a uniform scale, including its lettering and strokes.
/// A compass axis end at a uniform scale, including its lettering and strokes.
#[allow(clippy::too_many_arguments)]
pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, at: Pos2, depth: f64, hovered: bool, t: Tokens, scale: f32) {
let col = if d.x != 0.0 {
pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: [f64; 3], at: Pos2, depth: f64, hovered: bool, t: Tokens, scale: f32) {
let col = if d[0] != 0.0 {
t.axis_x
} else if d.y != 0.0 {
} else if d[1] != 0.0 {
t.axis_y
} else {
t.axis_z
@@ -196,7 +70,7 @@ pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: &
// On a hovered (white) disc the letter takes the axis colour.
let letter = if hovered { col } else { t.on_accent };
let label = name.to_uppercase();
let galley = painter.layout_no_wrap(label, crate::fonts::bold(solidcraft_theme::Tokens::get().font_compass * scale), letter);
let galley = painter.layout_no_wrap(label, crate::ui_kit::type_face::bold(solidcraft_theme::Tokens::get().font_compass * scale), letter);
let origin = at - galley.size() / 2.0;
let ppp = painter.ctx().pixels_per_point().max(0.5);
let origin = pos2((origin.x * ppp).round() / ppp, (origin.y * ppp).round() / ppp);
+63 -7
View File
@@ -193,7 +193,6 @@
"Create Issue…": "إنشاء بلاغ…",
"Cross curves": "المنحنيات العرضية",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter يتابع في GitHub.",
"Cube": "مكعب",
"Current orthographic view (1:1 mm)": "العرض المتعامد الحالي (1:1 mm)",
"Current perspective view": "العرض المنظوري الحالي",
"Current plane": "المستوى الحالي",
@@ -1509,6 +1508,7 @@
"cmd.timeline_stop": "إيقاف السجل",
"cmd.timeline_suppress": "تعطيل الميزة",
"cmd.ui.assistant": "إظهار المساعد أو إخفاؤه",
"cmd.ui.palette": "البحث في الأوامر",
"cmd.view.navigator": "متصفح العرض المخصص",
"cmd.view.render_browser": "مستعرض التصيير",
"cmd.view.render_dock": "نقل لوحة التصيير",
@@ -2431,6 +2431,7 @@
"parts.dimension.width": "العرض",
"parts.family.bearing": "محمل كريات أخدودي عميق (الغلاف)",
"parts.family.button_head_cap_screw": "برغي برأس زرّي بتجويف سداسي",
"parts.family.countersunk_socket_screw": "برغي برأس غاطس بتجويف سداسي",
"parts.family.dowel_pin": "مسمار توجيه أسطواني",
"parts.family.hex_bolt": "برغي سداسي الرأس",
"parts.family.hex_cap_screw_inch": "برغي سداسي الرأس (بوصة)",
@@ -2681,9 +2682,9 @@
"prefs.interface_note": "شكل SolidCraft وأماكن أجزائه.",
"prefs.interval_minutes": "{n} دقيقة",
"prefs.invert_curved_roll_arrows": "عكس سهمي التدوير المنحنيين",
"prefs.invert_curved_roll_arrows_note": "سهما التدوير المنحنيان عند زاوية المكعب يدوران العرض داخل مستوى الشاشة. عند التشغيل: سهم التدوير مع عقارب الساعة يدور عكسها، وسهم العكس يدور معها. عند الإيقاف: يدور كل سهم بالعرض حسب انحنائه.",
"prefs.invert_navigator_arrows": "عكس أسهم تدوير مكعب العرض",
"prefs.invert_navigator_arrows_note": "تدوّر الأسهم المحيطة بالمكعب الكاميرا في الاتجاه الذي تشير إليه، كما في Fusion وInventor: السهم الموجود على اليسار يشير إلى اليمين ويدوّر الكاميرا إلى اليمين. عند الإيقاف: يدوّر السهم المكعب نحو الجهة التي يقع فيها، فيدوّر السهم الأيسر الكاميرا إلى اليسار. لا يتأثر سهما الدوران المنحنيان.",
"prefs.invert_curved_roll_arrows_note": "سهما التدوير المنحنيان عند زاوية أداة التنقل يدوران العرض داخل مستوى الشاشة. عند التشغيل: سهم التدوير مع عقارب الساعة يدور عكسها، وسهم العكس يدور معها. عند الإيقاف: يدور كل سهم بالعرض حسب انحنائه.",
"prefs.invert_navigator_arrows": "عكس أسهم تدوير أداة التنقل",
"prefs.invert_navigator_arrows_note": "تدوّر الأسهم المحيطة بأداة التنقل الكاميرا في الاتجاه الذي تشير إليه، كما في Fusion وInventor: السهم الموجود على اليسار يشير إلى اليمين ويدوّر الكاميرا إلى اليمين. عند الإيقاف: يدوّر السهم العرض نحو الجهة التي يقع فيها، فيدوّر السهم الأيسر الكاميرا إلى اليسار. لا يتأثر سهما الدوران المنحنيان.",
"prefs.keep_unsaved_also_asks": "السؤال أيضًا عند إغلاق علامة التبويب",
"prefs.keep_unsaved_also_asks_tip": "عند إيقاف هذا الخيار، تُغلق علامة التبويب دون مربع حوار المتصفح بعد تحديث نسخة كل تصميم غير محفوظ.",
"prefs.keep_unsaved_in_browser": "الاحتفاظ بالعمل غير المحفوظ في هذا المتصفح",
@@ -2743,7 +2744,6 @@
"prefs.move_drag_highlights_tip": "عند الإيقاف: لا يُميَّز أي شيء تحت المؤشر أثناء سحب مكوّن بأسهم أداة التحريك، فتبقى التجميعات الكبيرة سلسة. مع عرض مقطع أو تحليل يبقى التمييز كما هو، ولا يُميَّز شيء أبدًا عند سحب جزء على امتداد وصلاته.",
"prefs.navigation": "التنقل",
"prefs.navigation.axes_signed": "المحاور",
"prefs.navigation.cube_compact": "المكعب",
"prefs.navigation.device": "الجهاز",
"prefs.navigation.free": "حر",
"prefs.navigation.orbit_style": "نمط التدوير",
@@ -2751,7 +2751,7 @@
"prefs.navigation.turntable": "طاولة دوارة",
"prefs.navigation_note": "كيف تحرك الفأرة ولوحة اللمس والفأرة ثلاثية الأبعاد العرض.",
"prefs.navigator_fit": "ملاءمة النموذج بعد تغيير العرض من متصفح العرض",
"prefs.navigator_fit_note": "النقر على وجه أو ركن أو سهم في المكعب، أو على محور في أداة المحاور، يوجّه العرض أيضًا نحو منتصف النموذج من المسافة الواحدة التي تُظهره كاملًا من كل جهة (كرته المحيطة). يستخدم العرض الرئيسي التكبير والمركز ذاتهما ما دام هذا مفعّلًا، فلا تغيّر العروض المختارة بعده سوى الاتجاه. عند الإيقاف: يدور العرض في مكانه محتفظًا بالهدف والتكبير.",
"prefs.navigator_fit_note": "النقر على وجه أو ركن أو سهم في أداة التنقل، أو على محور في أداة المحاور، يوجّه العرض أيضًا نحو منتصف النموذج من المسافة الواحدة التي تُظهره كاملًا من كل جهة (كرته المحيطة). يستخدم العرض الرئيسي التكبير والمركز ذاتهما ما دام هذا مفعّلًا، فلا تغيّر العروض المختارة بعده سوى الاتجاه. عند الإيقاف: يدور العرض في مكانه محتفظًا بالهدف والتكبير.",
"prefs.network_host_remove": "إزالة",
"prefs.network_locations": "السماح بمواقع الشبكة في التصاميم",
"prefs.network_locations_note": "للمؤسسات التي تحفظ التصاميم على خوادم الملفات. عند الإيقاف: الخوادم التي تسمح بها فقط.",
@@ -2826,6 +2826,8 @@
"prefs.remember_dialog_values.help": "معطّل افتراضيًا. عند تفعيله، بعد نجاح الأمر، يُعاد استخدام قيمه وخياراته الآمنة في المرة التالية التي تفتح فيها الأمر نفسه، حتى إغلاق SolidCraft.",
"prefs.render_legacy_image_defaults": "استخدام الإعدادات الافتراضية السابقة لصور التصيير",
"prefs.render_legacy_image_defaults_help": "الاحتفاظ بسير العمل اليدوي السابق: إعدادات جديدة بالتصيير النقطي على CPU، وأخذ العينات الثابت لتتبع الأشعة على CPU، وفتح النتائج يدويًا. إعادة ملاءمة العرض عند دخول التصيير، وتبديل اللقطات دون استعادة كاميراتها، واستخدام التأطير والتنقل السابقين. تعطيل التحديث التلقائي للصورة المباشرة. لا تتغير الإعدادات المحفوظة.",
"prefs.render_legacy_memory_admission": "استخدام فحوصات ذاكرة التصيير السابقة",
"prefs.render_legacy_memory_admission_help": "السماح بالتصيير المحلي ضمن حدود الذاكرة المشتركة وحدود المضيف الثابتة حتى عندما يتجاوز التقدير الذاكرة المتاحة. تظل حدود الجهاز وفحوصات تخصيص الذاكرة سارية.",
"prefs.render_previous_geometry_budget": "استخدام ميزانية الهندسة السابقة لمعاينة التصيير",
"prefs.render_previous_geometry_budget_help": "استعادة حد الهندسة الثابت السابق (512 ميبيبايت على سطح المكتب 64 بت، و128 ميبيبايت على المنصات الأخرى) بدلًا من الميزانية المعتمدة على الجهاز.",
"prefs.reports_saved": "{n} محفوظة",
@@ -3220,8 +3222,10 @@
"render.category_paint": "طلاء",
"render.category_plastic": "بلاستيك",
"render.category_wood": "خشب",
"render.caustics_area_lights": "تأثيرات الضوء المركّز من الأضواء المساحية",
"render.caustics_depth": "عمق مسار تجمّع الضوء",
"render.caustics_enable": "تجمّع الضوء عبر الزجاج من مصادر نقطية واتجاهية",
"render.caustics_environment": "تأثيرات الضوء المركّز من البيئة",
"render.caustics_help": "يتحكم عدد الفوتونات في الضجيج وكلفة الحساب. يوازن نصف قطر التجميع بين التفاصيل الدقيقة والتقدير الأنعم. يتطلب CPU إضاءة غير مباشرة.",
"render.caustics_path_required": "يتطلب تجمّع الضوء على CPU إضاءة غير مباشرة.",
"render.caustics_photons": "عدد الفوتونات",
@@ -3398,7 +3402,7 @@
"render.cpu_opaque_required": "يتطلب تتبع الأشعة على CPU خلفية معتمة. ألغِ تحديد «خلفية شفافة» للمتابعة.",
"render.cpu_options_pending": "لا تستطيع لوحة CPU هذه تحرير الانبعاث ولا تأثيرات السطح ولا الزجاج ولا تباين الخواص بعد. امسح خيارات API هذه قبل استخدامها.",
"render.cpu_path_depth": "عمق المسار",
"render.cpu_path_help": "تضيف الضوء المرتد بين الأسطح المعتمة والمار عبر أشكال الزجاج المدعومة. تتطلب التصيير التدريجي وظلال تتبع الأشعة. يحسب عمق المسار الأسطح المضاءة وحدود الزجاج وأحداث التشتت بدءًا من أول تقاطع (1–16). يقتصر العمق 1 على الإضاءة المباشرة للأسطح المعتمة؛ وتسمح القيم الأعلى بمزيد من الارتدادات أو عبور الزجاج وقد تستغرق وقتًا أطول. أوقف الإضاءة غير المباشرة لاستخدام الانعكاسات المخصصة أو التباين الاتجاهي.",
"render.cpu_path_help": "تضيف الضوء المرتد بين الأسطح المعتمة والمار عبر أشكال الزجاج المدعومة. تتطلب التصيير التدريجي وظلال تتبع الأشعة. يحسب عمق المسار الأسطح المضاءة وحدود الزجاج وأحداث التشتت بدءًا من أول تقاطع (1–64). يقتصر العمق 1 على الإضاءة المباشرة للأسطح المعتمة؛ وتسمح القيم الأعلى بمزيد من الارتدادات أو عبور الزجاج وقد تستغرق وقتًا أطول. أوقف الإضاءة غير المباشرة لاستخدام الانعكاسات المخصصة أو التباين الاتجاهي.",
"render.cpu_path_lighting": "إضاءة غير مباشرة",
"render.cpu_path_optics_reason": "تبقى إعداداتك كما هي. أوقف الإضاءة غير المباشرة لاستخدام الانعكاسات المخصصة أو التباين الاتجاهي.",
"render.cpu_point": "ضوء نقطي",
@@ -3438,6 +3442,12 @@
"render.cpu_subsurface_denoise_reason": "يتطلب التشتت داخل الزجاج على CPU إيقاف إزالة الضجيج. تبقى إعداداتك محفوظة.",
"render.cpu_subsurface_help": "يغيّر التشتت اتجاه الضوء داخل الزجاج المغلق. تتطلب المعدلات الموجبة إضاءة غير مباشرة مع إيقاف إزالة الضجيج. المعدل هو عدد التصادمات لكل مم؛ لا يضيف الصفر أي تشتت. يفضّل انحياز الاتجاه الموجب الأمام، والسالب الخلف.",
"render.cpu_summary": "تتبع الأشعة على CPU · انعكاسات · HDR · أضواء اتجاهية ونقطية",
"render.cpu_threads": "خيوط المعالجة",
"render.cpu_threads_auto": "تلقائي",
"render.cpu_threads_custom": "مخصص",
"render.cpu_threads_help": "يستخدم الوضع التلقائي خيوط المعالجة المتاحة. ينفّذ خيط واحد العمل تسلسليًا. قد تحتاج الخيوط الإضافية إلى ذاكرة أكبر.",
"render.cpu_threads_single": "واحد",
"render.cpu_threads_web": "يستخدم التصيير في المتصفح خيطًا تعاونيًا واحدًا. يُحفظ اختيار خيوط المعالجة الخاص بسطح المكتب.",
"render.cpu_transparent_help": "يزيل PNG الشفاف الخلفية ويحافظ على تغطية الحواف. يجب أن تبقى عتامة الأجسام والأوجه عند 100%.",
"render.cpu_volume_enable_path": "تفعيل الإضاءة غير المباشرة",
"render.cpu_volume_enable_path_plan": "تتبع أشعة CPU · عمق المسار {depth} · {spp} عينة/بكسل · بذرة {seed} · ظلال متتبعة. يحتفظ بالغلاف الجوي وميزانية الصورة المحددين.",
@@ -3446,6 +3456,40 @@
"render.cpu_volume_method_incompatible": "لا يمكن نقل إعدادات الإضاءة أو أخذ العينات أو الغلاف الجوي هذه إلى هذه الطريقة. تبقى إعداداتك محفوظة.",
"render.cpu_volume_path_required": "فعّل الإضاءة غير المباشرة لاستخدام الغلاف الجوي على CPU.",
"render.cpu_warm_cool": "دافئ وبارد",
"render.denoise_class_background": "متجاوز: خلفية",
"render.denoise_class_coverage": "متجاوز: تغطية جزئية",
"render.denoise_class_degenerate": "متجاوز: مسار متدهور",
"render.denoise_class_depth": "متجاوز: حد عمق المسار",
"render.denoise_class_emitter": "متجاوز: مصدر ضوء",
"render.denoise_class_exit": "متجاوز: خروج غير متوافق",
"render.denoise_class_identity": "متجاوز: حد هوية الدليل",
"render.denoise_class_lens": "متجاوز: عمق المجال",
"render.denoise_class_material": "متجاوز: مادة غير مدعومة",
"render.denoise_class_nested": "متجاوز: وسط متداخل",
"render.denoise_class_optical": "مؤهل: سطح عبر انكسار",
"render.denoise_class_reflection": "متجاوز: نهاية عاكسة",
"render.denoise_class_surface": "مؤهل: سطح",
"render.denoise_class_tir": "متجاوز: انعكاس داخلي كلي",
"render.denoise_class_transmission": "متجاوز: نفاذ إضافي",
"render.denoise_class_unavailable": "متجاوز: بلا دليل",
"render.denoise_class_volume": "متجاوز: تشتت حجمي",
"render.denoise_diagnostics_counts": "المؤهلة: {eligible} · المتجاوزة: {skipped}",
"render.denoise_diagnostics_enable": "جمع تشخيصات إزالة الضوضاء",
"render.denoise_diagnostics_grid": "أعداد البكسلات بالحجم الداخلي {width} × {height}.",
"render.denoise_diagnostics_help": "يجمع أعداد البكسلات المؤهلة والمتجاوزة حتى عند إيقاف إزالة الضوضاء. تعتمد الأعداد على حجم التصيير الداخلي، ويعتمد القناع على حجم الإخراج.",
"render.denoise_diagnostics_title": "تشخيصات إزالة الضوضاء",
"render.denoise_export_mask": "تصدير القناع بصيغة PNG",
"render.denoise_firefly": "تقليل البكسلات الساطعة المعزولة",
"render.denoise_format_mask": "قناع PNG",
"render.denoise_mask_copy_help": "صدّر قناع إزالة الضوضاء بصيغة PNG، أو عُد إلى الصورة المصيّرة لنسخها.",
"render.denoise_mask_unavailable": "فعّل تشخيصات إزالة الضوضاء وصيّر صورة لفحص قناعها.",
"render.denoise_optical_filter": "تصفية المرايا والزجاج",
"render.denoise_optical_filter_help": "تصفية الضوء المنعكس والنافذ المؤهّل. أوقف هذا الخيار لإزالة الضجيج من الأسطح المعتمة فقط.",
"render.denoise_passes": "مرات الترشيح",
"render.denoise_settings_help": "تُنعّم مرات الترشيح الإضافية مساحة أوسع. تحدد القوة مقدار الترشيح المطبّق في كل مرة. تبقى المسارات البصرية غير المدعومة دون ترشيح.",
"render.denoise_strength": "قوة إزالة الضوضاء",
"render.denoise_view_image": "الصورة المصيّرة",
"render.denoise_view_mask": "قناع إزالة الضوضاء",
"render.diag_bodies": "الأجسام المرئية",
"render.diag_job_details": "تفاصيل المهمة الملتقطة",
"render.diag_last_check": "آخر فحص مسبق",
@@ -4000,6 +4044,8 @@
"render.material_transient": "تجاوزات بصرية مؤقتة لعلامة تبويب المستند هذه. لا تُحفظ في التصميم ولا في إعداد التصيير ولا في مكتبة المواد.",
"render.material_v_up": "اتجاه V للنسيج يشير للأعلى في الصورة",
"render.material_weight": "وزن الطبقة (0–1)",
"render.metal_energy_compensation": "استعادة انعكاس المعادن الخشنة",
"render.metal_energy_compensation_help": "عوّض الضوء الذي تفقده الأسطح المعدنية الخشنة بين أوجهها المجهرية. يتطلب معالج CPU إضاءة غير مباشرة.",
"render.method_cpu": "تتبع الأشعة على CPU",
"render.method_gpu": "تتبع المسارات على GPU",
"render.method_limit_raster": "التنقيط يستخدم معاينة الاستوديو المعتمة الثابتة. اختر طريقة تتبع أشعة صريحة لعناصر التحكم البصرية المدعومة.",
@@ -4128,6 +4174,8 @@
"render.quality_denoise_on": "تقليل التشويش: مفعّل",
"render.quality_draft": "مسودة",
"render.quality_high": "عالية",
"render.quality_legacy_samples": "القيمة المحفوظة: {samples} عينة لكل بكسل. تقتصر الاختيارات الجديدة على {maximum} عينة لكل بكسل.",
"render.quality_preset_limit": "يتجاوز هذا الإعداد المسبق حد الاختيار البالغ 16384 عينة لكل بكسل. اختر عدد عينات لا يتجاوز 16384.",
"render.quality_projection_help": "تغيّر هذه المراجعة الدقة وميزانية العينات وعمق المسار والبذرة وتقليل التشويش فقط. تُحفظ الإعدادات الأخرى؛ ولا تثبت الميزانية تقارب الصورة.",
"render.quality_review": "مراجعة ميزانية العيّنات",
"render.quality_summary": "{width} × {height} · {samples} عينة · العمق {depth} · البذرة {seed} · {denoise}",
@@ -4275,6 +4323,7 @@
"render.show_shots_label": "إظهار شريط اللقطات",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "مخصص…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
@@ -4437,6 +4486,11 @@
"render.subsurface_enable": "مسار عشوائي للتشتت الداخلي",
"render.subsurface_help": "للعوازل المغلقة فقط. التشتت وامتصاص RGB معاملان منفصلان لكل mm. تتشتت المسارات داخل الهندسة الفعلية، ثم تنكسر أو تنعكس عند الحد. عطّل نفاذ الظل المستقيم وإزالة تشويش السطح والحجم الخارجي؛ واستخدم طبقة فرينل واحدة. ليس مظلل انتشار ولا شبكة كثافة ولا مادة مقيسة. الأوساط الكثيفة قد تحتاج مزيدًا من العيّنات وعمق المسار.",
"render.subsurface_rate": "معدل اصطدامات التشتت",
"render.supersampling": "أخذ عينات فائقة",
"render.supersampling_dimensions": "يُصيَّر بحجم {width} × {height} ويُصغَّر إلى {output_width} × {output_height}. يستخدم أربعة أضعاف عدد البكسلات.",
"render.supersampling_help": "يُصيَّر المشهد بضعف العرض والارتفاع، ثم يُصغَّر في فضاء الإضاءة الخطي. يبقى حجم الإخراج دون تغيير.",
"render.supersampling_off": "إيقاف",
"render.supersampling_x2": "2×",
"render.surface_import.author": "المؤلف",
"render.surface_import.busy": "جارٍ استيراد الصورة…",
"render.surface_import.complete": "اختر صورة وأدخل مؤلفها ومصدرها وترخيصها.",
@@ -4450,6 +4504,8 @@
"render.surface_import.saved_cpu": "احفظ الخرائط لاستخدامها في التصيير على CPU.",
"render.surface_import.source": "المصدر",
"render.surface_import.title": "استيراد صورة",
"render.texture_filtering": "ترشيح تفاصيل الخامات",
"render.texture_filtering_help": "استخدم مستويات التفاصيل والترشيح المتباين لتقليل تعرّج الخامات عند التصغير. يزيد استهلاك الذاكرة؛ يبقى الوضع القديم معطّلاً.",
"render.tone_aces_fitted": "ACES مُلاءَم",
"render.tone_reinhard": "راينهارد",
"render.transmission_scope": "يدعم زجاج GPU الأوساط المنفصلة المعتمدة. لا تتوفر الأوساط المتداخلة غير المقيدة ولا مقدّر نفاذ خالٍ من التشويش.",
-1
View File
@@ -144,7 +144,6 @@
"Create Issue…": "Create Issue…",
"Cross curves": "Cross curves",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter continues on GitHub.",
"Cube": "Cube",
"Current orthographic view (1:1 mm)": "Current orthographic view (1:1 mm)",
"Current perspective view": "Current perspective view",
"Current plane": "Current plane",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Prijavi problem…",
"Cross curves": "Presječne krive",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter nastavlja na GitHubu.",
"Cube": "Kocka",
"Current orthographic view (1:1 mm)": "Trenutni ortografski prikaz (1:1 mm)",
"Current perspective view": "Trenutni perspektivni prikaz",
"Current plane": "Trenutna ravan",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Kako SolidCraft izgleda i gdje su njegovi dijelovi.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Obrni zakrivljene strelice za valjanje",
"prefs.invert_curved_roll_arrows_note": "Dvije zakrivljene strelice u uglu kocke valjaju prikaz unutar ravni ekrana. Uključeno: strelica u smjeru kazaljke na satu valja prikaz suprotno od smjera kazaljke na satu, a strelica suprotno od smjera kazaljke na satu valja ga u smjeru kazaljke na satu. Isključeno: svaka strelica valja prikaz onako kako je zakrivljena.",
"prefs.invert_navigator_arrows": "Obrni strelice za rotaciju kocke prikaza",
"prefs.invert_navigator_arrows_note": "Strelice oko kocke orbitiraju kameru u smjeru u kojem pokazuju, kao u programima Fusion i Inventor: strelica s lijeve strane pokazuje udesno i zakreće kameru udesno. Isključeno: strelica okreće kocku prema strani na kojoj se nalazi, pa lijeva strelica zakreće kameru ulijevo. Postavka ne utiče na zakrivljene strelice za valjanje.",
"prefs.keep_work_on_tab_switch": "Zadrži dijaloge i alate otvorenima pri prebacivanju između dizajna",
"prefs.keep_work_on_tab_switch_note": "Svaka kartica dizajna zadržava svoj otvoreni dijalog ili alat, s onim što ste u njemu izabrali i upisali, te svoju karticu trake s alatima. Kada se vratite na dizajn, ponovo su tu. Isključeno: prebacivanje između dizajna otkazuje ih i prikazuje karticu Model.",
"prefs.labels": "Oznake",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigacija",
"prefs.navigation_note": "Kako miš, trackpad i 3D miš pomjeraju prikaz.",
"prefs.navigator_fit": "Uklopi model nakon promjene prikaza u navigatoru",
"prefs.navigator_fit_note": "Lice, ugao ili strelica kocke prikaza, odnosno osa alata za orijentaciju, također usmjerava pogled na sredinu modela s jedne udaljenosti na kojoj se uklapa iz svakog smjera (njegova granična sfera). Početni prikaz koristi isti zum i centar dok je ova opcija uključena, pa se prikazi koje izaberete poslije njega samo okreću. Isključeno: prikaz se okreće na mjestu i zadržava svoj cilj i zum.",
"prefs.network_host_remove": "Ukloni",
"prefs.network_locations": "Dozvoli mrežne lokacije u dizajnima",
"prefs.network_locations_note": "Isključeno: povezane datoteke dizajna na drugom računaru (\\\\server\\share) otvaraju se samo sa servera navedenih ovdje ili nakon što ga dozvolite kada se to zatraži.",
-6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Crear una incidència…",
"Cross curves": "Corbes transversals",
"Ctrl+Enter continues on GitHub.": "Ctrl+Retorn continua a GitHub.",
"Cube": "Cub",
"Current orthographic view (1:1 mm)": "Vista ortogràfica actual (1:1 mm)",
"Current perspective view": "Vista en perspectiva actual",
"Current plane": "Pla actual",
@@ -2659,9 +2658,6 @@
"prefs.interface_note": "Com es veu SolidCraft i on se situen les seves parts.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Invertir les fletxes de gir corbes",
"prefs.invert_curved_roll_arrows_note": "Les dues fletxes corbes a la cantonada del cub giren la vista dins del pla de la pantalla. Activades: la fletxa horària gira en sentit antihorari i l'antihorària gira en sentit horari. Desactivades: cada fletxa gira la vista cap on es corba.",
"prefs.invert_navigator_arrows": "Invertir les fletxes de rotació del cub de vista",
"prefs.invert_navigator_arrows_note": "Les fletxes al voltant del cub orbiten la càmera cap on apunten, com a Fusion i Inventor: la fletxa de l'esquerra apunta a la dreta i gira la càmera a la dreta. Desactivades: una fletxa gira el cub cap al costat on és, així la fletxa de l'esquerra gira la càmera a l'esquerra. Les fletxes de gir corbes no en queden afectades.",
"prefs.keep_unsaved_also_asks": "Continuar preguntant quan es tanca la pestanya",
"prefs.keep_unsaved_also_asks_tip": "Amb això desactivat, la pestanya es tanca sense la finestra del navegador un cop la còpia de cada disseny sense desar està al dia.",
"prefs.keep_unsaved_in_browser": "Conservar la feina sense desar en aquest navegador",
@@ -2721,7 +2717,6 @@
"prefs.move_drag_highlights_tip": "Desactivat: mentre les fletxes de Moure arrosseguen un component, no es ressalta res del que hi ha sota el punter, cosa que manté àgils els ensamblatges grans. Amb una secció o una anàlisi mostrades, es ressalta igualment, i una peça arrossegada al llarg de les seves unions no ho ressalta mai.",
"prefs.navigation": "Navegació",
"prefs.navigation.axes_signed": "Eixos",
"prefs.navigation.cube_compact": "Cub",
"prefs.navigation.device": "Dispositiu",
"prefs.navigation.free": "Lliure",
"prefs.navigation.orbit_style": "Estil d'òrbita",
@@ -2729,7 +2724,6 @@
"prefs.navigation.turntable": "Plat giratori",
"prefs.navigation_note": "Com mouen la vista el ratolí, el panel tàctil i el ratolí 3D.",
"prefs.navigator_fit": "Ajustar el model després d'un canvi de vista del navegador",
"prefs.navigator_fit_note": "Una cara, cantonada o fletxa del cub de vista, o un eix del gizmo, també miren el centre del model des de la distància que l'ajusta des de tots els costats (la seva esfera envolupant). Inici fa servir el mateix zoom i centre mentre això està activat, així les vistes triades després només giren. Desactivat: la vista gira al seu lloc, mantenint el seu objectiu i zoom.",
"prefs.network_host_remove": "Eliminar",
"prefs.network_locations": "Permetre ubicacions de xarxa als dissenys",
"prefs.network_locations_note": "Per a organitzacions que desen dissenys en servidors de fitxers. Desactivat: només els servidors que permeteu.",
-6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Vytvořit hlášení…",
"Cross curves": "Příčné křivky",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter pokračuje na GitHubu.",
"Cube": "Krychle",
"Current orthographic view (1:1 mm)": "Aktuální pravoúhlý pohled (1:1 mm)",
"Current perspective view": "Aktuální perspektivní pohled",
"Current plane": "Aktuální rovina",
@@ -2659,9 +2658,6 @@
"prefs.interface_note": "Vzhled aplikace SolidCraft a rozmístění jejích částí.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Obrátit zakřivené šipky natočení",
"prefs.invert_curved_roll_arrows_note": "Dvě zakřivené šipky v rohu krychle natáčejí pohled v rovině obrazovky. Zapnuto: šipka po směru hodinových ručiček natáčí pohled proti směru hodinových ručiček a šipka proti směru hodinových ručiček ho natáčí po směru. Vypnuto: každá šipka natáčí pohled tak, jak je zakřivená.",
"prefs.invert_navigator_arrows": "Obrátit šipky otáčení krychle pohledu",
"prefs.invert_navigator_arrows_note": "Šipky kolem krychle otáčejí kameru tím směrem, kam míří, jako ve Fusion a Inventor: šipka vlevo míří doprava a stáčí kameru doprava. Vypnuto: šipka otáčí krychli na stranu, na které leží, takže šipka vlevo stáčí kameru doleva. Zakřivené šipky natočení tím nejsou dotčeny.",
"prefs.keep_unsaved_also_asks": "Přesto se ptát při zavření karty",
"prefs.keep_unsaved_also_asks_tip": "Když je vypnuto, karta se zavře bez dotazu prohlížeče, jakmile je kopie každého neuloženého návrhu aktuální.",
"prefs.keep_unsaved_in_browser": "Uchovávat neuloženou práci v tomto prohlížeči",
@@ -2721,7 +2717,6 @@
"prefs.move_drag_highlights_tip": "Vypnuto: když šipky Přesunout táhnou komponentu, nic pod ukazatelem se nezvýrazní, takže velké sestavy zůstanou plynulé. Při zobrazeném řezu nebo analýze se zvýrazňuje i tak a díl tažený po svých spojích nezvýrazňuje nikdy.",
"prefs.navigation": "Navigace",
"prefs.navigation.axes_signed": "Osy",
"prefs.navigation.cube_compact": "Krychle",
"prefs.navigation.device": "Zařízení",
"prefs.navigation.free": "Volné",
"prefs.navigation.orbit_style": "Styl otáčení",
@@ -2729,7 +2724,6 @@
"prefs.navigation.turntable": "Otočný stůl",
"prefs.navigation_note": "Jak myš, trackpad a 3D myš pohybují pohledem.",
"prefs.navigator_fit": "Přizpůsobit model po změně pohledu v navigátoru",
"prefs.navigator_fit_note": "Plocha, roh nebo šipka krychle pohledu, případně osa orientačního ovladače, také zaměří pohled na střed modelu z jediné vzdálenosti, ze které je celý vidět ze všech stran (jeho ohraničující koule). Domů při zapnutí používá stejné přiblížení a střed, takže pohledy zvolené po něm se jen otáčejí. Vypnuto: pohled se otáčí na místě a zachovává svůj cíl a přiblížení.",
"prefs.network_host_remove": "Odebrat",
"prefs.network_locations": "Povolit síťová umístění v návrzích",
"prefs.network_locations_note": "Vypnuto: propojené soubory návrhu na jiném počítači (\\\\server\\share) se otevírají jen ze zde uvedených serverů nebo poté, co jeden povolíte na výzvu.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Opret fejlrapport…",
"Cross curves": "Tværkurver",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter fortsætter på GitHub.",
"Cube": "Terning",
"Current orthographic view (1:1 mm)": "Aktuel visning uden perspektiv (1:1 mm)",
"Current perspective view": "Aktuel perspektivvisning",
"Current plane": "Aktuelt plan",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "SolidCrafts udseende og placeringen af dets dele.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Vend de buede rulningspile om",
"prefs.invert_curved_roll_arrows_note": "De to buede pile i kubens hjørne ruller visningen i skærmplanet. Slået til: Pilen med uret ruller mod uret, og pilen mod uret ruller med uret. Slået fra: Hver pil ruller visningen den vej, den krummer.",
"prefs.invert_navigator_arrows": "Vend visningskubens rotationspile om",
"prefs.invert_navigator_arrows_note": "Pilene rundt om kuben roterer kameraet i den retning, de peger, som i Fusion og Inventor: Pilen til venstre peger mod højre og svinger kameraet til højre. Slået fra: En pil drejer kuben mod den side, den sidder på, så venstre pil svinger kameraet til venstre. De buede rulningspile påvirkes ikke.",
"prefs.keep_work_on_tab_switch": "Behold dialoger og værktøjer åbne, når du skifter design",
"prefs.keep_work_on_tab_switch_note": "Hver designfane beholder sin åbne dialog eller sit åbne værktøj med det, du har valgt og skrevet i den, og sin fane på båndet. De er der igen, når du vender tilbage til designet. Slået fra: Skift af design annullerer dem og viser fanen Model.",
"prefs.labels": "Etiketter",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigation",
"prefs.navigation_note": "Hvordan musen, pegefeltet og 3D-musen styrer visningen.",
"prefs.navigator_fit": "Tilpas modellen efter et skift af visning i navigatoren",
"prefs.navigator_fit_note": "En flade, et hjørne eller en pil på visningskuben eller en akse på orienteringsgizmoen ser også mod modellens midte fra den ene afstand, der får den til at passe fra alle sider (dens omsluttende kugle). Hjem bruger samme zoom og centrum, mens dette er slået til, så visninger, der vælges bagefter, kun drejer. Slået fra: Visningen drejer på stedet og beholder sit mål og sin zoom.",
"prefs.network_host_remove": "Fjern",
"prefs.network_locations": "Tillad netværksplaceringer i designs",
"prefs.network_locations_note": "Slået fra: Et designs linkede filer på en anden computer (\\\\server\\share) åbnes kun fra de servere, der er angivet her, eller efter at du har tilladt en, når du bliver spurgt.",
+59 -6
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Issue erstellen…",
"Cross curves": "Querkurven",
"Ctrl+Enter continues on GitHub.": "Strg+Enter geht weiter zu GitHub.",
"Cube": "Würfel",
"Current orthographic view (1:1 mm)": "Aktuelle orthogonale Ansicht (1:1 mm)",
"Current perspective view": "Aktuelle perspektivische Ansicht",
"Current plane": "Aktuelle Ebene",
@@ -1319,6 +1318,7 @@
"cmd.timeline_stop": "Verlauf anhalten",
"cmd.timeline_suppress": "Merkmal unterdrücken",
"cmd.ui.assistant": "Assistenten ein-/ausblenden",
"cmd.ui.palette": "Befehle suchen",
"cmd.view.navigator": "Eigener Ansichtsnavigator",
"cmd.view.render_browser": "Render-Browser",
"cmd.view.render_dock": "Render-Panel verschieben",
@@ -2221,6 +2221,7 @@
"parts.dimension.width": "Breite",
"parts.family.bearing": "Rillenkugellager (Hüllform)",
"parts.family.button_head_cap_screw": "Linsenkopfschraube mit Innensechskant",
"parts.family.countersunk_socket_screw": "Senkschraube mit Innensechskant",
"parts.family.dowel_pin": "Zylinderstift",
"parts.family.hex_bolt": "Sechskantschraube",
"parts.family.hex_cap_screw_inch": "Sechskantschraube (Zoll)",
@@ -2467,8 +2468,6 @@
"prefs.interface_language": "Oberflächensprache",
"prefs.interface_note": "Legt das Erscheinungsbild von SolidCraft und die Anordnung seiner Bereiche fest.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_navigator_arrows": "Drehpfeile des Ansichtswürfels umkehren",
"prefs.invert_navigator_arrows_note": "Die Pfeile um den Würfel drehen die Kamera in die Richtung, in die sie zeigen, wie in Fusion und Inventor: Der linke Pfeil zeigt nach rechts und schwenkt die Kamera nach rechts. Aus: Ein Pfeil dreht den Würfel zu der Seite hin, an der er sitzt, sodass der linke Pfeil die Kamera nach links schwenkt. Die gebogenen Rollpfeile sind davon nicht betroffen.",
"prefs.keep_work_on_tab_switch": "Dialoge und Werkzeuge beim Wechsel zwischen Konstruktionen offen lassen",
"prefs.keep_work_on_tab_switch_note": "Jeder Konstruktions-Tab behält seinen offenen Dialog oder sein Werkzeug mit allem, was darin ausgewählt und eingegeben wurde, sowie seinen Werkzeugleisten-Tab. Beim Zurückkehren zur Konstruktion ist alles wieder da. Aus: Ein Wechsel bricht sie ab und zeigt den Tab Modell.",
"prefs.labels": "Beschriftungen",
@@ -2524,7 +2523,6 @@
"prefs.move_drag_highlights_tip": "Aus: Solange die Pfeile von Verschieben eine Komponente ziehen, wird nichts unter dem Zeiger hervorgehoben; so bleiben große Baugruppen flüssig. Mit einem Schnitt oder einer Analyse wird es trotzdem hervorgehoben, und ein entlang seiner Gelenke gezogenes Teil hebt nie etwas hervor.",
"prefs.navigation": "Navigation",
"prefs.navigation.axes_signed": "Achsen",
"prefs.navigation.cube_compact": "Würfel",
"prefs.navigation.device": "Gerät",
"prefs.navigation.free": "Frei",
"prefs.navigation.orbit_style": "Drehstil",
@@ -2532,7 +2530,6 @@
"prefs.navigation.turntable": "Drehteller",
"prefs.navigation_note": "Legt fest, wie Maus, Trackpad und 3D-Maus die Ansicht steuern.",
"prefs.navigator_fit": "Modell nach einem Ansichtswechsel am Navigator einpassen",
"prefs.navigator_fit_note": "Eine Fläche, Ecke oder ein Pfeil des Würfels oder eine Achse des Gizmos blickt auch auf die Mitte des Modells, aus dem einen Abstand, der es von jeder Seite einpasst (seine Hüllkugel). Home nutzt denselben Zoom und dieselbe Mitte, solange dies an ist, sodass danach gewählte Ansichten sich nur drehen. Aus: Die Ansicht dreht sich an Ort und Stelle und behält Ziel und Zoom.",
"prefs.network_host_remove": "Entfernen",
"prefs.network_locations": "Netzwerkspeicherorte in Designs zulassen",
"prefs.network_locations_note": "Für Organisationen, die Entwürfe auf Dateiservern speichern. Aus: nur erlaubte Server.",
@@ -2591,6 +2588,8 @@
"prefs.remember_dialog_values.help": "Standardmäßig aus. Wenn aktiviert, werden nach erfolgreicher Ausführung eines Befehls seine sicheren Werte und Optionen beim nächsten Öffnen desselben Befehls wiederverwendet, bis SolidCraft geschlossen wird.",
"prefs.render_legacy_image_defaults": "Bisherige Render-Bildvorgaben verwenden",
"prefs.render_legacy_image_defaults_help": "Den bisherigen manuellen Ablauf beibehalten: neue Setups mit CPU-Raster, feste Abtastung beim CPU-Raytracing und manuell geöffnete Ergebnisse. Beim Wechsel zu Render die Ansicht neu einpassen, Aufnahmen ohne Wiederherstellung ihrer Kamera wechseln und die bisherige Bildausrichtung und Navigation verwenden. Automatische Live-Bildrückmeldungen deaktivieren. Gespeicherte Setups bleiben unverändert.",
"prefs.render_legacy_memory_admission": "Bisherige Render-Speicherprüfung verwenden",
"prefs.render_legacy_memory_admission_help": "Native Renderaufträge innerhalb der festen gemeinsamen und hostspezifischen Speichergrenzen zulassen, auch wenn der geschätzte Bedarf den verfügbaren RAM übersteigt. Gerätegrenzen und Prüfungen der Speicherzuweisung gelten weiterhin.",
"prefs.render_previous_geometry_budget": "Bisheriges Geometriebudget für die Render-Vorschau verwenden",
"prefs.render_previous_geometry_budget_help": "Stellt die bisherige feste Obergrenze für die Geometrie wieder her (512 MiB auf 64-Bit-Desktop-Systemen, andernfalls 128 MiB) anstelle des geräteabhängigen Budgets.",
"prefs.reports_saved": "{n} gespeichert",
@@ -2974,8 +2973,10 @@
"render.category_paint": "Lack",
"render.category_plastic": "Kunststoff",
"render.category_wood": "Holz",
"render.caustics_area_lights": "Kaustiken von Flächenlichtern",
"render.caustics_depth": "Kaustik-Pfadtiefe",
"render.caustics_enable": "Glaskaustiken durch Punkt- und Richtungslichter",
"render.caustics_environment": "Kaustiken aus der Umgebung",
"render.caustics_help": "Die Photonenzahl bestimmt Rauschen und Aufwand. Der Sammelradius tauscht scharfe Details gegen eine glattere Schätzung. CPU benötigt indirekte Beleuchtung.",
"render.caustics_path_required": "CPU-Kaustiken benötigen indirekte Beleuchtung.",
"render.caustics_photons": "Photonen",
@@ -3152,7 +3153,7 @@
"render.cpu_opaque_required": "CPU-Raytracing benötigt einen undurchsichtigen Hintergrund. Entfernen Sie das Häkchen bei „Transparenter Hintergrund“, um fortzufahren.",
"render.cpu_options_pending": "Dieses CPU-Bedienfeld kann Emission, Oberflächeneffekte, Glas oder Anisotropie noch nicht bearbeiten. Entfernen Sie diese API-Optionen, bevor Sie es verwenden.",
"render.cpu_path_depth": "Pfadtiefe",
"render.cpu_path_help": "Berechnet Licht, das zwischen undurchsichtigen Flächen und durch unterstützte Glasformen läuft. Erfordert progressives Rendern und Raytracing-Schatten. Die Pfadtiefe zählt beleuchtete Flächen, Glasgrenzen und Streuereignisse ab dem ersten Treffer (1–16). Tiefe 1 liefert direkte Beleuchtung auf undurchsichtigen Flächen; höhere Werte erlauben mehr Reflexionen oder Glasdurchgänge und können länger dauern. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
"render.cpu_path_help": "Berechnet Licht, das zwischen undurchsichtigen Flächen und durch unterstützte Glasformen läuft. Erfordert progressives Rendern und Raytracing-Schatten. Die Pfadtiefe zählt beleuchtete Flächen, Glasgrenzen und Streuereignisse ab dem ersten Treffer (1–64). Tiefe 1 liefert direkte Beleuchtung auf undurchsichtigen Flächen; höhere Werte erlauben mehr Reflexionen oder Glasdurchgänge und können länger dauern. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
"render.cpu_path_lighting": "Indirekte Beleuchtung",
"render.cpu_path_optics_reason": "Die Einstellungen bleiben erhalten. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
"render.cpu_point": "Punktlicht",
@@ -3192,6 +3193,12 @@
"render.cpu_subsurface_denoise_reason": "Streuung im Glas auf der CPU benötigt ausgeschaltete Rauschreduzierung. Die Einstellungen bleiben erhalten.",
"render.cpu_subsurface_help": "Streuung lenkt Licht im Inneren geschlossenen Glases um. Positive Raten benötigen indirekte Beleuchtung ohne Rauschreduzierung. Die Rate zählt Kollisionen pro mm; 0 fügt keine Streuung hinzu. Positive Richtungswerte bevorzugen vorwärts, negative rückwärts.",
"render.cpu_summary": "CPU-Raytracing · Reflexionen · HDR · Richtungs- und Punktlichter",
"render.cpu_threads": "CPU-Threads",
"render.cpu_threads_auto": "Auto",
"render.cpu_threads_custom": "Eigene",
"render.cpu_threads_help": "Auto verwendet die verfügbaren CPU-Threads. Ein Thread arbeitet seriell. Mehr Threads können mehr Speicher benötigen.",
"render.cpu_threads_single": "Einzeln",
"render.cpu_threads_web": "Im Browser wird ein kooperativer Thread verwendet. Die gespeicherte Desktop-Auswahl bleibt erhalten.",
"render.cpu_transparent_help": "Transparentes PNG entfernt den Hintergrund und erhält die Kantenabdeckung. Körper- und Flächendeckkraft müssen 100 % bleiben.",
"render.cpu_volume_enable_path": "Indirekte Beleuchtung einschalten",
"render.cpu_volume_enable_path_plan": "CPU-Raytracing · Pfadtiefe {depth} · {spp} Samples/Pixel · Seed {seed} · Raytracing-Schatten. Atmosphäre und Bildbudget bleiben erhalten.",
@@ -3200,6 +3207,40 @@
"render.cpu_volume_method_incompatible": "Diese Licht-, Sampling- oder Atmosphäreinstellungen lassen sich nicht auf diese Methode übertragen. Die Einstellungen bleiben erhalten.",
"render.cpu_volume_path_required": "Indirekte Beleuchtung einschalten, um CPU-Atmosphäre zu nutzen.",
"render.cpu_warm_cool": "Warm und kühl",
"render.denoise_class_background": "Übersprungen: Hintergrund",
"render.denoise_class_coverage": "Übersprungen: teilweise Abdeckung",
"render.denoise_class_degenerate": "Übersprungen: entarteter Pfad",
"render.denoise_class_depth": "Übersprungen: Pfadtiefengrenze",
"render.denoise_class_emitter": "Übersprungen: Lichtquelle",
"render.denoise_class_exit": "Übersprungen: unpassender Austritt",
"render.denoise_class_identity": "Übersprungen: Grenze der Führungskennungen",
"render.denoise_class_lens": "Übersprungen: Schärfentiefe",
"render.denoise_class_material": "Übersprungen: nicht unterstütztes Material",
"render.denoise_class_nested": "Übersprungen: verschachteltes Medium",
"render.denoise_class_optical": "Geeignet: Oberfläche durch Brechung",
"render.denoise_class_reflection": "Übersprungen: reflektierender Endpunkt",
"render.denoise_class_surface": "Geeignet: Oberfläche",
"render.denoise_class_tir": "Übersprungen: Totalreflexion",
"render.denoise_class_transmission": "Übersprungen: weitere Transmission",
"render.denoise_class_unavailable": "Übersprungen: keine Führungsdaten",
"render.denoise_class_volume": "Übersprungen: Volumenstreuung",
"render.denoise_diagnostics_counts": "Geeignet: {eligible} · Übersprungen: {skipped}",
"render.denoise_diagnostics_enable": "Entrauschungsdiagnose erfassen",
"render.denoise_diagnostics_grid": "Pixelzahlen bei interner Größe {width} × {height}.",
"render.denoise_diagnostics_help": "Erfasst geeignete und übersprungene Pixel auch ohne Entrauschung. Die Zählung nutzt die interne Rendergröße, die Maske die Ausgabegröße.",
"render.denoise_diagnostics_title": "Entrauschungsdiagnose",
"render.denoise_export_mask": "Maske als PNG exportieren",
"render.denoise_firefly": "Einzelne helle Pixel unterdrücken",
"render.denoise_format_mask": "Maske PNG",
"render.denoise_mask_copy_help": "Exportieren Sie die Entrauschungsmaske als PNG oder kehren Sie zum gerenderten Bild zurück, um es zu kopieren.",
"render.denoise_mask_unavailable": "Aktivieren Sie die Entrauschungsdiagnose und rendern Sie ein Bild, um seine Maske zu prüfen.",
"render.denoise_optical_filter": "Spiegel und Glas filtern",
"render.denoise_optical_filter_help": "Geeignetes reflektiertes und durchgelassenes Licht filtern. Ausschalten, um nur undurchsichtige Oberflächen zu entrauschen.",
"render.denoise_passes": "Filterdurchläufe",
"render.denoise_settings_help": "Mehr Durchläufe glätten einen größeren Bereich. Die Stärke regelt die Filterwirkung jedes Durchlaufs. Nicht unterstützte Lichtwege bleiben ungefiltert.",
"render.denoise_strength": "Entrauschungsstärke",
"render.denoise_view_image": "Gerendertes Bild",
"render.denoise_view_mask": "Entrauschungsmaske",
"render.diag_bodies": "Sichtbare Körper",
"render.diag_job_details": "Erfasste Auftragsdetails",
"render.diag_last_check": "Letzte Vorabprüfung",
@@ -3754,6 +3795,8 @@
"render.material_transient": "Vorübergehende optische Überschreibungen für diesen Dokument-Tab. Sie werden weder im Design noch im Render-Setup oder in der Materialbibliothek gespeichert.",
"render.material_v_up": "Textur-V zeigt im Bild nach oben",
"render.material_weight": "Klarlack-Gewicht (0–1)",
"render.metal_energy_compensation": "Reflexion rauer Metalle wiederherstellen",
"render.metal_energy_compensation_help": "Gleicht den Lichtverlust zwischen den Mikrofacetten rauer Metalle aus. Die CPU benötigt indirekte Beleuchtung.",
"render.method_cpu": "CPU-Raytracing",
"render.method_gpu": "GPU-Pathtracing",
"render.method_limit_raster": "Die Rasterung verwendet die feste undurchsichtige Studiovorschau. Wählen Sie ausdrücklich eine Raytracing-Methode für die unterstützten optischen Einstellungen.",
@@ -3882,6 +3925,8 @@
"render.quality_denoise_on": "Rauschreduzierung: Ein",
"render.quality_draft": "Entwurf",
"render.quality_high": "Hoch",
"render.quality_legacy_samples": "Gespeicherter Wert: {samples} spp. Neue Werte sind auf {maximum} spp begrenzt.",
"render.quality_preset_limit": "Diese Vorgabe überschreitet die Auswahlgrenze von 16384 spp. Wählen Sie höchstens 16384 Samples.",
"render.quality_projection_help": "Diese Prüfung ändert nur Auflösung, Abtastbudget, Pfadtiefe, Startwert und Rauschreduzierung. Andere Einstellungen bleiben erhalten; das Budget garantiert keine Konvergenz.",
"render.quality_review": "Sampling-Budget prüfen",
"render.quality_summary": "{width} × {height} · {samples} Samples · Tiefe {depth} · Startwert {seed} · {denoise}",
@@ -4029,6 +4074,7 @@
"render.show_shots_label": "Aufnahmenleiste einblenden",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "Benutzerdefiniert…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
@@ -4191,6 +4237,11 @@
"render.subsurface_enable": "Random Walk für innere Streuung",
"render.subsurface_help": "Nur für geschlossene Dielektrika. Streuung und RGB-Absorption sind getrennte Koeffizienten pro mm. Pfade streuen innerhalb der tatsächlichen Geometrie und werden dann an der Grenzfläche gebrochen oder reflektiert. Deaktivieren Sie die Transmission gerader Schattenstrahlen, das Entrauschen von Oberflächen und das äußere Volumen; verwenden Sie eine Fresnel-Schicht. Kein Diffusions-Shader, kein Dichtegitter und kein gemessenes Material. Dichte Medien können mehr Samples und Pfadtiefe benötigen.",
"render.subsurface_rate": "Streukollisionsrate",
"render.supersampling": "Supersampling",
"render.supersampling_dimensions": "Rendert {width} × {height} und verkleinert auf {output_width} × {output_height}. Verwendet die vierfache Pixelzahl.",
"render.supersampling_help": "Mit doppelter Breite und Höhe rendern und anschließend im linearen Farbraum verkleinern. Die Ausgabegröße bleibt unverändert.",
"render.supersampling_off": "Aus",
"render.supersampling_x2": "2×",
"render.surface_import.author": "Autor",
"render.surface_import.busy": "Bild wird importiert…",
"render.surface_import.complete": "Wählen Sie ein Bild und geben Sie Autor, Quelle und Lizenz an.",
@@ -4204,6 +4255,8 @@
"render.surface_import.saved_cpu": "Speichern Sie Texturen für das CPU-Rendering.",
"render.surface_import.source": "Quelle",
"render.surface_import.title": "Bild importieren",
"render.texture_filtering": "Texturdetails filtern",
"render.texture_filtering_help": "Mip-Stufen und anisotrope Filterung verringern Flimmern verkleinerter Texturen. Benötigt zusätzlichen Speicher; ausgeschaltet bleibt die bisherige Filterung.",
"render.tone_aces_fitted": "ACES angepasst",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPU-Glas unterstützt zertifizierte getrennte Medien. Unbeschränkt verschachtelte Medien und ein rauschfreier Transmissionsschätzer werden nicht bereitgestellt.",
+68 -10
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Create Issue…",
"Cross curves": "Cross curves",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter continues on GitHub.",
"Cube": "Cube",
"Current orthographic view (1:1 mm)": "Current orthographic view (1:1 mm)",
"Current perspective view": "Current perspective view",
"Current plane": "Current plane",
@@ -1509,6 +1508,7 @@
"cmd.timeline_stop": "Stop History",
"cmd.timeline_suppress": "Suppress Feature",
"cmd.ui.assistant": "Toggle Assistant",
"cmd.ui.palette": "Search Commands",
"cmd.view.navigator": "Custom View Navigator",
"cmd.view.render_browser": "Render Browser",
"cmd.view.render_dock": "Move Render Dock",
@@ -1889,13 +1889,13 @@
"help.sketch.construction_line": "Create an infinite construction line through two points. Use it to lay out a sketch without splitting profiles.",
"help.sketch.construction_line.angle": "Create an infinite construction line at an angle from sketch X. Pick the point and type the angle, or pick its direction.",
"help.sketch.construction_line.bisector": "Pick two intersecting straight sketch lines, then choose either angle bisector. The result follows its source lines.",
"help.sketch.construction_line.horizontal": "Create an infinite horizontal construction line through a point. Its horizontal constraint follows later edits.",
"help.sketch.construction_line.parallel": "Pick a straight sketch line, then place a parallel infinite construction line. Right-click to choose another reference.",
"help.sketch.construction_line.horizontal": "Create an infinite horizontal construction line through a point, or type its distance from the sketch X axis. A typed distance becomes a driving dimension. Its horizontal constraint follows later edits.",
"help.sketch.construction_line.parallel": "Pick a straight sketch line, then place a parallel infinite construction line or type its offset. A typed offset becomes a driving distance. Right-click to choose another reference.",
"help.sketch.construction_line.perpendicular": "Pick a straight sketch line, then place a perpendicular infinite construction line. Right-click to choose another reference.",
"help.sketch.construction_line.tangent_at": "Create a tangent at a point on a circle or circular arc, or an endpoint of an open spline or conic. Pick the curve, then its contact point.",
"help.sketch.construction_line.tangent_external": "Pick a circle or circular arc, then an external point and a tangent solution. The previews show only contacts on the drawn arc.",
"help.sketch.construction_line.tangent_two": "Pick two circles or circular arcs, then choose a common tangent in the preview. Contacts must lie on the drawn curves.",
"help.sketch.construction_line.vertical": "Create an infinite vertical construction line through a point. Its vertical constraint follows later edits.",
"help.sketch.construction_line.vertical": "Create an infinite vertical construction line through a point, or type its distance from the sketch Y axis. A typed distance becomes a driving dimension. Its vertical constraint follows later edits.",
"help.sketch.copy": "Copies the selected sketch curves and points, together with the constraints and dimensions among them. Paste them into this sketch or another one.",
"help.sketch.create": "Starts a new 2D sketch on a plane or on a flat face of the model. Every extrude, revolve and sweep begins with a sketch.",
"help.sketch.cut": "Copies the selected sketch geometry and deletes it from the sketch, in one undo step. Paste it elsewhere in this sketch or into another one.",
@@ -2431,6 +2431,7 @@
"parts.dimension.width": "Width",
"parts.family.bearing": "Deep groove ball bearing (envelope)",
"parts.family.button_head_cap_screw": "Button head cap screw",
"parts.family.countersunk_socket_screw": "Countersunk socket screw",
"parts.family.dowel_pin": "Parallel pin (dowel)",
"parts.family.hex_bolt": "Hex head bolt / screw",
"parts.family.hex_cap_screw_inch": "Hex cap screw (inch)",
@@ -2681,9 +2682,9 @@
"prefs.interface_note": "How SolidCraft looks and where its parts sit.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Invert the curved roll arrows",
"prefs.invert_curved_roll_arrows_note": "The two curved arrows at the cube's corner roll the view within the screen plane. On: the clockwise arrow rolls counterclockwise and the counterclockwise arrow rolls clockwise. Off: each arrow rolls the view the way it curls.",
"prefs.invert_navigator_arrows": "Invert the view cube rotation arrows",
"prefs.invert_navigator_arrows_note": "The arrows around the cube orbit the camera the way they point, as in Fusion and Inventor: the arrow on the left points right and swings the camera right. Off: an arrow rotates the cube toward the side it sits on, so the left arrow swings the camera left. The curved roll arrows are not affected.",
"prefs.invert_curved_roll_arrows_note": "The two curved arrows at the navigator's corner roll the view within the screen plane. On: the clockwise arrow rolls counterclockwise and the counterclockwise arrow rolls clockwise. Off: each arrow rolls the view the way it curls.",
"prefs.invert_navigator_arrows": "Invert the navigator rotation arrows",
"prefs.invert_navigator_arrows_note": "The arrows around the navigator orbit the camera the way they point, as in Fusion and Inventor: the arrow on the left points right and swings the camera right. Off: an arrow rotates the view toward the side it sits on, so the left arrow swings the camera left. The curved roll arrows are not affected.",
"prefs.keep_unsaved_also_asks": "Still ask when the tab closes",
"prefs.keep_unsaved_also_asks_tip": "With this off, the tab closes without the browser's box once every unsaved design's copy is current.",
"prefs.keep_unsaved_in_browser": "Keep unsaved work in this browser",
@@ -2743,7 +2744,6 @@
"prefs.move_drag_highlights_tip": "Off: while Move's arrows drag a component, nothing under the pointer is highlighted, which keeps large assemblies smooth. With a section or an analysis shown it is highlighted all the same, and a part dragged along its joints never highlights it.",
"prefs.navigation": "Navigation",
"prefs.navigation.axes_signed": "Axes",
"prefs.navigation.cube_compact": "Cube",
"prefs.navigation.device": "Device",
"prefs.navigation.free": "Free",
"prefs.navigation.orbit_style": "Orbit style",
@@ -2751,7 +2751,7 @@
"prefs.navigation.turntable": "Turntable",
"prefs.navigation_note": "How the mouse, trackpad and 3D mouse move the view.",
"prefs.navigator_fit": "Fit the model after a navigator view change",
"prefs.navigator_fit_note": "A face, corner or arrow of the view cube, or an axis of the gizmo, also looks at the middle of the model from the one distance that fits it from every side (its bounding sphere). Home uses the same zoom and centre while this is on, so views chosen after it only turn. Off: the view turns in place, keeping its target and zoom.",
"prefs.navigator_fit_note": "A face, corner or arrow of a navigator, or an axis of the gizmo, also looks at the middle of the model from the one distance that fits it from every side (its bounding sphere). Home uses the same zoom and centre while this is on, so views chosen after it only turn. Off: the view turns in place, keeping its target and zoom.",
"prefs.network_host_remove": "Remove",
"prefs.network_locations": "Allow network locations in designs",
"prefs.network_locations_note": "For organizations that keep designs on file servers. Off: only servers you allow.",
@@ -2826,6 +2826,8 @@
"prefs.remember_dialog_values.help": "Off by default. When on, after a command succeeds, its safe values and options are reused the next time you open the same command, until SolidCraft closes.",
"prefs.render_legacy_image_defaults": "Use previous Render image defaults",
"prefs.render_legacy_image_defaults_help": "Keep the previous manual workflow: new setups use CPU raster, CPU ray tracing keeps fixed sampling, and you open rendered results manually. Refit on Render entry, switch shots without restoring their cameras, and use the previous framing and navigation. Disable automatic live image feedback. Saved setups are unchanged.",
"prefs.render_legacy_memory_admission": "Use previous render memory checks",
"prefs.render_legacy_memory_admission_help": "Allow native renders within the fixed shared and host memory budgets even when their estimate exceeds available RAM. Device limits and allocation checks still apply.",
"prefs.render_previous_geometry_budget": "Use previous Render preview geometry budget",
"prefs.render_previous_geometry_budget_help": "Restore the previous fixed geometry limit (512 MiB on 64-bit desktop, 128 MiB elsewhere) instead of the device-based budget.",
"prefs.reports_saved": "{n} saved",
@@ -3220,8 +3222,10 @@
"render.category_paint": "Paint",
"render.category_plastic": "Plastic",
"render.category_wood": "Wood",
"render.caustics_area_lights": "Area light caustics",
"render.caustics_depth": "Caustic path depth",
"render.caustics_enable": "Glass caustics from point and directional lights",
"render.caustics_environment": "Environment caustics",
"render.caustics_help": "Photon count controls noise and work. Gather radius trades sharp detail for a smoother estimate. CPU requires Indirect lighting.",
"render.caustics_path_required": "CPU caustics require Indirect lighting.",
"render.caustics_photons": "Photons",
@@ -3398,7 +3402,7 @@
"render.cpu_opaque_required": "CPU ray tracing needs an opaque background. Clear Transparent background to continue.",
"render.cpu_options_pending": "This CPU panel cannot edit emission, surface effects, glass or anisotropy yet. Clear those API options before using it.",
"render.cpu_path_depth": "Path depth",
"render.cpu_path_help": "Adds light bouncing between opaque surfaces and through supported Glass shapes. Requires progressive rendering and traced shadows. Path depth counts shaded surfaces, Glass boundaries and scattering events, starting with the first hit (1–16). Depth 1 gives direct lighting on opaque surfaces; higher values allow more bounces or Glass crossings and may take longer. Turn off Indirect lighting to use custom reflections or anisotropy.",
"render.cpu_path_help": "Adds light bouncing between opaque surfaces and through supported Glass shapes. Requires progressive rendering and traced shadows. Path depth counts shaded surfaces, Glass boundaries and scattering events, starting with the first hit (1–64). Depth 1 gives direct lighting on opaque surfaces; higher values allow more bounces or Glass crossings and may take longer. Turn off Indirect lighting to use custom reflections or anisotropy.",
"render.cpu_path_lighting": "Indirect lighting",
"render.cpu_path_optics_reason": "Your settings are kept. Turn off Indirect lighting to use custom reflections or anisotropy.",
"render.cpu_point": "Point light",
@@ -3438,6 +3442,12 @@
"render.cpu_subsurface_denoise_reason": "CPU subsurface scattering needs Denoise off. Your settings are kept.",
"render.cpu_subsurface_help": "Scattering redirects light inside closed Glass. Positive rates need Indirect lighting with Denoise off. The rate is collisions per mm; 0 adds no scattering. Positive phase bias favors forward directions; negative favors backward.",
"render.cpu_summary": "CPU ray tracing · reflections · HDR · directional and point lights",
"render.cpu_threads": "CPU workers",
"render.cpu_threads_auto": "Auto",
"render.cpu_threads_custom": "Custom",
"render.cpu_threads_help": "Auto uses available CPU workers. One worker runs serially. More workers can need more memory.",
"render.cpu_threads_single": "Single",
"render.cpu_threads_web": "Browser rendering uses one cooperative worker. The saved desktop worker choice is preserved.",
"render.cpu_transparent_help": "Transparent PNG removes the background and keeps edge coverage. Body and face opacity must stay at 100%.",
"render.cpu_volume_enable_path": "Enable Indirect lighting",
"render.cpu_volume_enable_path_plan": "CPU ray tracing · path depth {depth} · {spp} samples/pixel · seed {seed} · traced shadows. Keeps the authored atmosphere and image budget.",
@@ -3446,6 +3456,40 @@
"render.cpu_volume_method_incompatible": "These light, sampling or atmosphere settings cannot be transferred to this method. Your settings are kept.",
"render.cpu_volume_path_required": "Enable Indirect lighting to use CPU Atmosphere.",
"render.cpu_warm_cool": "Warm and cool",
"render.denoise_class_background": "Skipped: background",
"render.denoise_class_coverage": "Skipped: partial coverage",
"render.denoise_class_degenerate": "Skipped: degenerate path",
"render.denoise_class_depth": "Skipped: path depth limit",
"render.denoise_class_emitter": "Skipped: light emitter",
"render.denoise_class_exit": "Skipped: incompatible exit",
"render.denoise_class_identity": "Skipped: guide identity limit",
"render.denoise_class_lens": "Skipped: depth of field",
"render.denoise_class_material": "Skipped: unsupported material",
"render.denoise_class_nested": "Skipped: nested medium",
"render.denoise_class_optical": "Eligible: refracted surface",
"render.denoise_class_reflection": "Skipped: reflective endpoint",
"render.denoise_class_surface": "Eligible: surface",
"render.denoise_class_tir": "Skipped: total internal reflection",
"render.denoise_class_transmission": "Skipped: further transmission",
"render.denoise_class_unavailable": "Skipped: no guide",
"render.denoise_class_volume": "Skipped: volume scattering",
"render.denoise_diagnostics_counts": "Eligible: {eligible} · Skipped: {skipped}",
"render.denoise_diagnostics_enable": "Collect denoising diagnostics",
"render.denoise_diagnostics_grid": "Pixel counts at internal size {width} × {height}.",
"render.denoise_diagnostics_help": "Collects eligible and skipped pixel counts even when denoising is off. Counts use the internal render size; the mask uses the output size.",
"render.denoise_diagnostics_title": "Denoising diagnostics",
"render.denoise_export_mask": "Export mask PNG",
"render.denoise_firefly": "Suppress isolated bright pixels",
"render.denoise_format_mask": "Mask PNG",
"render.denoise_mask_copy_help": "Export the denoising mask as PNG, or return to the rendered image to copy it.",
"render.denoise_mask_unavailable": "Enable denoising diagnostics and render an image to inspect its mask.",
"render.denoise_optical_filter": "Filter mirror and glass",
"render.denoise_optical_filter_help": "Filter eligible reflected and transmitted light. Turn off to denoise opaque surfaces only.",
"render.denoise_passes": "Filter passes",
"render.denoise_settings_help": "More passes smooth a wider area. Strength controls how much filtering each pass applies. Unsupported optical paths remain unfiltered.",
"render.denoise_strength": "Denoising strength",
"render.denoise_view_image": "Rendered image",
"render.denoise_view_mask": "Denoising mask",
"render.diag_bodies": "Visible bodies",
"render.diag_job_details": "Captured job details",
"render.diag_last_check": "Last preflight",
@@ -4000,6 +4044,8 @@
"render.material_transient": "Temporary optical overrides for this document tab. They are not saved in the design, render setup or material library.",
"render.material_v_up": "Texture V points upward in the image",
"render.material_weight": "Coat weight (0–1)",
"render.metal_energy_compensation": "Recover rough metal reflectance",
"render.metal_energy_compensation_help": "Compensate for light lost between rough metal microfacets. CPU rendering needs Indirect lighting.",
"render.method_cpu": "CPU ray tracing",
"render.method_gpu": "GPU path tracing",
"render.method_limit_raster": "Raster uses the fixed opaque studio preview. Select an explicit raytracing method for supported optical controls.",
@@ -4128,6 +4174,8 @@
"render.quality_denoise_on": "Noise reduction: On",
"render.quality_draft": "Draft",
"render.quality_high": "High",
"render.quality_legacy_samples": "Saved value: {samples} spp. New selections are limited to {maximum} spp.",
"render.quality_preset_limit": "This preset exceeds the 16384 spp selection limit. Choose a sample count up to 16384.",
"render.quality_projection_help": "This review changes only resolution, sample budget, path depth, seed and noise reduction. Other settings are kept; the budget does not certify convergence.",
"render.quality_review": "Review sampling budget",
"render.quality_summary": "{width} × {height} · {samples} samples · depth {depth} · seed {seed} · {denoise}",
@@ -4275,6 +4323,7 @@
"render.show_shots_label": "Show Shots strip",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "Custom…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
@@ -4437,6 +4486,11 @@
"render.subsurface_enable": "Interior scattering random walk",
"render.subsurface_help": "Closed dielectric only. Scattering and RGB absorption are separate per-mm coefficients. Paths scatter inside actual geometry, then refract or reflect at the boundary. Disable straight shadow transmission, surface denoising and exterior volume; use one Fresnel stratum. Not a diffusion shader, density grid or measured material. Dense media may need more samples and path depth.",
"render.subsurface_rate": "Scattering collision rate",
"render.supersampling": "Supersampling",
"render.supersampling_dimensions": "Renders {width} × {height} and reduces to {output_width} × {output_height}. Uses four times the pixels.",
"render.supersampling_help": "Render at twice the width and height, then reduce in linear light. The output size stays unchanged.",
"render.supersampling_off": "Off",
"render.supersampling_x2": "2×",
"render.surface_import.author": "Author",
"render.surface_import.busy": "Importing image…",
"render.surface_import.complete": "Choose an image and enter its author, source and license.",
@@ -4450,6 +4504,8 @@
"render.surface_import.saved_cpu": "Save maps to use them with CPU rendering.",
"render.surface_import.source": "Source",
"render.surface_import.title": "Import image",
"render.texture_filtering": "Filter texture detail",
"render.texture_filtering_help": "Use mip levels and anisotropic filtering to reduce texture aliasing under minification. Uses more memory; off retains the previous filtering.",
"render.tone_aces_fitted": "ACES fitted",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPU glass supports certified disjoint media. Unrestricted nested media and a noise-free transmission estimator are not provided.",
@@ -5242,12 +5298,14 @@
"ui.constraints_conflict": "Constraints conflict",
"ui.construction": "Construction",
"ui.construction.angle_hint": "Construction line: click a point, then type an angle or pick a direction",
"ui.construction.axis_hint": "Construction line: click a point, or type its distance from the sketch axis",
"ui.construction.bisector_hint": "Bisector: pick two intersecting straight lines, then the desired solution",
"ui.construction.common_hint": "Common tangent: pick two circles/arcs, then the desired solution",
"ui.construction.contact_hint": "Tangent: pick a circle/arc or open spline/conic, then the contact point (free-form curves: endpoints)",
"ui.construction.external_hint": "Tangent: pick a circle or arc, an external point, then the desired solution",
"ui.construction.free_hint": "Construction line: click two points; right-click to restart",
"ui.construction.normal_needs_length": "An infinite line is always construction: type a length to draw a normal line",
"ui.construction.parallel_hint": "Construction line: pick a straight line, then click or type the offset; right-click to change the reference",
"ui.construction.point_hint": "Construction line: click the point to pass through",
"ui.construction.reference_hint": "Construction line: pick a straight line, then a point; right-click to change the reference",
"ui.construction.ribbon_label": "X-Lines",
+127 -7
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Crear incidencia…",
"Cross curves": "Curvas transversales",
"Ctrl+Enter continues on GitHub.": "Ctrl+Intro continúa en GitHub.",
"Cube": "Cubo",
"Current orthographic view (1:1 mm)": "Vista ortográfica actual (1:1 mm)",
"Current perspective view": "Vista en perspectiva actual",
"Current plane": "Plano actual",
@@ -265,6 +264,7 @@
"Edge": "Arista",
"Edges": "Aristas",
"Edit": "Editar",
"Edit Holes": "Editar agujeros",
"Edit Profile Sketch": "Editar boceto del perfil",
"Edit shortcuts": "Editar atajos",
"Enable rule": "Activar regla",
@@ -976,6 +976,7 @@
"browser.calibrate_click_a": "Haz clic en la imagen para el Punto A, o escribe sus coordenadas.",
"browser.calibrate_click_b": "Haz clic en la imagen para el Punto B, o escribe sus coordenadas.",
"browser.calibrate_title": "Calibrar: {name}",
"browser.canvas_locked": "El lienzo está bloqueado. Desbloquéalo primero.",
"browser.capture_many": "Se han movido {count} componentes, pero sus posiciones no se han capturado.",
"browser.capture_note": "Capturar posición conserva la nueva posición; Revertir posición restaura la anterior.",
"browser.capture_one": "Se ha movido un componente, pero su posición no se ha capturado.",
@@ -1205,6 +1206,7 @@
"cmd.material_properties_directional": "Módulo elástico direccional",
"cmd.material_properties_validate": "Validar datos del material físico",
"cmd.mesh_convert": "Convertir malla",
"cmd.model_body_features": "Listar operaciones del cuerpo",
"cmd.model_edges": "Listar aristas",
"cmd.model_faces": "Listar caras",
"cmd.model_threads": "Listar roscas",
@@ -1233,8 +1235,11 @@
"cmd.panel.dismiss": "Descartar vista previa de panel",
"cmd.panel.float": "Panel flotante",
"cmd.panel.focus": "Enfocar panel",
"cmd.panel.focus.next": "Enfocar el panel siguiente",
"cmd.panel.focus.previous": "Enfocar el panel anterior",
"cmd.panel.list": "Listar paneles",
"cmd.panel.move": "Mover panel",
"cmd.panel.move.next_zone": "Mover el panel a la zona siguiente",
"cmd.panel.native_close": "Volver a acoplar sección de panel nativo",
"cmd.panel.open": "Abrir panel",
"cmd.panel.peek": "Vista previa de panel colapsado",
@@ -1242,6 +1247,12 @@
"cmd.panel.reset_pair": "Restablecer divisor de panel",
"cmd.panel.resize": "Redimensionar sección de panel",
"cmd.panel.resize_pair": "Redimensionar divisor de panel",
"cmd.panel.sash.down": "Redimensionar el divisor del panel hacia abajo",
"cmd.panel.sash.left": "Redimensionar el divisor del panel hacia la izquierda",
"cmd.panel.sash.reset": "Restablecer el divisor del panel enfocado",
"cmd.panel.sash.right": "Redimensionar el divisor del panel hacia la derecha",
"cmd.panel.sash.up": "Redimensionar el divisor del panel hacia arriba",
"cmd.panel.toggle": "Alternar el panel enfocado",
"cmd.parameters_add": "Añadir parámetro de usuario",
"cmd.parameters_change": "Cambiar parámetros",
"cmd.parameters_comment": "Comentar parámetro",
@@ -1365,6 +1376,7 @@
"cmd.sketch.construction_line.tangent_external": "Tangente por punto externo",
"cmd.sketch.construction_line.tangent_two": "Tangente común",
"cmd.sketch.construction_line.vertical": "Línea de construcción vertical",
"cmd.sketch.replace": "Reemplazar boceto",
"cmd.sketch_arc3d": "Arco 3D",
"cmd.sketch_arc_center_point": "Arco de centro y punto",
"cmd.sketch_arc_tangent": "Arco tangente",
@@ -1496,6 +1508,7 @@
"cmd.timeline_stop": "Detener historial",
"cmd.timeline_suppress": "Suprimir operación",
"cmd.ui.assistant": "Alternar Asistente",
"cmd.ui.palette": "Buscar comandos",
"cmd.view.navigator": "Navegador de vistas personalizado",
"cmd.view.render_browser": "Explorador de render",
"cmd.view.render_dock": "Mover acople de render",
@@ -2015,6 +2028,7 @@
"help.view.visual_style": "Switches how the model is drawn: shaded with or without edges, wireframe, or with hidden edges. Use wireframe to see inside, shaded for a clean look.",
"history.sketch_under_constrained": "Bajo restricción ({count} grados de libertad)",
"history.sketch_under_constrained_one": "Bajo restricción ({count} grado de libertad)",
"hole_folder.applies_to": "Los cambios se aplican a los {count} agujeros de {folder}; cada uno conserva su posición.",
"hole_presets.confirm_delete": "Confirmar eliminación",
"hole_presets.delete": "Eliminar",
"hole_presets.delete_prompt": "Eliminar esta preconfiguración personal? Los agujeros existentes no cambian.",
@@ -2417,6 +2431,7 @@
"parts.dimension.width": "Anchura",
"parts.family.bearing": "Rodamiento rígido de bolas (envolvente)",
"parts.family.button_head_cap_screw": "Tornillo cabeza botón",
"parts.family.countersunk_socket_screw": "Tornillo avellanado con hexágono interior",
"parts.family.dowel_pin": "Pasador cilíndrico",
"parts.family.hex_bolt": "Tornillo de cabeza hexagonal",
"parts.family.hex_cap_screw_inch": "Tornillo de cabeza hexagonal (pulgadas)",
@@ -2515,6 +2530,8 @@
"prefs.bottom_centre": "Centro abajo",
"prefs.bottom_right": "Derecha abajo",
"prefs.browser": "Explorador",
"prefs.browser_body_features": "Operaciones bajo los cuerpos",
"prefs.browser_body_features_note": "Cada cuerpo del explorador se despliega sobre las operaciones de la línea de tiempo que lo construyeron, en el orden de la línea de tiempo.",
"prefs.browser_components_open": "Componentes abiertos al inicio",
"prefs.browser_components_open_note": "Cada componente del explorador empieza abierto, como antes, en lugar de cerrado hasta que lo abras.",
"prefs.browser_eye_left": "Ojo junto a la flecha de plegado",
@@ -2555,7 +2572,11 @@
"prefs.design_tools.degrees": "Grados",
"prefs.design_tools.double_click_help": "O para editar uno vinculado en su lugar.",
"prefs.design_tools.ghost": "Mostrar la posición anterior de las piezas movidas como un fantasma",
"prefs.design_tools.hole_group": "Agrupar agujeros creados juntos",
"prefs.design_tools.hole_group_help": "Los agujeros creados de una vez comparten una carpeta en el historial, cerrada al principio; ábrela para editar un agujero por separado.",
"prefs.design_tools.hole_moves_help": "Volver a seleccionar la cara mueve el agujero en lugar de eliminarlo.",
"prefs.design_tools.hole_several": "Varios agujeros en una cara",
"prefs.design_tools.hole_several_help": "Hacer clic en otro punto de una cara añade otro agujero; hacer clic en un agujero lo quita.",
"prefs.design_tools.hole_snaps_help": "El centro de una cara y los centros de sus aristas redondeadas.",
"prefs.design_tools.imported_files": "Archivos importados",
"prefs.design_tools.note": "Nota",
@@ -2637,6 +2658,8 @@
"prefs.history": "Historial",
"prefs.history_bar_groups": "Plegar la barra Historial en grupos",
"prefs.history_bar_groups_note": "En un historial largo, una serie de operaciones de un mismo componente se pliega en una ficha, y las carpetas de operaciones empiezan plegadas. Un ensamblaje STEP insertado va a una carpeta Grupo. Desactivado, se muestra cada operación, las carpetas empiezan abiertas y no se crea ninguna carpeta.",
"prefs.history_folder_kind_icon": "Icono de carpeta según sus operaciones",
"prefs.history_folder_kind_icon_note": "Una carpeta del historial cuyas operaciones son todas del mismo tipo muestra su icono.",
"prefs.history_group_joints": "Agrupar uniones en el Historial",
"prefs.history_group_joints_note": "Dos o más uniones se muestran como un botón con su número, y al hacer clic se muestra cada una. Desactivado, se muestra cada unión.",
"prefs.history_origin_bodies": "Mostrar origen y cuerpos al principio",
@@ -2659,9 +2682,6 @@
"prefs.interface_note": "Cómo se ve SolidCraft y dónde se sitúan sus partes.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Invertir las flechas de giro curvo",
"prefs.invert_curved_roll_arrows_note": "Las dos flechas curvas en la esquina del cubo giran la vista en el plano de la pantalla. Activado: la flecha horaria gira antihoraria y la antihoraria gira horaria. Desactivado: cada flecha gira la vista hacia donde se curva.",
"prefs.invert_navigator_arrows": "Invertir las flechas de rotación del cubo de vista",
"prefs.invert_navigator_arrows_note": "Las flechas alrededor del cubo orbitan la cámara hacia donde apuntan, como en Fusion e Inventor: la flecha de la izquierda apunta a la derecha y gira la cámara a la derecha. Desactivado: una flecha rota el cubo hacia el lado en que está, así la flecha izquierda gira la cámara a la izquierda. Las flechas de giro curvo no se ven afectadas.",
"prefs.keep_unsaved_also_asks": "Preguntar igualmente al cerrar la pestaña",
"prefs.keep_unsaved_also_asks_tip": "Con esto desactivado, la pestaña se cierra sin el cuadro del navegador en cuanto la copia de cada diseño sin guardar está al día.",
"prefs.keep_unsaved_in_browser": "Conservar el trabajo sin guardar en este navegador",
@@ -2721,7 +2741,6 @@
"prefs.move_drag_highlights_tip": "Desactivado: mientras las flechas de Mover arrastran un componente no se resalta nada bajo el puntero, y los ensamblajes grandes se mueven con fluidez. Con un corte o un análisis visible se resalta igualmente, y una pieza arrastrada a lo largo de sus uniones nunca lo resalta.",
"prefs.navigation": "Navegación",
"prefs.navigation.axes_signed": "Ejes",
"prefs.navigation.cube_compact": "Cubo",
"prefs.navigation.device": "Dispositivo",
"prefs.navigation.free": "Libre",
"prefs.navigation.orbit_style": "Estilo de órbita",
@@ -2729,7 +2748,6 @@
"prefs.navigation.turntable": "Plato giratorio",
"prefs.navigation_note": "Cómo mueven la vista el ratón, el panel táctil y el ratón 3D.",
"prefs.navigator_fit": "Ajustar el modelo tras un cambio de vista del navegador",
"prefs.navigator_fit_note": "Una cara, esquina o flecha del cubo de vista, o un eje del gizmo, también mira el centro del modelo desde la única distancia que lo encaja desde todos los lados (su esfera envolvente). Inicio usa el mismo zoom y centro mientras esto está activado, así que las vistas elegidas después solo giran. Desactivado: la vista gira en su lugar, manteniendo su objetivo y zoom.",
"prefs.network_host_remove": "Eliminar",
"prefs.network_locations": "Permitir ubicaciones de red en diseños",
"prefs.network_locations_note": "Desactivado: los archivos vinculados de un diseño en otro equipo (\\\\servidor\\\\carpeta) solo se abren desde los servidores listados aquí, o tras permitir uno cuando se pregunte.",
@@ -2742,6 +2760,7 @@
"prefs.origin_plane_names": "Nombres de los planos de origen",
"prefs.origin_plane_names_note": "Nombres que se muestran en el explorador, diálogos y listas. {top} es XY, {front} es XZ y {right} es YZ",
"prefs.overall_speed": "Velocidad general",
"prefs.panel_layout.recovery": "No se han podido restaurar las disposiciones de paneles guardadas. Los datos guardados se conservan; Clásico sigue disponible.",
"prefs.parameter_list_rows": "Filas de la lista de parámetros",
"prefs.parameter_list_rows_tip": "Número de filas visibles en la lista de parámetros",
"prefs.persistent_ribbon_pressed": "Resaltar comandos activos de la cinta",
@@ -2803,6 +2822,8 @@
"prefs.remember_dialog_values.help": "Desactivado de forma predeterminada. Si se activa, cuando un comando termina correctamente, sus valores y opciones seguros se reutilizan la próxima vez que abras el mismo comando, hasta que se cierre SolidCraft.",
"prefs.render_legacy_image_defaults": "Usar los valores de imagen anteriores de Render",
"prefs.render_legacy_image_defaults_help": "Mantiene el flujo manual anterior: las configuraciones nuevas usan rasterizado por CPU, el ray tracing por CPU conserva el muestreo fijo y los resultados renderizados se abren a mano. Al entrar en Render se reencuadra, se cambia de toma sin restaurar sus cámaras y se usa el encuadre y la navegación anteriores. Se desactiva la vista previa automática de la imagen en vivo. Las configuraciones guardadas no cambian.",
"prefs.render_legacy_memory_admission": "Usar las comprobaciones de memoria de renderizado anteriores",
"prefs.render_legacy_memory_admission_help": "Permite los renderizados nativos dentro de los presupuestos fijos de memoria compartida y del host aunque su estimación supere la RAM disponible. Los límites del dispositivo y las comprobaciones de asignación se siguen aplicando.",
"prefs.render_previous_geometry_budget": "Usar presupuesto de geometría previo de Render",
"prefs.render_previous_geometry_budget_help": "Restaurar el límite fijo previo (512 MiB en escritorio de 64 bits, 128 MiB en otros) en lugar del presupuesto basado en dispositivo.",
"prefs.reports_saved": "{n} guardados",
@@ -2829,6 +2850,8 @@
"prefs.select_other_hidden": "Seleccionar geometría oculta con Seleccionar otro",
"prefs.select_other_hidden_help": "Activado: incluir cuerpos, bocetos y aristas ocultas u ocluidas cerca del puntero. Desactivado: us",
"prefs.selecting": "Selección",
"prefs.shared_panel_layout": "Usar la disposición de paneles compartida",
"prefs.shared_panel_layout_help": "Muestra el explorador, el historial y el asistente existentes en paneles movibles. El cambio se aplica al aplicar las preferencias.",
"prefs.shift": "Mayús",
"prefs.shift_add_ctrl_toggle": "Mayús solo añade; Ctrl/Cmd alterna",
"prefs.shift_add_note": "Activado: Mayús añade y Ctrl/Cmd alterna. Desactivado (predeterminado): ambos alternan.",
@@ -2851,6 +2874,8 @@
"prefs.sketch.drawing": "Dibujo",
"prefs.sketch.ellipse_axes": "Dibujar elipses con sus ejes",
"prefs.sketch.ellipse_axes_note": "Los ejes mayor y menor como líneas de construcción, para poder acotar los semiejes y los ejes. Desactivado: solo la elipse, y al pulsar D sobre ella se da su radio menor allí donde se arrastre el valor, dibujado y etiquetado como R, como antes.",
"prefs.sketch.escape_twice_finishes": "Doble Esc finaliza el boceto",
"prefs.sketch.escape_twice_finishes_note": "Dos pulsaciones rápidas de Esc finalizan el boceto. Desactivado: Esc solo cancela la herramienta o la selección, y el boceto solo se finaliza con Finalizar boceto.",
"prefs.sketch.exit_keep": "Mantener",
"prefs.sketch.exit_previous": "Volver a la vista anterior",
"prefs.sketch.exit_unless_rotated": "Salvo que se haya girado",
@@ -3193,8 +3218,10 @@
"render.category_paint": "Pintura",
"render.category_plastic": "Plástico",
"render.category_wood": "Madera",
"render.caustics_area_lights": "Cáusticas de luces de área",
"render.caustics_depth": "Profundidad de trayectoria de cáusticas",
"render.caustics_enable": "Cáusticas de vidrio con luces puntuales y direccionales",
"render.caustics_environment": "Cáusticas del entorno",
"render.caustics_help": "El número de fotones controla el ruido y el trabajo. El radio de captación cambia detalle nítido por una estimación más suave. La CPU requiere iluminación indirecta.",
"render.caustics_path_required": "Las cáusticas por CPU requieren iluminación indirecta.",
"render.caustics_photons": "Fotones",
@@ -3371,7 +3398,7 @@
"render.cpu_opaque_required": "El trazado CPU requiere un fondo opaco. Desactiva Fondo transparente para continuar.",
"render.cpu_options_pending": "Este panel CPU todavía no puede editar emisión, efectos de superficie, vidrio ni anisotropía. Borra esas opciones de la API antes de usarlo.",
"render.cpu_path_depth": "Profundidad de trayectoria",
"render.cpu_path_help": "Añade rebotes de luz entre superficies opacas y a través de formas de vidrio admitidas. Requiere renderizado progresivo y sombras trazadas. La profundidad de trayectoria cuenta superficies sombreadas, límites de vidrio y eventos de dispersión, empezando por el primer impacto (1–16). La profundidad 1 da iluminación directa sobre superficies opacas; los valores mayores permiten más rebotes o cruces de vidrio y pueden tardar más. Desactiva la iluminación indirecta para usar reflexiones personalizadas o anisotropía.",
"render.cpu_path_help": "Añade rebotes de luz entre superficies opacas y a través de formas de vidrio admitidas. Requiere renderizado progresivo y sombras trazadas. La profundidad de trayectoria cuenta superficies sombreadas, límites de vidrio y eventos de dispersión, empezando por el primer impacto (1–64). La profundidad 1 da iluminación directa sobre superficies opacas; los valores mayores permiten más rebotes o cruces de vidrio y pueden tardar más. Desactiva la iluminación indirecta para usar reflexiones personalizadas o anisotropía.",
"render.cpu_path_lighting": "Iluminación indirecta",
"render.cpu_path_optics_reason": "Se conservan tus ajustes. Desactiva la iluminación indirecta para usar reflexiones personalizadas o anisotropía.",
"render.cpu_point": "Luz puntual",
@@ -3411,6 +3438,12 @@
"render.cpu_subsurface_denoise_reason": "La dispersión subsuperficial por CPU necesita la reducción de ruido desactivada. Se conservan tus ajustes.",
"render.cpu_subsurface_help": "La dispersión redirige la luz dentro del vidrio cerrado. Las tasas positivas necesitan iluminación indirecta con la reducción de ruido desactivada. La tasa es de colisiones por mm; 0 no añade dispersión. Un sesgo de fase positivo favorece las direcciones hacia delante; uno negativo, las hacia atrás.",
"render.cpu_summary": "Trazado CPU · reflejos · HDR · luces direccionales y puntuales",
"render.cpu_threads": "Procesos de CPU",
"render.cpu_threads_auto": "Automático",
"render.cpu_threads_custom": "Personalizado",
"render.cpu_threads_help": "Automático usa los procesos de CPU disponibles. Un proceso se ejecuta en serie. Más procesos pueden necesitar más memoria.",
"render.cpu_threads_single": "Uno",
"render.cpu_threads_web": "El renderizado en el navegador usa un proceso cooperativo. Se conserva la elección de procesos guardada para el escritorio.",
"render.cpu_transparent_help": "El PNG transparente quita el fondo y conserva la cobertura de los bordes. La opacidad del cuerpo y de la cara debe mantenerse al 100 %.",
"render.cpu_volume_enable_path": "Activar iluminación indirecta",
"render.cpu_volume_enable_path_plan": "Ray tracing en CPU · profundidad {depth} · {spp} muestras/píxel · semilla {seed} · sombras trazadas. Conserva la atmósfera definida y el presupuesto de imagen.",
@@ -3419,6 +3452,40 @@
"render.cpu_volume_method_incompatible": "Estos ajustes de luz, muestreo o atmósfera no se pueden transferir a este método. Se conservan tus ajustes.",
"render.cpu_volume_path_required": "Activa la iluminación indirecta para usar la atmósfera por CPU.",
"render.cpu_warm_cool": "Cálido y frío",
"render.denoise_class_background": "Omitido: fondo",
"render.denoise_class_coverage": "Omitido: cobertura parcial",
"render.denoise_class_degenerate": "Omitido: trayectoria degenerada",
"render.denoise_class_depth": "Omitido: límite de profundidad de trayectoria",
"render.denoise_class_emitter": "Omitido: emisor de luz",
"render.denoise_class_exit": "Omitido: salida incompatible",
"render.denoise_class_identity": "Omitido: límite de identidad de guía",
"render.denoise_class_lens": "Omitido: profundidad de campo",
"render.denoise_class_material": "Omitido: material no admitido",
"render.denoise_class_nested": "Omitido: medio anidado",
"render.denoise_class_optical": "Elegible: superficie refractada",
"render.denoise_class_reflection": "Omitido: punto final reflectante",
"render.denoise_class_surface": "Elegible: superficie",
"render.denoise_class_tir": "Omitido: reflexión total interna",
"render.denoise_class_transmission": "Omitido: transmisión adicional",
"render.denoise_class_unavailable": "Omitido: sin guía",
"render.denoise_class_volume": "Omitido: dispersión de volumen",
"render.denoise_diagnostics_counts": "Elegibles: {eligible} · Omitidos: {skipped}",
"render.denoise_diagnostics_enable": "Recopilar diagnóstico de reducción de ruido",
"render.denoise_diagnostics_grid": "Recuentos de píxeles con el tamaño interno {width} × {height}.",
"render.denoise_diagnostics_help": "Recopila los recuentos de píxeles elegibles y omitidos incluso con la reducción de ruido desactivada. Los recuentos usan el tamaño interno de renderizado; la máscara usa el tamaño de salida.",
"render.denoise_diagnostics_title": "Diagnóstico de reducción de ruido",
"render.denoise_export_mask": "Exportar máscara PNG",
"render.denoise_firefly": "Suprimir los píxeles brillantes aislados",
"render.denoise_format_mask": "Máscara PNG",
"render.denoise_mask_copy_help": "Exporta la máscara de reducción de ruido como PNG o vuelve a la imagen renderizada para copiarla.",
"render.denoise_mask_unavailable": "Activa el diagnóstico de reducción de ruido y renderiza una imagen para inspeccionar su máscara.",
"render.denoise_optical_filter": "Filtrar espejos y vidrio",
"render.denoise_optical_filter_help": "Filtra la luz reflejada y transmitida elegible. Desactívalo para reducir el ruido solo en superficies opacas.",
"render.denoise_passes": "Pasadas del filtro",
"render.denoise_settings_help": "Más pasadas suavizan un área mayor. La intensidad controla cuánto filtra cada pasada. Las trayectorias ópticas no admitidas no se filtran.",
"render.denoise_strength": "Intensidad de la reducción de ruido",
"render.denoise_view_image": "Imagen renderizada",
"render.denoise_view_mask": "Máscara de reducción de ruido",
"render.diag_bodies": "Cuerpos visibles",
"render.diag_job_details": "Detalles del trabajo capturado",
"render.diag_last_check": "Última comprobación",
@@ -3973,6 +4040,8 @@
"render.material_transient": "Ajustes ópticos temporales de esta pestaña. No se guardan en el diseño, la configuración de render ni la biblioteca de materiales.",
"render.material_v_up": "La coordenada V apunta hacia arriba en la imagen",
"render.material_weight": "Peso del recubrimiento (0–1)",
"render.metal_energy_compensation": "Recuperar la reflectancia del metal rugoso",
"render.metal_energy_compensation_help": "Compensa la luz perdida entre las microfacetas del metal rugoso. El renderizado por CPU necesita iluminación indirecta.",
"render.method_cpu": "Trazado CPU",
"render.method_gpu": "Trazado GPU",
"render.method_limit_raster": "Raster utiliza la vista opaca de estudio fija. Selecciona un método de trazado de rayos para los controles ópticos compatibles.",
@@ -4101,6 +4170,8 @@
"render.quality_denoise_on": "Reducción de ruido: Activada",
"render.quality_draft": "Boceto",
"render.quality_high": "Alto",
"render.quality_legacy_samples": "Valor guardado: {samples} spp. Las selecciones nuevas se limitan a {maximum} spp.",
"render.quality_preset_limit": "Este preajuste supera el límite de selección de 16384 spp. Elige un número de muestras de hasta 16384.",
"render.quality_projection_help": "Esta revisión solo cambia la resolución, el presupuesto de muestras, la profundidad de trayectoria, la semilla y la reducción de ruido. Los demás ajustes no cambian; el presupuesto no certifica la convergencia.",
"render.quality_review": "Revisar presupuesto de muestras",
"render.quality_summary": "{width} × {height} · {samples} muestras · profundidad {depth} · semilla {seed} · {denoise}",
@@ -4248,6 +4319,7 @@
"render.show_shots_label": "Mostrar tira de tomas",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "Personalizado…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
@@ -4410,6 +4482,11 @@
"render.subsurface_enable": "Recorrido aleatorio de dispersión interna",
"render.subsurface_help": "Solo dieléctricos cerrados. Dispersión y absorción RGB son coeficientes separados por mm. Las trayectorias se dispersan dentro de la geometría y se refractan o reflejan en la frontera. Desactiva sombras rectas, reducción de ruido de superficies y volumen exterior; usa un estrato Fresnel. No es un filtro de difusión, rejilla de densidad ni material medido. Los medios densos pueden requerir más muestras y profundidad.",
"render.subsurface_rate": "Tasa de colisiones de dispersión",
"render.supersampling": "Supermuestreo",
"render.supersampling_dimensions": "Renderiza {width} × {height} y reduce a {output_width} × {output_height}. Usa el cuádruple de píxeles.",
"render.supersampling_help": "Renderiza al doble de ancho y alto y luego reduce en luz lineal. El tamaño de salida no cambia.",
"render.supersampling_off": "Desactivado",
"render.supersampling_x2": "2×",
"render.surface_import.author": "Autor",
"render.surface_import.busy": "Importando imagen…",
"render.surface_import.complete": "Elige una imagen e indica su autor, fuente y licencia.",
@@ -4423,6 +4500,8 @@
"render.surface_import.saved_cpu": "Guarda los mapas para usarlos con renderizado por CPU.",
"render.surface_import.source": "Fuente",
"render.surface_import.title": "Importar imagen",
"render.texture_filtering": "Filtrar el detalle de las texturas",
"render.texture_filtering_help": "Usa niveles mip y filtrado anisotrópico para reducir el aliasing de las texturas al reducirlas. Consume más memoria; desactivado mantiene el filtrado anterior.",
"render.tone_aces_fitted": "ACES ajustado",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "El vidrio GPU admite medios separados certificados. No se admiten medios anidados sin restricciones ni un estimador de transmisión sin ruido.",
@@ -5517,6 +5596,7 @@
"ui.heading_plastic": "PLÁSTICO",
"ui.heading_position": "POSICIÓN",
"ui.heading_prepare": "PREPARAR",
"ui.heading_press_pull": "PULSAR/TIRAR",
"ui.heading_rectangular_pattern": "PATRÓN RECTANGULAR",
"ui.heading_revolve": "REVOLUCIÓN",
"ui.heading_right": "DERECHA",
@@ -5759,6 +5839,42 @@
"ui.panel_host.surface": "Panel",
"ui.panel_host.tab": "Pestaña del panel",
"ui.panel_host.tabs": "Todas las pestañas",
"ui.panel_layout.applied": "Se ha aplicado {name} a {workspace}",
"ui.panel_layout.cancel": "Cancelar",
"ui.panel_layout.confirm_replace": "¿Reemplazar «{name}»?",
"ui.panel_layout.copy": "Copiar",
"ui.panel_layout.current": "Disposición actual",
"ui.panel_layout.custom": "Personalizada",
"ui.panel_layout.custom_hint": "Se han movido paneles desde {name}.",
"ui.panel_layout.details": "Detalles",
"ui.panel_layout.layout_workspace": "Espacio de trabajo de disposición",
"ui.panel_layout.page_defaults_hint": "Restablece los ajustes de esta página. La organización de los paneles y las disposiciones guardadas se conservan.",
"ui.panel_layout.panel_layout": "Disposición de paneles",
"ui.panel_layout.panel_style": "Estilo de paneles",
"ui.panel_layout.replace": "Reemplazar disposición",
"ui.panel_layout.replace_current": "Reemplazar con la actual",
"ui.panel_layout.replace_short": "Reemplazar",
"ui.panel_layout.reset": "Restablecer espacio de trabajo",
"ui.panel_layout.reset_hint": "Restablece {workspace} a su disposición predeterminada. Las disposiciones guardadas se conservan.",
"ui.panel_layout.reset_short": "Restablecer",
"ui.panel_layout.restore_failed": "No se ha podido restaurar tu disposición",
"ui.panel_layout.save": "Guardar disposición",
"ui.panel_layout.save_as": "Guardar como",
"ui.panel_layout.save_as_tile": "Guardar como…",
"ui.panel_layout.selected": "seleccionada",
"ui.panel_layout.suggested_name": "Disposición {n}",
"ui.panel_layout.try_again": "Reintentar",
"ui.panel_layout.validation.builtin": "Las disposiciones integradas no se pueden cambiar",
"ui.panel_layout.validation.duplicate": "Ya tienes este nombre",
"ui.panel_layout.validation.empty": "Introduce un nombre",
"ui.panel_layout.validation.full": "64 de 64 guardadas. Elige una para reemplazarla.",
"ui.panel_layout.validation.invalid": "Usa de 1 a 64 caracteres, sin caracteres de control",
"ui.panel_layout.workspace.classic": "Clásico",
"ui.panel_layout.workspace.modelling": "Modelado",
"ui.panel_layout.workspace.render": "Renderizado",
"ui.panel_layout.workspace.review": "Revisión",
"ui.panel_layout.workspace.sketch": "Boceto",
"ui.panel_layout.your_layout": "tu disposición",
"ui.parallel": "Paralelo",
"ui.parameters": "Parámetros",
"ui.part_size_needs_a_part": "Seleccione una pieza normalizada insertada (un tornillo, una tuerca, una arandela o un pasador) para cambiar su tamaño. Para agrandar o reducir un cuerpo, use Escala.",
@@ -5824,6 +5940,8 @@
"ui.profiles": "Perfiles",
"ui.project": "Proyectar",
"ui.project_face_edges_sketch": "Proyectar aristas de caras en bocetos nuevos",
"ui.project_references_construction": "Proyectar referencias como geometría de construcción",
"ui.project_references_construction_note": "Proyectar, Intersecar, Incluir geometría 3D y las aristas de cara de un boceto nuevo sobre una cara crean geometría de construcción: referencias sobre las que dibujar, excluidas de los perfiles. Siguen vinculadas al modelo.",
"ui.properties": "Propiedades",
"ui.properties.copy_matrix": "Copiar matriz",
"ui.properties.first_moments": "Primeros momentos",
@@ -5867,6 +5985,8 @@
"ui.renderer": "Motor de renderizado",
"ui.repeat": "Repetir",
"ui.repeat_nothing": "Nada que repetir aún: inicia una herramienta o un diálogo primero.",
"ui.replace_sketch.from_file": "Desde un archivo de diseño…",
"ui.replace_sketch.no_sketches": "Ese diseño no tiene ningún boceto que reutilizar",
"ui.replace_with_link_done": "Las copias ya son colocaciones del diseño vinculado.",
"ui.report_a_bug": "Notificar un error",
"ui.report_characters_left_one": "Queda {count} carácter",
-6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Luo ilmoitus…",
"Cross curves": "Poikkikäyrät",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter jatkaa GitHubissa.",
"Cube": "Kuutio",
"Current orthographic view (1:1 mm)": "Nykyinen ortografinen näkymä (1:1 mm)",
"Current perspective view": "Nykyinen perspektiivinäkymä",
"Current plane": "Nykyinen taso",
@@ -2681,9 +2680,6 @@
"prefs.interface_note": "Miltä SolidCraft näyttää ja missä sen osat sijaitsevat.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Käännä kaarevat kallistusnuolet",
"prefs.invert_curved_roll_arrows_note": "Kuution kulman kaksi kaarevaa nuolta kallistavat näkymää näytön tasossa. Päällä: myötäpäivään osoittava nuoli kallistaa vastapäivään ja vastapäivään osoittava myötäpäivään. Pois: kumpikin nuoli kallistaa näkymää kaartumissuuntaansa.",
"prefs.invert_navigator_arrows": "Käännä näkymäkuution kiertonuolet",
"prefs.invert_navigator_arrows_note": "Kuution ympärillä olevat nuolet kiertävät kameraa osoittamaansa suuntaan kuten Fusionissa ja Inventorissa: vasemmalla oleva nuoli osoittaa oikealle ja kääntää kameraa oikealle. Pois: nuoli kiertää kuutiota omaa puoltaan kohti, joten vasen nuoli kääntää kameraa vasemmalle. Kaarevat kallistusnuolet eivät muutu.",
"prefs.keep_unsaved_also_asks": "Kysy silti, kun välilehti suljetaan",
"prefs.keep_unsaved_also_asks_tip": "Kun tämä on pois päältä, välilehti sulkeutuu ilman selaimen kyselyikkunaa, kun jokaisen tallentamattoman mallin kopio on ajan tasalla.",
"prefs.keep_unsaved_in_browser": "Säilytä tallentamaton työ tässä selaimessa",
@@ -2743,7 +2739,6 @@
"prefs.move_drag_highlights_tip": "Pois: kun Siirrä-toiminnon nuolilla vedetään komponenttia, osoittimen alla olevaa kohdetta ei korosteta, mikä pitää suuret kokoonpanot sulavina. Kun leikkaus tai analyysi on näkyvissä, kohde korostetaan silti, eikä niveliään pitkin vedetty osa koskaan korosta sitä.",
"prefs.navigation": "Navigointi",
"prefs.navigation.axes_signed": "Akselit",
"prefs.navigation.cube_compact": "Kuutio",
"prefs.navigation.device": "Laite",
"prefs.navigation.free": "Vapaa",
"prefs.navigation.orbit_style": "Kiertotapa",
@@ -2751,7 +2746,6 @@
"prefs.navigation.turntable": "Pyörivä alusta",
"prefs.navigation_note": "Miten hiiri, kosketuslevy ja 3D-hiiri liikuttavat näkymää.",
"prefs.navigator_fit": "Sovita malli navigaattorin näkymänvaihdon jälkeen",
"prefs.navigator_fit_note": "Näkymäkuution pinta, kulma tai nuoli tai gizmon akseli katsoo myös mallin keskikohtaa siltä yhdeltä etäisyydeltä, joka sovittaa sen joka suunnasta (sen rajaava pallo). Aloitusnäkymä käyttää samaa zoomausta ja keskipistettä, kun tämä on päällä, joten sen jälkeen valitut näkymät vain kääntyvät. Pois: näkymä kääntyy paikallaan säilyttäen kohteensa ja zoomauksensa.",
"prefs.network_host_remove": "Poista",
"prefs.network_locations": "Salli verkkosijainnit malleissa",
"prefs.network_locations_note": "Pois: mallin linkitetyt tiedostot toisella tietokoneella (\\\\server\\share) avataan vain tässä luetelluilta palvelimilta tai sen jälkeen, kun sallit palvelimen kysyttäessä.",
+4 -5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Créer un ticket…",
"Cross curves": "Courbes transversales",
"Ctrl+Enter continues on GitHub.": "Ctrl+Entrée continue sur GitHub.",
"Cube": "Cube",
"Current orthographic view (1:1 mm)": "Vue orthographique actuelle (1:1 mm)",
"Current perspective view": "Vue en perspective actuelle",
"Current plane": "Plan actuel",
@@ -2256,9 +2255,9 @@
"prefs.interface_note": "L'apparence de SolidCraft et la place de ses éléments.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Inverser les flèches courbes de roulis",
"prefs.invert_curved_roll_arrows_note": "Les deux flèches courbes au coin du cube font rouler la vue dans le plan de l'écran. Activé : la flèche horaire fait rouler dans le sens antihoraire et la flèche antihoraire dans le sens horaire. Désactivé : chaque flèche fait rouler la vue dans le sens de sa courbure.",
"prefs.invert_navigator_arrows": "Inverser les flèches de rotation du cube de vue",
"prefs.invert_navigator_arrows_note": "Les flèches autour du cube font orbiter la caméra dans le sens où elles pointent, comme dans Fusion et Inventor : la flèche de gauche pointe vers la droite et fait pivoter la caméra vers la droite. Désactivé : une flèche fait tourner le cube vers le côté où elle se trouve, donc la flèche de gauche fait pivoter la caméra vers la gauche. Les flèches courbes de roulis ne sont pas concernées.",
"prefs.invert_curved_roll_arrows_note": "Les deux flèches courbes au coin du navigateur font rouler la vue dans le plan de l'écran. Activé : la flèche horaire fait rouler dans le sens antihoraire et la flèche antihoraire dans le sens horaire. Désactivé : chaque flèche fait rouler la vue dans le sens de sa courbure.",
"prefs.invert_navigator_arrows": "Inverser les flèches de rotation du navigateur de vue",
"prefs.invert_navigator_arrows_note": "Les flèches autour du navigateur font orbiter la caméra dans le sens où elles pointent, comme dans Fusion et Inventor : la flèche de gauche pointe vers la droite et fait pivoter la caméra vers la droite. Désactivé : une flèche fait tourner la vue vers le côté où elle se trouve, donc la flèche de gauche fait pivoter la caméra vers la gauche. Les flèches courbes de roulis ne sont pas concernées.",
"prefs.keep_work_on_tab_switch": "Garder dialogues et outils ouverts en changeant de conception",
"prefs.keep_work_on_tab_switch_note": "Chaque onglet de conception garde son dialogue ou son outil ouvert, avec ce que vous y avez sélectionné et saisi, ainsi que son onglet de barre d'outils. Vous les retrouvez en revenant à la conception. Désactivé : changer de conception les annule et affiche l'onglet Modèle.",
"prefs.labels": "Libellés",
@@ -2297,7 +2296,7 @@
"prefs.navigation": "Navigation",
"prefs.navigation_note": "Comment la souris, le pavé tactile et la souris 3D déplacent la vue.",
"prefs.navigator_fit": "Cadrer le modèle après un changement de vue du repère de navigation",
"prefs.navigator_fit_note": "Une face, un coin ou une flèche du cube de vue, ou un axe du gizmo, regarde aussi le milieu du modèle depuis la distance unique qui le cadre sous tous les côtés (sa sphère englobante). Accueil utilise le même zoom et le même centre tant que cette option est activée, de sorte que les vues choisies ensuite ne font que tourner. Désactivé : la vue tourne sur place, en gardant sa cible et son zoom.",
"prefs.navigator_fit_note": "Une face, un coin ou une flèche du navigateur de vue, ou un axe du gizmo, regarde aussi le milieu du modèle depuis la distance unique qui le cadre sous tous les côtés (sa sphère englobante). Accueil utilise le même zoom et le même centre tant que cette option est activée, de sorte que les vues choisies ensuite ne font que tourner. Désactivé : la vue tourne sur place, en gardant sa cible et son zoom.",
"prefs.network_host_remove": "Retirer",
"prefs.network_locations": "Autoriser les emplacements réseau dans les conceptions",
"prefs.network_locations_note": "Désactivé : les fichiers liés d'une conception situés sur un autre ordinateur (\\\\server\\share) ne sont ouverts que depuis les serveurs listés ici, ou après en avoir autorisé un sur demande.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Crear incidencia…",
"Cross curves": "Curvas transversais",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter continúa en GitHub.",
"Cube": "Cubo",
"Current orthographic view (1:1 mm)": "Vista ortográfica actual (1:1 mm)",
"Current perspective view": "Vista en perspectiva actual",
"Current plane": "Plano actual",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Como se ve SolidCraft e onde están as súas partes.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Inverter as frechas curvas de balanceo",
"prefs.invert_curved_roll_arrows_note": "As dúas frechas curvas da esquina do cubo fan xirar a vista no plano da pantalla. Activado: a frecha en sentido horario xira en sentido antihorario e a frecha en sentido antihorario xira en sentido horario. Desactivado: cada frecha xira a vista no sentido en que se curva.",
"prefs.invert_navigator_arrows": "Invertir as frechas de rotación do cubo de vistas",
"prefs.invert_navigator_arrows_note": "As frechas arredor do cubo fan orbitar a cámara na dirección na que apuntan, como en Fusion e Inventor: a frecha da esquerda apunta á dereita e move a cámara cara á dereita. Desactivado: unha frecha xira o cubo cara ao lado no que está, así que a frecha da esquerda move a cámara cara á esquerda. As frechas curvas de balanceo non se ven afectadas.",
"prefs.keep_work_on_tab_switch": "Manter abertos os diálogos e as ferramentas ao cambiar de deseño",
"prefs.keep_work_on_tab_switch_note": "Cada lapela de deseño conserva o seu diálogo ou ferramenta aberto, co que escolliches e escribiches nel, e a súa lapela da barra de ferramentas. Volven estar aí cando regresas ao deseño. Desactivado: cambiar de deseño cancélaos e mostra a lapela Modelo.",
"prefs.labels": "Etiquetas",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navegación",
"prefs.navigation_note": "Como moven a vista o rato, o panel táctil e o rato 3D.",
"prefs.navigator_fit": "Axustar o modelo despois dun cambio de vista do navegador",
"prefs.navigator_fit_note": "Unha cara, esquina ou frecha do cubo de vistas, ou un eixe do gizmo, tamén mira ao centro do modelo desde a única distancia que o abarca por todos os lados (a súa esfera delimitadora). Inicio usa o mesmo zoom e centro mentres isto está activado, así que as vistas escollidas despois só xiran. Desactivado: a vista xira no sitio, conservando o seu punto de mira e o seu zoom.",
"prefs.network_host_remove": "Quitar",
"prefs.network_locations": "Permitir ubicacións de rede nos deseños",
"prefs.network_locations_note": "Desactivado: os ficheiros ligados dun deseño que están noutro ordenador (\\\\server\\share) só se abren desde os servidores listados aquí, ou despois de que permitas un cando se che pregunte.",
+63 -7
View File
@@ -193,7 +193,6 @@
"Create Issue…": "יצירת דיווח…",
"Cross curves": "עקומות חוצות",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter ממשיך ב־GitHub.",
"Cube": "קובייה",
"Current orthographic view (1:1 mm)": "התצוגה האורתוגרפית הנוכחית (1:1 mm)",
"Current perspective view": "תצוגת הפרספקטיבה הנוכחית",
"Current plane": "המישור הנוכחי",
@@ -1509,6 +1508,7 @@
"cmd.timeline_stop": "עצירת ציר הזמן",
"cmd.timeline_suppress": "השבתת פיצ'ר",
"cmd.ui.assistant": "הצגה או הסתרה של העוזר",
"cmd.ui.palette": "חיפוש פקודות",
"cmd.view.navigator": "ניווט תצוגה מותאם אישית",
"cmd.view.render_browser": "דפדפן רינדור",
"cmd.view.render_dock": "הזז את עגינת הרינדור",
@@ -2431,6 +2431,7 @@
"parts.dimension.width": "רוחב",
"parts.family.bearing": "מסב כדורי חריץ עמוק (מעטפת)",
"parts.family.button_head_cap_screw": "בורג ראש כפתור עם שקע משושה",
"parts.family.countersunk_socket_screw": "בורג ראש שקוע עם שקע משושה",
"parts.family.dowel_pin": "פין גלילי",
"parts.family.hex_bolt": "בורג ראש משושה",
"parts.family.hex_cap_screw_inch": "בורג ראש משושה (אינץ')",
@@ -2681,9 +2682,9 @@
"prefs.interface_note": "איך SolidCraft נראה והיכן חלקיו ממוקמים.",
"prefs.interval_minutes": "{n} דק׳",
"prefs.invert_curved_roll_arrows": "היפוך חצי הגלילה המעוגלים",
"prefs.invert_curved_roll_arrows_note": "שני החצים המעוגלים בפינת הקובייה מסובבים את התצוגה במישור המסך. פועל: החץ עם כיוון השעון מסובב נגד כיוון השעון, והחץ נגד כיוון השעון מסובב עם כיוון השעון. כבוי: כל חץ מסובב את התצוגה לפי העקום שלו.",
"prefs.invert_navigator_arrows": "היפוך חצי סיבוב קוביית התצוגה",
"prefs.invert_navigator_arrows_note": "החצים סביב הקובייה מחוויים את המצלמה לכיוון שהחץ מצביע, כמו ב-Fusion ו-Inventor: החץ שמשמאל מצביע ימימה ומניע את המצלמה ימימה. כבוי: החץ מסובב את הקובייה אל הצד שבו הוא יושב, כך שחץ שמאל מניע את המצלמה שמאלה. חצי הגלילה המעוגלים אינם מושפעים.",
"prefs.invert_curved_roll_arrows_note": "שני החצים המעוגלים בפינת הנווט מסובבים את התצוגה במישור המסך. פועל: החץ עם כיוון השעון מסובב נגד כיוון השעון, והחץ נגד כיוון השעון מסובב עם כיוון השעון. כבוי: כל חץ מסובב את התצוגה לפי העקום שלו.",
"prefs.invert_navigator_arrows": "היפוך חצי סיבוב הנווט",
"prefs.invert_navigator_arrows_note": "החצים סביב הנווט מחוויים את המצלמה לכיוון שהחץ מצביע, כמו ב-Fusion ו-Inventor: החץ שמשמאל מצביע ימימה ומניע את המצלמה ימימה. כבוי: החץ מסובב את התצוגה אל הצד שבו הוא יושב, כך שחץ שמאל מניע את המצלמה שמאלה. חצי הגלילה המעוגלים אינם מושפעים.",
"prefs.keep_unsaved_also_asks": "לשאול גם כשהכרטיסייה נסגרת",
"prefs.keep_unsaved_also_asks_tip": "כשאפשרות זו כבויה, הכרטיסייה נסגרת ללא חלון הדפדפן לאחר שהעותק של כל תכן שלא נשמר עודכן.",
"prefs.keep_unsaved_in_browser": "לשמור עבודה שלא נשמרה בדפדפן זה",
@@ -2743,7 +2744,6 @@
"prefs.move_drag_highlights_tip": "כבוי: בזמן שחצי ההזזה גוררים רכיב, דבר אינו מודגש מתחת לסמן, וכך מכלולים גדולים נשארים חלקים. כשמוצג חתך או ניתוח ההדגשה נשארת, וחלק שנגרר לאורך המפרקים שלו לעולם אינו מדגיש.",
"prefs.navigation": "ניווט",
"prefs.navigation.axes_signed": "צירים",
"prefs.navigation.cube_compact": "קובייה",
"prefs.navigation.device": "התקן",
"prefs.navigation.free": "חופשי",
"prefs.navigation.orbit_style": "סגנון סיבוב",
@@ -2751,7 +2751,7 @@
"prefs.navigation.turntable": "שולחן מסתובב",
"prefs.navigation_note": "איך העכבר, משטח המגע והעכבר התלת־ממדי מזיזים את המבט.",
"prefs.navigator_fit": "התאמת המודל לאחר שינוי תצוגה בניווט התצוגה",
"prefs.navigator_fit_note": "לחיצה על פאה, פינה או חץ בקובייה, או על ציר בגיזמו, גם מביטה אל מרכז המודל מהמרחק האחד שמכיל אותו מכל צד (הכדור החוסם שלו). תצוגת בית משתמשת באותו זום ובאותו מרכז כל עוד זה פעיל, כך שתצוגות שנבחרות אחריה רק מסתובבות. כבוי: התצוגה מסתובבת במקומה ושומרת על היעד ועל הזום.",
"prefs.navigator_fit_note": "לחיצה על פאה, פינה או חץ בנווט, או על ציר בגיזמו, גם מביטה אל מרכז המודל מהמרחק האחד שמכיל אותו מכל צד (הכדור החוסם שלו). תצוגת בית משתמשת באותו זום ובאותו מרכז כל עוד זה פעיל, כך שתצוגות שנבחרות אחריה רק מסתובבות. כבוי: התצוגה מסתובבת במקומה ושומרת על היעד ועל הזום.",
"prefs.network_host_remove": "הסרה",
"prefs.network_locations": "התרת מיקומי רשת בתכנים",
"prefs.network_locations_note": "לארגונים השומרים תכנונים בשרתי קבצים. כשהאפשרות כבויה: רק שרתים שאישרתם.",
@@ -2826,6 +2826,8 @@
"prefs.remember_dialog_values.help": "כבוי כברירת מחדל. כשהוא פועל, לאחר הצלחת פקודה, הערכים והאפשרויות הבטוחים שלה ישמשו שוב בפעם הבאה שתפתח את אותה פקודה, עד לסגירת SolidCraft.",
"prefs.render_legacy_image_defaults": "שימוש בברירות המחדל הקודמות לתמונות הדמיה",
"prefs.render_legacy_image_defaults_help": "שמירה על תהליך העבודה הידני הקודם: הגדרות חדשות ברסטר CPU, דגימה קבועה בעקיבת קרניים ב־CPU ופתיחת התוצאות ידנית. התאמת התצוגה מחדש בכניסה להדמיה, מעבר בין צילומים בלי לשחזר את המצלמות שלהם ושימוש במסגור ובניווט הקודמים. עדכון אוטומטי של התמונה החיה מושבת. הגדרות שמורות אינן משתנות.",
"prefs.render_legacy_memory_admission": "שימוש בבדיקות הזיכרון הקודמות לרינדור",
"prefs.render_legacy_memory_admission_help": "מאפשר רינדור מקומי במסגרת מגבלות הזיכרון הקבועות המשותפות ושל המארח, גם כשההערכה חורגת מהזיכרון הזמין. מגבלות המכשיר ובדיקות הקצאת הזיכרון עדיין חלות.",
"prefs.render_previous_geometry_budget": "שימוש בתקציב הגאומטריה הקודם לתצוגה מקדימה של רינדור",
"prefs.render_previous_geometry_budget_help": "שחזור מגבלת הגאומטריה הקבועה הקודמת (512 MiB במחשב שולחני של 64 סיביות, 128 MiB בפלטפורמות אחרות) במקום התקציב המבוסס על ההתקן.",
"prefs.reports_saved": "{n} שמורים",
@@ -3220,8 +3222,10 @@
"render.category_paint": "צבע",
"render.category_plastic": "פלסטיק",
"render.category_wood": "עץ",
"render.caustics_area_lights": "קאוסטיקה מתאורת שטח",
"render.caustics_depth": "עומק מסלול ריכוז האור",
"render.caustics_enable": "ריכוז אור דרך זכוכית ממקורות נקודתיים וכיווניים",
"render.caustics_environment": "קאוסטיקה מתאורת הסביבה",
"render.caustics_help": "מספר הפוטונים שולט ברעש ובעומס החישוב. רדיוס האיסוף מאזן בין פרטים חדים לאומדן חלק יותר. CPU דורש תאורה עקיפה.",
"render.caustics_path_required": "ריכוז אור ב-CPU דורש תאורה עקיפה.",
"render.caustics_photons": "מספר פוטונים",
@@ -3398,7 +3402,7 @@
"render.cpu_opaque_required": "מעקב קרניים ב־CPU דורש רקע אטום. יש לבטל את „רקע שקוף” כדי להמשיך.",
"render.cpu_options_pending": "לוח CPU זה עדיין אינו יכול לערוך פליטה, אפקטי משטח, זכוכית או אנאיזוטרופיה. יש לנקות את אפשרויות ה־API האלה לפני השימוש בו.",
"render.cpu_path_depth": "עומק מסלול",
"render.cpu_path_help": "מחשבת אור שמוחזר בין משטחים אטומים ועובר דרך צורות זכוכית נתמכות. נדרשים רינדור הדרגתי וצללים במעקב קרניים. עומק המסלול סופר משטחים מוצללים, גבולות זכוכית ואירועי פיזור החל מהמפגש הראשון (1–16). עומק 1 נותן תאורה ישירה למשטחים אטומים; ערכים גבוהים מאפשרים יותר החזרות או מעברים בזכוכית ועשויים לדרוש יותר זמן. יש לכבות תאורה עקיפה כדי להשתמש בהשתקפויות מותאמות או באנאיזוטרופיה.",
"render.cpu_path_help": "מחשבת אור שמוחזר בין משטחים אטומים ועובר דרך צורות זכוכית נתמכות. נדרשים רינדור הדרגתי וצללים במעקב קרניים. עומק המסלול סופר משטחים מוצללים, גבולות זכוכית ואירועי פיזור החל מהמפגש הראשון (1–64). עומק 1 נותן תאורה ישירה למשטחים אטומים; ערכים גבוהים מאפשרים יותר החזרות או מעברים בזכוכית ועשויים לדרוש יותר זמן. יש לכבות תאורה עקיפה כדי להשתמש בהשתקפויות מותאמות או באנאיזוטרופיה.",
"render.cpu_path_lighting": "תאורה עקיפה",
"render.cpu_path_optics_reason": "ההגדרות שלך נשמרות. יש לכבות תאורה עקיפה כדי להשתמש בהשתקפויות מותאמות או באנאיזוטרופיה.",
"render.cpu_point": "אור נקודתי",
@@ -3438,6 +3442,12 @@
"render.cpu_subsurface_denoise_reason": "פיזור בתוך זכוכית ב־CPU דורש הסרת רעש כבויה. ההגדרות נשמרות.",
"render.cpu_subsurface_help": "פיזור משנה את כיוון האור בתוך זכוכית סגורה. קצב חיובי דורש תאורה עקיפה והסרת רעש כבויה. הקצב הוא מספר התנגשויות למ״מ; 0 אינו מוסיף פיזור. הטיית כיוון חיובית מעדיפה קדימה, ושלילית אחורה.",
"render.cpu_summary": "מעקב קרניים ב־CPU · החזרות · HDR · אורות כיווניים ונקודתיים",
"render.cpu_threads": "תהליכוני CPU",
"render.cpu_threads_auto": "אוטומטי",
"render.cpu_threads_custom": "מותאם",
"render.cpu_threads_help": "המצב האוטומטי משתמש בתהליכוני ה־CPU הזמינים. תהליכון יחיד פועל באופן סדרתי. תהליכונים נוספים עשויים לדרוש יותר זיכרון.",
"render.cpu_threads_single": "יחיד",
"render.cpu_threads_web": "רינדור בדפדפן משתמש בתהליכון שיתופי אחד. בחירת התהליכונים השמורה למחשב נשמרת.",
"render.cpu_transparent_help": "PNG שקוף מסיר את הרקע ושומר על כיסוי הקצוות. אטימות הגופים והפאות חייבת להישאר 100%.",
"render.cpu_volume_enable_path": "הפעלת תאורה עקיפה",
"render.cpu_volume_enable_path_plan": "מעקב קרניים ב־CPU · עומק מסלול {depth} · {spp} דגימות לפיקסל · זרע {seed} · צללים במעקב קרניים. האטמוספרה ותקציב התמונה שנקבעו נשמרים.",
@@ -3446,6 +3456,40 @@
"render.cpu_volume_method_incompatible": "לא ניתן להעביר הגדרות תאורה, דגימה או אטמוספרה אלה לשיטה זו. ההגדרות שלך נשמרות.",
"render.cpu_volume_path_required": "יש להפעיל תאורה עקיפה כדי להשתמש באטמוספרה ב־CPU.",
"render.cpu_warm_cool": "חם וקר",
"render.denoise_class_background": "דולג: רקע",
"render.denoise_class_coverage": "דולג: כיסוי חלקי",
"render.denoise_class_degenerate": "דולג: מסלול מנוון",
"render.denoise_class_depth": "דולג: מגבלת עומק מסלול",
"render.denoise_class_emitter": "דולג: מקור אור",
"render.denoise_class_exit": "דולג: יציאה לא תואמת",
"render.denoise_class_identity": "דולג: מגבלת מזהי הנחיה",
"render.denoise_class_lens": "דולג: עומק שדה",
"render.denoise_class_material": "דולג: חומר לא נתמך",
"render.denoise_class_nested": "דולג: תווך מקונן",
"render.denoise_class_optical": "מתאים: משטח דרך שבירה",
"render.denoise_class_reflection": "דולג: נקודת סיום מחזירה",
"render.denoise_class_surface": "מתאים: משטח",
"render.denoise_class_tir": "דולג: החזרה פנימית מלאה",
"render.denoise_class_transmission": "דולג: העברה נוספת",
"render.denoise_class_unavailable": "דולג: אין נתוני הנחיה",
"render.denoise_class_volume": "דולג: פיזור נפחי",
"render.denoise_diagnostics_counts": "מתאימים: {eligible} · דולגו: {skipped}",
"render.denoise_diagnostics_enable": "איסוף נתוני אבחון להפחתת רעש",
"render.denoise_diagnostics_grid": "ספירת פיקסלים בגודל הפנימי {width} × {height}.",
"render.denoise_diagnostics_help": "נאספים מספרי הפיקסלים המתאימים ואלה שדולגו, גם כשהפחתת הרעש כבויה. הספירה לפי גודל הרינדור הפנימי; המסכה לפי גודל הפלט.",
"render.denoise_diagnostics_title": "אבחון הפחתת רעש",
"render.denoise_export_mask": "ייצוא המסכה כ־PNG",
"render.denoise_firefly": "דיכוי פיקסלים בהירים מבודדים",
"render.denoise_format_mask": "מסכה PNG",
"render.denoise_mask_copy_help": "אפשר לייצא את מסכת הפחתת הרעש כ־PNG, או לחזור לתמונה המרונדרת כדי להעתיק אותה.",
"render.denoise_mask_unavailable": "יש להפעיל אבחון הפחתת רעש ולרנדר תמונה כדי לבדוק את המסכה שלה.",
"render.denoise_optical_filter": "סינון מראות וזכוכית",
"render.denoise_optical_filter_help": "סינון אור מוחזר ומועבר שמתאים לסינון. כיבוי האפשרות מגביל את הסרת הרעש למשטחים אטומים.",
"render.denoise_passes": "מעברי סינון",
"render.denoise_settings_help": "מעברים נוספים מחליקים אזור רחב יותר. העוצמה קובעת כמה סינון מופעל בכל מעבר. מסלולים אופטיים שאינם נתמכים נשארים ללא סינון.",
"render.denoise_strength": "עוצמת הפחתת רעש",
"render.denoise_view_image": "תמונה מרונדרת",
"render.denoise_view_mask": "מסכת הפחתת רעש",
"render.diag_bodies": "גופים גלויים",
"render.diag_job_details": "פרטי המשימה שנלכדו",
"render.diag_last_check": "בדיקה מקדימה אחרונה",
@@ -4000,6 +4044,8 @@
"render.material_transient": "דריסות אופטיות זמניות ללשונית המסמך הזאת. הן אינן נשמרות בתכן, בהגדרת ההדמיה או בספריית החומרים.",
"render.material_v_up": "כיוון V של הטקסטורה מצביע מעלה בתמונה",
"render.material_weight": "משקל הציפוי (0–1)",
"render.metal_energy_compensation": "שחזור ההחזר של מתכת מחוספסת",
"render.metal_energy_compensation_help": "מפצה על האור שאובד בין הפאות המיקרוסקופיות של מתכת מחוספסת. עיבוד במעבד דורש תאורה עקיפה.",
"render.method_cpu": "מעקב קרניים ב־CPU",
"render.method_gpu": "מעקב מסלולים ב־GPU",
"render.method_limit_raster": "רסטר משתמש בתצוגה המקדימה הקבועה של הסטודיו האטום. לפקדים האופטיים הנתמכים יש לבחור שיטת מעקב קרניים מפורשת.",
@@ -4128,6 +4174,8 @@
"render.quality_denoise_on": "הפחתת רעש: פעילה",
"render.quality_draft": "טיוטה",
"render.quality_high": "גבוהה",
"render.quality_legacy_samples": "ערך שמור: {samples} דגימות לפיקסל. בחירות חדשות מוגבלות ל־{maximum} דגימות לפיקסל.",
"render.quality_preset_limit": "הגדרה קבועה זו חורגת ממגבלת הבחירה של 16384 דגימות לפיקסל. יש לבחור עד 16384 דגימות.",
"render.quality_projection_help": "בדיקה זו משנה רק רזולוציה, תקציב דגימות, עומק נתיב, זרע והפחתת רעש. ההגדרות האחרות נשמרות; התקציב אינו מעיד על התכנסות התמונה.",
"render.quality_review": "סקירת תקציב הדגימה",
"render.quality_summary": "{width} × {height} · {samples} דגימות · עומק {depth} · זרע {seed} · {denoise}",
@@ -4275,6 +4323,7 @@
"render.show_shots_label": "הצג את סרגל הצילומים",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "מותאם…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
@@ -4437,6 +4486,11 @@
"render.subsurface_enable": "הילוך אקראי לפיזור פנימי",
"render.subsurface_help": "לדיאלקטרי סגור בלבד. פיזור ובליעת RGB הם מקדמים נפרדים לכל mm. המסלולים מתפזרים בתוך הגאומטריה בפועל, ואז נשברים או מוחזרים בגבול. יש לבטל העברת צל ישרה, הסרת רעש משטחית ונפח חיצוני; ולהשתמש בשכבת פרנל אחת. אינו שיידר דיפוזיה, אינו רשת צפיפות ואינו חומר מדוד. תווך צפוף עשוי לדרוש יותר דגימות ועומק מסלול.",
"render.subsurface_rate": "קצב התנגשויות הפיזור",
"render.supersampling": "דגימת יתר",
"render.supersampling_dimensions": "רינדור בגודל {width} × {height} והקטנה ל־{output_width} × {output_height}. נעשה שימוש בפי ארבעה פיקסלים.",
"render.supersampling_help": "רינדור ברוחב ובגובה כפולים, ואז הקטנה במרחב אור ליניארי. גודל הפלט נשאר ללא שינוי.",
"render.supersampling_off": "כבוי",
"render.supersampling_x2": "2×",
"render.surface_import.author": "יוצר",
"render.surface_import.busy": "מתבצע ייבוא תמונה…",
"render.surface_import.complete": "בחר תמונה והזן את היוצר, המקור והרישיון שלה.",
@@ -4450,6 +4504,8 @@
"render.surface_import.saved_cpu": "יש לשמור את המפות לשימוש בהדמיה ב־CPU.",
"render.surface_import.source": "מקור",
"render.surface_import.title": "ייבוא תמונה",
"render.texture_filtering": "סינון פרטי מרקם",
"render.texture_filtering_help": "שימוש ברמות פירוט ובסינון אנאיזוטרופי מפחית שינון במרקמים מוקטנים. דורש יותר זיכרון; כשהאפשרות כבויה נשמר הסינון הקודם.",
"render.tone_aces_fitted": "ACES מותאם",
"render.tone_reinhard": "ריינהרד",
"render.transmission_scope": "זכוכית GPU תומכת בתווכים נפרדים מאושרים. תווכים מקוננים ללא הגבלה ומעריך העברה ללא רעש אינם מסופקים.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Prijavi problem…",
"Cross curves": "Krivulje presjeka",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter nastavlja na GitHubu.",
"Cube": "Kocka",
"Current orthographic view (1:1 mm)": "Trenutni ortografski prikaz (1:1 mm)",
"Current perspective view": "Trenutni perspektivni prikaz",
"Current plane": "Trenutna ravnina",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Kako SolidCraft izgleda i gdje su njegovi dijelovi.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Obrni zakrivljene strelice za valjanje",
"prefs.invert_curved_roll_arrows_note": "Dvije zakrivljene strelice u kutu kocke valjaju prikaz unutar ravnine zaslona. Uključeno: strelica u smjeru kazaljke na satu valja prikaz suprotno od smjera kazaljke na satu, a strelica suprotno od smjera kazaljke na satu valja ga u smjeru kazaljke na satu. Isključeno: svaka strelica valja prikaz onako kako je zakrivljena.",
"prefs.invert_navigator_arrows": "Obrni strelice za rotaciju kocke prikaza",
"prefs.invert_navigator_arrows_note": "Strelice oko kocke orbitiraju kameru u smjeru u kojem pokazuju, kao u programima Fusion i Inventor: strelica s lijeve strane pokazuje udesno i zakreće kameru udesno. Isključeno: strelica okreće kocku prema strani na kojoj se nalazi, pa lijeva strelica zakreće kameru ulijevo. Postavka ne utječe na zakrivljene strelice za valjanje.",
"prefs.keep_work_on_tab_switch": "Zadrži dijaloge i alate otvorenima pri prebacivanju između dizajna",
"prefs.keep_work_on_tab_switch_note": "Svaka kartica dizajna zadržava svoj otvoreni dijalog ili alat, s onim što ste u njemu odabrali i upisali, te svoju karticu alatne trake. Kad se vratite na dizajn, ponovno su tu. Isključeno: prebacivanje između dizajna poništava ih i prikazuje karticu Model.",
"prefs.labels": "Oznake",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigacija",
"prefs.navigation_note": "Kako miš, dodirna pločica i 3D miš pomiču prikaz.",
"prefs.navigator_fit": "Uklopi model nakon promjene prikaza u navigatoru",
"prefs.navigator_fit_note": "Lice, kut ili strelica kocke prikaza, odnosno os orijentacijskog gizma, također usmjerava pogled na sredinu modela s jedne udaljenosti na kojoj se uklapa iz svakog smjera (njegova granična sfera). Početni prikaz koristi isti zum i središte dok je ova opcija uključena, pa se prikazi koje odaberete poslije njega samo okreću. Isključeno: prikaz se okreće na mjestu i zadržava svoj cilj i zum.",
"prefs.network_host_remove": "Ukloni",
"prefs.network_locations": "Dopusti mrežne lokacije u dizajnima",
"prefs.network_locations_note": "Isključeno: povezane datoteke dizajna na drugom računalu (\\\\server\\share) otvaraju se samo s poslužitelja navedenih ovdje ili nakon što ga dopustite kad se to zatraži.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Hibajegy létrehozása…",
"Cross curves": "Keresztgörbék",
"Ctrl+Enter continues on GitHub.": "A Ctrl+Enter a GitHubon folytatja.",
"Cube": "Kocka",
"Current orthographic view (1:1 mm)": "Aktuális ortografikus nézet (1:1 mm)",
"Current perspective view": "Aktuális perspektivikus nézet",
"Current plane": "Aktuális sík",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "A SolidCraft megjelenése és elemeinek elhelyezése.",
"prefs.interval_minutes": "{n} perc",
"prefs.invert_curved_roll_arrows": "Az íves gördülőnyilak megfordítása",
"prefs.invert_curved_roll_arrows_note": "A nézetkocka sarkában lévő két íves nyíl a nézetet a képernyő síkjában gördíti. Be: az óramutató járásával megegyező nyíl ellentétes irányba gördít, az ellentétes nyíl pedig az óramutató járásával megegyező irányba. Ki: minden nyíl abba az irányba gördíti a nézetet, amerre ível.",
"prefs.invert_navigator_arrows": "A nézetkocka forgatónyilainak megfordítása",
"prefs.invert_navigator_arrows_note": "A kocka körüli nyilak úgy keringetik a kamerát, ahogy mutatnak, mint a Fusion és az Inventor esetében: a bal oldali nyíl jobbra mutat, és a kamerát jobbra lendíti. Ki: a nyíl a kockát afelé az oldal felé forgatja, amelyiken van, így a bal nyíl a kamerát balra lendíti. Az íves gördülőnyilakat ez nem érinti.",
"prefs.keep_work_on_tab_switch": "A párbeszédablakok és eszközök nyitva tartása tervek közötti váltáskor",
"prefs.keep_work_on_tab_switch_note": "Minden tervfül megtartja a nyitott párbeszédablakát vagy eszközét azzal együtt, amit kijelölt és beírt benne, valamint a menüszalag-fülét. A tervhez visszatérve újra ott vannak. Ki: a tervek közötti váltás megszakítja őket, és a Modell fület mutatja.",
"prefs.labels": "Feliratok",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigáció",
"prefs.navigation_note": "Hogyan mozgatja a nézetet az egér, az érintőpad és a 3D-egér.",
"prefs.navigator_fit": "A modell illesztése navigátornézet-váltás után",
"prefs.navigator_fit_note": "A nézetkocka egy lapja, sarka vagy nyila, illetve a gizmo egy tengelye a modell közepére is ránéz abból az egyetlen távolságból, amelyből minden oldalról belefér (a határoló gömbje). Amíg ez be van kapcsolva, a Kezdőnézet ugyanazt a nagyítást és középpontot használja, így az utána választott nézetek csak elfordulnak. Ki: a nézet a helyén fordul el, megtartva a célpontját és a nagyítását.",
"prefs.network_host_remove": "Eltávolítás",
"prefs.network_locations": "Hálózati helyek engedélyezése a tervekben",
"prefs.network_locations_note": "Ki: egy terv másik számítógépen lévő hivatkozott fájljai (\\\\server\\share) csak az itt felsorolt kiszolgálókról nyílnak meg, vagy miután kérdésre engedélyez egyet.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Buat Issue…",
"Cross curves": "Kurva silang",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter melanjutkan di GitHub.",
"Cube": "Kubus",
"Current orthographic view (1:1 mm)": "Tampilan ortografis saat ini (1:1 mm)",
"Current perspective view": "Tampilan perspektif saat ini",
"Current plane": "Bidang saat ini",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Tampilan SolidCraft dan letak bagian-bagiannya.",
"prefs.interval_minutes": "{n} mnt",
"prefs.invert_curved_roll_arrows": "Balikkan panah roll melengkung",
"prefs.invert_curved_roll_arrows_note": "Dua panah melengkung di sudut kubus memutar (roll) tampilan di dalam bidang layar. Aktif: panah searah jarum jam memutar berlawanan arah jarum jam dan panah berlawanan arah jarum jam memutar searah jarum jam. Mati: tiap panah memutar tampilan sesuai arah lengkungannya.",
"prefs.invert_navigator_arrows": "Balikkan panah rotasi kubus tampilan",
"prefs.invert_navigator_arrows_note": "Panah di sekeliling kubus memutar orbit kamera ke arah yang ditunjuknya, seperti di Fusion dan Inventor: panah di kiri menunjuk ke kanan dan mengayunkan kamera ke kanan. Mati: panah memutar kubus ke sisi tempatnya berada, sehingga panah kiri mengayunkan kamera ke kiri. Panah roll yang melengkung tidak terpengaruh.",
"prefs.keep_work_on_tab_switch": "Biarkan dialog dan alat tetap terbuka saat berpindah desain",
"prefs.keep_work_on_tab_switch_note": "Setiap tab desain mempertahankan dialog atau alat yang terbuka, beserta apa yang Anda pilih dan ketik di dalamnya, dan tab toolbar-nya. Semuanya kembali saat Anda kembali ke desain itu. Mati: berpindah desain membatalkannya dan menampilkan tab Model.",
"prefs.labels": "Label",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigasi",
"prefs.navigation_note": "Cara mouse, trackpad, dan mouse 3D menggerakkan tampilan.",
"prefs.navigator_fit": "Buat model pas di tampilan setelah perubahan tampilan lewat navigator",
"prefs.navigator_fit_note": "Sebuah muka, sudut, atau panah pada kubus tampilan, atau sumbu pada gizmo, juga menghadap ke tengah model dari satu jarak yang membuatnya pas dari segala sisi (bola pembatasnya). Awal memakai zoom dan pusat yang sama selama opsi ini aktif, sehingga tampilan yang dipilih setelahnya hanya berputar. Mati: tampilan berputar di tempat, dengan target dan zoom tetap.",
"prefs.network_host_remove": "Hapus",
"prefs.network_locations": "Izinkan lokasi jaringan di desain",
"prefs.network_locations_note": "Nonaktif: file tertaut sebuah desain di komputer lain (\\\\server\\share) hanya dibuka dari server yang tercantum di sini, atau setelah Anda mengizinkan salah satunya saat ditanya.",
+944 -5
View File
File diff suppressed because it is too large Load Diff
+63 -7
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Issueを作成…",
"Cross curves": "交差する曲線",
"Ctrl+Enter continues on GitHub.": "Ctrl+EnterでGitHubに進みます。",
"Cube": "キューブ",
"Current orthographic view (1:1 mm)": "現在の平行投影(実寸・mm)",
"Current perspective view": "現在の透視投影",
"Current plane": "現在の平面",
@@ -1509,6 +1508,7 @@
"cmd.timeline_stop": "履歴を停止",
"cmd.timeline_suppress": "フィーチャーを抑制",
"cmd.ui.assistant": "アシスタントの表示切り替え",
"cmd.ui.palette": "コマンドを検索",
"cmd.view.navigator": "カスタム ビューナビゲーター",
"cmd.view.render_browser": "Renderブラウザー",
"cmd.view.render_dock": "レンダードックを移動",
@@ -2431,6 +2431,7 @@
"parts.dimension.width": "幅",
"parts.family.bearing": "深溝玉軸受(外形)",
"parts.family.button_head_cap_screw": "ボタンヘッド六角穴付きボルト",
"parts.family.countersunk_socket_screw": "六角穴付き皿ボルト",
"parts.family.dowel_pin": "平行ピン",
"parts.family.hex_bolt": "六角ボルト",
"parts.family.hex_cap_screw_inch": "六角ボルト(インチ)",
@@ -2681,9 +2682,9 @@
"prefs.interface_note": "SolidCraft の外観と各パネルの配置。",
"prefs.interval_minutes": "{n} 分",
"prefs.invert_curved_roll_arrows": "カーブしたロール矢印を反転",
"prefs.invert_curved_roll_arrows_note": "キューブの隅にある2つのカーブした矢印は、画面内でビューをロールします。オン: 時計回りの矢印が反時計回りに、反時計回りの矢印が時計回りにロールします。オフ: 各矢印は、そのカーブの向きにビューをロールします。",
"prefs.invert_navigator_arrows": "ビューキューブの回転矢印を反転",
"prefs.invert_navigator_arrows_note": "キューブの周りの矢印が、矢印の指す方向にカメラを回します(Fusion や Inventor と同じ動き):左の矢印は右を指し、カメラを右に振ります。オフ: 矢印は、そのある面の方へキューブを回すため、左の矢印はカメラを左に振ります。カーブしたロール矢印は影響を受けません。",
"prefs.invert_curved_roll_arrows_note": "ナビゲーターの隅にある2つのカーブした矢印は、画面内でビューをロールします。オン: 時計回りの矢印が反時計回りに、反時計回りの矢印が時計回りにロールします。オフ: 各矢印は、そのカーブの向きにビューをロールします。",
"prefs.invert_navigator_arrows": "ナビゲーターの回転矢印を反転",
"prefs.invert_navigator_arrows_note": "ナビゲーターの周りの矢印が、矢印の指す方向にカメラを回します(Fusion や Inventor と同じ動き):左の矢印は右を指し、カメラを右に振ります。オフ: 矢印は、その側へビューを回すため、左の矢印はカメラを左に振ります。カーブしたロール矢印は影響を受けません。",
"prefs.keep_unsaved_also_asks": "タブを閉じるときにも確認する",
"prefs.keep_unsaved_also_asks_tip": "オフにすると、未保存の各デザインのコピーが最新であれば、ブラウザーの確認なしでタブが閉じます。",
"prefs.keep_unsaved_in_browser": "未保存の作業をこのブラウザーに保持する",
@@ -2743,7 +2744,6 @@
"prefs.move_drag_highlights_tip": "オフ: 移動の矢印でコンポーネントをドラッグしている間はポインターの下をハイライトしないため、大きなアセンブリでも滑らかに動きます。断面や解析を表示中は通常どおりハイライトし、ジョイントに沿ってドラッグする部品ではハイライトしません。",
"prefs.navigation": "ナビゲーション",
"prefs.navigation.axes_signed": "軸",
"prefs.navigation.cube_compact": "キューブ",
"prefs.navigation.device": "デバイス",
"prefs.navigation.free": "自由",
"prefs.navigation.orbit_style": "回転方式",
@@ -2751,7 +2751,7 @@
"prefs.navigation.turntable": "ターンテーブル",
"prefs.navigation_note": "マウス、トラックパッド、3Dマウスによるビューの操作。",
"prefs.navigator_fit": "ナビゲーターでの視点変更後にモデルを全体表示",
"prefs.navigator_fit_note": "キューブの面・角・矢印やギズモの軸をクリックすると、どの方向からでも全体が収まる一定の距離(外接球)からモデルの中心を見ます。オンの間はホームビューも同じズームと中心を使うため、その後に選んだ視点は向きだけが変わります。オフ: 視点はその場で回転し、注視点とズームを保ちます。",
"prefs.navigator_fit_note": "ナビゲーターの面・角・矢印やギズモの軸をクリックすると、どの方向からでも全体が収まる一定の距離(外接球)からモデルの中心を見ます。オンの間はホームビューも同じズームと中心を使うため、その後に選んだ視点は向きだけが変わります。オフ: 視点はその場で回転し、注視点とズームを保ちます。",
"prefs.network_host_remove": "削除",
"prefs.network_locations": "デザイン内のネットワーク上の場所を許可",
"prefs.network_locations_note": "ファイルサーバーでデザインを管理する組織向け。オフの場合は許可したサーバーのみ。",
@@ -2826,6 +2826,8 @@
"prefs.remember_dialog_values.help": "既定ではオフです。オンにすると、コマンドが成功した後、SolidCraft を閉じるまで、同じコマンドを次に開いたときに安全な値とオプションを再利用します。",
"prefs.render_legacy_image_defaults": "以前のレンダー画像の既定値を使用",
"prefs.render_legacy_image_defaults_help": "以前の手動の画像ワークフローを使用します。新しいセットアップはCPUラスタ、CPUレイトレーシングは固定サンプリングとし、結果は手動で開きます。レンダーに入るたびにビューをフィットし、ショットを切り替えても保存カメラを復元せず、以前のフレーミングとナビゲーションを使用します。自動のライブ画像更新は無効にします。保存済みのセットアップは変更しません。",
"prefs.render_legacy_memory_admission": "以前のレンダリング用メモリチェックを使用",
"prefs.render_legacy_memory_admission_help": "推定使用量が使用可能なRAMを超えていても、共有およびホストの固定メモリ上限内でネイティブレンダリングを許可します。デバイスの制限とメモリ割り当てのチェックは引き続き適用されます。",
"prefs.render_previous_geometry_budget": "レンダープレビューで以前のジオメトリ予算を使用",
"prefs.render_previous_geometry_budget_help": "デバイスに基づく予算の代わりに、以前の固定ジオメトリ上限(64ビットデスクトップでは512 MiB、その他では128 MiB)に戻します。",
"prefs.reports_saved": "{n} 件保存済み",
@@ -3220,8 +3222,10 @@
"render.category_paint": "塗装",
"render.category_plastic": "プラスチック",
"render.category_wood": "木材",
"render.caustics_area_lights": "面光源のコースティクス",
"render.caustics_depth": "集光経路の深さ",
"render.caustics_enable": "点光源・平行光源によるガラスの集光",
"render.caustics_environment": "環境光のコースティクス",
"render.caustics_help": "光子数はノイズと計算量を調整します。収集半径を広げると細部より滑らかさを優先します。CPUでは間接照明が必要です。",
"render.caustics_path_required": "CPUの集光には間接照明が必要です。",
"render.caustics_photons": "光子数",
@@ -3398,7 +3402,7 @@
"render.cpu_opaque_required": "CPU レイトレーシングには不透明な背景が必要です。透明な背景を解除してください。",
"render.cpu_options_pending": "このCPUパネルでは、発光、表面効果、ガラス、異方性はまだ編集できません。使用前にそれらのAPIオプションを解除してください。",
"render.cpu_path_depth": "パス深度",
"render.cpu_path_help": "不透明な面の間で反射し、対応するガラス形状を通る光を計算します。プログレッシブ描画とレイトレーシングの影が必要です。パス深度は最初の交点から、照明を計算する面、ガラス境界、散乱イベントを数えます(1~16)。深度1は不透明面の直接照明のみです。値を上げると反射やガラスの通過を増やせますが、時間がかかる場合があります。独自の反射設定や異方性を使うには間接照明をオフにしてください。",
"render.cpu_path_help": "不透明な面の間で反射し、対応するガラス形状を通る光を計算します。プログレッシブ描画とレイトレーシングの影が必要です。パス深度は最初の交点から、照明を計算する面、ガラス境界、散乱イベントを数えます(1~64)。深度1は不透明面の直接照明のみです。値を上げると反射やガラスの通過を増やせますが、時間がかかる場合があります。独自の反射設定や異方性を使うには間接照明をオフにしてください。",
"render.cpu_path_lighting": "間接照明",
"render.cpu_path_optics_reason": "設定は保持されます。独自の反射設定や異方性を使うには間接照明をオフにしてください。",
"render.cpu_point": "点光源",
@@ -3438,6 +3442,12 @@
"render.cpu_subsurface_denoise_reason": "CPUの内部散乱ではノイズ除去をオフにしてください。設定値は保持されます。",
"render.cpu_subsurface_help": "散乱は閉じたガラスの内部で光の方向を変えます。正の値には間接照明を使い、ノイズ除去をオフにします。率は1mmあたりの衝突回数です。0では散乱を加えません。方向の偏りが正なら前方、負なら後方を優先します。",
"render.cpu_summary": "CPUレイトレーシング・反射・HDR・平行光と点光源",
"render.cpu_threads": "CPU ワーカー",
"render.cpu_threads_auto": "自動",
"render.cpu_threads_custom": "カスタム",
"render.cpu_threads_help": "自動では利用可能な CPU ワーカーを使用します。1 ワーカーでは順次実行します。ワーカー数を増やすとメモリ使用量が増える場合があります。",
"render.cpu_threads_single": "1 ワーカー",
"render.cpu_threads_web": "ブラウザーでのレンダリングは単一の協調ワーカーを使用します。保存されたデスクトップ用ワーカー設定は保持されます。",
"render.cpu_transparent_help": "透明PNGは背景を除き、輪郭の被覆率を保持します。ボディと面の不透明度は100%にしてください。",
"render.cpu_volume_enable_path": "間接照明を有効にする",
"render.cpu_volume_enable_path_plan": "CPUレイトレーシング · パス深度{depth} · {spp}サンプル/ピクセル · シード{seed} · レイトレースシャドウ。大気と画像予算の設定を保持します。",
@@ -3446,6 +3456,40 @@
"render.cpu_volume_method_incompatible": "この照明、サンプリング、大気の設定はこの方式に引き継げません。設定は保持されます。",
"render.cpu_volume_path_required": "CPUの大気を使うには間接照明を有効にしてください。",
"render.cpu_warm_cool": "暖色と寒色",
"render.denoise_class_background": "対象外: 背景",
"render.denoise_class_coverage": "対象外: 部分的なカバレッジ",
"render.denoise_class_degenerate": "対象外: 退化した経路",
"render.denoise_class_depth": "対象外: 経路深度の上限",
"render.denoise_class_emitter": "対象外: 発光体",
"render.denoise_class_exit": "対象外: 不整合な出射",
"render.denoise_class_identity": "対象外: ガイド識別子の上限",
"render.denoise_class_lens": "対象外: 被写界深度",
"render.denoise_class_material": "対象外: 未対応のマテリアル",
"render.denoise_class_nested": "対象外: 入れ子の媒質",
"render.denoise_class_optical": "対象: 屈折越しのサーフェス",
"render.denoise_class_reflection": "対象外: 反射する終点",
"render.denoise_class_surface": "対象: サーフェス",
"render.denoise_class_tir": "対象外: 全反射",
"render.denoise_class_transmission": "対象外: 追加の透過",
"render.denoise_class_unavailable": "対象外: ガイドなし",
"render.denoise_class_volume": "対象外: ボリューム散乱",
"render.denoise_diagnostics_counts": "対象: {eligible} · 対象外: {skipped}",
"render.denoise_diagnostics_enable": "ノイズ除去の診断情報を収集",
"render.denoise_diagnostics_grid": "内部サイズ {width} × {height} でのピクセル数。",
"render.denoise_diagnostics_help": "ノイズ除去がオフでも、対象と対象外のピクセル数を収集します。集計は内部レンダリングサイズ、マスクは出力サイズに対応します。",
"render.denoise_diagnostics_title": "ノイズ除去の診断",
"render.denoise_export_mask": "マスク PNG を書き出す",
"render.denoise_firefly": "孤立した高輝度ピクセルを抑制",
"render.denoise_format_mask": "マスク PNG",
"render.denoise_mask_copy_help": "ノイズ除去マスクを PNG で書き出すか、レンダリング画像に戻ってコピーしてください。",
"render.denoise_mask_unavailable": "ノイズ除去の診断を有効にして画像をレンダリングすると、マスクを確認できます。",
"render.denoise_optical_filter": "鏡とガラスをフィルター処理",
"render.denoise_optical_filter_help": "対象となる反射光と透過光にフィルターを適用します。オフにすると、不透明な表面のみのノイズを除去します。",
"render.denoise_passes": "フィルターパス数",
"render.denoise_settings_help": "パス数を増やすと、より広い範囲が平滑化されます。強度は各パスでのフィルター適用量を調整します。未対応の光路にはフィルターを適用しません。",
"render.denoise_strength": "ノイズ除去の強度",
"render.denoise_view_image": "レンダリング画像",
"render.denoise_view_mask": "ノイズ除去マスク",
"render.diag_bodies": "表示ボディ数",
"render.diag_job_details": "記録されたジョブの詳細",
"render.diag_last_check": "前回の事前確認",
@@ -4000,6 +4044,8 @@
"render.material_transient": "この文書タブの一時的な光学設定です。設計、レンダー設定、マテリアルライブラリには保存されません。",
"render.material_v_up": "テクスチャV軸は画像の上向き",
"render.material_weight": "コートの重み(0~1)",
"render.metal_energy_compensation": "粗い金属の反射を補正",
"render.metal_energy_compensation_help": "粗い金属の微小面間で失われる光を補います。CPU レンダリングには間接照明が必要です。",
"render.method_cpu": "CPU レイトレーシング",
"render.method_gpu": "GPU パストレーシング",
"render.method_limit_raster": "ラスターは固定の不透明スタジオプレビューを使用します。対応する光学設定には明示的なレイトレーシング方式を選択してください。",
@@ -4128,6 +4174,8 @@
"render.quality_denoise_on": "ノイズ低減:オン",
"render.quality_draft": "ドラフト",
"render.quality_high": "高品質",
"render.quality_legacy_samples": "保存値: {samples} spp。新しい選択値の上限は {maximum} spp です。",
"render.quality_preset_limit": "このプリセットは選択上限の 16384 spp を超えています。16384 以下のサンプル数を選択してください。",
"render.quality_projection_help": "この確認で変更するのは、解像度、サンプル予算、パス深度、シード、ノイズ低減だけです。他の設定は維持され、収束は保証されません。",
"render.quality_review": "サンプリング予算を確認",
"render.quality_summary": "{width} × {height} · {samples} サンプル · 深度 {depth} · シード {seed} · {denoise}",
@@ -4275,6 +4323,7 @@
"render.show_shots_label": "ショットストリップを表示",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "カスタム…",
"render.size_full_hd": "フルHD",
"render.size_hd": "HD",
@@ -4437,6 +4486,11 @@
"render.subsurface_enable": "内部散乱ランダムウォーク",
"render.subsurface_help": "閉じた誘電体専用。散乱とRGB吸収は別々の毎mm係数です。実際の形状内で散乱し、境界で屈折・反射します。直線透過影、表面ノイズ除去、外部ボリュームを無効にし、フレネル層数を1にします。拡散シェーダー、密度グリッド、実測物性ではありません。高密度ではサンプル数と深度が必要です。",
"render.subsurface_rate": "散乱衝突率",
"render.supersampling": "スーパーサンプリング",
"render.supersampling_dimensions": "{width} × {height} でレンダリングし、{output_width} × {output_height} に縮小します。ピクセル数は 4 倍になります。",
"render.supersampling_help": "幅と高さをそれぞれ 2 倍にしてレンダリングし、リニアライトで縮小します。出力サイズは変わりません。",
"render.supersampling_off": "オフ",
"render.supersampling_x2": "2×",
"render.surface_import.author": "作者",
"render.surface_import.busy": "画像をインポート中…",
"render.surface_import.complete": "画像を選び、作者、出典、ライセンスを入力してください。",
@@ -4450,6 +4504,8 @@
"render.surface_import.saved_cpu": "CPUレンダリングで使うにはマップを保存してください。",
"render.surface_import.source": "出典",
"render.surface_import.title": "画像をインポート",
"render.texture_filtering": "テクスチャの細部をフィルタリング",
"render.texture_filtering_help": "ミップレベルと異方性フィルタリングで縮小時のちらつきを抑えます。メモリ使用量が増えます。オフでは従来のフィルタリングを維持します。",
"render.tone_aces_fitted": "ACES近似",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPUガラスは検証された非交差媒体に対応します。無制限の入れ子媒体やノイズのない透過推定器は提供しません。",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "შეტყობინების შექმნა…",
"Cross curves": "განივი მრუდები",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter-ით GitHub-ზე გააგრძელებთ.",
"Cube": "კუბი",
"Current orthographic view (1:1 mm)": "მიმდინარე ორთოგრაფიული ხედი (1:1 mm)",
"Current perspective view": "მიმდინარე პერსპექტიული ხედი",
"Current plane": "მიმდინარე სიბრტყე",
@@ -2316,9 +2315,6 @@
"prefs.interface_note": "როგორ გამოიყურება SolidCraft და სად არის მისი ნაწილები.",
"prefs.interval_minutes": "{n} წთ",
"prefs.invert_curved_roll_arrows": "გორების მრუდე ისრების შებრუნება",
"prefs.invert_curved_roll_arrows_note": "კუბის კუთხესთან მდებარე ორი მრუდე ისარი ხედს ეკრანის სიბრტყეში აგორებს. ჩართული: საათის ისრის მიმართულების ისარი საათის ისრის საწინააღმდეგოდ აგორებს, საწინააღმდეგო კი — საათის ისრის მიმართულებით. გამორთული: თითოეული ისარი ხედს თავისი მოხრის მიმართულებით აგორებს.",
"prefs.invert_navigator_arrows": "ხედის კუბის ბრუნვის ისრების შებრუნება",
"prefs.invert_navigator_arrows_note": "კუბის გარშემო ისრები კამერას ორბიტაზე იმ მიმართულებით ატრიალებს, საითაც მიუთითებს, როგორც Fusion-სა და Inventor-ში: მარცხენა ისარი მარჯვნივ მიუთითებს და კამერას მარჯვნივ აბრუნებს. გამორთული: ისარი კუბს იმ მხრისკენ აბრუნებს, რომელზეც მდებარეობს, ამიტომ მარცხენა ისარი კამერას მარცხნივ აბრუნებს. გორების მრუდე ისრებზე ეს არ მოქმედებს.",
"prefs.keep_work_on_tab_switch": "პროექტებს შორის გადართვისას დიალოგებისა და ხელსაწყოების ღიად დატოვება",
"prefs.keep_work_on_tab_switch_note": "პროექტის თითოეული ჩანართი ინარჩუნებს თავის ღია დიალოგს ან ხელსაწყოს, მასში არჩეულსა და აკრეფილს, და ხელსაწყოთა ზოლის ჩანართს. პროექტში დაბრუნებისას ისინი ისევ იქ დაგხვდებათ. გამორთული: პროექტების გადართვა მათ აუქმებს და „მოდელის“ ჩანართს აჩვენებს.",
"prefs.labels": "წარწერები",
@@ -2355,7 +2351,6 @@
"prefs.navigation": "ნავიგაცია",
"prefs.navigation_note": "როგორ ამოძრავებს ხედს მაუსი, სენსორული პანელი და 3D მაუსი.",
"prefs.navigator_fit": "ნავიგატორით ხედის შეცვლის შემდეგ მოდელის ჩატევა",
"prefs.navigator_fit_note": "ხედის კუბის წახნაგი, კუთხე ან ისარი, ან მანიპულატორის ღერძი ასევე მოდელის შუაგულს უყურებს ერთი მანძილიდან, საიდანაც ის ყველა მხრიდან ეტევა (მისი შემომსაზღვრელი სფერო). სანამ ეს ჩართულია, საწყისი ხედი იმავე მასშტაბსა და ცენტრს იყენებს, ამიტომ მის შემდეგ არჩეული ხედები მხოლოდ ბრუნავს. გამორთული: ხედი ადგილზე ბრუნავს, სამიზნისა და მასშტაბის შენარჩუნებით.",
"prefs.network_host_remove": "წაშლა",
"prefs.network_locations": "პროექტებში ქსელური მდებარეობების დაშვება",
"prefs.network_locations_note": "ორგანიზაციებისთვის, რომლებიც პროექტებს ფაილურ სერვერებზე ინახავენ. გამორთული: მხოლოდ თქვენ მიერ დაშვებული სერვერები.",
+58 -4
View File
@@ -151,7 +151,6 @@
"Create Issue — review draft": "이슈 만들기 — 초안 검토",
"Create Issue…": "이슈 만들기…",
"Cross curves": "교차 곡선",
"Cube": "큐브",
"Current orthographic view (1:1 mm)": "현재 직교 투영 뷰 (1:1 mm)",
"Current perspective view": "현재 원근 투영 뷰",
"Current plane": "현재 평면",
@@ -1252,6 +1251,7 @@
"cmd.timeline_stop": "기록 재생 중지",
"cmd.timeline_suppress": "특성 비활성화",
"cmd.ui.assistant": "도우미 표시 전환",
"cmd.ui.palette": "명령 검색",
"cmd.view.navigator": "사용자 지정 뷰 탐색기",
"cmd.view.render_browser": "렌더 브라우저",
"cmd.view.render_dock": "렌더 도크 이동",
@@ -2082,7 +2082,6 @@
"prefs.move_drag_highlights_tip": "끄면 이동 화살표로 구성 요소를 끄는 동안 포인터 아래 항목을 강조 표시하지 않아 큰 어셈블리도 부드럽게 움직입니다. 단면이나 해석이 표시된 동안에는 그대로 강조 표시하며, 조인트를 따라 끄는 부품은 강조 표시하지 않습니다.",
"prefs.navigation": "탐색",
"prefs.navigation.axes_signed": "축",
"prefs.navigation.cube_compact": "큐브",
"prefs.navigation.device": "장치",
"prefs.navigation.free": "자유",
"prefs.navigation.orbit_style": "회전 방식",
@@ -2090,7 +2089,6 @@
"prefs.navigation.turntable": "턴테이블",
"prefs.navigation_note": "마우스, 트랙패드, 3D 마우스로 뷰를 움직이는 방식입니다.",
"prefs.navigator_fit": "뷰 탐색기로 뷰를 바꾼 뒤 모델 맞춤",
"prefs.navigator_fit_note": "큐브의 면, 모서리, 화살표나 기즈모의 축을 클릭하면 어느 방향에서든 전체가 들어가는 한 거리(경계 구)에서 모델의 중심을 바라봅니다. 켜져 있는 동안 홈 뷰도 같은 확대/축소와 중심을 쓰므로 그 뒤에 고른 뷰는 방향만 바뀝니다. 끄면 뷰가 제자리에서 회전하며 대상과 확대/축소를 유지합니다.",
"prefs.network_locations_note": "파일 서버에 디자인을 보관하는 조직용입니다. 끄면 허용한 서버만 사용합니다.",
"prefs.not_on_gpu": "이 GPU에서는 사용할 수 없음",
"prefs.on": "켜짐",
@@ -2129,6 +2127,8 @@
"prefs.remember_dialog_values.help": "기본값은 꺼짐입니다. 켜면 명령이 성공한 후 SolidCraft를 닫을 때까지 같은 명령을 다음에 열 때 안전한 값과 옵션을 다시 사용합니다.",
"prefs.render_legacy_image_defaults": "이전 렌더 이미지 기본값 사용",
"prefs.render_legacy_image_defaults_help": "이전의 수동 이미지 작업 방식을 유지합니다. 새 설정은 CPU 래스터를 사용하고 CPU 광선 추적은 고정 샘플링을 사용하며 결과는 직접 엽니다. 렌더에 들어갈 때 뷰를 다시 맞추고, 샷을 전환해도 저장된 카메라를 복원하지 않으며, 이전의 프레이밍과 탐색 방식을 사용합니다. 자동 실시간 이미지 피드백을 끕니다. 저장된 설정은 변경하지 않습니다.",
"prefs.render_legacy_memory_admission": "이전 렌더링 메모리 검사 사용",
"prefs.render_legacy_memory_admission_help": "예상 사용량이 사용 가능한 RAM을 초과해도 고정된 공유 및 호스트 메모리 한도 내에서 네이티브 렌더링을 허용합니다. 장치 제한과 메모리 할당 검사는 계속 적용됩니다.",
"prefs.reports_saved": "{n}개 저장됨",
"prefs.restore_page_defaults": "페이지 기본값 복원",
"prefs.ribbon_click_ends_tool": "실행 중인 도구의 버튼을 클릭하면 도구 종료",
@@ -2491,8 +2491,10 @@
"render.category_paint": "페인트",
"render.category_plastic": "플라스틱",
"render.category_wood": "목재",
"render.caustics_area_lights": "면광원 집광 효과",
"render.caustics_depth": "집광 경로 깊이",
"render.caustics_enable": "점광원 및 방향광의 유리 집광",
"render.caustics_environment": "환경광 집광 효과",
"render.caustics_help": "광자 수는 노이즈와 계산량을 조절합니다. 수집 반경은 세밀한 표현과 부드러운 추정 사이를 조절합니다. CPU에는 간접 조명이 필요합니다.",
"render.caustics_path_required": "CPU 집광에는 간접 조명이 필요합니다.",
"render.caustics_photons": "광자 수",
@@ -2669,7 +2671,7 @@
"render.cpu_opaque_required": "CPU 레이 트레이싱에는 불투명 배경이 필요합니다. 계속하려면 투명 배경을 해제하세요.",
"render.cpu_options_pending": "이 CPU 패널은 아직 발광, 표면 효과, 유리, 이방성을 편집할 수 없습니다. 사용하기 전에 해당 API 옵션을 해제하세요.",
"render.cpu_path_depth": "경로 깊이",
"render.cpu_path_help": "불투명 표면 사이에서 반사되고 지원되는 유리 형상을 통과하는 빛을 계산합니다. 점진적 렌더링과 레이 추적 그림자가 필요합니다. 경로 깊이는 첫 교점부터 조명이 계산되는 표면, 유리 경계 및 산란 이벤트를 셉니다(1–16). 깊이 1은 불투명 표면의 직접 조명만 계산합니다. 값을 높이면 반사나 유리 통과가 늘어나며 시간이 더 걸릴 수 있습니다. 사용자 반사 설정이나 이방성을 사용하려면 간접 조명을 끄세요.",
"render.cpu_path_help": "불투명 표면 사이에서 반사되고 지원되는 유리 형상을 통과하는 빛을 계산합니다. 점진적 렌더링과 레이 추적 그림자가 필요합니다. 경로 깊이는 첫 교점부터 조명이 계산되는 표면, 유리 경계 및 산란 이벤트를 셉니다(1–64). 깊이 1은 불투명 표면의 직접 조명만 계산합니다. 값을 높이면 반사나 유리 통과가 늘어나며 시간이 더 걸릴 수 있습니다. 사용자 반사 설정이나 이방성을 사용하려면 간접 조명을 끄세요.",
"render.cpu_path_lighting": "간접 조명",
"render.cpu_path_optics_reason": "설정은 유지됩니다. 사용자 반사 설정이나 이방성을 사용하려면 간접 조명을 끄세요.",
"render.cpu_point": "점 조명",
@@ -2709,6 +2711,12 @@
"render.cpu_subsurface_denoise_reason": "CPU 내부 산란을 사용하려면 노이즈 제거를 끄세요. 설정은 유지됩니다.",
"render.cpu_subsurface_help": "산란은 닫힌 유리 내부에서 빛의 방향을 바꿉니다. 양수 비율에는 간접광을 사용하고 노이즈 제거를 끄세요. 비율은 mm당 충돌 횟수이며, 0은 산란을 추가하지 않습니다. 방향 편향이 양수이면 전방, 음수이면 후방을 선호합니다.",
"render.cpu_summary": "CPU 레이 트레이싱 · 반사 · HDR · 방향성 및 점 조명",
"render.cpu_threads": "CPU 작업 스레드",
"render.cpu_threads_auto": "자동",
"render.cpu_threads_custom": "사용자 지정",
"render.cpu_threads_help": "자동은 사용 가능한 CPU 작업 스레드를 사용합니다. 단일 스레드는 순차적으로 실행됩니다. 스레드가 많을수록 메모리가 더 필요할 수 있습니다.",
"render.cpu_threads_single": "단일",
"render.cpu_threads_web": "브라우저 렌더링은 하나의 협력형 작업 스레드를 사용합니다. 저장된 데스크톱 스레드 설정은 유지됩니다.",
"render.cpu_transparent_help": "투명 PNG는 배경을 제거하고 가장자리 커버리지를 유지합니다. 바디와 면의 불투명도는 100%여야 합니다.",
"render.cpu_volume_enable_path": "간접 조명 켜기",
"render.cpu_volume_enable_path_plan": "CPU 레이 트레이싱 · 경로 깊이 {depth} · 픽셀당 {spp}샘플 · 시드 {seed} · 레이 트레이싱 그림자. 작성한 대기와 이미지 예산을 유지합니다.",
@@ -2717,6 +2725,40 @@
"render.cpu_volume_method_incompatible": "이 조명, 샘플링 또는 대기 설정을 이 방식으로 옮길 수 없습니다. 설정은 유지됩니다.",
"render.cpu_volume_path_required": "CPU 대기를 사용하려면 간접 조명을 켜세요.",
"render.cpu_warm_cool": "따뜻함과 차가움",
"render.denoise_class_background": "건너뜀: 배경",
"render.denoise_class_coverage": "건너뜀: 부분 커버리지",
"render.denoise_class_degenerate": "건너뜀: 퇴화 경로",
"render.denoise_class_depth": "건너뜀: 경로 깊이 한도",
"render.denoise_class_emitter": "건너뜀: 발광체",
"render.denoise_class_exit": "건너뜀: 호환되지 않는 출사",
"render.denoise_class_identity": "건너뜀: 가이드 식별자 한도",
"render.denoise_class_lens": "건너뜀: 피사계 심도",
"render.denoise_class_material": "건너뜀: 지원되지 않는 재질",
"render.denoise_class_nested": "건너뜀: 중첩된 매질",
"render.denoise_class_optical": "적용 가능: 굴절을 통해 보이는 표면",
"render.denoise_class_reflection": "건너뜀: 반사 종점",
"render.denoise_class_surface": "적용 가능: 표면",
"render.denoise_class_tir": "건너뜀: 전반사",
"render.denoise_class_transmission": "건너뜀: 추가 투과",
"render.denoise_class_unavailable": "건너뜀: 가이드 없음",
"render.denoise_class_volume": "건너뜀: 볼륨 산란",
"render.denoise_diagnostics_counts": "적용 가능: {eligible} · 건너뜀: {skipped}",
"render.denoise_diagnostics_enable": "노이즈 제거 진단 수집",
"render.denoise_diagnostics_grid": "내부 크기 {width} × {height} 기준 픽셀 수입니다.",
"render.denoise_diagnostics_help": "노이즈 제거가 꺼져 있어도 적용 가능 픽셀과 건너뛴 픽셀 수를 수집합니다. 집계는 내부 렌더링 크기 기준이며 마스크는 출력 크기를 사용합니다.",
"render.denoise_diagnostics_title": "노이즈 제거 진단",
"render.denoise_export_mask": "마스크 PNG 내보내기",
"render.denoise_firefly": "고립된 밝은 픽셀 억제",
"render.denoise_format_mask": "마스크 PNG",
"render.denoise_mask_copy_help": "노이즈 제거 마스크를 PNG로 내보내거나 렌더링 이미지로 돌아가 이미지를 복사하세요.",
"render.denoise_mask_unavailable": "노이즈 제거 진단을 켜고 이미지를 렌더링하면 마스크를 확인할 수 있습니다.",
"render.denoise_optical_filter": "거울과 유리 필터링",
"render.denoise_optical_filter_help": "필터링 가능한 반사광과 투과광을 처리합니다. 끄면 불투명한 표면의 노이즈만 제거합니다.",
"render.denoise_passes": "필터 반복 횟수",
"render.denoise_settings_help": "반복 횟수가 많을수록 더 넓은 영역을 부드럽게 합니다. 강도는 각 단계에서 적용하는 필터의 정도를 조절합니다. 지원되지 않는 광 경로에는 필터를 적용하지 않습니다.",
"render.denoise_strength": "노이즈 제거 강도",
"render.denoise_view_image": "렌더링 이미지",
"render.denoise_view_mask": "노이즈 제거 마스크",
"render.diag_bodies": "표시된 바디",
"render.diag_job_details": "캡처된 작업 세부 정보",
"render.diag_last_check": "마지막 사전 점검",
@@ -3269,6 +3311,8 @@
"render.material_transient": "이 문서 탭의 임시 광학 재정의입니다. 디자인, 렌더 설정, 재질 라이브러리에는 저장되지 않습니다.",
"render.material_v_up": "텍스처 V축은 이미지 위쪽",
"render.material_weight": "코트 가중치 (0~1)",
"render.metal_energy_compensation": "거친 금속의 반사 복원",
"render.metal_energy_compensation_help": "거친 금속의 미세면 사이에서 손실되는 빛을 보정합니다. CPU 렌더링에는 간접 조명이 필요합니다.",
"render.method_cpu": "CPU 레이 트레이싱",
"render.method_gpu": "GPU 패스 트레이싱",
"render.method_limit_raster": "래스터는 고정된 불투명 스튜디오 미리보기를 사용합니다. 지원되는 광학 컨트롤을 쓰려면 명시적인 레이 트레이싱 방식을 선택하세요.",
@@ -3397,6 +3441,8 @@
"render.quality_denoise_on": "노이즈 제거: 켜짐",
"render.quality_draft": "초안",
"render.quality_high": "높음",
"render.quality_legacy_samples": "저장된 값: {samples} spp. 새 선택값은 {maximum} spp로 제한됩니다.",
"render.quality_preset_limit": "이 프리셋은 선택 한도인 16384 spp를 초과합니다. 샘플 수를 16384 이하로 선택하세요.",
"render.quality_projection_help": "이 검토에서는 해상도, 샘플 예산, 경로 깊이, 시드 및 노이즈 제거만 변경합니다. 다른 설정은 유지되며 수렴은 보장되지 않습니다.",
"render.quality_review": "샘플링 예산 검토",
"render.quality_summary": "{width} × {height} · 샘플 {samples}개 · 깊이 {depth} · 시드 {seed} · {denoise}",
@@ -3543,6 +3589,7 @@
"render.show_shots_label": "샷 스트립 표시",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "사용자 지정…",
"render.size_full_hd": "풀 HD",
"render.size_hd": "HD",
@@ -3705,6 +3752,11 @@
"render.subsurface_enable": "내부 산란 랜덤 워크",
"render.subsurface_help": "닫힌 유전체 전용입니다. 산란과 RGB 흡수는 mm당 별도 계수입니다. 경로는 실제 형상 내부에서 산란한 뒤 경계에서 굴절하거나 반사합니다. 직선 그림자 투과, 표면 노이즈 제거, 외부 볼륨을 끄고 프레넬 층 하나를 사용하세요. 확산 셰이더, 밀도 그리드, 측정된 재질이 아닙니다. 밀도가 높은 매질에는 더 많은 샘플과 경로 깊이가 필요할 수 있습니다.",
"render.subsurface_rate": "산란 충돌률",
"render.supersampling": "슈퍼샘플링",
"render.supersampling_dimensions": "{width} × {height}로 렌더링한 뒤 {output_width} × {output_height}로 축소합니다. 픽셀 수가 네 배가 됩니다.",
"render.supersampling_help": "가로와 세로를 각각 두 배로 렌더링한 뒤 선형 광 공간에서 축소합니다. 출력 크기는 그대로 유지됩니다.",
"render.supersampling_off": "끄기",
"render.supersampling_x2": "2×",
"render.surface_import.author": "작성자",
"render.surface_import.busy": "이미지 가져오는 중…",
"render.surface_import.complete": "이미지를 선택하고 작성자, 출처, 라이선스를 입력하세요.",
@@ -3718,6 +3770,8 @@
"render.surface_import.saved_cpu": "CPU 렌더링에서 사용하려면 맵을 저장하세요.",
"render.surface_import.source": "출처",
"render.surface_import.title": "이미지 가져오기",
"render.texture_filtering": "텍스처 세부 필터링",
"render.texture_filtering_help": "밉 레벨과 비등방성 필터링으로 축소 시 텍스처 계단 현상을 줄입니다. 메모리를 더 사용하며 끄면 기존 필터링을 유지합니다.",
"render.tone_aces_fitted": "ACES 근사",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPU 유리는 보증된 분리 매질을 지원합니다. 제한 없는 중첩 매질과 노이즈 없는 투과 추정기는 제공되지 않습니다.",
File diff suppressed because it is too large Load Diff
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Cipta Isu…",
"Cross curves": "Lengkung silang",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter meneruskan di GitHub.",
"Cube": "Kubus",
"Current orthographic view (1:1 mm)": "Pandangan ortografik semasa (1:1 mm)",
"Current perspective view": "Pandangan perspektif semasa",
"Current plane": "Satah semasa",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Rupa SolidCraft dan kedudukan bahagiannya.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Songsangkan anak panah gelek melengkung",
"prefs.invert_curved_roll_arrows_note": "Dua anak panah melengkung di sudut kubus menggelek pandangan dalam satah skrin. Hidup: anak panah ikut arah jam menggelek lawan arah jam dan anak panah lawan arah jam menggelek ikut arah jam. Mati: setiap anak panah menggelek pandangan mengikut arah lengkungannya.",
"prefs.invert_navigator_arrows": "Songsangkan anak panah putaran kubus pandangan",
"prefs.invert_navigator_arrows_note": "Anak panah di sekeliling kubus mengorbit kamera mengikut arah yang ditunjuknya, seperti dalam Fusion dan Inventor: anak panah di kiri menunjuk ke kanan dan mengayunkan kamera ke kanan. Mati: anak panah memutarkan kubus ke arah sisi tempat ia berada, jadi anak panah kiri mengayunkan kamera ke kiri. Anak panah gelek yang melengkung tidak terjejas.",
"prefs.keep_work_on_tab_switch": "Kekalkan dialog dan alat terbuka apabila menukar reka bentuk",
"prefs.keep_work_on_tab_switch_note": "Setiap tab reka bentuk mengekalkan dialog atau alat yang dibukanya, bersama apa yang anda pilih dan taip di dalamnya, serta tab bar alatnya. Semuanya ada semula apabila anda kembali ke reka bentuk itu. Mati: menukar reka bentuk membatalkannya dan menunjukkan tab Model.",
"prefs.labels": "Label",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigasi",
"prefs.navigation_note": "Cara tetikus, pad jejak dan tetikus 3D menggerakkan pandangan.",
"prefs.navigator_fit": "Muatkan model selepas perubahan pandangan navigator",
"prefs.navigator_fit_note": "Muka, sudut atau anak panah pada kubus pandangan, atau paksi pada gizmo, turut menghadap ke tengah model dari satu jarak yang memuatkannya dari semua sisi (sfera pembatasnya). Utama menggunakan zum dan pusat yang sama selagi ini dihidupkan, jadi pandangan yang dipilih selepasnya hanya berputar. Mati: pandangan berputar di tempat, dengan sasaran dan zumnya kekal.",
"prefs.network_host_remove": "Buang",
"prefs.network_locations": "Benarkan lokasi rangkaian dalam reka bentuk",
"prefs.network_locations_note": "Dimatikan: fail terpaut bagi reka bentuk pada komputer lain (\\\\server\\share) hanya dibuka daripada pelayan yang disenaraikan di sini, atau selepas anda membenarkannya apabila ditanya.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Opprett sak…",
"Cross curves": "Tverrkurver",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter fortsetter på GitHub.",
"Cube": "Kube",
"Current orthographic view (1:1 mm)": "Gjeldende ortografisk visning (1:1 mm)",
"Current perspective view": "Gjeldende perspektivvisning",
"Current plane": "Gjeldende plan",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Hvordan SolidCraft ser ut, og hvor delene er plassert.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Inverter de krumme rullepilene",
"prefs.invert_curved_roll_arrows_note": "De to krumme pilene i hjørnet av kuben ruller visningen i skjermplanet. På: pilen med klokken ruller mot klokken, og pilen mot klokken ruller med klokken. Av: hver pil ruller visningen i den retningen den krummer seg.",
"prefs.invert_navigator_arrows": "Inverter rotasjonspilene på visningskuben",
"prefs.invert_navigator_arrows_note": "Pilene rundt kuben roterer kameraet i den retningen de peker, som i Fusion og Inventor: pilen til venstre peker mot høyre og svinger kameraet mot høyre. Av: en pil roterer kuben mot siden den står på, så venstre pil svinger kameraet mot venstre. De krumme rullepilene påvirkes ikke.",
"prefs.keep_work_on_tab_switch": "Behold dialoger og verktøy åpne når du bytter design",
"prefs.keep_work_on_tab_switch_note": "Hver designfane beholder sin åpne dialog eller sitt åpne verktøy, med det du har valgt og skrevet i den, og sin fane på båndet. De er der igjen når du kommer tilbake til designet. Av: å bytte design avbryter dem og viser fanen Modell.",
"prefs.labels": "Etiketter",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigasjon",
"prefs.navigation_note": "Hvordan mus, styreflate og 3D-mus beveger visningen.",
"prefs.navigator_fit": "Tilpass modellen etter en visningsendring fra navigatoren",
"prefs.navigator_fit_note": "En flate, et hjørne eller en pil på visningskuben, eller en akse på orienteringsindikatoren, ser også mot midten av modellen fra den ene avstanden som får den til å passe fra alle sider (den omsluttende kulen). Hjem bruker samme zoom og senter mens dette er på, så visninger som velges etterpå bare dreier. Av: visningen dreier på stedet og beholder målpunktet og zoomen sin.",
"prefs.network_host_remove": "Fjern",
"prefs.network_locations": "Tillat nettverksplasseringer i design",
"prefs.network_locations_note": "Av: lenkede filer i et design på en annen datamaskin (\\\\server\\share) åpnes bare fra serverne som er oppført her, eller etter at du har tillatt en når du blir spurt.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Issue aanmaken…",
"Cross curves": "Kruiscurven",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter gaat verder op GitHub.",
"Cube": "Kubus",
"Current orthographic view (1:1 mm)": "Huidige orthografische weergave (1:1 mm)",
"Current perspective view": "Huidige perspectiefweergave",
"Current plane": "Huidig vlak",
@@ -2251,9 +2250,6 @@
"prefs.interface_note": "Hoe SolidCraft eruitziet en waar de onderdelen staan.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "De gebogen rolpijlen omkeren",
"prefs.invert_curved_roll_arrows_note": "De twee gebogen pijlen op de hoek van de kubus laten de weergave in het schermvlak rollen. Aan: de pijl met de klok mee rolt tegen de klok in en de pijl tegen de klok in rolt met de klok mee. Uit: elke pijl rolt de weergave in de richting waarin hij krult.",
"prefs.invert_navigator_arrows": "De rotatiepijlen van de weergavekubus omkeren",
"prefs.invert_navigator_arrows_note": "De pijlen rond de kubus laten de camera roteren in de richting waarin ze wijzen, zoals in Fusion en Inventor: de pijl links wijst naar rechts en zwaait de camera naar rechts. Uit: een pijl draait de kubus naar de kant waar hij staat, dus de linkerpijl zwaait de camera naar links. De gebogen rolpijlen worden hierdoor niet beïnvloed.",
"prefs.keep_work_on_tab_switch": "Dialoogvensters en gereedschappen open houden bij het wisselen van ontwerp",
"prefs.keep_work_on_tab_switch_note": "Elk ontwerptabblad behoudt zijn geopende dialoogvenster of gereedschap, met wat je erin hebt gekozen en getypt, en zijn lint-tabblad. Ze zijn er weer als je terugkeert naar het ontwerp. Uit: wisselen van ontwerp breekt ze af en toont het tabblad Model.",
"prefs.labels": "Labels",
@@ -2290,7 +2286,6 @@
"prefs.navigation": "Navigatie",
"prefs.navigation_note": "Hoe de muis, het trackpad en de 3D-muis de weergave verplaatsen.",
"prefs.navigator_fit": "Het model passend in beeld brengen na een wijziging van de navigatorweergave",
"prefs.navigator_fit_note": "Een vlak, hoek of pijl van de weergavekubus, of een as van de gizmo, kijkt ook naar het midden van het model, vanaf de ene afstand waarop het van alle kanten in beeld past (de omsluitende bol). Beginweergave gebruikt dezelfde zoom en hetzelfde middelpunt zolang dit aan staat, zodat weergaven die je daarna kiest alleen draaien. Uit: de weergave draait ter plaatse en behoudt het richtpunt en de zoom.",
"prefs.network_host_remove": "Verwijderen",
"prefs.network_locations": "Netwerklocaties in ontwerpen toestaan",
"prefs.network_locations_note": "Uit: gekoppelde bestanden van een ontwerp op een andere computer (\\\\server\\share) worden alleen geopend vanaf de servers in deze lijst, of nadat je er een toestaat wanneer erom wordt gevraagd.",
@@ -0,0 +1,21 @@
{
"en": { "ui.agent_chat_snap_none_found": "No coding agent found. The Snap package is confined, so it cannot see or start the coding agents installed on this computer. To use them, install SolidCraft from the AppImage, .deb or .rpm package instead. An agent that uses an OpenAI-compatible API works in the Snap package too: set one up with Add agent." },
"ar": { "ui.agent_chat_snap_none_found": "لم يُعثر على وكيل برمجي. تعمل حزمة Snap في بيئة معزولة، لذا لا يمكنها رؤية الوكلاء البرمجيين المثبّتين على هذا الحاسوب ولا تشغيلهم. لاستخدامهم، ثبّت SolidCraft من حزمة AppImage أو .deb أو .rpm بدلًا من ذلك. يعمل الوكيل الذي يستخدم واجهة برمجية متوافقة مع OpenAI في حزمة Snap أيضًا: أعدّه من «إضافة وكيل»." },
"cs": { "ui.agent_chat_snap_none_found": "Nebyl nalezen žádný kódovací agent. Balíček Snap běží v izolovaném prostředí, takže nevidí kódovací agenty nainstalované v tomto počítači a nemůže je spustit. Chcete-li je používat, nainstalujte SolidCraft z balíčku AppImage, .deb nebo .rpm. Agent, který používá API kompatibilní s OpenAI, v balíčku Snap funguje: nastavte ho přes Přidat agenta." },
"de": { "ui.agent_chat_snap_none_found": "Kein Coding-Agent gefunden. Das Snap-Paket ist abgeschottet und kann die auf diesem Computer installierten Coding-Agenten weder sehen noch starten. Um sie zu nutzen, installieren Sie SolidCraft stattdessen aus dem AppImage-, .deb- oder .rpm-Paket. Ein Agent, der eine OpenAI-kompatible API nutzt, funktioniert auch im Snap-Paket: Richten Sie ihn mit Agent hinzufügen ein." },
"es": { "ui.agent_chat_snap_none_found": "No se ha encontrado ningún agente de programación. El paquete Snap está confinado, así que no puede ver ni iniciar los agentes de programación instalados en este equipo. Para usarlos, instala SolidCraft desde el paquete AppImage, .deb o .rpm. Un agente que use una API compatible con OpenAI también funciona en el paquete Snap: configúralo con Añadir agente." },
"fr": { "ui.agent_chat_snap_none_found": "Aucun agent de programmation trouvé. Le paquet Snap est confiné, il ne peut donc ni voir ni lancer les agents de programmation installés sur cet ordinateur. Pour les utiliser, installer plutôt SolidCraft depuis le paquet AppImage, .deb ou .rpm. Un agent qui utilise une API compatible OpenAI fonctionne aussi dans le paquet Snap. Le configurer avec Ajouter un agent." },
"he": { "ui.agent_chat_snap_none_found": "לא נמצא סוכן קוד. חבילת ה־Snap מבודדת, ולכן היא לא יכולה לראות או להפעיל את סוכני הקוד שמותקנים במחשב הזה. כדי להשתמש בהם, יש להתקין את SolidCraft מחבילת AppImage, .deb או .rpm במקום זאת. סוכן שמשתמש ב־API תואם OpenAI עובד גם בחבילת ה־Snap, ואפשר להגדיר אותו בעזרת הוספת סוכן." },
"it": { "ui.agent_chat_snap_none_found": "Nessun agente di programmazione trovato. Il pacchetto Snap è confinato, quindi non può vedere né avviare gli agenti di programmazione installati su questo computer. Per usarli, installa SolidCraft dal pacchetto AppImage, .deb o .rpm. Un agente che usa un’API compatibile con OpenAI funziona anche nel pacchetto Snap: configuralo con Aggiungi agente." },
"ja": { "ui.agent_chat_snap_none_found": "コーディングエージェントが見つかりません。Snap パッケージは制限された環境で動作するため、このコンピューターにインストールされたコーディングエージェントを認識することも起動することもできません。それらを使うには、代わりに AppImage、.deb または .rpm パッケージから SolidCraft をインストールしてください。OpenAI 互換 API を使うエージェントは Snap パッケージでも動作します。「エージェントを追加」から設定してください。" },
"ko": { "ui.agent_chat_snap_none_found": "코딩 에이전트를 찾지 못했습니다. Snap 패키지는 격리된 환경에서 실행되므로 이 컴퓨터에 설치된 코딩 에이전트를 보거나 시작할 수 없습니다. 이 에이전트를 사용하려면 대신 AppImage, .deb 또는 .rpm 패키지로 SolidCraft를 설치하세요. OpenAI 호환 API를 사용하는 에이전트는 Snap 패키지에서도 작동합니다. 에이전트 추가에서 설정하세요." },
"nl": { "ui.agent_chat_snap_none_found": "Geen codeeragent gevonden. Het Snap-pakket is afgeschermd en kan de codeeragents die op deze computer zijn geïnstalleerd niet zien of starten. Installeer SolidCraft in plaats daarvan via het AppImage-, .deb- of .rpm-pakket om ze te gebruiken. Een agent die een OpenAI-compatibele API gebruikt, werkt ook in het Snap-pakket: stel er een in met Agent toevoegen." },
"pl": { "ui.agent_chat_snap_none_found": "Nie znaleziono agenta programistycznego. Pakiet Snap działa w izolacji, więc nie widzi agentów programistycznych zainstalowanych na tym komputerze i nie może ich uruchomić. Aby z nich korzystać, zainstaluj SolidCraft z pakietu AppImage, .deb lub .rpm. Agent korzystający z API zgodnego z OpenAI działa także w pakiecie Snap: skonfiguruj go przyciskiem Dodaj agenta." },
"pt-BR": { "ui.agent_chat_snap_none_found": "Nenhum agente de programação encontrado. O pacote Snap é confinado, então não consegue ver nem iniciar os agentes de programação instalados neste computador. Para usá-los, instale o SolidCraft pelo pacote AppImage, .deb ou .rpm. Um agente que usa uma API compatível com OpenAI também funciona no pacote Snap: configure-o em Adicionar agente." },
"ru": { "ui.agent_chat_snap_none_found": "Кодирующий агент не найден. Пакет Snap работает в изолированной среде, поэтому не видит и не может запускать кодирующих агентов, установленных на этом компьютере. Чтобы пользоваться ими, установите SolidCraft из пакета AppImage, .deb или .rpm. Агент, использующий API, совместимый с OpenAI, работает и в пакете Snap: настройте его через «Добавить агент»." },
"tr": { "ui.agent_chat_snap_none_found": "Kodlama ajanı bulunamadı. Snap paketi yalıtılmış çalıştığı için bu bilgisayarda yüklü kodlama ajanlarını göremez ve başlatamaz. Bunları kullanmak için SolidCraft'ı bunun yerine AppImage, .deb veya .rpm paketinden yükleyin. OpenAI uyumlu API kullanan bir ajan Snap paketinde de çalışır: Ajan ekle ile ayarlayın." },
"uk": { "ui.agent_chat_snap_none_found": "Агента для програмування не знайдено. Пакет Snap працює в ізольованому середовищі, тому не бачить і не може запускати агентів для програмування, установлених на цьому комп’ютері. Щоб користуватися ними, установіть SolidCraft з пакета AppImage, .deb або .rpm. Агент, що використовує API, сумісний з OpenAI, працює і в пакеті Snap: налаштуйте його через «Додати агента»." },
"vi": { "ui.agent_chat_snap_none_found": "Không tìm thấy tác tử lập trình nào. Gói Snap chạy trong môi trường bị giới hạn nên không thể thấy hay khởi động các tác tử lập trình được cài trên máy tính này. Để dùng chúng, hãy cài SolidCraft từ gói AppImage, .deb hoặc .rpm thay vào đó. Tác tử dùng API tương thích OpenAI vẫn hoạt động trong gói Snap: hãy thiết lập bằng Thêm AI Agent." },
"zh": { "ui.agent_chat_snap_none_found": "未找到编程代理。Snap 软件包运行在受限环境中,无法看到或启动此计算机上安装的编程代理。要使用它们,请改用 AppImage、.deb 或 .rpm 软件包安装 SolidCraft。使用 OpenAI 兼容 API 的代理在 Snap 软件包中同样可用:请通过“添加智能体”进行设置。" },
"zh-TW": { "ui.agent_chat_snap_none_found": "未找到程式設計代理。Snap 套件在受限環境中執行,無法看到或啟動這台電腦上安裝的程式設計代理。若要使用它們,請改用 AppImage、.deb 或 .rpm 套件安裝 SolidCraft。使用 OpenAI 相容 API 的代理在 Snap 套件中同樣可用:請透過「新增代理」進行設定。" }
}
@@ -0,0 +1,26 @@
{
"en": {
"component.error.ambiguous_name": "Several components are named `{name}`. Use a numeric component ID: {ids}."
},
"ar": {
"component.error.ambiguous_name": "توجد عدة مكونات بالاسم `{name}`. استخدم معرّف مكوّن رقميًا: {ids}."
},
"de": {
"component.error.ambiguous_name": "Mehrere Komponenten heißen `{name}`. Verwende eine numerische Komponenten-ID: {ids}."
},
"he": {
"component.error.ambiguous_name": "לכמה רכיבים יש השם `{name}`. יש להשתמש במזהה רכיב מספרי: {ids}."
},
"ja": {
"component.error.ambiguous_name": "複数のコンポーネントに `{name}` という名前が付いています。数値のコンポーネント ID を使用してください: {ids}。"
},
"ko": {
"component.error.ambiguous_name": "이름이 `{name}`인 구성요소가 여러 개입니다. 숫자 구성요소 ID를 사용하세요: {ids}."
},
"vi": {
"component.error.ambiguous_name": "Nhiều thành phần có tên `{name}`. Hãy dùng ID thành phần dạng số: {ids}."
},
"zh": {
"component.error.ambiguous_name": "多个组件名为 `{name}`。请使用数字组件 ID:{ids}。"
}
}
@@ -0,0 +1,18 @@
{
"en": { "ui.browser_collapse_below": "Collapse Below", "ui.browser_expand_below": "Expand Below" },
"ar": { "ui.browser_collapse_below": "طي ما بالأسفل", "ui.browser_expand_below": "توسيع ما بالأسفل" },
"de": { "ui.browser_collapse_below": "Darunter einklappen", "ui.browser_expand_below": "Darunter ausklappen" },
"es": { "ui.browser_collapse_below": "Contraer debajo", "ui.browser_expand_below": "Expandir debajo" },
"fr": { "ui.browser_collapse_below": "Réduire en dessous", "ui.browser_expand_below": "Développer en dessous" },
"he": { "ui.browser_collapse_below": "כיווץ מה שמתחת", "ui.browser_expand_below": "הרחבת מה שמתחת" },
"it": { "ui.browser_collapse_below": "Comprimi sotto", "ui.browser_expand_below": "Espandi sotto" },
"ja": { "ui.browser_collapse_below": "この下を折りたたむ", "ui.browser_expand_below": "この下を展開" },
"ko": { "ui.browser_collapse_below": "하위 항목 접기", "ui.browser_expand_below": "하위 항목 펼치기" },
"nl": { "ui.browser_collapse_below": "Onderliggende samenvouwen", "ui.browser_expand_below": "Onderliggende uitvouwen" },
"pl": { "ui.browser_collapse_below": "Zwiń poniżej", "ui.browser_expand_below": "Rozwiń poniżej" },
"ru": { "ui.browser_collapse_below": "Свернуть вложенное", "ui.browser_expand_below": "Развернуть вложенное" },
"uk": { "ui.browser_collapse_below": "Згорнути вкладене", "ui.browser_expand_below": "Розгорнути вкладене" },
"vi": { "ui.browser_collapse_below": "Thu gọn bên dưới", "ui.browser_expand_below": "Mở rộng bên dưới" },
"zh": { "ui.browser_collapse_below": "折叠下级", "ui.browser_expand_below": "展开下级" },
"zh-TW": { "ui.browser_collapse_below": "折疊下層", "ui.browser_expand_below": "展開下層" }
}
@@ -0,0 +1,22 @@
{
"ar": {
"ui.construction.axis_hint": "خط إنشائي: انقر نقطة، أو أدخل بعدها عن محور الرسم",
"ui.construction.parallel_hint": "خط إنشائي: اختر خطًا مستقيمًا ثم انقر أو أدخل الإزاحة؛ انقر بزر الفأرة الأيمن لتغيير المرجع"
},
"he": {
"ui.construction.axis_hint": "ישר עזר: לחיצה על נקודה, או הקלדת המרחק מציר הסקיצה",
"ui.construction.parallel_hint": "ישר עזר: בחירת ישר, ואז לחיצה או הקלדת ההיסט; לחיצה ימנית להחלפת הייחוס"
},
"ja": {
"ui.construction.axis_hint": "補助直線: 点をクリックするか、スケッチ軸からの距離を入力",
"ui.construction.parallel_hint": "補助直線: 直線を選択し、クリックするかオフセットを入力。右クリックで参照を変更"
},
"vi": {
"ui.construction.axis_hint": "Đường dựng hình: nhấp một điểm, hoặc nhập khoảng cách tới trục phác thảo",
"ui.construction.parallel_hint": "Đường dựng hình: chọn đường thẳng, rồi nhấp hoặc nhập khoảng dời; nhấp chuột phải để đổi tham chiếu"
},
"zh": {
"ui.construction.axis_hint": "构造直线:单击一点,或输入它到草图轴的距离",
"ui.construction.parallel_hint": "构造直线:先选直线,再单击或输入偏移;右键单击更换参照"
}
}
@@ -0,0 +1,34 @@
{
"ar": {
"prefs.history_steps_component": "خطوات السجل حسب المكوّن",
"prefs.history_steps_component_note": "عندما يكون مكوّن نشطًا أو محددًا، تتنقل عناصر تحكم السجل بين ميزات ذلك المكوّن كما يعرضها السجل في الأسفل. عند إيقافه تتنقل بين كل ميزات التصميم. السجل على اليسار أو تحت المستعرض يتنقل دائمًا بين كل الميزات."
},
"de": {
"prefs.history_steps_component": "Verlaufsschritte je Komponente",
"prefs.history_steps_component_note": "Ist eine Komponente aktiv oder ausgewählt, gehen die Verlaufssteuerelemente durch deren Features, so wie der Verlauf unten sie zeigt. Aus: Sie gehen durch alle Features des Designs. Der Verlauf links oder unter dem Browser geht immer durch alle Features."
},
"en": {
"prefs.history_steps_component": "History steps by component",
"prefs.history_steps_component_note": "With a component active or picked, the History controls step through that component's features, as the History at the bottom shows them. Off: they step through every feature of the design. The History on the left or under the browser always steps through every feature."
},
"he": {
"prefs.history_steps_component": "צעדי ציר הזמן לפי רכיב",
"prefs.history_steps_component_note": "כשרכיב פעיל או נבחר, פקדי ציר הזמן עוברים בין הפיצ'רים של אותו רכיב, כפי שציר הזמן למטה מציג אותם. כבוי: הם עוברים בין כל הפיצ'רים של התכנון. ציר הזמן משמאל או מתחת לדפדפן עובר תמיד בין כל הפיצ'רים."
},
"ja": {
"prefs.history_steps_component": "コンポーネント単位で履歴を移動",
"prefs.history_steps_component_note": "コンポーネントがアクティブまたは選択されているとき、履歴コントロールは下部の履歴に表示されるとおり、そのコンポーネントのフィーチャだけを移動します。オフにすると、デザインのすべてのフィーチャを移動します。左側またはブラウザー下の履歴は常にすべてのフィーチャを移動します。"
},
"ko": {
"prefs.history_steps_component": "구성 요소별 히스토리 이동",
"prefs.history_steps_component_note": "구성 요소가 활성화되었거나 선택되어 있으면 기록 컨트롤이 아래쪽 히스토리에 보이는 대로 그 구성 요소의 피처만 이동합니다. 끄면 디자인의 모든 피처를 이동합니다. 왼쪽 또는 브라우저 아래의 히스토리는 항상 모든 피처를 이동합니다."
},
"vi": {
"prefs.history_steps_component": "Bước Lịch sử theo cụm chi tiết",
"prefs.history_steps_component_note": "Khi một cụm chi tiết đang kích hoạt hoặc được chọn, các điều khiển Lịch sử đi qua các tính năng của cụm đó, như dòng Lịch sử ở cạnh dưới hiển thị. Tắt: chúng đi qua mọi tính năng của thiết kế. Cây Lịch sử ở bên trái hoặc dưới cây thư mục luôn đi qua mọi tính năng."
},
"zh": {
"prefs.history_steps_component": "历史按组件逐步移动",
"prefs.history_steps_component_note": "当某个组件处于活动或选中状态时,历史控件按底部历史的显示,只在该组件的特征之间移动。关闭后,它们在设计的所有特征之间移动。左侧或浏览器下方的历史始终在所有特征之间移动。"
}
}
@@ -0,0 +1,47 @@
{
"en": {
"measure.perimeter": "Perimeter",
"measure.perimeter_inner": "Inner perimeter",
"measure.perimeter_outer": "Outer perimeter"
},
"ar": {
"measure.perimeter": "المحيط",
"measure.perimeter_inner": "المحيط الداخلي",
"measure.perimeter_outer": "المحيط الخارجي"
},
"de": {
"measure.perimeter": "Umfang",
"measure.perimeter_inner": "Innenumfang",
"measure.perimeter_outer": "Außenumfang"
},
"fr": {
"measure.perimeter": "Périmètre",
"measure.perimeter_inner": "Périmètre intérieur",
"measure.perimeter_outer": "Périmètre extérieur"
},
"he": {
"measure.perimeter": "היקף",
"measure.perimeter_inner": "היקף פנימי",
"measure.perimeter_outer": "היקף חיצוני"
},
"ja": {
"measure.perimeter": "周長",
"measure.perimeter_inner": "内周長",
"measure.perimeter_outer": "外周長"
},
"ko": {
"measure.perimeter": "둘레",
"measure.perimeter_inner": "내부 둘레",
"measure.perimeter_outer": "외부 둘레"
},
"vi": {
"measure.perimeter": "Chu vi",
"measure.perimeter_inner": "Chu vi trong",
"measure.perimeter_outer": "Chu vi ngoài"
},
"zh": {
"measure.perimeter": "周长",
"measure.perimeter_inner": "内周长",
"measure.perimeter_outer": "外周长"
}
}
@@ -0,0 +1,11 @@
{
"ar": { "cmd.inspect_parameter_variants": "مقارنة بدائل المعلمات" },
"de": { "cmd.inspect_parameter_variants": "Parametervarianten vergleichen" },
"en": { "cmd.inspect_parameter_variants": "Compare Parameter Variants" },
"es": { "cmd.inspect_parameter_variants": "Comparar variantes de parámetros" },
"he": { "cmd.inspect_parameter_variants": "השוואת חלופות פרמטרים" },
"ja": { "cmd.inspect_parameter_variants": "パラメーターの組み合わせを比較" },
"ko": { "cmd.inspect_parameter_variants": "매개변수 조합 비교" },
"vi": { "cmd.inspect_parameter_variants": "So sánh các phương án tham số" },
"zh": { "cmd.inspect_parameter_variants": "比较参数方案" }
}
@@ -0,0 +1,14 @@
{
"en": { "ui.properties.copy_value": "Click to copy", "ui.properties.value_copied": "Value copied" },
"ar": { "ui.properties.copy_value": "انقر للنسخ", "ui.properties.value_copied": "تم نسخ القيمة" },
"de": { "ui.properties.copy_value": "Klicken, um den Wert zu kopieren", "ui.properties.value_copied": "Wert kopiert" },
"es": { "ui.properties.copy_value": "Haz clic para copiar", "ui.properties.value_copied": "Valor copiado" },
"fr": { "ui.properties.copy_value": "Cliquer pour copier", "ui.properties.value_copied": "Valeur copiée" },
"he": { "ui.properties.copy_value": "לחיצה מעתיקה את הערך", "ui.properties.value_copied": "הערך הועתק" },
"it": { "ui.properties.copy_value": "Fai clic per copiare", "ui.properties.value_copied": "Valore copiato" },
"ja": { "ui.properties.copy_value": "クリックでコピー", "ui.properties.value_copied": "値をコピーしました" },
"ko": { "ui.properties.copy_value": "클릭하여 복사", "ui.properties.value_copied": "값을 복사했습니다" },
"vi": { "ui.properties.copy_value": "Nhấp để sao chép", "ui.properties.value_copied": "Đã sao chép giá trị" },
"zh": { "ui.properties.copy_value": "点击复制", "ui.properties.value_copied": "已复制数值" },
"zh-TW": { "ui.properties.copy_value": "按一下以複製", "ui.properties.value_copied": "已複製數值" }
}
+82
View File
@@ -0,0 +1,82 @@
{
"ar": {
"document.discard_row": "تجاهل",
"document.finish_save_prompt": "أكمل الرد على نافذة الحفظ قبل فتح تصميم آخر.",
"document.not_saved_yet": "لم يُحفظ بعد",
"document.save_many": "حفظ التغييرات في {n} من التصاميم؟",
"document.save_row": "حفظ",
"document.save_selected": "حفظ المحدد",
"prefs.save_prompt_one_at_a_time": "السؤال عن كل تصميم غير محفوظ على حدة عند إغلاق عدة تصاميم",
"prefs.save_prompt_one_at_a_time_help": "عرض نافذة حفظ لكل تصميم غير محفوظ بدلاً من قائمة مجمّعة."
},
"de": {
"document.discard_row": "Verwerfen",
"document.finish_save_prompt": "Beantworten Sie die Speicherabfrage, bevor Sie einen weiteren Entwurf öffnen.",
"document.not_saved_yet": "Noch nicht gespeichert",
"document.save_many": "Änderungen an {n} Entwürfen speichern?",
"document.save_row": "Speichern",
"document.save_selected": "Auswahl speichern",
"prefs.save_prompt_one_at_a_time": "Beim Schließen mehrerer Entwürfe für jeden ungespeicherten Entwurf einzeln fragen",
"prefs.save_prompt_one_at_a_time_help": "Für jeden ungespeicherten Entwurf eine eigene Speicherabfrage statt einer gemeinsamen Liste anzeigen."
},
"en": {
"document.discard_row": "Discard",
"document.finish_save_prompt": "Finish answering the save prompt before opening another design.",
"document.not_saved_yet": "Not saved yet",
"document.save_many": "Save changes to {n} designs?",
"document.save_row": "Save",
"document.save_selected": "Save Selected",
"prefs.save_prompt_one_at_a_time": "Ask about each unsaved design separately when closing several",
"prefs.save_prompt_one_at_a_time_help": "Show one save prompt per unsaved design instead of a combined list."
},
"he": {
"document.discard_row": "ביטול שינויים",
"document.finish_save_prompt": "יש לסיים לענות על חלון השמירה לפני פתיחת תכן אחר.",
"document.not_saved_yet": "עדיין לא נשמר",
"document.save_many": "לשמור שינויים ב־{n} תכנים?",
"document.save_row": "שמירה",
"document.save_selected": "שמירת הנבחרים",
"prefs.save_prompt_one_at_a_time": "לשאול על כל תכן שלא נשמר בנפרד בעת סגירת כמה",
"prefs.save_prompt_one_at_a_time_help": "להציג חלון שמירה נפרד לכל תכן שלא נשמר במקום רשימה משותפת."
},
"ja": {
"document.discard_row": "破棄",
"document.finish_save_prompt": "別のデザインを開く前に、保存の確認に回答してください。",
"document.not_saved_yet": "未保存",
"document.save_many": "{n} 件のデザインの変更を保存しますか?",
"document.save_row": "保存",
"document.save_selected": "選択項目を保存",
"prefs.save_prompt_one_at_a_time": "複数を閉じるとき、未保存のデザインごとに確認する",
"prefs.save_prompt_one_at_a_time_help": "一覧でまとめて確認する代わりに、未保存のデザインごとに保存を確認します。"
},
"ko": {
"document.discard_row": "버리기",
"document.finish_save_prompt": "다른 디자인을 열기 전에 저장 여부를 선택하세요.",
"document.not_saved_yet": "아직 저장하지 않음",
"document.save_many": "디자인 {n}개의 변경 사항을 저장하시겠습니까?",
"document.save_row": "저장",
"document.save_selected": "선택 항목 저장",
"prefs.save_prompt_one_at_a_time": "여러 디자인을 닫을 때 저장하지 않은 디자인마다 따로 묻기",
"prefs.save_prompt_one_at_a_time_help": "통합 목록 대신 저장하지 않은 디자인마다 저장 여부를 묻습니다."
},
"vi": {
"document.discard_row": "Bỏ thay đổi",
"document.finish_save_prompt": "Hãy trả lời xong hộp thoại lưu trước khi mở thiết kế khác.",
"document.not_saved_yet": "Chưa lưu",
"document.save_many": "Lưu thay đổi cho {n} thiết kế?",
"document.save_row": "Lưu",
"document.save_selected": "Lưu mục đã chọn",
"prefs.save_prompt_one_at_a_time": "Hỏi riêng từng thiết kế chưa lưu khi đóng nhiều thiết kế",
"prefs.save_prompt_one_at_a_time_help": "Hiển thị một hộp thoại lưu cho mỗi thiết kế chưa lưu thay vì một danh sách chung."
},
"zh": {
"document.discard_row": "放弃",
"document.finish_save_prompt": "请先回答保存提示,再打开其他设计。",
"document.not_saved_yet": "尚未保存",
"document.save_many": "保存 {n} 个设计的更改?",
"document.save_row": "保存",
"document.save_selected": "保存所选项",
"prefs.save_prompt_one_at_a_time": "关闭多个设计时,逐个询问未保存的设计",
"prefs.save_prompt_one_at_a_time_help": "对每个未保存的设计分别显示保存提示,而不是合并列表。"
}
}
@@ -0,0 +1,38 @@
{
"en": {
"Segments": "Segments",
"Split Edge": "Split Edge",
"Remove this segment": "Remove this segment",
"One more stretch of the picked edge, each with its own angle, height and turns": "One more stretch of the picked edge, each with its own angle, height and turns"
},
"ar": {
"Segments": "المقاطع",
"Split Edge": "تقسيم الحافة",
"Remove this segment": "إزالة هذا المقطع",
"One more stretch of the picked edge, each with its own angle, height and turns": "مقطع آخر من الحافة المختارة، لكل منها زاويتها وارتفاعها ولفّاتها"
},
"he": {
"Segments": "מקטעים",
"Split Edge": "פיצול המקצוע",
"Remove this segment": "הסרת המקטע הזה",
"One more stretch of the picked edge, each with its own angle, height and turns": "מקטע נוסף של המקצוע הנבחר, שלכל אחד זווית, גובה וכיפופים משלו"
},
"ja": {
"Segments": "セグメント",
"Split Edge": "エッジを分割",
"Remove this segment": "このセグメントを削除",
"One more stretch of the picked edge, each with its own angle, height and turns": "選択したエッジのもう一つの区間。それぞれに独自の角度、高さ、曲げ方向があります"
},
"vi": {
"Segments": "Đoạn",
"Split Edge": "Chia cạnh",
"Remove this segment": "Xóa đoạn này",
"One more stretch of the picked edge, each with its own angle, height and turns": "Thêm một đoạn nữa của cạnh đã chọn, mỗi đoạn có góc, chiều cao và hướng uốn riêng"
},
"zh": {
"Segments": "分段",
"Split Edge": "分割边",
"Remove this segment": "移除此分段",
"One more stretch of the picked edge, each with its own angle, height and turns": "所选边的又一段,每段都有自己的角度、高度和折弯方向"
}
}
+104
View File
@@ -0,0 +1,104 @@
{
"uk": {
"Edit Holes": "Редагувати отвори",
"Select the curves to divide first": "Спершу виберіть криві для поділу",
"browser.canvas_locked": "Полотно заблоковано. Спершу розблокуйте його.",
"cmd.model_body_features": "Перелічити операції тіла",
"cmd.panel.focus.next": "Сфокусувати наступну панель",
"cmd.panel.focus.previous": "Сфокусувати попередню панель",
"cmd.panel.move.next_zone": "Перемістити панель до наступної зони",
"cmd.panel.sash.down": "Змінити розмір роздільника панелей униз",
"cmd.panel.sash.left": "Змінити розмір роздільника панелей ліворуч",
"cmd.panel.sash.reset": "Скинути роздільник сфокусованої панелі",
"cmd.panel.sash.right": "Змінити розмір роздільника панелей праворуч",
"cmd.panel.sash.up": "Змінити розмір роздільника панелей угору",
"cmd.panel.toggle": "Перемкнути сфокусовану панель",
"cmd.pointcloud_reload": "Перезавантажити хмару точок",
"cmd.sketch.replace": "Замінити ескіз",
"cmd.sketch_point_divide": "Поділити криву",
"cmd.ui.palette": "Пошук команд",
"file_load.reloading_cloud": "Перезавантаження хмари точок",
"help.sketch.point.divide": "Розміщує точки, що поділяють вибрані криві на рівні за довжиною частини. Точки залишаються на кожній кривій і зберігають свій крок, коли крива змінюється.",
"hole_folder.applies_to": "Зміни застосовуються до {count} отворів у теці {folder}; кожен зберігає своє місце.",
"nav.orbit_cursor_wrap.help": "Перетягування для обертання, що сягає краю виду, переносить курсор на протилежний край і продовжує обертання. Вимкнено: перетягування зупиняється на краю екрана. Не діє у Wayland і в браузері.",
"nav.orbit_cursor_wrap.label": "Переносити курсор під час обертання",
"parts.family.countersunk_socket_screw": "Гвинт із потайною головкою та внутрішнім шестигранником",
"prefs.browser_body_features": "Операції під тілами",
"prefs.browser_body_features_note": "Кожне тіло в браузері розгортається, показуючи операції часового ланцюжка, що його побудували, у порядку ланцюжка.",
"prefs.design_tools.hole_group": "Групувати отвори, створені разом",
"prefs.design_tools.hole_group_help": "Отвори, створені за один раз, опиняються в одній теці в історії, спершу закритій; відкрийте її, щоб редагувати отвір окремо.",
"prefs.design_tools.hole_several": "Кілька отворів на одній грані",
"prefs.design_tools.hole_several_help": "Вибір іншого місця на грані додає ще один отвір; вибір отвору видаляє його.",
"prefs.history_folder_kind_icon": "Значок теки за її операціями",
"prefs.history_folder_kind_icon_note": "Тека історії, усі операції якої одного типу, показує їхній значок.",
"prefs.panel_layout.recovery": "Не вдалося відновити збережені компонування панелей. Збережені дані не втрачено; «Класичне» залишається доступним.",
"prefs.shared_panel_layout": "Використовувати спільне компонування панелей",
"prefs.shared_panel_layout_help": "Показувати наявні браузер, історію та асистента в переміщуваних панелях. Зміна набуде чинності після застосування налаштувань.",
"prefs.sketch.escape_twice_finishes": "Подвійний Esc завершує ескіз",
"prefs.sketch.escape_twice_finishes_note": "Два швидкі натискання Esc завершують ескіз. Вимкнено: Esc лише скасовує інструмент чи вибір, а ескіз завершує «Завершити ескіз».",
"render.caustics_depth": "Глибина шляху каустики",
"render.caustics_enable": "Каустика скла від точкових і спрямованих джерел світла",
"render.caustics_help": "Кількість фотонів керує шумом і обсягом роботи. Радіус збирання обмінює чіткі деталі на плавнішу оцінку. CPU потребує непрямого освітлення.",
"render.caustics_path_required": "Каустика на CPU потребує непрямого освітлення.",
"render.caustics_photons": "Фотони",
"render.caustics_radius": "Радіус збирання",
"render.glass_abbe": "Число Аббе",
"render.glass_dispersion": "Хроматична дисперсія",
"render.glass_film": "Інтерференція тонкої плівки",
"render.glass_film_ior": "IOR плівки",
"render.glass_film_thickness": "Товщина плівки",
"render.glass_model_enable": "Розширена модель скла",
"render.glass_model_help": "Шорсткість скла розсіює промені, що проходять крізь нього. Дисперсія й колір плівки використовують три смуги довжин хвиль. Для шорстких листів потрібно вимкнути пряме пропускання тіней.",
"render.glass_roughness": "Шорсткість скла",
"render.gpu_job_shader_wait": "Підготовка шейдерів може тривати хвилини",
"sketch.divide_hint": "Точки поділяють кожну криву на рівні за довжиною частини й зберігають їх, коли крива змінюється.",
"ui.first_dimension_scales": "Перший розмір масштабує креслення",
"ui.first_dimension_scales_tip": "Увімкнено: перший лінійний розмір ескізу масштабує креслення, яке він вимірює, тож форма зберігається. Вимкнено: розмір задає лише те, що вимірює.",
"ui.heading_divide_curve": "ПОДІЛИТИ КРИВУ",
"ui.heading_press_pull": "НАТИСНУТИ/ВИТЯГНУТИ",
"ui.mesh_import_origin": "Перемістити до початку координат",
"ui.mesh_import_origin_tip": "Центрувати сітку по X і Y, розмістивши її основу на Z = 0. Коли вимкнено, сітка зберігає координати, записані у файлі.",
"ui.panel_layout.applied": "Застосовано «{name}» у робочій області «{workspace}»",
"ui.panel_layout.cancel": "Скасувати",
"ui.panel_layout.confirm_replace": "Замінити «{name}»?",
"ui.panel_layout.copy": "Копіювати",
"ui.panel_layout.current": "Поточне компонування",
"ui.panel_layout.custom": "Власне",
"ui.panel_layout.custom_hint": "Панелі переміщено після застосування: {name}.",
"ui.panel_layout.details": "Подробиці",
"ui.panel_layout.layout_workspace": "Робоча область компонування",
"ui.panel_layout.page_defaults_hint": "Скидає налаштування цієї сторінки. Розташування панелей і збережені компонування залишаються без змін.",
"ui.panel_layout.panel_layout": "Компонування панелей",
"ui.panel_layout.panel_style": "Стиль панелей",
"ui.panel_layout.replace": "Замінити компонування",
"ui.panel_layout.replace_current": "Замінити поточним",
"ui.panel_layout.replace_short": "Замінити",
"ui.panel_layout.reset": "Скинути робочу область",
"ui.panel_layout.reset_hint": "Повернути робочу область «{workspace}» до типового розташування. Збережені компонування залишаються без змін.",
"ui.panel_layout.reset_short": "Скинути",
"ui.panel_layout.restore_failed": "Не вдалося відновити ваше компонування",
"ui.panel_layout.save": "Зберегти компонування",
"ui.panel_layout.save_as": "Зберегти як",
"ui.panel_layout.save_as_tile": "Зберегти як…",
"ui.panel_layout.selected": "вибрано",
"ui.panel_layout.suggested_name": "Компонування {n}",
"ui.panel_layout.try_again": "Спробувати знову",
"ui.panel_layout.validation.builtin": "Вбудовані компонування не можна змінювати",
"ui.panel_layout.validation.duplicate": "Ця назва вже використовується",
"ui.panel_layout.validation.empty": "Введіть назву",
"ui.panel_layout.validation.full": "Збережено 64 з 64. Виберіть одне для заміни.",
"ui.panel_layout.validation.invalid": "Використовуйте від 1 до 64 символів без керівних символів",
"ui.panel_layout.workspace.classic": "Класичне",
"ui.panel_layout.workspace.modelling": "Моделювання",
"ui.panel_layout.workspace.render": "Рендеринг",
"ui.panel_layout.workspace.review": "Огляд",
"ui.panel_layout.workspace.sketch": "Ескіз",
"ui.panel_layout.your_layout": "ваше компонування",
"ui.point_cloud_reload": "Перезавантажити з файлу",
"ui.project_references_construction": "Проєктувати посилання як допоміжну геометрію",
"ui.project_references_construction_note": "«Проєктувати», «Перетин», «Включити 3D-геометрію» і ребра грані, які додає новий ескіз на грані, створюють допоміжну геометрію: посилання для креслення, не включені до профілів. Вони залишаються пов'язаними з моделлю.",
"ui.replace_sketch.from_file": "З файлу проєкту…",
"ui.replace_sketch.no_sketches": "У цьому проєкті немає ескізу, який можна взяти",
"ui.segments": "Сегменти"
}
}
-6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Utwórz zgłoszenie…",
"Cross curves": "Krzywe poprzeczne",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter przechodzi dalej na GitHubie.",
"Cube": "Sześcian",
"Current orthographic view (1:1 mm)": "Bieżący widok ortogonalny (1:1 mm)",
"Current perspective view": "Bieżący widok perspektywiczny",
"Current plane": "Bieżąca płaszczyzna",
@@ -2659,9 +2658,6 @@
"prefs.interface_note": "Wygląd SolidCraft i rozmieszczenie jego elementów.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Odwróć zakrzywione strzałki przechylenia",
"prefs.invert_curved_roll_arrows_note": "Dwie zakrzywione strzałki w rogu kostki przechylają widok w płaszczyźnie ekranu. Włączone: strzałka zgodna z ruchem wskazówek zegara przechyla przeciwnie, a przeciwna – zgodnie. Wyłączone: każda strzałka przechyla widok w stronę, w którą jest zakrzywiona.",
"prefs.invert_navigator_arrows": "Odwróć strzałki obrotu kostki widoku",
"prefs.invert_navigator_arrows_note": "Strzałki wokół kostki obracają kamerę w kierunku, w który wskazują, jak w Fusion i Inventor: strzałka po lewej wskazuje w prawo i obraca kamerę w prawo. Wyłączone: strzałka obraca kostkę w stronę, po której się znajduje, więc lewa strzałka obraca kamerę w lewo. Zakrzywione strzałki przechylenia nie są zmieniane.",
"prefs.keep_unsaved_also_asks": "Mimo to pytaj przy zamykaniu karty",
"prefs.keep_unsaved_also_asks_tip": "Gdy wyłączone, karta zamyka się bez okna przeglądarki, jeśli kopia każdego niezapisanego projektu jest aktualna.",
"prefs.keep_unsaved_in_browser": "Przechowuj niezapisaną pracę w tej przeglądarce",
@@ -2721,7 +2717,6 @@
"prefs.move_drag_highlights_tip": "Wyłączone: gdy strzałki Przesuń przeciągają komponent, nic pod wskaźnikiem nie jest podświetlane, co zachowuje płynność dużych złożeń. Przy pokazanym przekroju lub analizie podświetlenie i tak działa, a część przeciągana wzdłuż złączy nigdy go nie podświetla.",
"prefs.navigation": "Nawigacja",
"prefs.navigation.axes_signed": "Osie",
"prefs.navigation.cube_compact": "Kostka",
"prefs.navigation.device": "Urządzenie",
"prefs.navigation.free": "Swobodne",
"prefs.navigation.orbit_style": "Styl obracania",
@@ -2729,7 +2724,6 @@
"prefs.navigation.turntable": "Obrotnica",
"prefs.navigation_note": "Jak mysz, gładzik i mysz 3D poruszają widokiem.",
"prefs.navigator_fit": "Dopasuj model po zmianie widoku w nawigatorze",
"prefs.navigator_fit_note": "Ściana, narożnik lub strzałka kostki widoku albo oś wskaźnika orientacji kieruje też widok na środek modelu z odległości, z której mieści się on z każdej strony (jego sfera otaczająca). Gdy to jest włączone, Widok główny używa tego samego powiększenia i środka, więc widoki wybrane później tylko się obracają. Wyłączone: widok obraca się w miejscu, zachowując cel i powiększenie.",
"prefs.network_host_remove": "Usuń",
"prefs.network_locations": "Zezwalaj na lokalizacje sieciowe w projektach",
"prefs.network_locations_note": "Wyłączone: połączone pliki projektu na innym komputerze (\\\\serwer\\udział) są otwierane tylko z serwerów wymienionych tutaj albo po zezwoleniu, gdy pojawi się pytanie.",
-6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Criar issue…",
"Cross curves": "Curvas transversais",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter continua no GitHub.",
"Cube": "Cubo",
"Current orthographic view (1:1 mm)": "Vista ortográfica atual (1:1 mm)",
"Current perspective view": "Vista em perspectiva atual",
"Current plane": "Plano atual",
@@ -2659,9 +2658,6 @@
"prefs.interface_note": "A aparência do SolidCraft e onde ficam suas partes.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Inverter as setas curvas de rolagem",
"prefs.invert_curved_roll_arrows_note": "As duas setas curvas no canto do cubo rolam a vista no plano da tela. Ativado: a seta horária rola no sentido anti-horário e a seta anti-horária rola no sentido horário. Desativado: cada seta rola a vista no sentido em que se curva.",
"prefs.invert_navigator_arrows": "Inverter as setas de rotação do cubo de vista",
"prefs.invert_navigator_arrows_note": "As setas em volta do cubo orbitam a câmera para onde apontam, como no Fusion e no Inventor: a seta da esquerda aponta para a direita e gira a câmera para a direita. Desativado: uma seta gira o cubo em direção ao lado em que está, então a seta da esquerda gira a câmera para a esquerda. As setas curvas de rolagem não são afetadas.",
"prefs.keep_unsaved_also_asks": "Perguntar mesmo assim ao fechar a aba",
"prefs.keep_unsaved_also_asks_tip": "Com isto desativado, a aba fecha sem a caixa do navegador quando a cópia de cada projeto não salvo estiver atualizada.",
"prefs.keep_unsaved_in_browser": "Guardar trabalho não salvo neste navegador",
@@ -2721,7 +2717,6 @@
"prefs.move_drag_highlights_tip": "Desativado: enquanto as setas de Mover arrastam um componente, nada sob o ponteiro é destacado, o que mantém montagens grandes fluidas. Com um corte ou uma análise exibidos, o destaque acontece mesmo assim, e uma peça arrastada ao longo de suas juntas nunca o destaca.",
"prefs.navigation": "Navegação",
"prefs.navigation.axes_signed": "Eixos",
"prefs.navigation.cube_compact": "Cubo",
"prefs.navigation.device": "Dispositivo",
"prefs.navigation.free": "Livre",
"prefs.navigation.orbit_style": "Estilo de órbita",
@@ -2729,7 +2724,6 @@
"prefs.navigation.turntable": "Mesa giratória",
"prefs.navigation_note": "Como o mouse, o trackpad e o mouse 3D movem a vista.",
"prefs.navigator_fit": "Ajustar o modelo após uma mudança de vista do navegador",
"prefs.navigator_fit_note": "Uma face, um canto ou uma seta do cubo de vista, ou um eixo do gizmo, também olha para o meio do modelo a partir da distância única que o enquadra de todos os lados (sua esfera envolvente). Início usa o mesmo zoom e centro enquanto isto estiver ativado, então as vistas escolhidas depois só giram. Desativado: a vista gira no lugar, mantendo seu alvo e zoom.",
"prefs.network_host_remove": "Remover",
"prefs.network_locations": "Permitir locais de rede nos projetos",
"prefs.network_locations_note": "Para organizações que mantêm projetos em servidores de arquivos. Desativado: apenas servidores que você permitir.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Creează raport…",
"Cross curves": "Curbe transversale",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter continuă pe GitHub.",
"Cube": "Cub",
"Current orthographic view (1:1 mm)": "Vizualizare ortografică curentă (1:1 mm)",
"Current perspective view": "Vizualizare în perspectivă curentă",
"Current plane": "Planul curent",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Cum arată SolidCraft și unde sunt așezate elementele sale.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Inversează săgețile curbe de ruliu",
"prefs.invert_curved_roll_arrows_note": "Cele două săgeți curbe din colțul cubului rotesc vizualizarea în planul ecranului. Activat: săgeata în sens orar rotește în sens antiorar, iar săgeata în sens antiorar rotește în sens orar. Dezactivat: fiecare săgeată rotește vizualizarea în direcția în care se curbează.",
"prefs.invert_navigator_arrows": "Inversează săgețile de rotație ale cubului de vizualizare",
"prefs.invert_navigator_arrows_note": "Săgețile din jurul cubului rotesc camera în jurul modelului în direcția în care indică, ca în Fusion și Inventor: săgeata din stânga indică spre dreapta și rotește camera spre dreapta. Dezactivat: o săgeată rotește cubul spre partea pe care se află, deci săgeata din stânga rotește camera spre stânga. Săgețile curbe de ruliu nu sunt afectate.",
"prefs.keep_work_on_tab_switch": "Păstrează deschise dialogurile și instrumentele la schimbarea proiectului",
"prefs.keep_work_on_tab_switch_note": "Fiecare filă de proiect își păstrează dialogul sau instrumentul deschis, cu ce ați ales și ați tastat în el, și fila ei din bara de instrumente. Le regăsiți când reveniți la proiect. Dezactivat: schimbarea proiectului le anulează și afișează fila Model.",
"prefs.labels": "Etichete",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Navigare",
"prefs.navigation_note": "Cum mută vizualizarea mouse-ul, trackpadul și mouse-ul 3D.",
"prefs.navigator_fit": "Potrivește modelul după o schimbare de vizualizare din navigator",
"prefs.navigator_fit_note": "O față, un colț sau o săgeată a cubului de vizualizare ori o axă a reperului de orientare privește și spre mijlocul modelului, de la singura distanță care îl cuprinde din toate părțile (sfera lui de încadrare). Acasă folosește același zoom și același centru cât timp opțiunea este activată, așa că vizualizările alese după aceea doar se rotesc. Dezactivat: vizualizarea se rotește pe loc, păstrându-și ținta și zoomul.",
"prefs.network_host_remove": "Elimină",
"prefs.network_locations": "Permite locațiile de rețea în proiecte",
"prefs.network_locations_note": "Dezactivat: fișierele legate ale unui proiect aflate pe alt computer (\\\\server\\share) se deschid doar de pe serverele listate aici sau după ce permiteți unul, când vi se cere.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Создать Issue…",
"Cross curves": "Поперечные кривые",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter — продолжить на GitHub.",
"Cube": "Куб",
"Current orthographic view (1:1 mm)": "Текущий ортогональный вид (1:1 мм)",
"Current perspective view": "Текущий перспективный вид",
"Current plane": "Текущая плоскость",
@@ -2232,9 +2231,6 @@
"prefs.interface_note": "Как выглядит SolidCraft и где расположены его части.",
"prefs.interval_minutes": "{n} мин",
"prefs.invert_curved_roll_arrows": "Инвертировать изогнутые стрелки крена",
"prefs.invert_curved_roll_arrows_note": "Две изогнутые стрелки в углу куба поворачивают вид в плоскости экрана. Вкл.: стрелка по часовой стрелке поворачивает вид против часовой, а стрелка против часовой — по часовой. Выкл.: каждая стрелка поворачивает вид в ту сторону, куда она изогнута.",
"prefs.invert_navigator_arrows": "Инвертировать стрелки вращения куба видов",
"prefs.invert_navigator_arrows_note": "Стрелки вокруг куба вращают камеру в ту сторону, куда указывают, как в Fusion и Inventor: стрелка слева указывает вправо и поворачивает камеру вправо. Выкл.: стрелка поворачивает куб в сторону, где она расположена, поэтому левая стрелка поворачивает камеру влево. Изогнутые стрелки крена не затрагиваются.",
"prefs.keep_work_on_tab_switch": "Сохранять открытые диалоги и инструменты при переключении между дизайнами",
"prefs.keep_work_on_tab_switch_note": "Каждая вкладка дизайна сохраняет свой открытый диалог или инструмент вместе с тем, что вы в нём выбрали и ввели, а также свою вкладку панели инструментов. Они снова на месте, когда вы возвращаетесь к дизайну. Выкл.: при переключении дизайнов они отменяются, и показывается вкладка «Модель».",
"prefs.labels": "Подписи",
@@ -2271,7 +2267,6 @@
"prefs.navigation": "Навигация",
"prefs.navigation_note": "Как мышь, тачпад и 3D-мышь двигают вид.",
"prefs.navigator_fit": "Вписывать модель после смены вида в навигаторе",
"prefs.navigator_fit_note": "Грань, угол или стрелка куба видов либо ось гизмо также направляет взгляд на середину модели с единственного расстояния, с которого она целиком видна со всех сторон (её габаритная сфера). «Главная» при включённом параметре использует тот же масштаб и центр, поэтому выбранные после неё виды только поворачиваются. Выкл.: вид поворачивается на месте, сохраняя свою цель и масштаб.",
"prefs.network_host_remove": "Убрать",
"prefs.network_locations": "Разрешить сетевые расположения в дизайнах",
"prefs.network_locations_note": "Выкл.: связанные файлы дизайна на другом компьютере (\\\\server\\share) открываются только с перечисленных здесь серверов или после того, как вы разрешите сервер по запросу.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Пријави проблем…",
"Cross curves": "Пресечне криве",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter наставља на GitHub-у.",
"Cube": "Коцка",
"Current orthographic view (1:1 mm)": "Тренутни ортографски приказ (1:1 mm)",
"Current perspective view": "Тренутни перспективни приказ",
"Current plane": "Тренутна раван",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "Како SolidCraft изгледа и где су његови делови.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Обрни закривљене стрелице за ваљање",
"prefs.invert_curved_roll_arrows_note": "Две закривљене стрелице у углу коцке ваљају приказ унутар равни екрана. Укључено: стрелица у смеру казаљке на сату ваља приказ супротно од смера казаљке на сату, а стрелица супротно од смера казаљке на сату ваља га у смеру казаљке на сату. Искључено: свака стрелица ваља приказ онако како је закривљена.",
"prefs.invert_navigator_arrows": "Обрни стрелице за ротацију коцке приказа",
"prefs.invert_navigator_arrows_note": "Стрелице око коцке орбитирају камеру у смеру у којем показују, као у програмима Fusion и Inventor: стрелица са леве стране показује удесно и закреће камеру удесно. Искључено: стрелица окреће коцку ка страни на којој се налази, па лева стрелица закреће камеру улево. Подешавање не утиче на закривљене стрелице за ваљање.",
"prefs.keep_work_on_tab_switch": "Задржи дијалоге и алате отвореним при пребацивању између дизајна",
"prefs.keep_work_on_tab_switch_note": "Свака картица дизајна задржава свој отворени дијалог или алат, са оним што сте у њему изабрали и уписали, те своју картицу траке са алатима. Када се вратите на дизајн, поново су ту. Искључено: пребацивање између дизајна отказује их и приказује картицу Модел.",
"prefs.labels": "Ознаке",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Навигација",
"prefs.navigation_note": "Како миш, додирна табла и 3D миш померају приказ.",
"prefs.navigator_fit": "Уклопи модел након промене приказа у навигатору",
"prefs.navigator_fit_note": "Лице, угао или стрелица коцке приказа, односно оса гизма оријентације, такође усмерава поглед на средину модела са једног растојања на којем се уклапа из сваког смера (његова гранична сфера). Почетни приказ користи исти зум и центар док је ова опција укључена, па се прикази које изаберете после њега само окрећу. Искључено: приказ се окреће на месту и задржава свој циљ и зум.",
"prefs.network_host_remove": "Уклони",
"prefs.network_locations": "Дозволи мрежне локације у дизајнима",
"prefs.network_locations_note": "Искључено: повезане датотеке дизајна на другом рачунару (\\\\server\\share) отварају се само са сервера наведених овде или након што га дозволите када се то затражи.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Skapa ärende…",
"Cross curves": "Korskurvor",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter fortsätter på GitHub.",
"Cube": "Kub",
"Current orthographic view (1:1 mm)": "Aktuell ortografisk vy (1:1 mm)",
"Current perspective view": "Aktuell perspektivvy",
"Current plane": "Aktuellt plan",
@@ -2241,9 +2240,6 @@
"prefs.interface_note": "Hur SolidCraft ser ut och var dess delar placeras.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_curved_roll_arrows": "Invertera de böjda rullningspilarna",
"prefs.invert_curved_roll_arrows_note": "De två böjda pilarna vid kubens hörn rullar vyn i skärmplanet. På: medurspilen rullar moturs och moturspilen rullar medurs. Av: varje pil rullar vyn åt det håll den kröker sig.",
"prefs.invert_navigator_arrows": "Invertera rotationspilarna på vykuben",
"prefs.invert_navigator_arrows_note": "Pilarna runt kuben orbiterar kameran åt det håll de pekar, som i Fusion och Inventor: pilen till vänster pekar åt höger och svänger kameran åt höger. Av: en pil roterar kuben mot den sida den sitter på, så den vänstra pilen svänger kameran åt vänster. De böjda rullningspilarna påverkas inte.",
"prefs.keep_work_on_tab_switch": "Behåll dialoger och verktyg öppna när du byter konstruktion",
"prefs.keep_work_on_tab_switch_note": "Varje konstruktionsflik behåller sin öppna dialog eller sitt öppna verktyg, med det du har valt och skrivit i den, och sin flik i menyfliksområdet. De finns kvar när du kommer tillbaka till konstruktionen. Av: att byta konstruktion avbryter dem och visar fliken Modell.",
"prefs.labels": "Etiketter",
@@ -2280,7 +2276,6 @@
"prefs.navigation": "Navigering",
"prefs.navigation_note": "Hur musen, styrplattan och 3D-musen flyttar vyn.",
"prefs.navigator_fit": "Anpassa modellen efter en vyändring i navigatorn",
"prefs.navigator_fit_note": "En yta, ett hörn eller en pil på vykuben, eller en axel på orienteringsgizmot, tittar också mot modellens mitt från det enda avstånd som får den att rymmas från alla sidor (dess begränsningssfär). Hem använder samma zoom och centrum medan detta är på, så vyer som väljs efteråt bara vrids. Av: vyn vrids på stället och behåller sitt mål och sin zoom.",
"prefs.network_host_remove": "Ta bort",
"prefs.network_locations": "Tillåt nätverksplatser i konstruktioner",
"prefs.network_locations_note": "Av: en konstruktions länkade filer på en annan dator (\\\\server\\share) öppnas bara från de servrar som listas här, eller efter att du tillåtit en när du tillfrågas.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "Sorun Oluştur…",
"Cross curves": "Çapraz eğriler",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter ile GitHub'da devam edilir.",
"Cube": "Küp",
"Current orthographic view (1:1 mm)": "Geçerli ortografik görünüm (1:1 mm)",
"Current perspective view": "Geçerli perspektif görünüm",
"Current plane": "Geçerli düzlem",
@@ -2240,9 +2239,6 @@
"prefs.interface_note": "SolidCraft'ın nasıl göründüğü ve parçalarının nerede durduğu.",
"prefs.interval_minutes": "{n} dk",
"prefs.invert_curved_roll_arrows": "Eğri yuvarlanma oklarını ters çevir",
"prefs.invert_curved_roll_arrows_note": "Küpün köşesindeki iki eğri ok, görünümü ekran düzlemi içinde yuvarlar. Açık: saat yönündeki ok saat yönünün tersine, saat yönünün tersindeki ok saat yönünde yuvarlar. Kapalı: her ok görünümü kıvrıldığı yöne yuvarlar.",
"prefs.invert_navigator_arrows": "Görünüm küpünün döndürme oklarını ters çevir",
"prefs.invert_navigator_arrows_note": "Küpün çevresindeki oklar kamerayı gösterdikleri yönde döndürür, tıpkı Fusion ve Inventor'daki gibi: soldaki ok sağı gösterir ve kamerayı sağa savurur. Kapalı: bir ok küpü bulunduğu tarafa doğru döndürür, yani sol ok kamerayı sola savurur. Kıvrık yuvarlanma okları bundan etkilenmez.",
"prefs.keep_work_on_tab_switch": "Tasarımlar arasında geçerken diyalogları ve araçları açık tut",
"prefs.keep_work_on_tab_switch_note": "Her tasarım sekmesi, açık diyaloğunu veya aracını, içinde seçtiklerinizi ve yazdıklarınızı ve şerit sekmesini korur. Tasarıma döndüğünüzde yeniden oradadırlar. Kapalı: tasarımlar arasında geçmek bunları iptal eder ve Model sekmesini gösterir.",
"prefs.labels": "Etiketler",
@@ -2279,7 +2275,6 @@
"prefs.navigation": "Gezinme",
"prefs.navigation_note": "Farenin, trackpad'in ve 3B farenin görünümü nasıl hareket ettirdiği.",
"prefs.navigator_fit": "Gezgin görünümü değiştikten sonra modeli sığdır",
"prefs.navigator_fit_note": "Görünüm küpünün bir yüzü, köşesi veya oku ya da gizmonun bir ekseni, modelin ortasına da modeli her yönden sığdıran tek bir uzaklıktan (sınırlayıcı küresi) bakar. Bu açıkken Ana görünüm aynı yakınlaştırmayı ve merkezi kullanır; böylece sonrasında seçilen görünümler yalnızca döner. Kapalı: görünüm hedefini ve yakınlaştırmasını koruyarak yerinde döner.",
"prefs.network_host_remove": "Kaldır",
"prefs.network_locations": "Tasarımlarda ağ konumlarına izin ver",
"prefs.network_locations_note": "Kapalı: bir tasarımın başka bir bilgisayardaki bağlı dosyaları (\\\\server\\paylaşım) yalnızca burada listelenen sunuculardan veya sorulduğunda izin verdiklerinizden açılır.",
-6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Створити повідомлення про проблему…",
"Cross curves": "Поперечні криві",
"Ctrl+Enter continues on GitHub.": "Ctrl+Enter продовжує на GitHub.",
"Cube": "Куб",
"Current orthographic view (1:1 mm)": "Поточний ортографічний вид (1:1 mm)",
"Current perspective view": "Поточний перспективний вид",
"Current plane": "Поточна площина",
@@ -2652,9 +2651,6 @@
"prefs.interface_note": "Як виглядає SolidCraft і де розташовані його частини.",
"prefs.interval_minutes": "{n} хв",
"prefs.invert_curved_roll_arrows": "Інвертувати вигнуті стрілки обертання",
"prefs.invert_curved_roll_arrows_note": "Дві вигнуті стрілки в куті куба обертають вид у площині екрана. Увімкнено: стрілка за годинниковою стрілкою обертає проти годинникової, а стрілка проти годинникової — за годинниковою. Вимкнено: кожна стрілка обертає вид у напрямку свого завитку.",
"prefs.invert_navigator_arrows": "Інвертувати стрілки обертання куба виду",
"prefs.invert_navigator_arrows_note": "Стрілки навколо куба обертають камеру в той бік, куди вказують, як у Fusion та Inventor: стрілка ліворуч вказує праворуч і повертає камеру праворуч. Вимк.: стрілка обертає куб у той бік, де вона розташована, тож ліва стрілка повертає камеру ліворуч. Вигнуті стрілки крену не змінюються.",
"prefs.keep_unsaved_also_asks": "Усе одно запитувати під час закриття вкладки",
"prefs.keep_unsaved_also_asks_tip": "Коли це вимкнено, вкладка закривається без вікна браузера, щойно копія кожного незбереженого проєкту актуальна.",
"prefs.keep_unsaved_in_browser": "Зберігати незбережену роботу в цьому браузері",
@@ -2714,7 +2710,6 @@
"prefs.move_drag_highlights_tip": "Вимкнено: коли стрілки «Перемістити» тягнуть компонент, нічого під вказівником не підсвічується, що зберігає плавність великих збірок. Якщо показано переріз чи аналіз, підсвічування все одно працює, а деталь, яку тягнуть уздовж її з’єднань, ніколи його не вмикає.",
"prefs.navigation": "Навігація",
"prefs.navigation.axes_signed": "Осі",
"prefs.navigation.cube_compact": "Куб",
"prefs.navigation.device": "Пристрій",
"prefs.navigation.free": "Вільне",
"prefs.navigation.orbit_style": "Стиль обертання",
@@ -2722,7 +2717,6 @@
"prefs.navigation.turntable": "Поворотний стіл",
"prefs.navigation_note": "Як миша, трекпад і 3D-миша рухають вид.",
"prefs.navigator_fit": "Вміщувати модель після зміни виду в навігаторі",
"prefs.navigator_fit_note": "Грань, кут або стрілка куба виду чи вісь гізмо також дивляться на середину моделі з єдиної відстані, з якої її видно цілком з усіх боків (її обмежувальна сфера). «Головний вид» за ввімкненого параметра використовує той самий масштаб і центр, тож види, вибрані після нього, лише повертаються. Вимк.: вид повертається на місці, зберігаючи свою ціль і масштаб.",
"prefs.network_host_remove": "Вилучити",
"prefs.network_locations": "Дозволяти мережеві розташування в проєктах",
"prefs.network_locations_note": "Для організацій, які зберігають проєкти на файлових серверах. Вимкнено: лише дозволені вами сервери.",
+62 -6
View File
@@ -193,7 +193,6 @@
"Create Issue…": "Tạo Issue…",
"Cross curves": "Đường cong giao cắt",
"Ctrl+Enter continues on GitHub.": "Nhấn Ctrl+Enter để tiếp tục trên GitHub.",
"Cube": "Khối lập phương",
"Current orthographic view (1:1 mm)": "Góc nhìn trực giao hiện tại (1:1 mm)",
"Current perspective view": "Góc nhìn phối cảnh hiện tại",
"Current plane": "Mặt phẳng hiện tại",
@@ -1509,6 +1508,7 @@
"cmd.timeline_stop": "Dừng lịch sử",
"cmd.timeline_suppress": "Tạm nén tính năng",
"cmd.ui.assistant": "Bật/tắt trợ lý",
"cmd.ui.palette": "Tìm kiếm lệnh",
"cmd.view.navigator": "Khối điều hướng góc nhìn tùy chỉnh",
"cmd.view.render_browser": "Trình duyệt kết xuất",
"cmd.view.render_dock": "Di chuyển neo kết xuất",
@@ -2431,6 +2431,7 @@
"parts.dimension.width": "Bề rộng",
"parts.family.bearing": "Ổ bi cầu rãnh sâu (bao ngoài)",
"parts.family.button_head_cap_screw": "Vít đầu tròn lục giác chìm",
"parts.family.countersunk_socket_screw": "Vít đầu chìm lục giác chìm",
"parts.family.dowel_pin": "Chốt trụ",
"parts.family.hex_bolt": "Bu lông đầu lục giác",
"parts.family.hex_cap_screw_inch": "Bu lông đầu lục giác (inch)",
@@ -2681,9 +2682,9 @@
"prefs.interface_note": "Diện mạo của SolidCraft và cách bố trí các bảng điều khiển.",
"prefs.interval_minutes": "{n} phút",
"prefs.invert_curved_roll_arrows": "Đảo ngược mũi tên cuộn cong",
"prefs.invert_curved_roll_arrows_note": "Hai mũi tên cong ở góc khối cuộn khung nhìn trong mặt phẳng màn hình. Khi bật: mũi tên theo chiều kim đồng hồ cuộn ngược chiều kim đồng hồ và ngược lại. Khi tắt: mỗi mũi tên cuộn khung nhìn theo chiều cong của nó.",
"prefs.invert_curved_roll_arrows_note": "Hai mũi tên cong ở góc bộ điều hướng cuộn khung nhìn trong mặt phẳng màn hình. Khi bật: mũi tên theo chiều kim đồng hồ cuộn ngược chiều kim đồng hồ và ngược lại. Khi tắt: mỗi mũi tên cuộn khung nhìn theo chiều cong của nó.",
"prefs.invert_navigator_arrows": "Đảo ngược mũi tên xoay của khối điều hướng",
"prefs.invert_navigator_arrows_note": "Các mũi tên quanh khối xoay camera theo hướng mũi tên chỉ, như trong Fusion và Inventor: mũi tên bên trái chỉ sang phải và đưa camera sang phải. Khi tắt: mũi tên xoay khối về phía cạnh chứa nó, nên mũi tên bên trái đưa camera sang trái. Hai mũi tên cuộn cong không bị ảnh hưởng.",
"prefs.invert_navigator_arrows_note": "Các mũi tên quanh bộ điều hướng xoay camera theo hướng mũi tên chỉ, như trong Fusion và Inventor: mũi tên bên trái chỉ sang phải và đưa camera sang phải. Khi tắt: mũi tên xoay khung nhìn về phía chứa nó, nên mũi tên bên trái đưa camera sang trái. Hai mũi tên cuộn cong không bị ảnh hưởng.",
"prefs.keep_unsaved_also_asks": "Vẫn hỏi khi đóng thẻ",
"prefs.keep_unsaved_also_asks_tip": "Khi tắt, thẻ sẽ đóng mà không có hộp thoại của trình duyệt khi bản sao của mọi thiết kế chưa lưu đã được cập nhật.",
"prefs.keep_unsaved_in_browser": "Giữ công việc chưa lưu trong trình duyệt này",
@@ -2743,7 +2744,6 @@
"prefs.move_drag_highlights_tip": "Tắt: khi các mũi tên của Di chuyển kéo một thành phần, không có gì dưới con trỏ được tô sáng, nhờ đó các cụm lắp ráp lớn vẫn mượt. Khi đang hiện mặt cắt hoặc phân tích thì vẫn tô sáng như thường, và chi tiết được kéo dọc theo khớp nối không bao giờ tô sáng.",
"prefs.navigation": "Điều hướng góc nhìn",
"prefs.navigation.axes_signed": "Trục",
"prefs.navigation.cube_compact": "Khối lập phương",
"prefs.navigation.device": "Thiết bị",
"prefs.navigation.free": "Tự do",
"prefs.navigation.orbit_style": "Kiểu xoay",
@@ -2751,7 +2751,7 @@
"prefs.navigation.turntable": "Bàn xoay",
"prefs.navigation_note": "Cách chuột, bàn di cảm ứng và chuột 3D di chuyển góc nhìn.",
"prefs.navigator_fit": "Khớp mô hình sau khi đổi góc nhìn bằng khối điều hướng",
"prefs.navigator_fit_note": "Nhấp vào một mặt, góc hoặc mũi tên của khối lập phương, hoặc một trục của gizmo, cũng nhìn vào tâm mô hình từ một khoảng cách duy nhất chứa trọn nó từ mọi phía (khối cầu bao). Khi bật, góc nhìn chuẩn (Home) dùng cùng mức thu phóng và tâm đó, nên các góc nhìn chọn sau chỉ xoay. Tắt: góc nhìn xoay tại chỗ, giữ nguyên điểm nhìn và mức thu phóng.",
"prefs.navigator_fit_note": "Nhấp vào một mặt, góc hoặc mũi tên của bộ điều hướng, hoặc một trục của gizmo, cũng nhìn vào tâm mô hình từ một khoảng cách duy nhất chứa trọn nó từ mọi phía (khối cầu bao). Khi bật, góc nhìn chuẩn (Home) dùng cùng mức thu phóng và tâm đó, nên các góc nhìn chọn sau chỉ xoay. Tắt: góc nhìn xoay tại chỗ, giữ nguyên điểm nhìn và mức thu phóng.",
"prefs.network_host_remove": "Xóa",
"prefs.network_locations": "Cho phép các vị trí mạng trong thiết kế",
"prefs.network_locations_note": "Dành cho tổ chức lưu thiết kế trên máy chủ tệp. Khi tắt: chỉ các máy chủ bạn cho phép.",
@@ -2826,6 +2826,8 @@
"prefs.remember_dialog_values.help": "Tắt theo mặc định. Khi bật, sau khi lệnh thành công, các giá trị và tùy chọn an toàn của lệnh sẽ được dùng lại vào lần tiếp theo bạn mở cùng lệnh đó, cho đến khi SolidCraft đóng.",
"prefs.render_legacy_image_defaults": "Dùng mặc định ảnh Kết xuất trước đây",
"prefs.render_legacy_image_defaults_help": "Giữ quy trình hình ảnh thủ công trước đây: thiết lập mới dùng raster CPU, dò tia CPU dùng lấy mẫu cố định và mở kết quả thủ công. Căn vừa lại khung nhìn khi vào Kết xuất, chuyển góc chụp mà không khôi phục camera đã lưu, đồng thời dùng cách lấy khung hình và điều hướng trước đây. Tắt phản hồi ảnh trực tiếp tự động. Các thiết lập đã lưu không thay đổi.",
"prefs.render_legacy_memory_admission": "Dùng cách kiểm tra bộ nhớ kết xuất trước đây",
"prefs.render_legacy_memory_admission_help": "Cho phép kết xuất gốc trong giới hạn bộ nhớ dùng chung và của máy chủ cố định, ngay cả khi mức ước tính vượt quá RAM khả dụng. Giới hạn thiết bị và kiểm tra cấp phát bộ nhớ vẫn được áp dụng.",
"prefs.render_previous_geometry_budget": "Dùng ngân sách hình học trước đây cho bản xem trước kết xuất",
"prefs.render_previous_geometry_budget_help": "Khôi phục giới hạn hình học cố định trước đây (512 MiB trên máy tính 64 bit, 128 MiB trên các nền tảng khác) thay cho ngân sách dựa trên thiết bị.",
"prefs.reports_saved": "Đã lưu {n}",
@@ -3220,8 +3222,10 @@
"render.category_paint": "Sơn",
"render.category_plastic": "Nhựa",
"render.category_wood": "Gỗ",
"render.caustics_area_lights": "Tụ quang từ đèn diện tích",
"render.caustics_depth": "Độ sâu đường tụ quang",
"render.caustics_enable": "Tụ quang qua kính từ nguồn điểm và nguồn định hướng",
"render.caustics_environment": "Tụ quang từ ánh sáng môi trường",
"render.caustics_help": "Số photon điều chỉnh nhiễu và khối lượng tính toán. Bán kính thu thập đánh đổi chi tiết sắc nét để có ước lượng mượt hơn. CPU cần chiếu sáng gián tiếp.",
"render.caustics_path_required": "Tụ quang trên CPU cần chiếu sáng gián tiếp.",
"render.caustics_photons": "Số photon",
@@ -3398,7 +3402,7 @@
"render.cpu_opaque_required": "Dò tia trên CPU yêu cầu nền đục. Hãy bỏ chọn 'Nền trong suốt' để tiếp tục.",
"render.cpu_options_pending": "Bảng điều khiển CPU này chưa hỗ trợ chỉnh sửa tự phát xạ, hiệu ứng bề mặt, thủy tinh hoặc bất đẳng hướng. Hãy xóa các tùy chọn API đó trước khi sử dụng.",
"render.cpu_path_depth": "Độ sâu đường truyền",
"render.cpu_path_help": "Tính ánh sáng phản xạ giữa các bề mặt đục và truyền qua các hình kính được hỗ trợ. Cần kết xuất lũy tiến và bóng dò tia. Độ sâu đường đi đếm các bề mặt được chiếu sáng, ranh giới kính và các sự kiện tán xạ từ giao điểm đầu tiên (1–16). Độ sâu 1 chỉ chiếu sáng trực tiếp bề mặt đục; giá trị cao hơn cho phép thêm lần phản xạ hoặc xuyên kính và có thể lâu hơn. Tắt Chiếu sáng gián tiếp để dùng phản xạ tùy chỉnh hoặc dị hướng.",
"render.cpu_path_help": "Tính ánh sáng phản xạ giữa các bề mặt đục và truyền qua các hình kính được hỗ trợ. Cần kết xuất lũy tiến và bóng dò tia. Độ sâu đường đi đếm các bề mặt được chiếu sáng, ranh giới kính và các sự kiện tán xạ từ giao điểm đầu tiên (1–64). Độ sâu 1 chỉ chiếu sáng trực tiếp bề mặt đục; giá trị cao hơn cho phép thêm lần phản xạ hoặc xuyên kính và có thể lâu hơn. Tắt Chiếu sáng gián tiếp để dùng phản xạ tùy chỉnh hoặc dị hướng.",
"render.cpu_path_lighting": "Chiếu sáng gián tiếp",
"render.cpu_path_optics_reason": "Các thiết lập được giữ nguyên. Tắt Chiếu sáng gián tiếp để dùng phản xạ tùy chỉnh hoặc dị hướng.",
"render.cpu_point": "Nguồn sáng điểm",
@@ -3438,6 +3442,12 @@
"render.cpu_subsurface_denoise_reason": "Tán xạ bên trong kính trên CPU cần tắt Khử nhiễu. Các cài đặt được giữ nguyên.",
"render.cpu_subsurface_help": "Tán xạ đổi hướng ánh sáng bên trong kính kín. Tốc độ dương cần Chiếu sáng gián tiếp và tắt Khử nhiễu. Tốc độ là số va chạm mỗi mm; 0 không thêm tán xạ. Độ lệch hướng dương ưu tiên phía trước, âm ưu tiên phía sau.",
"render.cpu_summary": "Dò tia CPU · phản xạ · HDR · nguồn sáng định hướng và điểm",
"render.cpu_threads": "Luồng CPU",
"render.cpu_threads_auto": "Tự động",
"render.cpu_threads_custom": "Tùy chỉnh",
"render.cpu_threads_help": "Chế độ tự động dùng các luồng CPU khả dụng. Một luồng chạy tuần tự. Nhiều luồng hơn có thể cần nhiều bộ nhớ hơn.",
"render.cpu_threads_single": "Một luồng",
"render.cpu_threads_web": "Kết xuất trong trình duyệt dùng một luồng phối hợp. Lựa chọn luồng đã lưu cho ứng dụng máy tính được giữ nguyên.",
"render.cpu_transparent_help": "PNG trong suốt loại bỏ nền và giữ độ phủ ở biên. Độ đục của thân và mặt phải giữ ở 100%.",
"render.cpu_volume_enable_path": "Bật chiếu sáng gián tiếp",
"render.cpu_volume_enable_path_plan": "Dò tia CPU · độ sâu đường đi {depth} · {spp} mẫu/pixel · hạt giống {seed} · bóng dò tia. Giữ nguyên khí quyển và ngân sách ảnh đã đặt.",
@@ -3446,6 +3456,40 @@
"render.cpu_volume_method_incompatible": "Không thể chuyển các thiết lập đèn, lấy mẫu hoặc khí quyển này sang phương pháp này. Thiết lập của bạn được giữ nguyên.",
"render.cpu_volume_path_required": "Bật chiếu sáng gián tiếp để dùng Khí quyển trên CPU.",
"render.cpu_warm_cool": "Ấm và lạnh",
"render.denoise_class_background": "Bỏ qua: nền",
"render.denoise_class_coverage": "Bỏ qua: độ phủ một phần",
"render.denoise_class_degenerate": "Bỏ qua: đường truyền suy biến",
"render.denoise_class_depth": "Bỏ qua: giới hạn độ sâu đường truyền",
"render.denoise_class_emitter": "Bỏ qua: nguồn phát sáng",
"render.denoise_class_exit": "Bỏ qua: điểm thoát không tương thích",
"render.denoise_class_identity": "Bỏ qua: giới hạn định danh hướng dẫn",
"render.denoise_class_lens": "Bỏ qua: độ sâu trường ảnh",
"render.denoise_class_material": "Bỏ qua: vật liệu không hỗ trợ",
"render.denoise_class_nested": "Bỏ qua: môi trường lồng nhau",
"render.denoise_class_optical": "Đủ điều kiện: bề mặt qua khúc xạ",
"render.denoise_class_reflection": "Bỏ qua: điểm cuối phản xạ",
"render.denoise_class_surface": "Đủ điều kiện: bề mặt",
"render.denoise_class_tir": "Bỏ qua: phản xạ toàn phần",
"render.denoise_class_transmission": "Bỏ qua: truyền qua tiếp",
"render.denoise_class_unavailable": "Bỏ qua: không có dữ liệu hướng dẫn",
"render.denoise_class_volume": "Bỏ qua: tán xạ thể tích",
"render.denoise_diagnostics_counts": "Đủ điều kiện: {eligible} · Bỏ qua: {skipped}",
"render.denoise_diagnostics_enable": "Thu thập chẩn đoán khử nhiễu",
"render.denoise_diagnostics_grid": "Số pixel ở kích thước nội bộ {width} × {height}.",
"render.denoise_diagnostics_help": "Thu thập số pixel đủ điều kiện và bị bỏ qua ngay cả khi tắt khử nhiễu. Số lượng dựa trên kích thước kết xuất nội bộ; mặt nạ dùng kích thước đầu ra.",
"render.denoise_diagnostics_title": "Chẩn đoán khử nhiễu",
"render.denoise_export_mask": "Xuất mặt nạ PNG",
"render.denoise_firefly": "Giảm các pixel sáng đơn lẻ",
"render.denoise_format_mask": "PNG mặt nạ",
"render.denoise_mask_copy_help": "Xuất mặt nạ khử nhiễu dưới dạng PNG, hoặc quay lại ảnh đã kết xuất để sao chép.",
"render.denoise_mask_unavailable": "Bật chẩn đoán khử nhiễu và kết xuất ảnh để kiểm tra mặt nạ.",
"render.denoise_optical_filter": "Lọc gương và kính",
"render.denoise_optical_filter_help": "Lọc ánh sáng phản xạ và truyền qua đủ điều kiện. Tắt để chỉ khử nhiễu các bề mặt không trong suốt.",
"render.denoise_passes": "Số lượt lọc",
"render.denoise_settings_help": "Nhiều lượt lọc hơn làm mịn vùng rộng hơn. Cường độ điều chỉnh mức lọc áp dụng trong mỗi lượt. Các đường truyền quang học không được hỗ trợ vẫn không được lọc.",
"render.denoise_strength": "Cường độ khử nhiễu",
"render.denoise_view_image": "Ảnh đã kết xuất",
"render.denoise_view_mask": "Mặt nạ khử nhiễu",
"render.diag_bodies": "Số khối hiển thị",
"render.diag_job_details": "Chi tiết tác vụ đã ghi nhận",
"render.diag_last_check": "Lần kiểm tra sơ bộ gần nhất",
@@ -4000,6 +4044,8 @@
"render.material_transient": "Ghi đè quang học tạm thời cho tab tài liệu này. Chúng không được lưu trong thiết kế, thiết lập kết xuất hoặc thư viện vật liệu.",
"render.material_v_up": "Trục V kết cấu hướng lên trên trong ảnh",
"render.material_weight": "Trọng số lớp phủ bóng (0–1)",
"render.metal_energy_compensation": "Khôi phục phản xạ của kim loại nhám",
"render.metal_energy_compensation_help": "Bù ánh sáng thất thoát giữa các vi diện của kim loại nhám. Kết xuất CPU cần chiếu sáng gián tiếp.",
"render.method_cpu": "Dò tia trên CPU",
"render.method_gpu": "Dò đường truyền tia trên GPU (Path Tracing)",
"render.method_limit_raster": "Quét mảng (Raster) dùng bản xem trước studio đục cố định. Chọn phương thức dò tia rõ ràng để dùng các điều khiển quang học được hỗ trợ.",
@@ -4128,6 +4174,8 @@
"render.quality_denoise_on": "Giảm nhiễu: Bật",
"render.quality_draft": "Bản nháp",
"render.quality_high": "Cao",
"render.quality_legacy_samples": "Giá trị đã lưu: {samples} mẫu/pixel. Lựa chọn mới được giới hạn ở {maximum} mẫu/pixel.",
"render.quality_preset_limit": "Thiết lập sẵn này vượt quá giới hạn 16384 mẫu/pixel. Hãy chọn số mẫu tối đa là 16384.",
"render.quality_projection_help": "Bản xem xét này chỉ thay đổi độ phân giải, ngân sách mẫu, độ sâu đường đi, hạt giống và giảm nhiễu. Các thiết lập khác được giữ nguyên; ngân sách không chứng nhận ảnh đã hội tụ.",
"render.quality_review": "Xem xét định mức lấy mẫu",
"render.quality_summary": "{width} × {height} · {samples} mẫu · độ sâu {depth} · hạt giống {seed} · {denoise}",
@@ -4275,6 +4323,7 @@
"render.show_shots_label": "Hiện dải cảnh chụp",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K UHD",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "Tùy chỉnh…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
@@ -4437,6 +4486,11 @@
"render.subsurface_enable": "Bước đi ngẫu nhiên tán xạ bên trong",
"render.subsurface_help": "Chỉ dành cho chất điện môi kín. Hệ số tán xạ và hấp thụ RGB là các hệ số riêng biệt trên mỗi mm. Tia sáng tán xạ bên trong hình học thực tế, sau đó khúc xạ hoặc phản xạ tại ranh giới. Hãy tắt bóng đổ thẳng, khử nhiễu bề mặt và thể tích bên ngoài; dùng một phân tầng Fresnel. Đây không phải bộ đổ bóng khuếch tán, lưới mật độ hay vật liệu đo đạc. Môi trường đậm đặc có thể cần nhiều mẫu và độ sâu tia lớn hơn.",
"render.subsurface_rate": "Tần suất va chạm tán xạ",
"render.supersampling": "Lấy mẫu siêu phân giải",
"render.supersampling_dimensions": "Kết xuất ở {width} × {height} rồi thu nhỏ về {output_width} × {output_height}. Sử dụng số pixel gấp bốn lần.",
"render.supersampling_help": "Kết xuất với chiều rộng và chiều cao gấp đôi, rồi thu nhỏ trong không gian ánh sáng tuyến tính. Kích thước đầu ra không đổi.",
"render.supersampling_off": "Tắt",
"render.supersampling_x2": "2×",
"render.surface_import.author": "Tác giả",
"render.surface_import.busy": "Đang nhập ảnh…",
"render.surface_import.complete": "Chọn ảnh rồi nhập tác giả, nguồn và giấy phép.",
@@ -4450,6 +4504,8 @@
"render.surface_import.saved_cpu": "Lưu bản đồ để dùng khi kết xuất bằng CPU.",
"render.surface_import.source": "Nguồn",
"render.surface_import.title": "Nhập ảnh",
"render.texture_filtering": "Lọc chi tiết kết cấu",
"render.texture_filtering_help": "Dùng các mức mip và lọc bất đẳng hướng để giảm răng cưa khi thu nhỏ kết cấu. Tốn thêm bộ nhớ; tắt để giữ cách lọc trước đây.",
"render.tone_aces_fitted": "ACES khớp",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "Thủy tinh GPU hỗ trợ các môi trường rời rạc đã xác thực. Không cung cấp môi trường lồng nhau không giới hạn hay ước tính truyền dẫn không nhiễu.",
-5
View File
@@ -154,7 +154,6 @@
"Create Issue…": "創建 Issue…",
"Cross curves": "交叉曲線",
"Ctrl+Enter continues on GitHub.": "按Ctrl+Enter在GitHub中繼續。",
"Cube": "立方體",
"Current orthographic view (1:1 mm)": "目前正交視圖(1:1毫米)",
"Current perspective view": "目前透視視圖",
"Current plane": "目前平面",
@@ -2249,9 +2248,6 @@
"prefs.interface_note": "SolidCraft 的外觀和面板佈局。",
"prefs.interval_minutes": "{n} 分鐘",
"prefs.invert_curved_roll_arrows": "反轉彎曲的滾轉箭頭",
"prefs.invert_curved_roll_arrows_note": "立方體角落的兩個彎曲箭頭會在螢幕平面內滾轉檢視。開啟時:順時針箭頭會逆時針滾轉,逆時針箭頭會順時針滾轉。關閉時:每個箭頭依其彎曲的方向滾轉檢視。",
"prefs.invert_navigator_arrows": "反轉檢視立方體的旋轉箭頭",
"prefs.invert_navigator_arrows_note": "立方體周圍的箭頭會依其所指方向環繞相機,與 Fusion 和 Inventor 相同:左側箭頭指向右方,並將相機向右擺動。關閉時:箭頭會將立方體轉向它所在的一側,因此左側箭頭會將相機向左擺動。彎曲的滾轉箭頭不受影響。",
"prefs.keep_work_on_tab_switch": "切換設計時保持對話方塊與工具開啟",
"prefs.keep_work_on_tab_switch_note": "每個設計分頁都會保留其開啟的對話方塊或工具、您在其中選取與輸入的內容,以及其工具列索引標籤。回到該設計時,它們會恢復原狀。關閉時:切換設計會取消它們,並顯示「模型」索引標籤。",
"prefs.labels": "標籤",
@@ -2288,7 +2284,6 @@
"prefs.navigation": "導航",
"prefs.navigation_note": "游標、觸控板和3D游標如何移動視圖。",
"prefs.navigator_fit": "變更導覽器檢視後使模型符合視圖",
"prefs.navigator_fit_note": "檢視立方體的面、角或箭頭,或方向控制器的某個軸,也會從能容納整個模型各側的單一距離(其邊界球)看向模型中心。啟用時,「主檢視」使用相同的縮放與中心,因此之後選擇的檢視只會旋轉。關閉時:檢視原地旋轉,保留其目標與縮放。",
"prefs.network_host_remove": "刪除",
"prefs.network_locations": "認可設計中的網絡位置",
"prefs.network_locations_note": "關閉:設計中位於其他計算機 (\\\\server\\share) 上的鏈接檔案,只會從此處列出的服務器開啟,或在詢問時你認可的服務器開啟。",
+63 -7
View File
@@ -193,7 +193,6 @@
"Create Issue…": "创建 Issue…",
"Cross curves": "交叉曲线",
"Ctrl+Enter continues on GitHub.": "按Ctrl+Enter在GitHub中继续。",
"Cube": "立方体",
"Current orthographic view (1:1 mm)": "当前正交视图(1:1毫米)",
"Current perspective view": "当前透视视图",
"Current plane": "当前平面",
@@ -1509,6 +1508,7 @@
"cmd.timeline_stop": "停止历史播放",
"cmd.timeline_suppress": "抑制特征",
"cmd.ui.assistant": "切换助手显示",
"cmd.ui.palette": "搜索命令",
"cmd.view.navigator": "自定义视图导航器",
"cmd.view.render_browser": "渲染浏览器",
"cmd.view.render_dock": "移动渲染停靠面板",
@@ -2431,6 +2431,7 @@
"parts.dimension.width": "宽度",
"parts.family.bearing": "深沟球轴承(外形)",
"parts.family.button_head_cap_screw": "圆头内六角螺钉",
"parts.family.countersunk_socket_screw": "沉头内六角螺钉",
"parts.family.dowel_pin": "圆柱销",
"parts.family.hex_bolt": "六角头螺栓",
"parts.family.hex_cap_screw_inch": "六角头螺栓(英制)",
@@ -2681,9 +2682,9 @@
"prefs.interface_note": "SolidCraft 的外观和面板布局。",
"prefs.interval_minutes": "{n} 分钟",
"prefs.invert_curved_roll_arrows": "反转弯曲的翻滚箭头",
"prefs.invert_curved_roll_arrows_note": "立方体角落的两个弯曲箭头在屏幕平面内翻滚视图。开启:顺时针箭头逆时针翻滚,逆时针箭头顺时针翻滚。关闭:每个箭头按其弯曲方向翻滚视图。",
"prefs.invert_navigator_arrows": "反转视图立方体的旋转箭头",
"prefs.invert_navigator_arrows_note": "立方体周围的箭头按箭头所指的方向转动相机,与 Fusion 和 Inventor 相同:左侧箭头指向右,相机向右转动。关闭:箭头把立方体转向它所在的那一面,因此左侧箭头使相机向左转动。弯曲的翻滚箭头不受影响。",
"prefs.invert_curved_roll_arrows_note": "导航器角落的两个弯曲箭头在屏幕平面内翻滚视图。开启:顺时针箭头逆时针翻滚,逆时针箭头顺时针翻滚。关闭:每个箭头按其弯曲方向翻滚视图。",
"prefs.invert_navigator_arrows": "反转导航器的旋转箭头",
"prefs.invert_navigator_arrows_note": "导航器周围的箭头按箭头所指的方向转动相机,与 Fusion 和 Inventor 相同:左侧箭头指向右,相机向右转动。关闭:箭头把视图转向它所在的那一侧,因此左侧箭头使相机向左转动。弯曲的翻滚箭头不受影响。",
"prefs.keep_unsaved_also_asks": "关闭标签页时仍然询问",
"prefs.keep_unsaved_also_asks_tip": "关闭此项后,只要每个未保存设计的副本都是最新的,标签页就会直接关闭,不显示浏览器的提示框。",
"prefs.keep_unsaved_in_browser": "在此浏览器中保留未保存的工作",
@@ -2743,7 +2744,6 @@
"prefs.move_drag_highlights_tip": "关闭:用移动的箭头拖动组件时,不高亮指针下的对象,大型装配体也能保持流畅。显示剖切或分析时仍照常高亮;沿关节拖动零件时从不高亮。",
"prefs.navigation": "导航",
"prefs.navigation.axes_signed": "坐标轴",
"prefs.navigation.cube_compact": "立方体",
"prefs.navigation.device": "设备",
"prefs.navigation.free": "自由",
"prefs.navigation.orbit_style": "旋转方式",
@@ -2751,7 +2751,7 @@
"prefs.navigation.turntable": "转台",
"prefs.navigation_note": "鼠标、触控板和3D鼠标如何移动视图。",
"prefs.navigator_fit": "视图导航器切换视图后适配模型",
"prefs.navigator_fit_note": "点击立方体的面、角或箭头,或坐标轴指示器的轴时,也会从各个方向都能容纳模型的同一距离(其包围球)注视模型中心。开启期间主视图使用相同的缩放和中心,因此之后选择的视图只改变方向。关闭:视图原地旋转,保持目标点和缩放。",
"prefs.navigator_fit_note": "点击导航器的面、角或箭头,或坐标轴指示器的轴时,也会从各个方向都能容纳模型的同一距离(其包围球)注视模型中心。开启期间主视图使用相同的缩放和中心,因此之后选择的视图只改变方向。关闭:视图原地旋转,保持目标点和缩放。",
"prefs.network_host_remove": "删除",
"prefs.network_locations": "允许设计中的网络位置",
"prefs.network_locations_note": "适用于将设计存放在文件服务器上的组织。关闭时:仅允许您授权的服务器。",
@@ -2826,6 +2826,8 @@
"prefs.remember_dialog_values.help": "默认关闭。开启后,命令成功执行后,在关闭 SolidCraft 前,下次打开同一命令时会重用其安全的值和选项。",
"prefs.render_legacy_image_defaults": "使用以前的渲染图像默认值",
"prefs.render_legacy_image_defaults_help": "保留以前的手动图像流程:新设置使用 CPU 光栅渲染,CPU 光线追踪使用固定采样,并手动打开结果。进入渲染时重新适配视图,切换镜头时不恢复其保存的相机,并使用以前的取景和导航方式。禁用自动实时图像反馈。已保存的设置保持不变。",
"prefs.render_legacy_memory_admission": "使用以前的渲染内存检查",
"prefs.render_legacy_memory_admission_help": "即使估算用量超过可用RAM,也允许在固定的共享内存和主机内存限额内进行原生渲染。设备限制和内存分配检查仍然适用。",
"prefs.render_previous_geometry_budget": "使用先前的渲染预览几何内存预算",
"prefs.render_previous_geometry_budget_help": "恢复先前的固定几何内存上限(64位桌面为512 MiB,其他平台为128 MiB),替代基于设备的预算。",
"prefs.reports_saved": "已保存 {n} 份",
@@ -3220,8 +3222,10 @@
"render.category_paint": "涂漆",
"render.category_plastic": "塑料",
"render.category_wood": "木材",
"render.caustics_area_lights": "面光源焦散",
"render.caustics_depth": "焦散路径深度",
"render.caustics_enable": "点光源和方向光的玻璃焦散",
"render.caustics_environment": "环境光焦散",
"render.caustics_help": "光子数控制噪点和计算量。增大收集半径可得到更平滑的估计,但会损失细节。CPU需要间接照明。",
"render.caustics_path_required": "CPU焦散需要间接照明。",
"render.caustics_photons": "光子数",
@@ -3398,7 +3402,7 @@
"render.cpu_opaque_required": "CPU 光线追踪需要不透明背景。请关闭透明背景。",
"render.cpu_options_pending": "此CPU面板暂不支持编辑自发光、表面效果、玻璃或各向异性。使用前请清除这些API选项。",
"render.cpu_path_depth": "路径深度",
"render.cpu_path_help": "计算在不透明表面之间反射并穿过受支持玻璃形状的光。需要渐进渲染和光线追踪阴影。路径深度从首次相交开始计算着色表面、玻璃边界和散射事件(1–16)。深度 1 只计算不透明表面的直接照明;更高的值允许更多反射或玻璃穿越,也可能耗时更长。使用自定义反射或各向异性时,请关闭间接照明。",
"render.cpu_path_help": "计算在不透明表面之间反射并穿过受支持玻璃形状的光。需要渐进渲染和光线追踪阴影。路径深度从首次相交开始计算着色表面、玻璃边界和散射事件(1–64)。深度 1 只计算不透明表面的直接照明;更高的值允许更多反射或玻璃穿越,也可能耗时更长。使用自定义反射或各向异性时,请关闭间接照明。",
"render.cpu_path_lighting": "间接照明",
"render.cpu_path_optics_reason": "设置会保留。使用自定义反射或各向异性时,请关闭间接照明。",
"render.cpu_point": "点光源",
@@ -3438,6 +3442,12 @@
"render.cpu_subsurface_denoise_reason": "CPU 内部散射需要关闭降噪。设置会保留。",
"render.cpu_subsurface_help": "散射会改变闭合玻璃内部的光线方向。正散射率需要开启间接光照并关闭降噪。散射率表示每毫米的碰撞次数;0 不增加散射。方向偏置为正时偏向前方,为负时偏向后方。",
"render.cpu_summary": "CPU光线追踪 · 反射 · HDR · 平行光与点光源",
"render.cpu_threads": "CPU 工作线程",
"render.cpu_threads_auto": "自动",
"render.cpu_threads_custom": "自定义",
"render.cpu_threads_help": "自动模式使用可用的 CPU 工作线程。单线程按顺序执行。更多线程可能需要更多内存。",
"render.cpu_threads_single": "单线程",
"render.cpu_threads_web": "浏览器渲染使用一个协作式工作线程。已保存的桌面线程选择会保留。",
"render.cpu_transparent_help": "透明PNG移除背景并保留边缘覆盖率。实体和面的不透明度必须保持100%。",
"render.cpu_volume_enable_path": "启用间接照明",
"render.cpu_volume_enable_path_plan": "CPU 光线追踪 · 路径深度 {depth} · 每像素 {spp} 个采样 · 种子 {seed} · 追踪阴影。保留已编写的大气和图像预算。",
@@ -3446,6 +3456,40 @@
"render.cpu_volume_method_incompatible": "这些灯光、采样或大气设置无法转移到此方式。您的设置会保留。",
"render.cpu_volume_path_required": "启用间接照明以使用 CPU 大气。",
"render.cpu_warm_cool": "暖色与冷色",
"render.denoise_class_background": "已跳过:背景",
"render.denoise_class_coverage": "已跳过:部分覆盖",
"render.denoise_class_degenerate": "已跳过:退化路径",
"render.denoise_class_depth": "已跳过:路径深度上限",
"render.denoise_class_emitter": "已跳过:发光体",
"render.denoise_class_exit": "已跳过:不兼容的出射",
"render.denoise_class_identity": "已跳过:引导标识上限",
"render.denoise_class_lens": "已跳过:景深",
"render.denoise_class_material": "已跳过:不支持的材质",
"render.denoise_class_nested": "已跳过:嵌套介质",
"render.denoise_class_optical": "可滤波:折射后的表面",
"render.denoise_class_reflection": "已跳过:反射终点",
"render.denoise_class_surface": "可滤波:表面",
"render.denoise_class_tir": "已跳过:全内反射",
"render.denoise_class_transmission": "已跳过:进一步透射",
"render.denoise_class_unavailable": "已跳过:无引导数据",
"render.denoise_class_volume": "已跳过:体积散射",
"render.denoise_diagnostics_counts": "可滤波:{eligible} · 已跳过:{skipped}",
"render.denoise_diagnostics_enable": "收集降噪诊断信息",
"render.denoise_diagnostics_grid": "内部尺寸 {width} × {height} 下的像素计数。",
"render.denoise_diagnostics_help": "即使关闭降噪,也会收集可滤波和跳过的像素数量。计数基于内部渲染尺寸;蒙版使用输出尺寸。",
"render.denoise_diagnostics_title": "降噪诊断",
"render.denoise_export_mask": "导出蒙版 PNG",
"render.denoise_firefly": "抑制孤立亮点",
"render.denoise_format_mask": "蒙版 PNG",
"render.denoise_mask_copy_help": "将降噪蒙版导出为 PNG,或返回渲染图像后复制图像。",
"render.denoise_mask_unavailable": "启用降噪诊断并渲染图像后,即可查看其蒙版。",
"render.denoise_optical_filter": "过滤镜面和玻璃",
"render.denoise_optical_filter_help": "对符合条件的反射光和透射光进行过滤。关闭后,仅对不透明表面降噪。",
"render.denoise_passes": "滤波轮数",
"render.denoise_settings_help": "更多轮次会平滑更大范围。强度控制每轮滤波的应用程度。不支持的光路保持未滤波状态。",
"render.denoise_strength": "降噪强度",
"render.denoise_view_image": "渲染图像",
"render.denoise_view_mask": "降噪蒙版",
"render.diag_bodies": "可见实体数",
"render.diag_job_details": "已捕获任务详情",
"render.diag_last_check": "上次预检",
@@ -4000,6 +4044,8 @@
"render.material_transient": "此文档标签页的临时光学设置,不保存到设计、渲染设置或材质库中。",
"render.material_v_up": "纹理 V 轴指向图像上方",
"render.material_weight": "涂层权重(0–1)",
"render.metal_energy_compensation": "恢复粗糙金属的反射",
"render.metal_energy_compensation_help": "补偿粗糙金属微表面之间损失的光能。CPU 渲染需要间接照明。",
"render.method_cpu": "CPU 光线追踪",
"render.method_gpu": "GPU 路径追踪",
"render.method_limit_raster": "光栅化使用固定的不透明工作室预览;要使用受支持的光学控件,请明确选择光线追踪方式。",
@@ -4128,6 +4174,8 @@
"render.quality_denoise_on": "降噪:开启",
"render.quality_draft": "草稿",
"render.quality_high": "高",
"render.quality_legacy_samples": "已保存的值:{samples} spp。新选择的上限为 {maximum} spp。",
"render.quality_preset_limit": "此预设超过 16384 spp 的选择上限。请选择不超过 16384 的采样数。",
"render.quality_projection_help": "此检查仅更改分辨率、采样预算、路径深度、种子和降噪。其他设置保持不变,预算不保证收敛。",
"render.quality_review": "审阅采样预算",
"render.quality_summary": "{width} × {height} · {samples} 次采样 · 深度 {depth} · 种子 {seed} · {denoise}",
@@ -4275,6 +4323,7 @@
"render.show_shots_label": "显示镜头条",
"render.size_4k": "4K",
"render.size_4k_uhd": "4K 超高清",
"render.size_8k_uhd": "8K UHD",
"render.size_custom": "自定义…",
"render.size_full_hd": "全高清",
"render.size_hd": "HD",
@@ -4437,6 +4486,11 @@
"render.subsurface_enable": "内部散射随机游走",
"render.subsurface_help": "仅适用于封闭介质。散射和RGB吸收使用独立的每毫米系数。路径在实际几何内部散射,并在边界折射或反射。关闭直线透射阴影、表面降噪和外部体积;使用一个菲涅耳分层。这不是扩散着色器、密度网格或实测材料。高密度可能需要更多样本和路径深度。",
"render.subsurface_rate": "散射碰撞率",
"render.supersampling": "超采样",
"render.supersampling_dimensions": "以 {width} × {height} 渲染,再缩小至 {output_width} × {output_height}。使用四倍像素。",
"render.supersampling_help": "以双倍宽度和高度渲染,然后在线性光空间中缩小。输出尺寸保持不变。",
"render.supersampling_off": "关闭",
"render.supersampling_x2": "2×",
"render.surface_import.author": "作者",
"render.surface_import.busy": "正在导入图像…",
"render.surface_import.complete": "请选择图像并填写作者、来源和许可。",
@@ -4450,6 +4504,8 @@
"render.surface_import.saved_cpu": "保存贴图以用于 CPU 渲染。",
"render.surface_import.source": "来源",
"render.surface_import.title": "导入图像",
"render.texture_filtering": "过滤纹理细节",
"render.texture_filtering_help": "使用多级纹理和各向异性过滤,减少纹理缩小时的锯齿。需要更多内存;关闭时保留原有过滤方式。",
"render.tone_aces_fitted": "ACES 拟合",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPU 玻璃支持经过验证且互不相交的介质;不支持任意嵌套介质,也不提供无噪声透射估计器。",
+4 -4
View File
@@ -39,10 +39,10 @@
],
"control_border": "#ffffff",
"control_fill": "#000000",
"cube_border": "#ffffff",
"cube_face": "#000000",
"cube_hover": "#000000",
"cube_text": "#ffffff",
"navigator_border": "#ffffff",
"navigator_face": "#000000",
"navigator_hover": "#000000",
"navigator_text": "#ffffff",
"dialog_bg": "#000000",
"dialog_body": "#000000",
"dialog_border": "#ffffff",
+4 -4
View File
@@ -39,10 +39,10 @@
],
"control_border": "#000000",
"control_fill": "#ffffff",
"cube_border": "#000000",
"cube_face": "#ffffff",
"cube_hover": "#ffffff",
"cube_text": "#000000",
"navigator_border": "#000000",
"navigator_face": "#ffffff",
"navigator_hover": "#ffffff",
"navigator_text": "#000000",
"dialog_bg": "#ffffff",
"dialog_body": "#ffffff",
"dialog_border": "#000000",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#d4d0ca",
"control_fill": "#fffdfa",
"cube_border": "#b5b0a9",
"cube_face": "#fffdfa",
"cube_hover": "#d4d0ca",
"cube_text": "#13110e",
"navigator_border": "#b5b0a9",
"navigator_face": "#fffdfa",
"navigator_hover": "#d4d0ca",
"navigator_text": "#13110e",
"dialog_bg": "#f9f6f2",
"dialog_body": "#f9f6f2",
"dialog_border": "#d4d0ca",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#533812",
"control_fill": "#1d1406",
"cube_border": "#533812",
"cube_face": "#36250c",
"cube_hover": "#563a13",
"cube_text": "#efa335",
"navigator_border": "#533812",
"navigator_face": "#36250c",
"navigator_hover": "#563a13",
"navigator_text": "#efa335",
"dialog_bg": "#100b04",
"dialog_body": "#1d1406",
"dialog_border": "#533812",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#315777",
"control_fill": "#112d4b",
"cube_border": "#315777",
"cube_face": "#163b60",
"cube_hover": "#285f89",
"cube_text": "#e3f0fa",
"navigator_border": "#315777",
"navigator_face": "#163b60",
"navigator_hover": "#285f89",
"navigator_text": "#e3f0fa",
"dialog_bg": "#0c223c",
"dialog_body": "#112d4b",
"dialog_border": "#315777",
@@ -42,10 +42,10 @@
],
"control_border": "#45475a",
"control_fill": "#313244",
"cube_border": "#45475a",
"cube_face": "#313244",
"cube_hover": "#302b46",
"cube_text": "#cdd6f4",
"navigator_border": "#45475a",
"navigator_face": "#313244",
"navigator_hover": "#302b46",
"navigator_text": "#cdd6f4",
"dialog_bg": "#1e1e2e",
"dialog_body": "#1e1e2e",
"dialog_border": "#45475a",
+4 -4
View File
@@ -39,10 +39,10 @@
],
"control_border": "#55ffff",
"control_fill": "#000000",
"cube_border": "#55ffff",
"cube_face": "#000000",
"cube_hover": "#aa00aa",
"cube_text": "#ffffff",
"navigator_border": "#55ffff",
"navigator_face": "#000000",
"navigator_hover": "#aa00aa",
"navigator_text": "#ffffff",
"dialog_bg": "#000000",
"dialog_body": "#000000",
"dialog_border": "#55ffff",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#5d5246",
"control_fill": "#3c352d",
"cube_border": "#706354",
"cube_face": "#3c352d",
"cube_hover": "#5d5246",
"cube_text": "#e4e1dd",
"navigator_border": "#706354",
"navigator_face": "#3c352d",
"navigator_hover": "#5d5246",
"navigator_text": "#e4e1dd",
"dialog_bg": "#312b25",
"dialog_body": "#312b25",
"dialog_border": "#4b4238",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#dcd3c2",
"control_fill": "#fdfaf3",
"cube_border": "#b7b1a7",
"cube_face": "#fdfaf3",
"cube_hover": "#dcd3c2",
"cube_text": "#14110d",
"navigator_border": "#b7b1a7",
"navigator_face": "#fdfaf3",
"navigator_hover": "#dcd3c2",
"navigator_text": "#14110d",
"dialog_bg": "#faf7f1",
"dialog_body": "#faf7f1",
"dialog_border": "#dcd3c2",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#b0b0b4",
"control_fill": "#ececec",
"cube_border": "#9a9aa0",
"cube_face": "#f2f2f4",
"cube_hover": "#cce5ff",
"cube_text": "#262628",
"navigator_border": "#9a9aa0",
"navigator_face": "#f2f2f4",
"navigator_hover": "#cce5ff",
"navigator_text": "#262628",
"dialog_bg": "#ececec",
"dialog_body": "#f3f3f3",
"dialog_border": "#c6c6c8",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#484d55",
"control_fill": "#303338",
"cube_border": "#484d55",
"cube_face": "#303338",
"cube_hover": "#293c49",
"cube_text": "#e0e2e5",
"navigator_border": "#484d55",
"navigator_face": "#303338",
"navigator_hover": "#293c49",
"navigator_text": "#e0e2e5",
"dialog_bg": "#25272b",
"dialog_body": "#25272b",
"dialog_border": "#484d55",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#285333",
"control_fill": "#0b1d10",
"cube_border": "#285333",
"cube_face": "#0f3617",
"cube_hover": "#175625",
"cube_text": "#40ee66",
"navigator_border": "#285333",
"navigator_face": "#0f3617",
"navigator_hover": "#175625",
"navigator_text": "#40ee66",
"dialog_bg": "#061009",
"dialog_body": "#0b1d10",
"dialog_border": "#285333",
+4 -4
View File
@@ -45,10 +45,10 @@
"preview_new": "#fabd2f",
"preview_sketch": "#b8bb26",
"preview_cut": "#fb49346e",
"cube_face": "#ebdbb2",
"cube_border": "#665c54",
"cube_text": "#282828",
"cube_hover": "#83a598",
"navigator_face": "#ebdbb2",
"navigator_border": "#665c54",
"navigator_text": "#282828",
"navigator_hover": "#83a598",
"viewport_top": "#32302f",
"viewport_bottom": "#32302f",
"grid": "#443f3c",
+4 -4
View File
@@ -44,10 +44,10 @@
"preview_new": "#d79921",
"preview_sketch": "#98971a",
"preview_cut": "#cc241d6e",
"cube_face": "#fbf1c7",
"cube_border": "#7c6f64",
"cube_text": "#3c3836",
"cube_hover": "#83a598",
"navigator_face": "#fbf1c7",
"navigator_border": "#7c6f64",
"navigator_text": "#3c3836",
"navigator_hover": "#83a598",
"viewport_top": "#fcf5d8",
"viewport_bottom": "#ebdbb2",
"grid": "#d5c4a1",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#a0a0a0",
"control_fill": "#151515",
"cube_border": "#a0a0a0",
"cube_face": "#151515",
"cube_hover": "#002c45",
"cube_text": "#ffffff",
"navigator_border": "#a0a0a0",
"navigator_face": "#151515",
"navigator_hover": "#002c45",
"navigator_text": "#ffffff",
"dialog_bg": "#0c0c0c",
"dialog_body": "#000000",
"dialog_border": "#a0a0a0",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#4c566a",
"control_fill": "#3b4252",
"cube_border": "#4c566a",
"cube_face": "#3b4252",
"cube_hover": "#35495b",
"cube_text": "#eceff4",
"navigator_border": "#4c566a",
"navigator_face": "#3b4252",
"navigator_hover": "#35495b",
"navigator_text": "#eceff4",
"dialog_bg": "#2e3440",
"dialog_body": "#2e3440",
"dialog_border": "#4c566a",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#50546d",
"control_fill": "#343746",
"cube_border": "#50546d",
"cube_face": "#343746",
"cube_hover": "#393149",
"cube_text": "#f8f8f2",
"navigator_border": "#50546d",
"navigator_face": "#343746",
"navigator_hover": "#393149",
"navigator_text": "#f8f8f2",
"dialog_bg": "#282a36",
"dialog_body": "#282a36",
"dialog_border": "#50546d",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#474b4f",
"control_fill": "#2c2e31",
"cube_border": "#585b5f",
"cube_face": "#2c2e31",
"cube_hover": "#474b4f",
"cube_text": "#e1e3e5",
"navigator_border": "#585b5f",
"navigator_face": "#2c2e31",
"navigator_hover": "#474b4f",
"navigator_text": "#e1e3e5",
"dialog_bg": "#222527",
"dialog_body": "#222527",
"dialog_border": "#383b3e",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#4a5262",
"control_fill": "#272b34",
"cube_border": "#5c6370",
"cube_face": "#abb2bf",
"cube_hover": "#61afef",
"cube_text": "#21252b",
"navigator_border": "#5c6370",
"navigator_face": "#abb2bf",
"navigator_hover": "#61afef",
"navigator_text": "#21252b",
"dialog_bg": "#282c34",
"dialog_body": "#3b4453",
"dialog_border": "#4b5263",
+6 -6
View File
@@ -38,10 +38,10 @@
],
"control_border": "#4a4f54",
"control_fill": "#303235",
"cube_border": "#50565b",
"cube_face": "#34373a",
"cube_hover": "#53616c",
"cube_text": "#dededc",
"navigator_border": "#50565b",
"navigator_face": "#34373a",
"navigator_hover": "#53616c",
"navigator_text": "#dededc",
"dialog_bg": "#2b2d2f",
"dialog_body": "#2b2d2f",
"dialog_border": "#3d4145",
@@ -451,7 +451,7 @@
"hover_profile": "#b7bdc230",
"origin_plane_hover": "#b7bdc260",
"profile_fill": "#b7bdc228",
"cube_hover": "#454b50",
"navigator_hover": "#454b50",
"chip": "#b7bdc2",
"logo": [
"#dde0e3",
@@ -498,7 +498,7 @@
"hover_profile": "#aabcae30",
"origin_plane_hover": "#aabcae60",
"profile_fill": "#aabcae28",
"cube_hover": "#414d45",
"navigator_hover": "#414d45",
"chip": "#aabcae",
"logo": [
"#d1ddd4",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#888888",
"control_fill": "#dddddd",
"cube_border": "#888888",
"cube_face": "#eeeeee",
"cube_hover": "#ccccff",
"cube_text": "#222222",
"navigator_border": "#888888",
"navigator_face": "#eeeeee",
"navigator_hover": "#ccccff",
"navigator_text": "#222222",
"dialog_bg": "#dddddd",
"dialog_body": "#eeeeee",
"dialog_border": "#888888",
+4 -4
View File
@@ -38,10 +38,10 @@
],
"control_border": "#586e75",
"control_fill": "#073642",
"cube_border": "#586e75",
"cube_face": "#073642",
"cube_hover": "#586e75",
"cube_text": "#93a1a1",
"navigator_border": "#586e75",
"navigator_face": "#073642",
"navigator_hover": "#586e75",
"navigator_text": "#93a1a1",
"dialog_bg": "#073642",
"dialog_body": "#073642",
"dialog_border": "#586e75",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#b6b3a1",
"control_fill": "#e3ddc9",
"cube_border": "#b6b3a1",
"cube_face": "#e3ddc9",
"cube_hover": "#e1e8d9",
"cube_text": "#586e75",
"navigator_border": "#b6b3a1",
"navigator_face": "#e3ddc9",
"navigator_hover": "#e1e8d9",
"navigator_text": "#586e75",
"dialog_bg": "#eee8d5",
"dialog_body": "#eee8d5",
"dialog_border": "#b6b3a1",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#808080",
"control_fill": "#c0c0c0",
"cube_border": "#808080",
"cube_face": "#c0c0c0",
"cube_hover": "#8080ff",
"cube_text": "#000000",
"navigator_border": "#808080",
"navigator_face": "#c0c0c0",
"navigator_hover": "#8080ff",
"navigator_text": "#000000",
"dialog_bg": "#c0c0c0",
"dialog_body": "#c0c0c0",
"dialog_border": "#808080",
+4 -4
View File
@@ -42,10 +42,10 @@
],
"control_border": "#414868",
"control_fill": "#24283b",
"cube_border": "#414868",
"cube_face": "#24283b",
"cube_hover": "#253452",
"cube_text": "#c0caf5",
"navigator_border": "#414868",
"navigator_face": "#24283b",
"navigator_hover": "#253452",
"navigator_text": "#c0caf5",
"dialog_bg": "#1a1b26",
"dialog_body": "#1a1b26",
"dialog_border": "#414868",
@@ -0,0 +1 @@
| assets/locales/lv.json | SolidCraft contributors | Original Latvian translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |

Some files were not shown because too many files have changed in this diff Show More