* 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)
* 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>
* 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).
* 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>
* 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>
* 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>
* 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>
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>
* 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>
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>
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>
* 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>
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>
* 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>
* 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)
Fixestangency-labs/solidcraft#13539Fixestangency-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>
* 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>
* 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>
* 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>
* 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>
* 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>
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>
* 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>
* 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>
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>
* 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>
* 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>
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>
* 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>
* 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>
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>
* 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>
* kernel: smooth_edges measures each edge's ends too, so a G2 corner's side to its round is drawn (#13542)
Fixestangency-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>
* 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>
* 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.
* 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>
* 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>
* 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>
`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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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.
* 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.
* 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>
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>
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>
* 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>
* 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>
* 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
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>
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.
* 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>
* 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>
* 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>
* 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>
The corpus grows as cases are added, so the merged kernel -> main sync saw
1,337 bodies instead of 1,332 (all five new ones identical) and failed.
Every differing body is already pinned by name; the totals now check that
the rest are identical or within rounding.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
* 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>
* 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
* 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>
* 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>
* 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>
* 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>
* 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).
* 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>
* 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>
* 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>
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>
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>
* 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>
* 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>
- 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>
* 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>
* 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>
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>
* 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
* 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).
Fixestangency-labs/solidcraft#13540
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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).
* 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>
* 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>
* 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>
* 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.
* 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.
* 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.
* 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>
* 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>
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).
* 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
928 changed files with 77191 additions and 29935 deletions
# 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.
@@ -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>
@@ -12,7 +12,7 @@ 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`.
@@ -20,7 +20,7 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
- 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`.
| 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 |
@@ -843,6 +843,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 |
@@ -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`.
constUSAGE_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";
"parts.family.hex_cap_screw_inch":"برغي سداسي الرأس (بوصة)",
@@ -2826,6 +2828,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_help":"السماح بالتصيير المحلي ضمن حدود الذاكرة المشتركة وحدود المضيف الثابتة حتى عندما يتجاوز التقدير الذاكرة المتاحة. تظل حدود الجهاز وفحوصات تخصيص الذاكرة سارية.",
"prefs.render_previous_geometry_budget":"استخدام ميزانية الهندسة السابقة لمعاينة التصيير",
"prefs.render_previous_geometry_budget_help":"استعادة حد الهندسة الثابت السابق (512 ميبيبايت على سطح المكتب 64 بت، و128 ميبيبايت على المنصات الأخرى) بدلًا من الميزانية المعتمدة على الجهاز.",
"prefs.reports_saved":"{n} محفوظة",
@@ -3220,8 +3224,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 +3404,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 +3444,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 +3458,40 @@
"render.cpu_volume_method_incompatible":"لا يمكن نقل إعدادات الإضاءة أو أخذ العينات أو الغلاف الجوي هذه إلى هذه الطريقة. تبقى إعداداتك محفوظة.",
"render.cpu_volume_path_required":"فعّل الإضاءة غير المباشرة لاستخدام الغلاف الجوي على CPU.",
"render.denoise_diagnostics_grid":"أعداد البكسلات بالحجم الداخلي {width} × {height}.",
"render.denoise_diagnostics_help":"يجمع أعداد البكسلات المؤهلة والمتجاوزة حتى عند إيقاف إزالة الضوضاء. تعتمد الأعداد على حجم التصيير الداخلي، ويعتمد القناع على حجم الإخراج.",
"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 +4046,8 @@
"render.material_transient":"تجاوزات بصرية مؤقتة لعلامة تبويب المستند هذه. لا تُحفظ في التصميم ولا في إعداد التصيير ولا في مكتبة المواد.",
"render.material_v_up":"اتجاه V للنسيج يشير للأعلى في الصورة",
"render.subsurface_help":"للعوازل المغلقة فقط. التشتت وامتصاص RGB معاملان منفصلان لكل mm. تتشتت المسارات داخل الهندسة الفعلية، ثم تنكسر أو تنعكس عند الحد. عطّل نفاذ الظل المستقيم وإزالة تشويش السطح والحجم الخارجي؛ واستخدم طبقة فرينل واحدة. ليس مظلل انتشار ولا شبكة كثافة ولا مادة مقيسة. الأوساط الكثيفة قد تحتاج مزيدًا من العيّنات وعمق المسار.",
"render.subsurface_rate":"معدل اصطدامات التشتت",
"render.supersampling":"أخذ عينات فائقة",
"render.supersampling_dimensions":"يُصيَّر بحجم {width} × {height} ويُصغَّر إلى {output_width} × {output_height}. يستخدم أربعة أضعاف عدد البكسلات.",
"render.supersampling_help":"يُصيَّر المشهد بضعف العرض والارتفاع، ثم يُصغَّر في فضاء الإضاءة الخطي. يبقى حجم الإخراج دون تغيير.",
"render.texture_filtering_help":"استخدم مستويات التفاصيل والترشيح المتباين لتقليل تعرّج الخامات عند التصغير. يزيد استهلاك الذاكرة؛ يبقى الوضع القديم معطّلاً.",
"render.tone_aces_fitted":"ACES مُلاءَم",
"render.tone_reinhard":"راينهارد",
"render.transmission_scope":"يدعم زجاج GPU الأوساط المنفصلة المعتمدة. لا تتوفر الأوساط المتداخلة غير المقيدة ولا مقدّر نفاذ خالٍ من التشويش.",
"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_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_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 +2978,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.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_optics_reason":"Die Einstellungen bleiben erhalten. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
"render.cpu_point":"Punktlicht",
@@ -3192,6 +3198,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_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.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_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.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.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.",
"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.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 +4260,8 @@
"render.surface_import.saved_cpu":"Speichern Sie Texturen für das CPU-Rendering.",
"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.",
"parts.family.hex_cap_screw_inch":"Hex cap screw (inch)",
@@ -2826,6 +2828,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_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_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_help":"Restore the previous fixed geometry limit (512 MiB on 64-bit desktop, 128 MiB elsewhere) instead of the device-based budget.",
"render.caustics_help":"Photon count controls noise and work. Gather radius trades sharp detail for a smoother estimate. CPU requires Indirect lighting.",
"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 +3444,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.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_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 +4046,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.",
"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.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.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.",
"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":"בורג ראש משושה (אינץ')",
@@ -2826,6 +2828,8 @@
"prefs.remember_dialog_values.help":"כבוי כברירת מחדל. כשהוא פועל, לאחר הצלחת פקודה, הערכים והאפשרויות הבטוחים שלה ישמשו שוב בפעם הבאה שתפתח את אותה פקודה, עד לסגירת SolidCraft.",
"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 בפלטפורמות אחרות) במקום התקציב המבוסס על ההתקן.",
"render.caustics_help":"מספר הפוטונים שולט ברעש ובעומס החישוב. רדיוס האיסוף מאזן בין פרטים חדים לאומדן חלק יותר. CPU דורש תאורה עקיפה.",
"render.caustics_path_required":"ריכוז אור ב-CPU דורש תאורה עקיפה.",
"render.caustics_photons":"מספר פוטונים",
@@ -3398,7 +3404,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 +3444,12 @@
"render.cpu_subsurface_denoise_reason":"פיזור בתוך זכוכית ב־CPU דורש הסרת רעש כבויה. ההגדרות נשמרות.",
"render.cpu_subsurface_help":"פיזור משנה את כיוון האור בתוך זכוכית סגורה. קצב חיובי דורש תאורה עקיפה והסרת רעש כבויה. הקצב הוא מספר התנגשויות למ״מ; 0 אינו מוסיף פיזור. הטיית כיוון חיובית מעדיפה קדימה, ושלילית אחורה.",
"render.denoise_diagnostics_grid":"ספירת פיקסלים בגודל הפנימי {width} × {height}.",
"render.denoise_diagnostics_help":"נאספים מספרי הפיקסלים המתאימים ואלה שדולגו, גם כשהפחתת הרעש כבויה. הספירה לפי גודל הרינדור הפנימי; המסכה לפי גודל הפלט.",
"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 +4046,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 +4176,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.subsurface_help":"לדיאלקטרי סגור בלבד. פיזור ובליעת RGB הם מקדמים נפרדים לכל mm. המסלולים מתפזרים בתוך הגאומטריה בפועל, ואז נשברים או מוחזרים בגבול. יש לבטל העברת צל ישרה, הסרת רעש משטחית ונפח חיצוני; ולהשתמש בשכבת פרנל אחת. אינו שיידר דיפוזיה, אינו רשת צפיפות ואינו חומר מדוד. תווך צפוף עשוי לדרוש יותר דגימות ועומק מסלול.",
"render.subsurface_rate":"קצב התנגשויות הפיזור",
"render.supersampling":"דגימת יתר",
"render.supersampling_dimensions":"רינדור בגודל {width} × {height} והקטנה ל־{output_width} × {output_height}. נעשה שימוש בפי ארבעה פיקסלים.",
"render.supersampling_help":"רינדור ברוחב ובגובה כפולים, ואז הקטנה במרחב אור ליניארי. גודל הפלט נשאר ללא שינוי.",
"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 +2494,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.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 +2714,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 +2728,40 @@
"render.cpu_volume_method_incompatible":"이 조명, 샘플링 또는 대기 설정을 이 방식으로 옮길 수 없습니다. 설정은 유지됩니다.",
"render.metal_energy_compensation_help":"거친 금속의 미세면 사이에서 손실되는 빛을 보정합니다. CPU 렌더링에는 간접 조명이 필요합니다.",
"render.method_cpu":"CPU 레이 트레이싱",
"render.method_gpu":"GPU 패스 트레이싱",
"render.method_limit_raster":"래스터는 고정된 불투명 스튜디오 미리보기를 사용합니다. 지원되는 광학 컨트롤을 쓰려면 명시적인 레이 트레이싱 방식을 선택하세요.",
@@ -3397,6 +3444,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 +3592,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 +3755,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":"가로와 세로를 각각 두 배로 렌더링한 뒤 선형 광 공간에서 축소합니다. 출력 크기는 그대로 유지됩니다.",
"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_help":"Показувати наявні браузер, історію та асистента в переміщуваних панелях. Зміна набуде чинності після застосування налаштувань.",
"prefs.sketch.escape_twice_finishes_note":"Два швидкі натискання Esc завершують ескіз. Вимкнено: Esc лише скасовує інструмент чи вибір, а ескіз завершує «Завершити ескіз».",
"render.caustics_depth":"Глибина шляху каустики",
"render.caustics_enable":"Каустика скла від точкових і спрямованих джерел світла",
"render.caustics_help":"Кількість фотонів керує шумом і обсягом роботи. Радіус збирання обмінює чіткі деталі на плавнішу оцінку. CPU потребує непрямого освітлення.",
"render.caustics_path_required":"Каустика на CPU потребує непрямого освітлення.",
"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.point_cloud_reload":"Перезавантажити з файлу",
"ui.project_references_construction":"Проєктувати посилання як допоміжну геометрію",
"ui.project_references_construction_note":"«Проєктувати», «Перетин», «Включити 3D-геометрію» і ребра грані, які додає новий ескіз на грані, створюють допоміжну геометрію: посилання для креслення, не включені до профілів. Вони залишаються пов'язаними з моделлю.",
"ui.replace_sketch.from_file":"З файлу проєкту…",
"ui.replace_sketch.no_sketches":"У цьому проєкті немає ескізу, який можна взяти",
"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 +2432,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)",
@@ -2826,6 +2828,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 +3224,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 +3404,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_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 +3444,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_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 +3458,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_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_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 +4046,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 +4176,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.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 +4506,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.",
"prefs.render_legacy_image_defaults_help":"保留以前的手动图像流程:新设置使用 CPU 光栅渲染,CPU 光线追踪使用固定采样,并手动打开结果。进入渲染时重新适配视图,切换镜头时不恢复其保存的相机,并使用以前的取景和导航方式。禁用自动实时图像反馈。已保存的设置保持不变。",
.params("occurrence; path?: exact placement; space?: local|world (default local); translate?: [x,y,z]; axis?: [x,y,z], angle?: radians or \"30 deg\", origin?: [x,y,z]; matrix?: [12 or 16 numbers] (instead of translate/axis/angle/origin: a rigid move as a row-major 3x4 rotation-and-translation or 4x4 matrix, flat or as rows; one that is a rotation only up to rounding is orthonormalised, a scale, shear or mirror is refused); tolerance?: largest accepted |RᵀR − I| (default 1e-9, at most 1e-3); capture?: bool (default true; false keeps it pending until Capture Position); move_grounded?: bool (default false: a grounded occurrence refuses; true moves it and it stays grounded). Nested occurrence definitions remain shared. Replies {occurrence, transform, pending}; when joints hold it to something fixed and put it back, transform is where it is and the reply adds reverted: true and a warning naming the joint"),
.params("occurrence; path?: exact placement; space?: local|world (default local); translate?: [x,y,z]; axis?: [x,y,z], angle?: radians or \"30 deg\", origin?: [x,y,z]; to?: {position: [x,y,z], axis?: [x,y,z], angle?} or to_matrix?: [12 or 16 numbers] (absolute placement: where it goes in space, not a move; not with translate/axis/angle/origin/matrix); matrix?: [12 or 16 numbers] (instead of translate/axis/angle/origin: a rigid move as a row-major 3x4 rotation-and-translation or 4x4 matrix, flat or as rows; one that is a rotation only up to rounding is orthonormalised, a scale, shear or mirror is refused); tolerance?: largest accepted |RᵀR − I| (default 1e-9, at most 1e-3); capture?: bool (default true; false keeps it pending until Capture Position); move_grounded?: bool (default false: a grounded occurrence refuses; true moves it and it stays grounded). Nested occurrence definitions remain shared. Replies {occurrence, transform, pending}; when joints hold it to something fixed and put it back, transform is where it is and the reply adds reverted: true and a warning naming the joint"),
.params("distance: expr (or through_all: true); taper?: angle expr (positive: outward, so the far end is larger along the extrusion direction; negative: inward, so the far end is smaller); corner?: preserve_curvature|tangent|round (how a taper closes a sharp corner its offset opens: each wall continues as itself, a line's plane or an arc's cone, until it meets its neighbour; each continues straight along its end tangent; or an arc about the corner. Without it, each profile preserves curvature where every curve is a line or an arc and takes tangent where another curve has a corner, and the feature's warning says so; a corner rule given is kept, and refused where the kernel cannot build it); sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [{curves: [ids]}] (the profile whose outer loop is made of exactly these curves, or pieces of them; found again on every recompute, so it follows parameter changes; islands inside are its holes; an error when no profile or several match) | [{point:[x,y]}] (index and point picks are stored by their curves when that finds the same profile; a pick on a drawn profile that reference geometry, such as the projected outline of the face the sketch is on, cuts into regions takes the whole drawn profile); exact_regions?: bool (each index or point pick is exactly the region it names, as the dialogs send); default: every drawn profile, except that a loop inside another is its hole and a loop inside that hole is material again; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); faces?: [[x,y,z],…] (coplanar faces of one body); face_body?: name (whose face, where faces of several bodies meet at the point); direction?: positive|negative|symmetric (distance is total thickness, split evenly around the start plane); convention?: total_distance|legacy_per_side (how a symmetric distance is read; default total_distance, split evenly around the start plane; legacy_per_side: the distance on each side, as older symmetric extrudes keep); distance2?: expr (Two Sides: a second distance on the side direction leaves free, the first side may be through_all or to; with direction: negative the first side goes against the normal and distance2 along it; a negative distance only turns its own side over); start_offset?: expr (along the sketch normal, also with direction: negative); to?: [x,y,z] (To Object: a face, vertex or point; to_offset?: expr past it); from?: [x,y,z] (From Object: start at that face or point); operation?: new|join|cut|intersect|auto (cut into a body, otherwise join existing solids; new in an empty component); touching?: separate|one_body (what a join makes of pieces that share no face: separate, the default, makes each piece that touches another only along an edge or at a point a body of its own and keeps pieces that touch nothing in one body; one_body keeps every piece in one body); targets?: [body] (Join from a face, or from a sketch on a body's face, defaults to that face's body; explicit targets or participants override); name?; body_name?; thin?: true (Thin: a wall along sketch curves instead of the profiles) with curves?: [curve ids] (open chains and closed loops, joined where they meet end to end; default: every drawn curve of the sketch), thickness: expr, side?: center|inside|outside|two_sides (a closed loop's inside is the area it encloses, an open chain's the side it bends toward, a straight line's the left of its first curve as drawn; default center), thickness2?: expr (two_sides: the thickness outside); corners are mitred, crossing walls are one"),
.params("axis: sketch line id | x | y (sketch axes) | X|Y|Z (world) | {type: world, axis: X|Y|Z} (as returned by timeline.inspect) | construction axis name (followed when edited) | {origin, dir} (origin: numbers or length expressions, followed when parameters change) | {edge|face: [x,y,z]} (a straight or circular edge, or a cylinder, cone or torus face: about its axis, followed as the model changes); angle?: expr (default 360 deg); angle2?: expr (Two Sides: the other side's angle); direction?: symmetric (angle split either side, independent of axis direction); convention?: sketch_normal|legacy_axis_start (default sketch_normal; legacy retains older angular starts); to?: [x,y,z] (To Object: turn until that face, vertex or point); sketch?, profiles? | face: [x,y,z] (a planar body face as the profile) and face_body?: name (whose face, where faces of several bodies meet), operation?, touching?, targets?, name?, body_name?"),
.params("base: XY|XZ|YZ|plane name, or a planar face {face: [x,y,z], body?, index?, path?}; axis: X|Y|Z|construction axis name (where it is now)|{origin, dir} | {edge|face: [x,y,z]} (a straight or circular edge, or a cylinder, cone or torus face: about its axis, followed as the model changes); angle: expr; direction_only?: bool (default false: turn about the axis as a line; true: project the axis direction onto base and turn about the line through pivot, or the base origin); pivot?: point ref (only with direction_only; vertex, sketch point, construction point); name?"),
"faces: [[x,y,z] points on the faces to split, or face names from model.faces]; body?; and the splitting tool: plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended) — or sketch: id or name (its curves, along the sketch's normal)",
.params("body: name; plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended) — or sketch: id, line: curve id (a straight sketch line, along the sketch's normal)"),
.params("faces: [[x,y,z] points on planar faces, or face names from model.faces]; target: plane (XY|XZ|YZ|construction plane|{origin, normal}) | target_face: [x,y,z] on a parallel planar face; body?"),
.params("bodies: [names]; from: [x,y,z] | \"origin\" | {snap: vertex|edge_mid|face_center|circle_center, at: [x,y,z]} | from_face: [x,y,z] | from_plane: XY|XZ|YZ|construction or offset plane name or id|{origin, normal}; to: (the same) | to_face: [x,y,z] | to_plane: (a plane as from_plane; the point or face goes onto it along its normal, and the plane may move later: the bodies follow); from_frame / to_frame: a frame found again whenever the design changes, so the bodies follow it, in place of that side's point, face or plane: a joint origin as joint.create takes one ({face|circle|edge|point: [x,y,z]}, a face's name, a named joint origin) or {sketch, point|curve} (a sketch point, or a circle's, arc's or ellipse's centre, z along the sketch's normal), on the bodies' own component; faces, planes and frames also turn to meet; flip?: bool; axes?: [x, y, z] (point-to-point only: which world axes the offset moves along; default all three, at least one true); angle?: expr (a turn about the target's direction after the move)"),
.params("path: STEP, IGES, FreeCAD .FCStd or Rhino .3dm file (its bodies join the design as an Import base feature in a new component named after the file, placed in the active component; an assembly's products become nested components, a repeated part one component with an occurrence per placement, all held as one rigid group until component.unlock → component, component_name, occurrence, subcomponents?, rigid_joints?, group?: the folder); folder?: bool (default false; true: an import that brings more than one item into the history (its Import feature and the rigid joints holding its parts) goes into a new history folder named `Group #N`); name? (the feature's and the component's); up?: z|y (default z: the file's axes are kept; y: the file has Y up, so the import's component is turned a quarter turn about X, Y becoming Z → stand_up: its occurrence); names?: auto|solid|product|instance (default auto: keep meaningful solid names, replace placeholders with the nearest named product, else instance; solid: the solid's own name; product: the part's name; instance: the occurrence's name, else the product's); matrix?: [12 or 16 numbers] (where the new component's occurrence sits in the active component, after `up`: a rigid row-major 3x4 rotation-and-translation or 4x4 matrix, flat or as rows, as occurrence.move takes it; a scale, shear or mirror is refused before the file is read); tolerance? (see occurrence.move)"),
.params("path: .3mf, .stl, .obj, .ply (with faces), .glb or .gltf file (its meshes join the design as mesh bodies in a new component named after the file → component, component_name, occurrence; a glTF file's node tree becomes nested components held as one rigid group until component.unlock, a mesh placed again another occurrence, its materials the bodies' appearances, and its metres millimetres); name?; up?: z|y for a glTF file (default y: glTF has Y up, so the import's component is turned a quarter turn about X, Y becoming Z → stand_up: its occurrence; z: the file's axes are kept); units?: mm|cm|m|in|ft for an STL or OBJ file, or a PLY file with faces (default mm): the unit its coordinates are in, scaled to millimetres as the file is read (a 3MF or glTF file states its own). The design keeps the millimetre mesh and not the unit, so insert the file again to try another; origin?: bool (default false: the mesh keeps its file's coordinates; true: once read, scaled and stood up, the import's occurrence is moved so the box around its bodies is centred on X and Y of the active component with its lowest point on Z = 0 → moved_to_origin: [x, y, z], the shift made)"),
.contract(&INSERT_MESH_CONTRACT)
.output(output_schema_for::<MeshInserted>)
.params("path: .3mf, .stl, .obj, .ply (with faces), .glb or .gltf file (its meshes join the design as mesh bodies in a new component named after the file → component, component_name, occurrence; a glTF file's node tree becomes nested components held as one rigid group until component.unlock, a mesh placed again another occurrence, its materials the bodies' appearances, and its metres millimetres); name?; up?: z|y for a glTF file (default y: glTF has Y up, so the import's component is turned a quarter turn about X, Y becoming Z → stand_up: its occurrence; z: the file's axes are kept); units?: mm|cm|m|in|ft for an STL or OBJ file, or a PLY file with faces (default mm): the unit its coordinates are in, scaled to millimetres as the file is read (a 3MF or glTF file states its own). The design keeps the millimetre mesh and not the unit, so insert the file again to try another; origin?: bool (default false: the mesh keeps its file's coordinates; true: once read, scaled and stood up, the import's occurrence is moved so the box around its bodies is centred on X and Y of the active component with its lowest point on Z = 0 → moved_to_origin: [x, y, z], the shift made); crease_angle_degrees?: 0..180 (0 clears imported shading normals for the previous flat-shading behavior; positive angles regenerate angle-weighted normals across manifold edges below this crease angle; omitted keeps authored OBJ normals/smoothing groups, or flat shading when absent; geometry stays unchanged)"),
.params("path; format?: stl|stla|obj|step|3mf|3mf-tagged|urdf|json (default from extension; 3mf-tagged: 3MF whose triangles carry their B-rep face, at full precision, for meshing and analysis tools; docs/formats/3mf-face-tags.md; obj: the colours of the bodies go in a material library (`.mtl`) written next to the mesh file; urdf: the whole assembly as a robot, with STL meshes written next to it; docs/urdf.md); bodies?: [names] (JSON and URDF export the complete design); urdf only: mesh_prefix? (before each mesh file name, e.g. package://robot/meshes/), effort? (default 1000), velocity? (default 10); quality?: standard|fine|finest (STL, OBJ and 3MF: how closely the mesh follows curved faces, a chord tolerance of 2e-4, 5e-5 or 1e-5 of each body's size; default standard); objects?: body|component (3MF only: one object per body, the default, or one object for each placed component with two or more bodies, with those bodies as its parts, so a slicer loads them as one object with parts)"),
// One command per format, so each File › Export item can have a key of its own; started
// from a key or command search, the app opens the save picker (the mesh dialog for STL and
.params("path; mesh_prefix?; effort? (default 1000); velocity? (default 10) → {path, format, files, links, joints, warnings}: the whole assembly as a robot, with STL meshes written next to it; docs/urdf.md"),
CommandSpec::new("file.save_mesh","Save As Mesh",save_mesh)
CommandSpec::new("file.save_mesh","Save As Mesh",crate::checked!("file.save_mesh",save_mesh))
.params("path; format?: stl|3mf|3mf-tagged (default from the extension, else stl; 3mf-tagged: docs/formats/3mf-face-tags.md); bodies?: [names] (default: all); ascii?: bool (STL); slicer?: id or name from file.slicers, the program path it lists, or a program the user added under Custom… (send the saved file there, through the same steps as file.send_to_slicer: a slicer that has to be started first is waited for; may be deprecated later in favour of file.send_to_slicer or a Send to App workflow, kept for scripts); single_instance?: bool (default true; see file.send_to_slicer); quality?: standard|fine|finest (STL, OBJ and 3MF: how closely the mesh follows curved faces, a chord tolerance of 2e-4, 5e-5 or 1e-5 of each body's size; default standard); objects?: body|component (3MF only: one object per body, the default, or one object for each placed component with two or more bodies, with those bodies as its parts, so a slicer loads them as one object with parts)"),
CommandSpec::new("file.send_to_slicer","Send to Slicer",send_to_slicer)
.params("consent: bool (required: true only once the user has agreed to upload the model to Printables' servers; it stays there for 30 minutes and opening it needs a Printables sign-in); bodies?: [names] (default: the selected bodies); format?: 3mf|stl (default 3mf, in millimetres) → uploads the bodies to EasyPrint, Printables' slicer in the browser, and opens it in the browser. 100 MB at most. Works in the desktop app and the browser build; the CLI has no way to upload yet"),
.params("→ the slicers found on this machine: [{id, name, program, icon?}]; pass an id, a name or the program path as file.send_to_slicer's or file.save_mesh's `slicer`"),
.params("body?: mesh body name (default: the selected mesh body); tolerance?: length expression (default 0.05 mm): reconstruct a closed constant-section mechanical mesh as editable planar and cylindrical faces, with a parametric timeline feature in the mesh body's owning component (the active component must be that component or an ancestor; otherwise activate the named component first); the active component is unchanged; name?"),
.params("body?: mesh body name (default: the selected mesh body); tolerance?: length expression (default 0.05 mm): rebuild a closed mesh as an editable solid — a constant-section part as planar and cylindrical faces, any other closed mesh as the faceted solid its triangles describe — with a parametric timeline feature in the mesh body's owning component (the active component must be that component or an ancestor; otherwise activate the named component first); the active component is unchanged; name?"),
];
/// The mesh body to convert: `body`, else the one selected body.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.