Compare commits

...

216 Commits

Author SHA1 Message Date
github-actions[bot] ed7206cc0d Regenerate generated files and fold pending files [skip ci] 2026-11-05 11:14:17 +00:00
JIJINBEI 16f3b939b3 Update egui/eframe to 0.37 (#9933)
* Update egui/eframe to 0.37 (WIP)

egui, eframe and egui-wgpu 0.36 -> 0.37. SolidCraft's egui-wgpu patch is
rebased onto 0.37.0; the vendored accesskit_macos is dropped (0.27.2 has
the #7535 fix).

Not finished: 36 solidcraft-ui-egui lib tests fail, and the full lib run
crashes with SIGSEGV when tests run in parallel.

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

* Tests: clear texture deltas that egui 0.37 refuses to drop

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

* Drop non-finite pointer input before egui; keep test time monotonic

egui 0.37 debug-asserts on NaN wheel deltas, on time moving backwards in the
pointer history, and on add_space inside a Grid.

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

* Hold a click that follows typing in the same frame until the next frame

egui 0.37 drops a field's focus on a click elsewhere when the field first
reads its style, before it reads the frame's typing, so text typed in the
frame of a click was lost.

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

* Name a document tab's close button "Close tab"

egui 0.37 names an unnamed widget after its tooltip, so the tab's × became a
second control named Close beside a dialog's. The space mouse guard test now
clicks where each button was: by name it found nothing behind the guard.

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

* Turn on egui's default_fonts: 0.37 no longer does

The built-in Hack is the UI's symbol fallback (#2955). The app got it through
eframe, but solidcraft-ui-egui on its own (its tests) did not.

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

* Text menu: act on the selection the menu click took the focus from

egui 0.37 drops a field's focus on a click elsewhere before the field draws,
and an unfocused field loses its selection, so Copy, Cut and Paste from the
right-click menu saw no selection.

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

* Placement field: keep its native height under egui 0.37's interact floor

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

* Tests: pin the theme's 2/3 point radii; measure Japanese ribbon labels with a Japanese face

egui 0.37 rounds widgets 4 points by default, which the theme deliberately
does not follow. The ribbon test measured Japanese with missing glyphs, whose
width changed in 0.37; with BIZ UDPGothic, 駆動 wraps after its particle like
変換 does.

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

* Tests: click the render inspector's Perspective checkbox by position

egui 0.37 orders the accessibility tree differently, so the name found the
stage's Perspective segment first.

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

* Translate "Close tab" for the complete catalogs

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

* Point the focus workarounds at emilk/egui#8763

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

* Cargo.lock: update only what egui 0.37 needs

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

* Web: Id::new -> Id::unique for the wasm-only windows

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

* Command search: keep its hint centred under egui 0.37's interact floor

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

* egui 0.37 renames in code main added since the branch

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

* egui 0.37 renames in code main added since the last rebase

Id::new to Id::unique, ui.id() to ui.scope_id(), WidgetType to Role, the
wheel events' source, and the button stroke read from the widget visuals.

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

* Review: disclosures report expanded, late screenshots drained, chat width reset on both sides

A disclosure's node says whether it is open as expanded, as egui's own
collapsing header does. The screenshot channel is drained every frame, so a
shot whose request was cancelled is dropped. Resetting the chat panel's width
forgets both sides' state, as before 0.37, and the request stays with its
window when tabs switch. Tests main added since the last rebase clear their
texture deltas.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 10:31:50 +00:00
CaRLymx ad230eb4a4 Catalan (ca): retranslate the catalog from English (2026-11-05) (#10896)
* i18n: register the Catalan catalog

Add `ca` to the locale list, between Bosnian and Czech, and attribute
assets/locales/ca.json.

* Catalan (ca): translate preferences and the 61 new messages

- preferences: every prefs.* message is now Catalan (355 strings).
- the 61 messages added upstream since the last catch-up: the layout page,
  sketch options, navigation options and the custom navbar anchors.
- drop the four keys upstream no longer has: thread.direction,
  thread.left_hand, thread.right_hand and ui.print_face_one_body.

Work in progress: ui.*, cmd.*, render.*, the loose sentences and help.*
still mix Spanish and Catalan.

* Catalan (ca): translate the first half of ui.*

400 messages: the 3D mouse settings and calibration, the agent chat and the
AI preferences.

* Catalan (ca): translate ui.* up to the equation and edit-in-place messages

200 more messages: the toolbar and browser controls, colour vision, design
properties, the details panels and editing a linked part in place.

* Catalan (ca): translate ui.* up to the navigation bridge

200 more messages: the ribbon headings, the history panel, the graphics
presets, the physical material review and the mesh formats.

* Catalan (ca): finish ui.*, 1,284 messages

The ribbon headings, the properties, the slicer and point cloud panels, the
view angles and the workspace descriptions are now Catalan. ui.* has no
Spanish left.

* Catalan (ca): translate the command names, 508 messages

Every cmd.* entry, including the render library, the sheet metal tools, the
sketch tools and the named views.

* Catalan (ca): translate the CPU render settings and the environment panel

200 more messages: the CPU light rigs, the reflection targets, the resource
profiles, the preflight details and the stage and HDR environments.

* Catalan (ca): translate the render errors, the shot framing and the inspector

200 more messages: every render error, the frame presets and guides, the image
status, the HDR source details and the appearance inspector.

* Catalan (ca): translate the render library, the lights and the looks

200 more messages: the resource catalog, the physical light editor, the colour
looks and the GPU material and glass overrides.

* Catalan (ca): translate the render output, results and ribbon

200 more messages: the output editor and its size limits, the presentation
filters, the completed-image results, the aging and physical references and
the render ribbon.

* Catalan (ca): finish render.*, 2,392 messages

The saved shots, the stage panels, the studio lights, the subsurface and volume
transport, the wear layers and the render browser. render.* has no Spanish left.

* Catalan (ca): translate the standalone labels, 200 messages

The browser and panel labels, the sheet metal bends and reliefs, the boss, rib,
rest and snap fit drag hints and the issue report steps.

* Catalan (ca): translate the export, folder and material labels, 200 more

The export menu, the sheet metal bend table and reliefs, the font and history
options, the joint labels and the issue report help.

* Catalan (ca): translate the remaining panel labels, 200 more

The report and recovery messages, the profile and edge pickers, the rules
editor, the screenshot steps and the sketch selection prompts.

* Catalan (ca): translate the sheet metal, AI preferences and browser strings

200 more: the standing-up offer for Y-up files, the trim and stitch tools, the
AI availability and permission settings and the browser node names.

* Catalan (ca): translate the crash reports and a large part of help.*

200 more: the copies, the crash reporter, the DXF export progress, the file
loading statuses and the help texts for the sketch and solid tools.

* Catalan (ca): translate the home page, the hole presets and the material editor

200 more: the start page and its dates, the install steps, the issue report
notices, the assembly move handles, the material property card and the motion
keys.

* Catalan (ca): translate the navigation bar and the input presets

200 more: the navigation modes, the navbar customiser and its descriptions,
the network share warning, the fastener dimensions and the picking prompts.

* Catalan (ca): finish the catalog, 5,591 messages

The search box, the settings notes, the sheet metal personal rules, the sketch
report and text tools, the timeline menus, the updater and the web file
prompts. The catalog now has no Spanish left.

* Catalan (ca): translate the 634 messages the pending script had missed

The script only flagged strings identical to the Spanish or English catalog, so
every untouched string that differed from es.json stayed in Spanish: the whole
help.* reference, the import report, the joint limits, most preferences and the
fillet and continuity tips. They are now Catalan, and the script counts a
message as done only when a block file contains it.

* Catalan (ca): keep the sketch hints and stop splitting l·l

The sketch tool hints and engine errors are not in en.json, so they live in a
pending file: add the Catalan messages there instead of letting them fall back
to English. The new asset gets its attribution row under attribution/pending,
so this branch no longer edits ATTRIBUTION.md directly.

The painted-text sweep split labels on the bare middle dot, which is Catalan's
l·l inside a word: "Cancel·la" became "Cancel" and the test reported English
where the catalog is translated. Split only on the spaced middle dot.

* i18n: carry the complete catalogs in the pending sketch hints

A pending file that adds English messages also adds them for the catalogs
delivered complete (ar, he, ja, vi, zh), or i18n --check fails. They keep the
English text, as in the Spanish catch-up branch.

* i18n: attribute the pending hints and fix the break message

The pending sketch hints add an asset, so they get the same attribution row as
in the Spanish catch-up branch (same file, so the two merges without a
conflict). The engine has nothing to break, so say so in English.

* Catalan (ca): translate the 30 new messages

The blur preferences, the dimension step settings and the fillet turn modes
arrived upstream after this branch was opened; merge main and translate them.

* i18n: use US spelling in the pending hints and add en-GB

The English catalog is US-spelled, so the English fallbacks say center; en-GB
keeps centre for the four messages that differ.

* Catalan (ca): fold the pending hints and catch up with main

`main` folded `sketch-tool-hints.json` into every catalog when #10895 merged
and deleted the pending file. This branch still carried it, so the merged tree
failed `cargo xtask lists` with 186 duplicate ids and one repeated attribution
row. The 26 hints now live in `ca.json` and the two pending files go; the
catalog row in `attribution/pending/catalan-catalog.md` stays.

The 118 messages `en.json` gained since this branch opened are translated: the
26 hints and 92 new ones (AI HTTP and instructions preferences, surface-map
import, the ribbon-tabs-only view, hidden-edge selection, recovery, the custom
view navigator and a handful of dialog tips). The obsolete
`cmd.inspect_colored_components_disable` is gone, so `ca.json` matches
`en.json` key for key.

The shaded-with-hidden-edges label said `Sombreado`, which is Spanish.

---------

Co-authored-by: CaRLymx <24634598+carlymx@users.noreply.github.com>
2026-11-05 10:31:17 +00:00
Mario Geiger c1760d4857 Point clouds: draw a PLY file's edges and faces (#8817) (#8820)
* Point clouds: a PLY file's edges and faces as beads and triangles (#5151)

A PLY file's `edge` and `face` elements become segments and triangles
between the cloud's points (a face as a fan), each with its own properties;
a file with them is read whole, since thinning would cut them. A point's
numbers vary along the segments and across the triangles that meet there,
an element's are constant on it.

Segments with `width` and `height` are drawn as beads of that stadium
cross-section, meeting mitred where one runs into the next, from one
indexed template; without them, as lines. Points no segment or triangle
ends on stay dots. A range filter cuts an element where its value crosses
an end, on the GPU and in the tracks alike: each element weighs its
measure (a dot 1, a segment its length, a triangle its area, each kind
over its own total) and spreads it exactly over the values it covers, a
segment's evenly and a triangle's as the tent its corners make.

Columns saved without the element they are on are on the points, so
designs saved with point properties alone still open.

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

* Point clouds: past max_points a PLY is thinned again; the near plane cuts elements exactly

A PLY with edges or faces and more vertices than `max_points` was refused, where it used to be
thinned. It is thinned again: thinning would cut its edges and faces, so they are left out, with
a warning, and raising `max_points` keeps them.

A segment or triangle crossing the near plane moved its corners behind the eye to a made-up
place, which pulled a wedge across the view. A triangle now keeps its true clip-space corners,
which the rasteriser clips exactly. A bead is cut where its axis crosses the plane, a crossing
exact because clip-space coordinates are affine along a line, and its numbers are exact there.

A ramp accepts MAX_BINS bands but an element was split into 4097, so a ramp finer than that lost
measure: half of a segment at 8192. Only the whole numbers have more bands than MAX_BINS.

`xtask mesh-decisions` reads any field named `triangles` as a mesh; a cloud's own complex holds
none, so its three modules are skipped with that reason.

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

* Point clouds: a ramp's measure is whole past both its ends; a column on the points says nothing

A ramp of MAX_BINS bands has one band below it and one above it, so an element running past both
ends crosses MAX_BINS + 1 bands past its first. The limit was one short, and such an element lost
what lay above the ramp.

A column on the points now leaves `on` out when saved, so a design holding only points keeps the
form it had, as the field's comment and the pending format note already say.

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

* Point clouds: a set filter keeps part of a bead whose ends differ; an unreadable point takes only what ends on it

The set filter's whole-element test read the first corner alone, so a bead whose
first end was not kept vanished, while the range tracks counted the part kept. It
now drops a bead only when one value at every corner is not kept; otherwise the
fragments decide, as for triangles.

A PLY with edges or faces and one unreadable vertex was refused whole. It now
keeps the subcomplex the readable points span, renumbered, with its numbers, and
warns about what it left out. A file with numbers on its points gains the same.

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

* Point clouds: build the triangle corner weights without a dynamic vector write

The Windows D3D12 shader compiler rejected w[vi] = 1.0 (error X3500).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-05 10:23:20 +00:00
github-actions[bot] 4d69d8c87a Regenerate generated files and fold pending files [skip ci] 2026-11-05 10:35:00 +00:00
Frank fa2faf99e2 Dialogs: an opt-in setting to remember values and options for this app session (#11135)
* Dialogs: remember values and options for this app session (off by default)

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

* Dialogs: translate the remember-values help text again

The help text changed to say the setting is off by default, and the
complete catalogs (ar, he, ja, vi, zh) lost their translation of it.

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

* Dialogs: recall gives a new face a fresh thread suggestion and rebuilds Move/Hole mode inputs

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

* Dialogs: format recall

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 09:48:28 +00:00
github-actions[bot] 20467e39e2 Regenerate generated files and fold pending files [skip ci] 2026-11-05 09:56:27 +00:00
Christian Hammacher 020e3bebe3 Status line: say which design an Open or Insert brought in (#7858) (#7869)
* Status line: say which file an Open or Insert brought in, and whether it got a new tab

"Open done" and "Insert done" were English text built in code, so they stayed English in every
language and didn't say what was opened. They now read "Opened <file> in a new tab", "Opened
<file>" when a blank design was replaced, or "Inserted <file>", from the message catalogs.

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

* Status line: an opened design is named as its tab names it

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

* Status line: an Open's test and Korean strings

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

* Status line: an Open's message in the chosen language, and none for a design that found no tab

The message is built outside the window's language scope, so it takes the locale from the preference as the other statuses of the file loader do
An opened design is named as its tab shows it (a default name translated), an inserted one as its component is named
An open that finds no tab left keeps the error and answers it, instead of naming the design shown as opened
The test injects the finished open instead of running and polling a worker

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

* Status line: a design to recover that finds no tab stays offered

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

* Status line: a design to recover that hits the tab limit stays offered too

An Open or recovery with every tab taken was refused before the design
was read, and a design to recover was dropped from the list on that path.
It goes back in the list, as when its read fails. The staged-open test
reads the new status.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:18:10 +00:00
Sain2d 6029a38ea0 Korean: translate the remaining messages, so nothing shows in English (#6413)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 08:12:27 +00:00
NOWN-DEV 9c93a6c340 Sheet metal convert: a bend split by cut-outs across it is one bend (#7530) (#7581)
A bend that cut-outs across it split into several cylinder faces came out slit, silently: only
the picked piece was bent, the rest of the bend was cut away with the cut-out, and the volume came
out short. Picking the other pieces too was refused ("two faces of the sheet that meet along more
than one edge"). A picked bend cylinder now takes every coaxial piece of the same radius between
the same two faces, on both skins; in the flat, a face that meets its bend neighbour along several
collinear runs gets one bend edge, and the notches between them and the bend zone's pieces there
become holes of the sheet.

Test: a_bend_split_by_slots_across_it_converts_as_one_bend (slots cut through a tray's unfolded
bends, one piece picked or every piece): the volume, flat size, outline area, bends and the three
cut-outs are the source's.

Fixes #7530.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:37:58 +00:00
github-actions[bot] 9fdb7fdefc Regenerate generated files and fold pending files [skip ci] 2026-11-05 07:53:13 +00:00
lewis2e 718d5a4383 Intersection Curve: take two sketch curves, no helper surfaces (#11048) (#11049)
* claim: #11048

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

* Intersection Curve: take two sketch curves, no helper surfaces (#11048)

Intersection Curve now also takes curves of other sketches. Each curve
stands in for the surface you would get by extruding it along its own
sketch's normal, both ways and as far as needed, so two sketch curves
(or a curve and a face) give their 3D crossing without the two Surface >
Extrude features it used to take. The surface is never added to the
model. Sketches on parallel planes are refused with an error saying why.

The result stays a linked 3D wire that follows edits to either curve.
In the tool, a curve of another sketch fills a side like a face does;
after the first pick the status bar asks for the second, a right-click
starts the picks over, and the hint returns once the curve is made.

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

* Intersection Curve: keep other tools' right-click, update the tooltip, add a UI scenario (#11048)

The right-click change now applies to Intersection Curve only: Project to
Surface and Spun Profile behave as on main again.

The toolbar tooltip now says Intersection Curve also takes curves of other
sketches, in every catalog that has it, using each language's wording from
the Project to Surface tooltip.

New UI scenario intersection_curve_from_two_sketch_curves: a line on XY and
a line on YZ give one linked 3D wire along X at y = 0, z = 5; it also checks
the second-pick prompt and that a right-click starts the picks over.

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

* Intersection Curve: a picked spline wall meets whole, in one curve (#11048, #9009)

On the SMLib kernel a fit-point spline's wall is one face per span, and
Mesh::surface_patch keeps smooth-joined faces apart, so a face pick met
only the span under the pick and gave part of the crossing (#9009 again,
for faces as well as for sketch curves). A face pick now takes the faces
joined across seams and smooth joins, never across a crease.

The imprint also splits a crossing at every face boundary of either
side, so a crossing came as one wire per span. The pieces are now joined
end to end into one wire, keeping their exact curves; a piece that runs
the other way is reversed as a record curve.

A curve side's sweep is sized from the other body's exact bounds, not
its display mesh. The #9009 test's premise that the spans are one
surface_patch no longer holds and is removed; its checks are unchanged.

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

* Intersection Curve: a picked face joins only tangent spline faces, from exact geometry (#11048)

Which faces a face pick means is now decided on the exact surfaces, not the
display mesh: the picked face and the spline faces joined to it at least
tangentially (the kernel's seam check), so a spline wall's spans still meet
as one (#9009) while a plane, a round or any analytic face is that face
alone. The pieces of a crossing are joined within ten times the kernel's
intersection tolerance instead of a fixed 1e-4 mm.

Tests: a flat face beside a round and the round itself each give their own
crossing; two sketch curves give one curve at 1/100 and 100 times the size.

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

* Known-failing tests: the picked spline wall's intersection curve passes now (#11048, #9009)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 06:52:54 +00:00
github-actions[bot] 1c10f82b34 Regenerate generated files and fold pending files [skip ci] 2026-11-05 07:16:32 +00:00
Jackson Oursland ca4f3ed1a0 Book: getting started, sheet metal and import/export get pictures (#10732)
* Book: sketching step by step, with three clips and a numbered screenshot

The sketching page now opens with a first sketch in four steps: start a sketch on the Top
plane, draw a 60 x 40 rectangle and a 12 mm circle with typed values, dimension the circle
until the sketch is fully constrained, and finish. Each step has a short clip, and the last a
screenshot numbering the Sketch tools, a constraint, a dimension, the sketch's state and
Finish Sketch.

doc_shots.py gains what these needed: a setup that runs before recording, so a clip starts
where the last one ended; sketch framing; Enter sent as two input frames; and clips that keep
their pauses (hovering a plane).

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

* Book: the sketching page's general notes get a heading after Step by step

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

* Book: sketching adds Offset and Trim; the sketching figures again on 0.29.0

Two more clips after the sketching walkthrough: Offset draws the rectangle's outline 4 mm inside
it with one click (Chain), and Trim cuts a notch where a circle crosses the top side. All the
sketching figures are made again on SolidCraft 0.29.0, with the clips keeping a moment on each
result.

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

* Book: solid features step by step, with four clips

The solid features page now opens with the sketch from the sketching page made into an open
box in four steps, each with a short clip: extrude it 12 mm, shell it with the top open,
round its outer top edges, then make it 20 mm tall from the timeline while the shell and the
fillet rebuild. The panel table and what follows get a heading of their own.

doc_shots.py: the feature shots share their steps with the setups that lead up to the next
shot, and fail when a feature on the timeline carries a warning or an error, so no figure
shows one. Shell comes before Fillet for that reason: shelling through the rounds needed the
kernel's boolean retry. trim_idle.py takes --hold, the seconds of each idle stretch to keep
(default 0.1 as before); the doc clips keep 0.8 s, so a reader sees each result. The app gets
up to three minutes to answer on a busy machine.

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

* Book: features add Hole and Circular Pattern; the Parameters dialog, numbered

The features walkthrough gains two steps before the timeline edit: a 4 mm hole on the box's
floor, then Circular Pattern repeating it four times around the tube. The edit at the end now
rebuilds all of it. The parameters page gets a screenshot of the Parameters dialog numbering
apply as you go, user parameters, model parameters by feature, an expression and its value.
The features clips are made again on SolidCraft 0.29.0.

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

* Book: components and joints step by step, with three clips

The components and joints page now opens with a two-leaf hinge in three steps, each with a
short clip: the two bodies made into components from the Bodies folder's menu (the first one
grounded), a revolute joint on their barrel ends with Flip turning Leaf B the right way round,
and a drag that swings Leaf B about the hinge while grounded Leaf A stays put. What follows
gets a heading of its own.

doc_shots.py builds the leaves from commands, with Leaf B lifted and set back so the joint
visibly brings it into place.

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

* Book: assemblies add Drive Joints; the assembly clips again on 0.29.0

A fourth step drives the hinge with Drive Joints: the slider swings Leaf B while a ghost shows
where it started, and OK keeps the new angle. The assembly clips are made again on SolidCraft
0.29.0.

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

* Book: getting started, sheet metal and import/export get pictures

Getting started gains a section on the start page: a numbered screenshot (new design, open,
import, recent, samples, the File menu) and a clip opening a sample from it. Sheet metal opens
with a tray in three steps, each with a clip: a base flange from a sketched rectangle, edge
flanges from two top edges, and the flat pattern with its bends. Import and export opens with
the File menu, numbered.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 06:31:44 +00:00
SS-EN a1d66c238e MCP: batch runs the steps of a script file (path) (#5957) (#5959)
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 06:02:13 +00:00
thocsmith 70603de654 Emboss: an emboss or deboss that changes nothing says so (#10940) (#10944)
* claim: #10940

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

* Emboss: an emboss or deboss that changes nothing says so (#10940)

solid.emboss on a body its tool missed (a sketch on the far side of the
material, a depth that stops short) was recorded as a success with no
warning, so a sheet metal part looked embossed when it was not. The
evaluation now compares the bodies before and after and adds a warning
when none changed. A working emboss or deboss is silent.

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

* Emboss: the changed-nothing warning names what to check, not a cause it can't know (#10940)

CodeRabbit pointed out that an emboss can change nothing for other reasons
than missing the body (for example a profile that lies inside material that
is already there), so the warning no longer claims the profile misses the
body. It says the operation changed nothing and suggests checking where the
profile lies, which side of the face the sketch is on, and the depth.

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

* Emboss test: put the missing sketch below the sheet, since main's sheet metal emboss now cuts from its underside (#10940)

On current main a deboss from the XY plane reaches the sheet (it is changed and gets
the sheet metal flat-pattern warning), so the test no longer showed an emboss that
changes nothing. A sketch 5 mm below the sheet misses it on both bases.

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

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 05:59:00 +00:00
Bartosz Błachut db20c8be23 Timeline: deleting a sketch entity reuses the results it replaced (#3923) (#11283)
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 05:46:56 +00:00
github-actions[bot] 8f4fe740c7 Regenerate generated files and fold pending files [skip ci] 2026-11-05 06:00:07 +00:00
thocsmith aae4282f1e Polygon: By side shows the Length and Sides boxes (#11271) (#11287)
* claim: #11271

* Polygon By side: Length and Sides boxes, the typed length a side dimension (#11271)

The edge polygon tool showed no value boxes after its first click. It now
shows Length (the side from the first click) and Sides, as the inscribed
and circumscribed modes show Radius and Sides. A typed length places the
second corner and becomes a Length dimension on the first side:
sketch.draw.dimensioned takes a `length` role for sketch.polygon.edge,
an alternative to its radius (as_drawn still adds the radius when no
length is given; both together are refused).

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

* Polygon By side test: read the design's own state so it compiles against main (#11271)

Main's Session::world_state() now returns a Result, so the new test failed
Quick compile on the merge. The sketch is in no component, so the design's
own state shows the same sketch on both the old and new bases.

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

* Polygon By side test: take Vec2 from solidcore_record, where main moved it (#11271)

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

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 05:35:52 +00:00
Colin Faulkingham 18b698ffa8 Keep raised models’ ground shadows on the ground (#9838) (#10257)
* claim: #9838

* fix: anchor raised ground shadows to the origin plane (#9838)

* docs: attribute ground shadow preference messages
2026-11-05 05:14:38 +00:00
github-actions[bot] 051e6236c3 Regenerate generated files and fold pending files [skip ci] 2026-11-05 05:26:55 +00:00
selune 8691cefbbf Render: save wood images with physical grain controls (#1599)
* Render: save wood images with physical grain controls

* Render: prepare plain glass before marking the GPU host ready
2026-11-05 04:49:46 +00:00
NOWN-DEV 6d2465b52d Sketch: a fillet whose lines already leave it is not swapped against a mirrored reference (#10401)
The fillet keeping of #5677 swaps an arc's ends when its sweep jumps by more
than half a turn with both its lines turned. Judged against a reference that
shows the corner mirrored (positions before a move, or a mirrored start), a
fillet the solve left exactly where it was looks turned, and the swap made it
its long complement: a 45-degree fillet on a 135-degree corner became a
315-degree arc with both lines cutting back through it, while the solve
reported ok.

The swap is now decided by the lines: an arc whose tangent lines all leave it
after the solve already rounds its corner and keeps its ends. A swap still
gives a corner its arc back when its lines cut back through it. A second test
keeps a real fillet over three quarters of a turn whose corner mirrored as the
long arc its lines leave (a narrower fix, letting a swap only shorten an arc,
would break it).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 04:28:39 +00:00
semmelrinde c517ca0393 Lip and groove: the rim face follows a parameter change (#11209) (#11425)
A lip kept its rim face as a plain point, so it got no persistent face
name. When a parameter made the box taller, the point stayed below the new
rim and the lip failed ("the plastic feature's placement is no longer on
its body face"). The face is now a face pick, as a boss's is, and the lip
names its body like the other placed plastic features. Both are saved as
the same [x, y, z], so files don't change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 04:04:17 +00:00
github-actions[bot] ee1bdea97b Regenerate generated files and fold pending files [skip ci] 2026-11-05 04:28:35 +00:00
github-actions[bot] dc54d2f229 Regenerate generated files and fold pending files [skip ci] 2026-11-05 04:16:48 +00:00
raykholo b916f9bf33 Ribbon: an option to fold it to its tab row (#10112) (#10214)
* Ribbon: an option to fold it to its tab row (#10112)

Preferences > Layout > "Show only the ribbon's tabs" (off by default), the
Ribbon Tabs Only command (view.ribbon_tabs_only), and its settings-profile key.
Rebuilt on main's redesigned Layout page.

* Ribbon Tabs Only: switching tabs with a panel menu open no longer keeps the overlay open (#10112)

A tab click now forgets the open panel drop-down of the previous tab, so the pointer leaving or Escape still closes the overlay.

* Ribbon Tabs Only: count its settings-profile key in the profile membership test (#10112)
2026-11-05 03:42:58 +00:00
SS-EN 733f0b2254 MCP: opt-in compact inspect_design and tool output (split from #2901) (#3144)
* MCP: compact inspect_design and tool JSON, off by default (--compact / SOLIDCRAFT_MCP_COMPACT)

Split out of #2901 per review: the smaller inspect_design changed the default
for everyone, so it is now an MCP server option that is off unless asked for.

- solidcraft-cli mcp --compact, or SOLIDCRAFT_MCP_COMPACT=1 (true/yes/on;
  0/false/no/off/empty leave it off; anything else is an error at start-up).
  solidcraft_mcp::Options::resolve is the one place that decides, so an app
  Preferences setting can feed it later.
- With it on, inspect_design summarises sketches (id, name, plane, counts of
  points, curves, constraints and profiles, dof, status), returns full contents
  only for the sketches named in `sketches` (names/ids, true, false or
  "summary"), takes a `bodies` filter and measures only those; unknown names
  list the existing ones. Tool JSON is compact rather than pretty-printed.
- Off, tools/list, argument checks, inspect_design and tool JSON are exactly
  as before.
- document.inspect gains an opt-in `sketch_counts` (plane, points,
  constraints); without it its reply is unchanged.

Tests: both modes in the MCP suite, Options parsing, and the CLI flag and
variable end to end.

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

* MCP compact output: a Preferences setting too (AI › Agents through MCP)

The desktop app's Preferences › AI gets "Compact output" under a new "Agents
through MCP" section, off by default and shown with the chat off too. The app
reports it on the control channel as `mcp.options` ({"compact": bool}); the
agent chat lets its own MCP server read it.

A bridged MCP server (`solidcraft-cli mcp --connect`) asks the app when an
agent first lists or calls a tool and keeps the answer for the session, so the
tools it listed keep their shape. An app not running yet, or busy past 2 s, is
asked again on the next call; an app from before the setting counts as off.
`--compact` and SOLIDCRAFT_MCP_COMPACT=1 still turn it on whatever the app says.

Tests: the server follows the app's answer (waits for it, keeps it, the flag
still wins); the checkbox reaches `mcp.options` and Restore Defaults clears it;
the chat gate allows `mcp.options`. docs/mcp.md and docs/control-protocol.md.

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

* MCP compact output: review fixes (mcp.options never blocks, headless test, locales, prefs doc)

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

* MCP compact output: the setting is a section of the AI page; the bridged test answers mcp.options

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

* MCP compact output: the preference test sets it through ui.prefs; rustfmt

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

* MCP compact output: look for a newly started app before asking its options; counts from sketch.inspect for an older app

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 03:33:33 +00:00
Alexander Puzynia ee02080a25 Two-distance chamfers of free-form rims: a test on the SMLib kernel (#4332) (#4333)
* claim: #4332

* Two-distance and distance-angle chamfers of free-form rims: a test on the SMLib kernel (#4332)

A spline, Bézier or text outline's rim used to be refused by the two-distance and
distance-angle chamfers. The SMLib kernel (#11351) chamfers it along the rim; this test pins
that down on a four-Bézier oval, picked as its tangent chain the way a pick takes it: the
volume taken off is the analytic one for two distances (both ways round) and for a distance
and an angle.
2026-11-05 03:25:50 +00:00
Homero Thompson 99d96482af Sketch: groundwork for pierce constraints, no UI yet (#3599) (#11388)
* Sketch: the geometry of a pierce, a point where another sketch's curve crosses this plane (#3599)

* Sketch: the Pierce constraint, a point on a curve of another sketch (#3599)

* Engine: draw a Pierce constraint at the point it holds (#3599)

* Engine: the pierce tests wait for the command that is not written yet

* style: rustfmt, the imports of the pierce geometry in the order rustfmt wants

The rebase onto the current main resolved the rename of Plane, Vec2 and Vec3
from solidcraft_geom to solidcore_record and left the two use lines in the
order the resolution wrote them. rustfmt wants them the other way round, and
the fmt phase of the gate reads the whole tree.

No token of the file changed: the same 2983 tokens before and after, only the
order of the two use lines moved.

---------

Co-authored-by: bherbruck <bherbruck@gmail.com>
Co-authored-by: vdirienzo <vdirienzo@users.noreply.github.com>
2026-11-05 03:20:41 +00:00
github-actions[bot] a5be592b7d Regenerate generated files and fold pending files [skip ci] 2026-11-05 03:32:27 +00:00
aiapienthusiast d92923a964 Agent chat: a Cheaper Inference preset for OpenAI-compatible endpoints (#11174) 2026-11-05 02:58:56 +00:00
froglemonade2 71598c0354 Agent control: ui.menu refuses items the design doesn't have, reports the History bar's right-click menu, and gives Edit Joint Limits its own id (#10745) (#10746)
* claim: #10745

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

* Control channel: ui.menu refuses missing targets, reports the History item menu, and tells the joint menu items apart (#10745)

- ui.menu looks a target up and refuses one the design doesn't have ("no body `NoSuchBody`").
- The History bar's feature menu is the shared context menu (Target::Timeline), so ui.menu
  reports it, ui.menuPick runs its items and {"type":"timeline","feature":id} opens it.
  Its Show All toggle moves to TreeState so the menu can list Delete after the marker.
- Edit Joint Limits is ui.editJointLimits and opens Edit Joint with Limits expanded.

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

* Icon audit: open the section and canvas menus on a real section and canvas; a hidden section has a menu too

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

* ui.menu: check a group's or folder's scope and folder name; Delete after the marker gets its own id

A group or folder target is refused unless its scope is a row of its component (`c<id>` or a
placement of it) and the folder is bodies, sketches or construction. The History item menu's
delete of everything after the marker is ui.deleteAfterMarker, so ui.menuPick can tell it from
Delete (review on #10746).

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

* ui.menu: a component's own c<id> scope is the design root's only; test that Delete after the marker deletes

The browser draws a part's folders and groups under its placements (o:<path>), so a c<id> scope
for any component but the root names a row that isn't there (review on #10746). The History test
now picks ui.deleteAfterMarker and checks the features are gone.

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

* ui.menu: a placement scope must be spelt as the browser keys it

o:012 parses to placement 12 but isn't the row's key (o:12), so Show/Hide would store a key no
row reads (review on #10746).

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

* History item menu: no Delete after the marker when nothing shown is after it

A timeline target for a feature the bar's component filter leaves out listed no features, so the
item did nothing (review on #10746).

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

* ui.menu: a plane target must be construction geometry

The plane menu's Show/Hide keys the feature's name as an origin item, so any other feature got a
menu whose actions don't fit it (review on #10746).

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

* ui.menu: accept the view navigator's and selection filter's menus, which main added

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 02:35:55 +00:00
Homero Thompson 06a8a9b7d2 AGENTS.md and the contributing skill: say how to work as an agent (#8059)
* AGENTS.md and the contributing skill: say how to work as an agent

Two rules that the loop's own agents follow but the repository never
wrote down, so every agent that reads it had to rediscover them:

* The code, the comments, the commit messages and the docs are in English,
  always. A comment in another language is a defect: the people who
  maintain the code cannot review it. A user-visible string is not inline
  in the code either; it goes to the message catalogs, and the UI text
  skill says how to name and translate it.
* Parallel work runs in parallel, and dependent work stays sequential.
  One subagent per independent part, with a self-contained brief, and no
  two subagents writing the same file. A fix and the test that proves it,
  a refactor and the callers it breaks: those stay in order.

AGENTS.md is the router, so it gets the two rules in one line each, and
skills/solidcraft-contributing/SKILL.md gets the "Working as an agent"
section with the detail.

* AGENTS.md: fit the two agent rules under the router cap

`cargo xtask skills` reads the router as a router: one line per rule,
with the detail in the skill the line points at. The two new lines
broke that contract twice over. They carried their own detail (a
comment in another language is a defect; one subagent per part, no two
writing the same file) instead of pointing at the "Working as an
agent" section that already holds it, and the file grew to 5361
characters, over the 5000 a router may have.

So the two lines shrink to what a router line is, and five clauses
that repeated a rule the pointed-at skill already states in full give
up their place:

* "Act when the code, docs and rules settle a question" is in the
  contributing skill, in more detail.
* The unsafe allowlist is the crash-safety skill's own list.
* "No second PR for an issue a lower-numbered one closes" and "never
  force-push main, no pushes just to catch up with main" are the
  contributing skill's claim and push rules.
* "Do not switch branches while the gate runs" is in the contributing
  skill too.

No rule is lost: each one still has exactly one place that states it,
and that place is a skill. The router is 4970 characters.
2026-11-05 02:25:46 +00:00
Colin Faulkingham 4d2691e636 Assistant: opt-in HTTP authentication for private endpoints (#5341) (#10674)
* claim: #5341

* Allow per-endpoint HTTP authentication with explicit consent

* fix: discard model listings after HTTP consent revocation
2026-11-05 02:12:56 +00:00
Nathalie Schönwetter 6e17874cb9 A Move that copies can be patterned or mirrored (#5931) (#6018)
Co-authored-by: Nathalie Schönwetter <nathalie@cfw-ag.ch>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 02:04:01 +00:00
NAX LAB bf22dad8e8 Fix: flake build fails on git dependencies in Cargo.lock (#12146)
* Set `allowBuiltinFetchGit = true` in `cargoLock`:

* Remove comment
2026-11-05 01:26:31 +00:00
semmelrinde 731029cd87 Report a bug: put the whole report in the GitHub link whenever GitHub keeps it (#7103) (#7104)
Open GitHub issue form filled the issue through the link only up to
2,000 characters, with every space and line break encoded as three, so
most reports opened GitHub with the title alone and their text on the
clipboard, and many arrived empty (#10556, #10654).

The link now encodes only what a query value can't carry (a space is
`+`) and is limited by what GitHub keeps: 6,000 characters, and 7,000
once its sign-in page encodes it again. A report too long for that still
goes through the clipboard, whole.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:09:37 +00:00
Raymond Thurman 6b5c5dc6e8 Sketch on a thickened surface's face (#7641) (#7703)
* claim: #7641

* Sketch on a thickened surface follows the face (#7641)

Thicken dropped the kernel's face history, so Create Sketch refused
every face of the solid. Carry that history through, the same way
Shell and Trim do.

The regression covers the offset face and an end wall, and checks
that a boss on the offset face follows a thickness change and reopens.
2026-11-05 01:09:18 +00:00
github-actions[bot] e7ce6c282a Regenerate generated files and fold pending files [skip ci] 2026-11-05 02:47:03 +00:00
rugbymauri 67c58cbd28 Sheet metal rules: a design parameter named Thickness no longer overrides it (#9623) (#9633)
* claim: #9623

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

* Sheet metal and plastic rules: Thickness in a rule's expressions is the rule's thickness

A rule's expressions read `Thickness` as the rule's thickness only while the
design had no parameter of that name; with one, the default gap, reliefs and
a `Thickness` bend radius took the parameter instead (a 2 mm rule beside
Thickness = 10 mm closed corners to a 10 mm gap). Now `Thickness` is always
the rule's, as documented; the rule's own thickness still reads the design's
parameters, so `thickness: Thickness` keeps naming the design parameter.

Renaming a parameter called Thickness, its users and what it drives follow
the same reading: only a rule's thickness field refers to it. Renaming a
parameter to Thickness is refused while another rule field uses it, since
that field would then read the rule's thickness instead.

Fixes #9623

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-11-05 01:07:30 +00:00
austin62jake 7c89f7445f Assembly: moving a component without capturing its position marks the design unsaved (#9736) (#9851)
* Claim #9736

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

* Assembly: a move not captured yet is an unsaved change (#9736)

A component moved without capturing its position left the design reading
as saved: file.save wrote the file without the move and said nothing, and
closing the tab didn't ask. Now:

- Session::is_dirty is true while a move is pending, so closing a tab or
  quitting asks "save changes?".
- file.save and file.save_as refuse while a move is pending, naming the
  component and the capture and revert commands.
- In the app, Save As (and Save As JSON, Save a copy, Download design)
  asks the Capture Position question first, as Save already did; a tab
  closing after "Save" waits for that answer, and Cancel keeps it open.

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

* Close: the save-changes card waits behind the Capture Position question (#9736)

Saving a tab that closes asks the Capture Position question first; the
"save changes?" card stayed drawn over it and took a second answer. It
now hides and ignores answers until the question is answered. The design
container doc says a save stops for a move not captured.

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

* Assembly: an exploded view shown is not a move to save (#9736)

explode.show displays a view through the pending moves, as an uncaptured
move does. Those placements last only while the view is shown, so they
don't make the design unsaved and don't stop a save
(Session::moved_uncaptured). A move on top of a shown view still does.

The relink test saved with a move pending; it captures it first now.

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

* Assembly: the pending-move save test reads the world state as main now gives it

world_state() returns a Result since the kernel merge; the test unwraps it.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-05 01:06:46 +00:00
ShawnMarx 26adcfa997 Docs: add BoxInsertDesigner to the showcase (#10095)
* Docs: add BoxInsertDesigner to the showcase

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

* Docs: attribution rows for the BoxInsertDesigner showcase recording

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:06:36 +00:00
github-actions[bot] 1e41dddae2 Regenerate generated files and fold pending files [skip ci] 2026-11-05 02:11:35 +00:00
PapaEcho a9b219bc9f joint: let the origin be the design origin or a sketch point (#10290) (#10584)
The joint origin input only accepted faces, edges and vertices, so the world origin and sketch points could not be picked to place a joint. Extend the origin input's snap mask with ORIGIN and POINTS and resolve a sketch point (and the empty-body world-origin vertex) to a world snap in snap_of, so a joint can be placed on the design origin or a hole centre like Fusion.
2026-11-05 01:06:11 +00:00
semmelrinde 32dc8750df Sketch Project: faces and edges of a part's second copy now project (#9957) (#10721)
* Sketch Project: a face or edge of a part's second placement projects where that placement is (#9957)

Projecting the face of `Block (Part:2)` failed with "face of Block: the
face no longer exists". The pick's body name was turned into the
definition's own name, and the model a sketch reads holds only each
component's first placement, so the face was looked for on the first
placement, somewhere else.

Sketch picks now keep the name the browser shows, and the further
placements a sketch's links name are placed into the model it reads
(`with_placed_copies`), when the sketch is evaluated and when a link is
added or refreshed. Only the named ones are placed. A link to a body
the model lacks also makes the sketch follow every placement, so moving
the copy moves what was projected from it.

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

* Sketch Project: a projection from a second placement follows renames of its part and body (#9957)

The link keeps the copy by the name the browser shows (`Block (Part:2)`),
which a rename of the component or of the body changes. component.rename
and body.rename now rename it in the sketches' links as well.

The copies a sketch reads are found through their component's bodies,
not by going through every body for every placement.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:05:40 +00:00
semmelrinde ceeff58a7f Scale: X, Y and Z factors from the dialog (#10782) (#10983)
The Scale dialog gets a Type row, Uniform or Per axis. Per axis shows
X, Y and Z rows and sends factors, which solid.scale already takes. A
Scale made with factors opens per axis with its three values; before,
they rode along as an extra and the dialog's factor did nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:05:10 +00:00
github-actions[bot] a55286953a Regenerate generated files and fold pending files [skip ci] 2026-11-05 01:37:26 +00:00
github-actions[bot] 9a0846ef68 Regenerate generated files and fold pending files [skip ci] 2026-11-05 01:28:10 +00:00
raykholo 06749ac464 Add the Fair Surface and Match Surface dialogs (#4644) (#4653)
* claim: #4644

* Add the Fair Surface and Match Surface dialogs (#4644)

Neither command had a dialog, so the toolbar and command search ran them
with no parameters and they failed with "`body` is required".

Fair Surface picks the surface or some of its faces and sets the
strength, the iterations, whether the edges are kept and an optional
tolerance (blank: 0.001 of the body's size). Match Surface picks the
surface or face to rebuild, the target and an edge chain of the target,
and the continuity (G1, G2 or G3, default G2). Both preview, and Edit
Feature restores the saved picks and settings. New labels go through
the catalogs with English, Japanese, Chinese, German and Korean text.

* Keep surface-dialog strings pending with complete translations (#4644)

* Fair tolerance is a length; Fair and Match picks reopen on their saved bodies

The Fair dialog's tolerance now goes through the same design-unit length
conversion as the other surface lengths, on OK and when an edit is refilled.
Editing a Fair or Match feature looks its face and edge picks up on the body
they were saved on, so a coincident face or edge of another body cannot take
their place.

* Surface dialogs: strings stay in the pending catalog only (#4644)

* Surface dialogs: edge picks use the session world, as main's edge_sel now does (#4644)

* Surface dialogs: refuse to refill when a saved pick no longer resolves, and pass body names by reference (#4644)

* Surface dialogs: Fair and Match now open dialogs, so the refused-command tab test no longer lists them (#4644)
2026-11-05 00:05:53 +00:00
semmelrinde a6e7126bf1 Closed stderr pipes no longer crash the app, CLI or MCP server (#10111) (#10126)
* Stderr: a closed stderr no longer stops the app, the CLI or the MCP server (#10111)

`eprintln!` panics with "failed printing to stderr" when stderr is a
pipe its reader has closed. A process another program starts writes
into such a pipe: `solidcraft mcp` under an agent, and the app that
one starts, which inherits it. #10111 is the app stopping that way
while an agent (Codex) connected. `solidcraft_geom::note!` (re-exported
as `solidcraft_engine::note!`) writes the same line and ignores a
failed write, as the CLI's logger already does; the app, the CLI, the
MCP server, the crash hook and the crates they run use it instead of
`eprintln!` outside tests.

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

* Stderr: test that a closed stderr leaves the CLI's exit code as it is (#10111)

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

* Stderr: the old name's alias and the fuzz command don't stop on a closed stderr either (#10111)

`solvecraft-cli` writes its deprecation notice before it starts
`solidcraft-cli`, so an agent still configured with the old name got the
panic before the MCP server started (found in review). It and the fuzz
command now write through `note!` too, and the test covers the alias.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:29:07 +00:00
Christian Hammacher 4a906699ee Browser: the component menu keeps its everyday actions and groups the rest in flyouts (#8769) (#8770)
* Browser: the component menu keeps its everyday actions and groups the rest in flyouts

A component's or placement's right-click menu had grown to about thirty entries in seven
blocks. Activate, Edit in Place, Open Linked Design, Move/Copy, Show/Hide, Isolate, the
clipboard, Delete, Rename, Appearance, Physical Material and Properties stay on the menu;
Ground, Capture and Revert Position, Unlock Contents and Turn Up Axis move to a Position
flyout, Relink Source or Extract to File and the exports to File, New Component, New Group
and Paste New to New, and Show All Bodies, Cycle Component Color, Expand All and Collapse
All to Display. context_menu::entries lists a menu with its flyouts, as ui.menuPick reads it.

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

* Browser: the component menu nests its flyouts by topic, and flyouts no longer scroll

Position (Ground, Capture and Revert Position, Unlock Contents, Turn Up Axis), Component
(New Component, Rename, Appearance, Physical Material, Cycle Component Color, Relink Source
or Extract to File, Properties; Design on the design's row), Export (Export, Save As Mesh,
Send to Slicer) and Browser (New Group, Expand All, Collapse All) each open a flyout; Paste
New stays with the clipboard and Show All Bodies with Show/Hide. Flyouts open inside flyouts,
so Appearance keeps its quick choices inside Component; the keys walk in and out of them.

A flyout no longer scrolls or sits away from its row: every flyout has its own area, measured
whole, where a short one used to squeeze every later one into its height. A component's only
body is no longer offered as a component of its own on a folded Bodies folder's entries.

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

* Browser: the component menu starts with what a double-click does, and keeps the old order of its sections

A linked component's menu starts with Edit in Place and Open Linked Design when a double-click
edits it in place (double_click_components), and with Activate otherwise. Position, Component
and Export follow Move/Copy, then the clipboard, then Show/Hide and Isolate, with Browser last,
as the sections came before the flyouts.

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

* Browser: Show All Bodies is on a component's menu only while a body is hidden

It is listed while a body is hidden itself, in a hidden placement, or in a hidden Bodies folder or
group of bodies, anywhere in the design, since that is what it shows.

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

* Browser: Relink Source and Extract to File sit right below Rename in the Component flyout

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

* Browser: Placement and Organize flyouts, Turn Up Axis back as a flyout, and flyout fixes

Placement (Ground, Turn Up Axis, Capture and Revert Position, Unlock Contents) follows
Move/Copy above the first separator, Turn Up Axis opens its two turns in a flyout again, and
the Browser flyout is called Organize. The Component flyout has three blocks: New Component
with Relink Source or Extract to File, then Appearance, Physical Material and Cycle Component
Color, then Rename and Properties.

A flyout of a flyout that opened to the left opens to the left too instead of covering the
menu, Left back to the menu chooses the row that opened the flyout, ui.menuPick refuses a row
that only opens a flyout, and the icon audit checks every level. The guides and the control
protocol describe the new menu paths.

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

* Browser: ui.menu gives a flyout's rows their placeholder flag too

ui.menu set placeholder only on the top-level rows, so a client reading
a flyout row's placeholder got nothing. The icon audit now reads the flag
of the flyout rows instead of resolving their icons itself.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:05:26 +00:00
github-actions[bot] a2a7102859 Regenerate generated files and fold pending files [skip ci] 2026-11-05 00:53:06 +00:00
cronos a137f2d459 Sketch Trim: keep the driving distance when trimming a line offset first (#11442) 2026-11-04 22:23:12 +00:00
thocsmith f87dd73661 Selection filter: from command search it offers Faces, Bodies, Components (#11505) (#11553)
* claim: #11505

* Selection filter: started from command search, it offers Faces, Bodies, Components (#11505)

Command search ran select.filter with no parameters, which could only fail
with "missing field `mode`". Started from the UI (command search, a
shortcut, ui.start), it now opens its three modes as a menu with the
current one marked, as View > Visual style and Navigation tool do (#8868).
Programmatic calls keep their contract and open nothing.

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

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:23:02 +00:00
PaddleStroke af5644aee6 Ribbon: switch panel menus on hover after the first click (#11447) (#11713)
* claim: #11447

* Add an option to switch ribbon panel menus on hover

* Update profile membership count for ribbon hover setting
2026-11-04 22:22:38 +00:00
SixSolid abeb232f17 Sketch Mirror: use an origin axis that is not in the sketch plane (#8201) (#12091)
* claim: #8201

* Sketch Mirror: use an origin axis that is not in the sketch plane (#8201)

A sketch on a part face does not hold the world X, Y or Z axis, so Mirror refused the pick. It now mirrors about the axis's projection into the sketch plane and keeps that line linked as fixed reference geometry. An axis perpendicular to the plane stays refused because it has no line in the plane. The sketch's own X and Y axes keep working as before.
2026-11-04 22:22:10 +00:00
raykholo 1a27c8399d Linux 3D mouse: push/lift and tilt/twist axes are no longer swapped (#3523) (#4833)
* claim: #3523

* Linux 3D mouse: decode spacenavd's axes in its documented frame (#3523)

spacenavd reports x to the right, y up and z away from the user, with the
rotations about those axes. The decoder treated y as away and z as up, so
with a stock spacenavd push and lift traded places, as did sideways tilt
and twist. Map the daemon's axes to the raw HID frame as translation
[x, -z, -y] and rotation [rx, -rz, -ry].

The decode still runs before a device's calibration and the per-axis
motion choice, so both apply unchanged on top of the corrected reading.
The tests now move each daemon axis on its own, in both directions, so a
swap of two axes can no longer pass.
2026-11-04 22:13:57 +00:00
Jackson Oursland 5ab59e7c1b Assistant: DeepSeek and Qwen endpoint presets (#5016) (#5063)
Adds DeepSeek and Qwen presets to both endpoint protocols: OpenAI-compatible (api.deepseek.com, dashscope-intl compatible-mode/v1) and Anthropic-compatible (api.deepseek.com/anthropic, dashscope-intl apps/anthropic).

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-11-04 22:13:47 +00:00
SiroArt 9a274f91df Planned bodies: smaller GPU chunks, and a chunk keeps its colours while they are unchanged (#5490)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 22:13:38 +00:00
PaddleStroke 08fa042039 Dialogs: drag a value's label sideways to change it (#2217) (#9685)
* claim: #2217

* Dialogs: drag a value's label sideways to change it (#2217)

A value row's label (expr_row, hint_row) now scrubs a literal value: about a
hundredth of its size per point of drag, on that step's grid, Shift ten times
finer, Ctrl/Cmd ten times coarser. The unit and decimal mark stay as typed, a
size never turns negative, and expressions and parameters are left alone. The
text changes as if typed, so previews follow and OK still makes one command.

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

* Label drag: measure from the press point, cap values, keep scrub kit-private

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:10:04 +00:00
P'tite Tête 09bb8aaaf5 Revolve: an axis origin written as an expression is no longer read as zero (#10252)
* fix: Revolve axis origin takes expressions instead of silently zero

- AxisRef::Line origin is a PointExpr evaluated with parameter values in the design unit (format note in pending)
- An unreadable origin or dir is a BadParams error instead of (0, 0, 0)
- Edit Feature keeps the saved expressions of an untouched origin or dir, for solid and surface revolves, in moved components too
- Face role names stay stable with a parameter-driven axis

* chore: Re-run PR checks after the description fix

* fix: Edit Feature of a revolve maps nothing where nothing is placed

- unplaced designs skip world_state and the pick table, as before the merge
- the untouched-edit test checks that OK applied the edit (CodeRabbit)

---------

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-11-04 22:09:27 +00:00
Mateusz Sych 46edd8d749 Agent chat: Custom instructions in Preferences, added to the system note (#8220) (#11105)
* claim: #8220

* Agent chat: Custom instructions in Preferences, added to the system note (#8220)

Preferences > AI gets a multi-line Custom instructions field, saved with
the other AI preferences (agent_instructions, kept by page resets like
other user-written text). The chat adds the text after its own system
note when a session starts, by the routes #9811 set up: ACP session/new
(the Claude adapter's _meta key, the standard systemPrompt field, the
Antigravity key) and the system message or field of every API protocol.
Connect and New chat set it, so an edit applies from the next chat and
never changes the one running. Blank text adds nothing; at most 4000
characters are kept, in the field, over ui.agent_chat and from a hand-
edited settings file. ui.agent_chat takes and reports `instructions`.

* Custom instructions: typing in the field stays in the field

The text area did not report itself to Preferences search as a key sink, so with
the field focused, search took the typed characters and moved focus away. The
field now notes the sink while it has focus, like the other preference fields,
and the #8220 test types into it.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-04 22:07:30 +00:00
semmelrinde c5d3dabd1b Split Body: the tool face follows a parameter change (#11674) (#11699)
The tool face of Split Body is now a face pick: it is named when it is
picked and found again by that name, as other face picks are, so a split
by a rail's face follows the rail when a parameter moves it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:06:13 +00:00
Ben Rog-Wilhelm 92171bd79b Hole: a hole on a face that another part touches drills its own part, not the other one (#12066) (#12067)
* claim: #12066

* Hole: a hole on a face that another part touches drills its own part (#12066)

A hole placed by position with no direction drills against the outward
normal of the face at the point. That face was looked for on every body,
so where another part touched the face at the hole, the part made first
won. The hole then drilled away from the part `targets` named and cut only
the tool's run-in pocket into it, without an error.

A hole that names its bodies now goes by their faces wherever one of them
passes through its position, when it is made (hole_direction_at) and when
a design is evaluated (hole_dir_into_face). Saved holes whose direction
leaves their own body's face there are mirrored into it, as format 18 does
for a thin plate's far side; the pending format note records it. A hole
whose bodies have no face at its position (placed on another part's face
to drill the part behind it) goes by every body's faces, as before.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:05:30 +00:00
CamWight 2ce55baba1 Dimensions: a radius leader and an aligned dimension's line reach their text (#12093)
The value text is drawn level, in an upright box, and the line runs
through its middle. The layout stopped the line where the box's farthest
corner reaches along it (half.x*|u.x| + half.y*|u.y|), not where the line
leaves the box (min(half.x/|u.x|, half.y/|u.y|)). They agree only when the
line is level or upright: at 45 degrees a radius leader stopped about
12 px short of its text, and an aligned dimension's line broke too wide
around its value.

One helper, box_exit, gives where the line leaves the box, for the radial
leader, the linear break and the leader to a text pulled past the
extension lines. Whether the text fits between the arrows still goes by
the whole box. A text box that isn't finite counts as empty, and one
larger than a sketch can be is held to MAX_COORD.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:05:13 +00:00
github-actions[bot] 6870f9539c Regenerate generated files and fold pending files [skip ci] 2026-11-04 23:51:34 +00:00
rugbymauri 2998d412b6 Primitives: clicking a pick field no longer jumps to the Diameter box (#10428) (#10536)
* claim: #10428

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

* Primitives: a pick field waits for its pick and says what it is for

Clicking Face, Reference 1 or 2 in the Cylinder window, or Point in the
Box, Sphere and Torus windows, and then clicking something the field
does not take (empty space, the cylinder's own preview) gave the
keyboard to Diameter, so the field no longer looked active and nothing
said why the click picked nothing. A pick field clicked into now keeps
the keyboard until a click picks something, and a click it does not
take leaves a note under the form saying what the field is for.

These fields also get their own tooltips: Face is the face or vertex
the cylinder stands on, Reference 1 and 2 are straight edges of that
face the distances below are measured from, and Point is a vertex,
edge, face, sketch point or the origin whose point fills X, Y and Z.

Fixes #10428

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-11-04 22:04:36 +00:00
Daniel 2396b6be05 Hole: the drag handle looks like the Rest, Boss and Snap Fit handles (#6818) (#6819)
* claim: #6818

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

* Hole: the handle drawn with the shared marker (#6818)

A Single hole's drag handle (Move holes by clicking or dragging) is drawn with the placement
marker from place_gizmo/look.rs instead of its own ring and cross, so a hole and a Rest on the
same model show one language for the same action. place_gizmo::marker is the one crate-visible
door: it runs the marker's hover and drag motions under the handle's id and draws the crosshair,
never dimmed. Hole keeps its snapping, the click that takes a hole out, its handle names and its
hover text.

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

* Hole: hole_handle removed (#6818)

The shared marker draws the handle now; nothing else used it.

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

* Hole: the marker takes no colours it does not use (#6818)

place_gizmo::marker took a &Tokens only to hand it to crosshair, which reads its colours itself,
so the hole's show fetched tokens nothing read. The argument and that fetch go.

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

* Hole: a test sees the handle drawn as the shared marker (#6818)

The hole tests found the handle by its published position, which the look does not change. This
one reads what the frame paints round it: the white dot and four dashed arms at rest, no ring
round the centre, arrowheads when pointed at, solid shafts while it drags. It fails with the old
ring and cross back on the call line.

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

* Place handles: marker hover eases in 120 ms, drag in 220 ms (Refs #6818)

The marker answered the pointer in 220 ms and a drag in 360 ms, which felt slow on review. Both
stay in look.rs as the one place a later preference can read them.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 21:55:30 +00:00
austin62jake 3c1074cfc6 Timeline: moving a construction axis now updates the features that use it (#9544) (#9545)
* Evaluation: a construction axis moved past a feature makes it evaluate again (#9544)

The state digest in a feature's cache key left out the construction
geometry before it, and the context digest hashes construction features
without their order. A feature started from the same key with or without
the axis before it, so after a move it kept its old result, and a cached
state restored whole brought back the construction list it was made with.

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

* Evaluation: say plainly what an empty construction list hashes (#9544)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 21:55:19 +00:00
PaddleStroke c4a63a1d43 GPU path tracer: don't rerun a setup that crashed in the driver (#9236) (#9666)
* claim: #9236

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

* GPU path tracer: don't rerun a setup that crashed in the driver (#9236)

A driver that takes the process down while it compiles the path tracer
(RADV on an RX 7600) is beyond error scopes and unwind guards. A running
setup now keeps gpu-path-setup.json (its adapter key) in the settings
folder, removed when the setup answers or the app closes normally. A
launch that finds it for the same adapter leaves the setup failed with an
explanation and a Try again button; CPU ray tracing stays available, and
a driver update changes the key and so tries again.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:54:45 +00:00
dudedagger42 2fdcc5ae49 Appearance: a picture of your own behind the model (#11353)
* Appearance: a picture of your own behind the model

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

* Background picture: a label that fits its row

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:53:10 +00:00
austin62jake 4678a04827 Agents and scripts: the contact commands describe their exact parameters and reply (#11949) (#11950)
* claim: #11949

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

* Contact sets: input and output schemas for the seven contact commands (#11949)

describe_command now gives the exact parameters and reply of
contact.enable_sets, enable_all, disable_all, create, edit, list and
check, and the warn and strict parameter checks use them. The handlers
read their parameters from the raw JSON, so the contracts in
schema/motion.rs read each key as its handler does, with its message:
`occurrences` (OccurrenceKeys) and `set` are required in the schemas
but left to the handlers' messages in the types, through a small
`require` transform. The replies are built from typed values instead of
json! literals, with the same JSON; the listed sets mirror the doc
crate's ContactSet.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 21:52:53 +00:00
kaltenbrunnermarkus 63ffadfebf Import: a curve that refers to itself is refused instead of overflowing the stack (#11893)
A 2D TRIMMED_CURVE in a pcurve read its basis straight through read_curve,
past the depth check that only the cached entry points (curve_in, curve_2d,
surface_in) made. A trimmed curve whose basis is itself, or a loop of two,
recursed until the stack overflowed and the app closed.

The depth check now sits in read_curve and read_surface, which every curve
and surface read passes through, so any chain of references, a cycle
included, ends at MAX_GEOM_DEPTH with an error that names the entity:
"curve #9: definitions nested more than 12 deep, or referring to
themselves". The other nested entities (surface, seam, intersection and
bounded surface curves, pcurves, 3D offset curves, offset, trimmed,
revolution and extrusion surfaces) already went through the checked entry
points; they now share the one check.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:45:25 +00:00
kaltenbrunnermarkus aee947f0f9 Sketch: hovering over a large sketch no longer slows down (#9614)
* claim: Sketch: keep profile outlines and trim/extend cuts between mouse moves

* Sketch: keep profile outlines and trim/extend cuts between mouse moves

Hovering a large sketch sampled every profile's loops into polylines for
each point asked, and Trim/Extend intersected the hovered curve with every
other curve on every mouse move.

- Loop2::contains rejects points clearly outside a box from the loop's end
  and control points before sampling; Region2::outline samples the loops
  once (RegionOutline). Profile keeps both in OnceLocks and answers
  Profile::contains from them (viewport pick, profile_at_point, profile
  selection by points). Answers are unchanged: the box margin covers
  rounding, and non-finite values or negative conic weights skip the box.
- sketch_modify splits the cuts on a curve (TrimHover) from choosing the
  piece around the cursor. The UI keeps one per sketch state and curve,
  and Extend's preview per sketch state, curve and growing end (afresh
  when the sketch has an infinite line).

Plate with 400 circles (801 profiles), release, per mouse move: UI frame
5.19 -> 1.11 ms, with Trim 9.45 -> 1.50 ms; 30 lines x 30 splines: Trim
piece 6.10 -> 0.0002 ms. Equivalence tests compare with the old answers.

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

* Sketch: keep the profile outline helpers in the sketch crate

The outer loop's box, the clearly-outside test and the outline sampled
once move from geom into crates/sketch/src/profiles.rs as private items
built from geom's public types, so the PR leaves crates/geom untouched.
Profile::contains answers exactly as before; the geom test moves to the
sketch profile tests.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:42:06 +00:00
Daniel ad56dd6a93 Box window: a picked point fills Corner and Center in the design's unit (#12051) (#12052)
* Box window: a picked point fills Corner and Center in the design's unit

A vertex, edge or face picked into the Box window's Point input wrote its X, Y and Z as millimetre text whatever the design's length unit was, so an inch design showed "50.8 mm" where the other fields read inches. The fields now take the point through length_input::model, as Move and the Hole references do, so they read "2 in" there and stay "50.8 mm" in a millimetre design. The same applies to the Sphere and Torus centre.

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

* Box window: a picked point reads to a millionth of the design's unit

length_input::model divides without rounding, so a vertex at 38.1 mm read "1.5000000000000002 in" in an inch design. length_input::shown rounds to a millionth of the design's unit, as the millimetre text did before, and the test pins that case.

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

* Box window: a picked point keeps the micrometre in every design unit

length_input::shown rounds to a millionth of the design's unit, and the picked point's text is what OK sends, so in a metre design a vertex could move by up to half a millimetre. Document::design_length keeps the micrometre in every unit and still writes 38.1 mm as "1.5 in". The test now covers a metre design with a point that is not round in mm, and the Sphere and Torus centre.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:40:17 +00:00
ABDULLAH cafe015ef9 Arabic: sketch tool hints and errors, view-mode name, face term and Fillet name in the hidden-edge selection preference (#12080)
* Arabic: view-mode name, face term and Fillet command name in the hidden-edge selection preference

* Arabic: translate the sketch tool hints and errors, clearer Fillet phrase in the hidden-edge selection help
2026-11-04 21:40:08 +00:00
Daniel 155387bac1 Sketch Trim: a cut on a shape's centre ends the piece there, with no second point (#12101) (#12103)
* Sketch Trim: a cut on a shape's centre ends the piece there, with no second point

Trimming half of a center rectangle's diagonal left two points at the centre. Trim dropped the diagonal's Midpoint constraint and ended the kept half on a new point held only on the other diagonal, so the old centre was free and the new end slid along the other diagonal. Dragging a corner pulled them apart.

A cut that lands on a point held at the middle of the trimmed line or of the cutter now ends the kept piece on that point. When the trimmed line held it, the Midpoint moves to the cutter if the point is the cutter's middle too (a rectangle's diagonals bisect each other), and otherwise the point is held on the cutter as a new end would be.

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

* Sketch Trim: weld the cut end before a parallel distance moves onto it

A distance between parallel lines that measured from the trimmed line's end moves to the kept piece's start. When that start is a cut welded to a centre point, the move named the new point, which the trim then removed along with the dimension. The weld now happens first.

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

* Sketch Trim: test that a parallel distance moves onto the welded centre

Trimming the start half of a center rectangle's diagonal moves a distance to a parallel line onto the kept half's start, which is the centre. The test fails if the weld runs after that move: the dimension then names the cut point and is removed with it.

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

* Sketch Trim: the centre comment names the case that reaches it

Only a line carries a Midpoint, so the comment's ellipse case could not reach this code.

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

* Sketch Trim: a crossing line whose middle is only near the cut gets no midpoint hold

Trimming a line at its held midpoint moved that hold onto the cutter whenever the cutter's middle sat at the cut, so a crossing line that merely happened to have its middle there was tied to the point. The hold now moves only when the sketch makes the two lines diagonals of a parallelogram (each pair of opposite sides held parallel, horizontal or vertical), as a center rectangle's are; otherwise the point stays on the cutter.

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

* Sketch Trim: ask the solver whether a cutter's middle is held at a cut midpoint

Moving a cut midpoint's hold onto the cutter only for a parallelogram of parallel, horizontal or vertical sides missed rectangles the sketch holds another way: with the bottom side held square to a side instead of horizontal, trimming a diagonal at the centre left the centre held only on the other diagonal, one degree of freedom freer than before. The hold now moves when the sketch already implies it, that is when adding it to the sketch as it was takes away no degree of freedom.

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

* Sketch Trim: solve for the implied midpoint only when a cut lands on one

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:39:58 +00:00
Daniel de1db6fb8c Parameters: reach the end of a long expression the same way on every OS in its test (#12104)
a_long_expression_opens_wide_while_edited_and_enter_applies_it moved the cursor to the end of the wrapped field with Ctrl+End. The control channel's cmd flag also sets ctrl, and on macOS egui treats Ctrl with End as an unhandled Emacs-style chord, so the cursor stayed where the click put it. The typed " * 2" did not land at the end, the parameter kept its old expression, and the test failed on macOS while it passed on Linux.

The test now uses Command+Down with ctrl off, which egui maps to the end of the text on every platform. What the test checks is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:39:49 +00:00
github-actions[bot] c47e848f4a Regenerate generated files and fold pending files [skip ci] 2026-11-04 23:04:10 +00:00
github-actions[bot] 8b4fe10995 Regenerate generated files and fold pending files [skip ci] 2026-11-04 22:53:51 +00:00
Daniel 8fb046aaa8 Sketch: the whole sketch updates while you type a dimension's value (#11584) (#11585)
* claim: #11584

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

* Sketch: the sketch follows a dimension's value while it is typed (#11584)

While a new dimension's value was typed, only the dimension moved: the sketch's curves, points, constraint glyphs and other dimensions stayed at the old size until Enter. The typed value was already solved in a scratch session; it is now solved in the frame it is typed, and the open sketch is drawn from that solve, for every kind of curve, within the display cache's limits per sketch. Esc draws the design again. Picking and snapping of curves and points stay on the design, and no hover highlight of the design is painted while a value is typed. A value that does not solve keeps the last one that did.

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

* Dimension: the sketch scene follows each solve of a typed value, not its address (#11584)

The sketch scene was keyed on the preview's address, which a later solve can have once the drawn one is freed; the scene would then stay at the old value. Each solve now has a number, and the scene is keyed on it.

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

* Dimension: while a value is typed, no design profile is lit over the sketch, and its curves are sampled before its fills (#11584)

The profile under the pointer was lit where the design has it, over the sketch drawn at the typed value; it is no longer, and the highlight is built again when the value is let go. The typed sketch's profiles were sampled before its curves from the one budget, so a sketch whose fills used it up lost its curves; the curves now go first.

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

* Dimension: a selected profile is not lit over the sketch while a value is typed either (#11584)

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

* Dimension: the view goes to the typed preview only once a value is typed (#11584)

A picked dimension's value box opened with a draft that is the design as it is, and the view already treated it as the typed preview: the profile under the pointer, the selection and the hover went dark before anything was typed. The preview now starts with the first key, completion or reference.

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

* Dimension: a reference picked into the value is solved at once (#11584)

The overlays read the preview before the editor solves a changed value, so for the click's frame they drew the solve before the reference.

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

* Dimension: a selected sketch curve is not lit where the design has it while a value is typed (#11584)

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

* Dimension: the pointer's x, y readout is not drawn while a value is typed, as the hover is not (#11584)

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

* Dimension: curvature combs, the design's teeth, are not drawn while a value is typed (#11584)

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

* Dimension: while a value is typed only the open sketch loses its hover and profile highlights; other sketches keep theirs (#11584)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:34:23 +00:00
leValdemar19 7b31c0567b Sketch Transform: turn and scale about a picked point, such as a rectangle's corner (#11718) (#11797)
* claim: #11718

* Sketch Transform: turn and scale about a picked point, such as a rectangle's corner (#11718)

The Transform window turned and scaled the picked entities only about the
middle of their box. It now has a Center field, as Move does. A sketch point
picked there is the point Scale and Angle work about, and Position moves it.
The box, its corner handles and the knob in the view follow the same pivot.
With Center empty, Transform works as before.

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

* Sketch Transform: a click on a corner picks it as Center while Center waits for a pick (#11718)

The rectangle corner the issue wants to turn about sits under the box's
corner handle, so a click there started a scale. While the Center field
waits for a pick, the box's handles leave clicks to the sketch.

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

* Sketch Transform: the handles work again once Center has its point (#11718)

Center is the window's last field, so a pick there left it active, and the
box's handles stayed off until another field was clicked. They are now off
only while Center is empty and waiting for its pick.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:33:06 +00:00
Daniel 5efdd52854 Ribbon: a tool pinned into the Finish Sketch group shows pressed (#12049) (#12050)
* Ribbon: a tool pinned into the Finish Sketch group shows pressed

The Finish Sketch and Finish Editing groups never drew the pressed state for any of their buttons, so a drawing tool dragged into them (the group at the ribbon's right edge takes drops) looked like a one-shot action while it ran. Only Finish, Cancel and Finish Edit are now left out of the pressed state.

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

* Ribbon: test that Finish, Cancel and Finish Edit stay plain under a press

None of the three runs as a tool or leaves a dialog behind, so a press is the one way they could light up. The tests now press each one and fail if any of them is dropped from the exclusion. Finish Edit is pressed and released in the edit-in-place test, so the release is also the click that finishes the edit.

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

* Ribbon: Finish with AutoConstrain stays plain under a press like Finish

Finish with AutoConstrain belongs to the Finish Sketch group and ends the sketch like Finish, but it was not in the list of buttons left out of the pressed state, so once pinned as a button it lit while the pointer was held on it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:32:12 +00:00
takuto-NA 61ada52984 Reuse unchanged imported meshes during joint motion (#5476) (#5480)
* perf(mesh): reuse imported contents and display triangles

* fix(viewport): preserve reference mesh detail and glass fallback

* fix(viewport): preserve small faded mesh ordering without OIT
2026-11-04 21:30:58 +00:00
SS-EN 6578d591ac control::execute: check the undo depth before placing the world for the first-body fit (#5940)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:30:48 +00:00
JK3D d9421bf2ca Browser: an option to start folders closed and open rows only on request (#8768) (#8771)
* claim: #8768

* feat: Browser folders start closed and find a body only when asked, behind a preference

Preferences › Layout › Browser rows start closed (`browser_rows_start_closed`, off by
default) starts the Browser's folders closed, and placements too whatever "Components start
open" (#9274) says. Rows then open with their arrow, Expand All or Find in Browser, which opens
just the rows down to its body and closes again the ones the last find opened. A body picked in
the 3D view leaves the Browser as it is, and only the body Find in Browser found has the rows
above it marked, so a large assembly's placements are not named again after each edit.

With the preference off, folders start open, placements start as "Components start open"
says, and a pick in the 3D view opens the Browser down to its body, as on main. Find in Browser
looks the body's placement up once and opens its rows before drawing, instead of each row
testing the revealed path while it is drawn.

* Browser tests: read the row order from the Browser's content frame

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:29:23 +00:00
PaddleStroke 774c45a512 Dynamics: rigid-body solver core with joints, gravity and springs (#9719) (#9721)
* claim: #9719

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

* Dynamics: rigid-body solver core with joints, gravity and springs (#9719)

A new L1 crate, solidcraft-dynamics (geom only): rigid bodies in maximal coordinates, joint constraint equations with analytic Jacobians for rigid, revolute, slider, cylindrical, planar and ball joints, gravity, linear spring-dampers and constant loads; the index-1 equations of motion solved through a rank-revealing Schur complement (repeated joint equations are solved, not refused); RK4 and Dormand-Prince 5(4) with position and velocity projection; joint values, rates, reactions and energies at every output sample; caps on bodies, joints, rows, steps and samples; Result errors only.

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

* Dynamics: take Vec3 from the record crate after the layer split, refresh the lock file

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 21:27:17 +00:00
P'tite Tête 315d8cfeb7 fix: sketch.intersect keeps a small round between short walls (#9962)
- section() kept the loose fit whenever it had fewer curves, even when it had merged 2 mm walls and an R2 round into one R2.85 arc that misses the walls, leaving the outline open
- The tight fit is now also kept when the loose one strays from the exact crossings (on planes and rulings) and the tight one does not; capped work, other curves count as near
- Test: a box with R2 corner rounds and four tapered grooves stopping 2 mm short, cut at Y=20

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-11-04 21:27:05 +00:00
TaeHeon Kim 4ab1640dc8 OCLP detection: run /usr/sbin/nvram by its full path (#10109)
The probe ran `nvram` through PATH, which a Finder launch may not list
and which a look-alike earlier in it could change. The doc comment that
said exec would need unsafe is replaced by a description of what the
code does. Behaviour is otherwise unchanged.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 21:26:51 +00:00
raykholo 3aee35fe01 Web app: keep unsaved work in the browser instead of the leave-page box (#10220) (#10221)
* claim: #10220

* Web app: keep unsaved work in the browser instead of the leave-page box (#10220)

A new preference, off by default, on Preferences > General > Saving (web
app only): "Keep unsaved work in this browser instead of asking when the
tab closes". With it on, each open design with unsaved changes keeps a
copy in the browser's private storage (keep-<page>-<design>.sc/.json),
written once the design has been still for 0.8 s (at most 4 s after the
first change it lacks) and again on pagehide or when the page is hidden.
While every copy is current, beforeunload doesn't ask. A copy goes when
its design is saved, closed, or the preference is turned off.

The next visit offers the copies newer than their design's last save in
SolidCraft's own window, "Restore unsaved design: <name>, changed
<time>?", one window for several, with Restore, Open saved version, and
Discard. A page holds a Web Lock while it runs, so another open tab's
copies are never offered or removed.

With the preference off, the single autosave and the browser's prompt
work exactly as before.

* Web app: the restore window comes from the UI kit's recovery module (#10220)

The kit's text and divider helpers are private to the UI crate, so the
"Restore unsaved design?" window moves to recovery_ui
(browser_copies_window), beside the desktop recovery window, and the web
shell only maps its answer. Also keeps the window handle for the page
listeners instead of using the one moved into the app.

* Web app: restore window buttons sit in one row (#10220)

* Web app: keep unsaved work and still ask, unless told not to (#10220)

Keeping a copy in the browser no longer replaces the browser's leave-page
box by itself. A second option, shown once work is kept and on by default,
leaves the box in place; off, the box appears only while a copy isn't current.

* Web app: kept-copy review fixes (#10220)

A failed write of a kept copy waits 30 s before the next try, a recovery
copy is dropped only after the remembered saved file has opened, the
preference is documented under its current label, and the ui.prefs list
is sorted again.
2026-11-04 21:26:23 +00:00
raykholo 5562d9c6c8 Quick-access bar: pin ribbon commands to the application bar (#10223) (#10224)
* claim: #10223

* Quick-access bar: pin ribbon commands to the application bar (#10223)

A command's menu (a ribbon button, a section drop-down row or a command
search result) has Add to Quick Access: the command's own icon joins the
application bar after Save, Undo and Redo. A click starts it as its
ribbon button does, a drag along the bar reorders the buttons, and the
button's menu has Remove from Quick Access. Empty, and so not drawn, by
default; it holds up to 24 commands.

The list is kept with the toolbar's customisations (`quick_access` in
the toolbar's saved document), so it is saved, exported with the
keyboard shortcuts and carried by settings profiles.

* Quick access: a pinned family variant no longer replaces the ribbon choice

A quick-access click now starts the pinned command with start_pinned, so it
starts exactly that command and leaves the sketch family remembered choice
(the main ribbon button) unchanged.
2026-11-04 21:26:13 +00:00
Colin Faulkingham d16496e057 Navigation: honor reverse zoom for mouse drags (#10354) (#10365)
* claim: #10354

* fix(ui): honor reverse zoom for mouse drags
2026-11-04 21:26:01 +00:00
Arjan Duijs 0690ee5875 Web app: run as a modelling editor inside another page that keeps the design files (#8517) (#11273)
* claim: #8517

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

* web: editing embed for host pages (?embed=editor, #8517)

A host page that keeps the files itself and frames SolidCraft for modelling needs
the modelling UI without the application chrome. `?embed=editor` runs the web app
as such an embed:

- no application bar (logo, File menu, document tabs, update, help, report a bug)
  and no start page; the layout is docked;
- the ribbon, browser, timeline and properties stay; Ctrl+Z and Ctrl+Y still undo
  and redo;
- Ctrl+N, Ctrl+O, Ctrl+S, Ctrl+W and Ctrl+, are left to the host;
- nothing is kept in the browser (no stored designs, autosave, recovery prompt,
  preferences or leave-page prompt), as with the existing viewer embed.

The existing `?embed&open=` viewer embed is unchanged. SolveApp gets one flag,
`embed_editor`, for desktop and test hosts too.

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

* control: document.export / document.import / document.saved for a host (#8517)

An editing embed's host keeps the design itself. Three control methods
(window.solidcraft.request in the browser; the desktop control channel too; not
the agent chat's, whose method list is unchanged):

- `document.export` -> {name, revision, dirty, format: "json", text}: the active
  design as a `.json` design file holds it. `revision` is the one
  `document.revision` answers, so a host that follows the design compares one number.
- `document.import {text, name?}`: replaces the active design with that text,
  timeline and parameters as saved, opened saved with a new `document_id`. It runs
  as a design opened in a tab of its own, with the old tab closed without asking,
  so whatever was open in the old design leaves with it. A text that does not read
  leaves the design as it was. Waits behind a file loading or a design change, as
  commands do, and evaluates on the window thread.
- `document.saved {revision}`: the host saved that revision; refused when the
  design has changed since.

They are control methods, not commands: they act on the app's open design. The
browser API test gets a round trip through all three.

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

---------

Co-authored-by: aduijs <7501203+aduijs@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:23:25 +00:00
Colin Faulkingham 1dd921fc52 Components: stop Capture All Positions from doubling moves (#11259) (#11364)
* Model cache: a body made by rounds keeps them, so a later round blends them the same after opening

A design gave another body when opened from its model cache: the rounds a body was made of
lived only in memory, so a later round at their mitre was built sequentially (or refused, as
too large) after opening and blended with them when evaluated anew. The saved model now
records, per kernel body made by rounds, the body they were made on (a place in the bodies,
stored once though no state holds it) and the edges with their radii; reading it attaches
them back down each chain, and a thinned model keeps those bodies with the bodies made from
them. Model cache format 7.

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

* Tests: a corner patch of rounds 2/3/4 takes no more than the strips leave in its block

three_rounds_at_a_corner bounded the patch's extra cut at 3 mm³ beyond the strips, a figure
with no derivation; SMLib's all-at-once body at radii 2, 3, 4 takes 3.099 (V 7878.579574
against strips 7881.678). That body is the strips' outside the block its patch lies in: its
rounds are the strips' cylinders ending at a's section y = rc, b's x = 20 − rc and c's rails
where they meet a's and b's (the patch's four corners, now asserted), so the patch takes at
most what the strips leave in that block (11.86 at 2/2/3, 32.69 at 2/3/4). The lower bound is
that block; the upper one, at most the strips' volume, stays.

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

* Tests: the io crate's hand-built saved models list their rounds, as format 7 records do

Model cache format 7 (f3cf0c4f3) records a `rounds` list in every saved model, and reading one
without it is refused ("saved model: rounds"), as is a model without any of its other lists:
the states, the preview and the thinned model all came back empty. The io tests' saved_model
and timeline_model wrote format-6 records, so six tests (the round trip, the repeated part
stored once, and the four too-big-for-the-file cases) read nothing. Their bodies are none made
by rounds: the list is empty, and every expectation stays as it was.

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

* SMLib: pin eaa998a (tangency-labs/smlib#68, #69, #70)

Batch 4: drafts and loops, mitre corners, rim chamfers, rulings, tight box, stitch, point
classification, eased corners. Batch 5: a face's tight box divides its cells at the surface's
knots (thread boxes exact); a cylinder, cone, sphere or torus is extended along its own circles.
Batch 6: a revolution's extension closes its turn; a dropped point's foot is the converged one.
The C API is ABI 5 (smlib_blend_dropping and smlib_brep_classify_point are new; nothing existing
changed), so the binding expects 5.

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

* Tests: a strip's tangent and curvature run-offs are the distance long across the surface

SMLib measures an extension's distance across the surface (smlib_extend_sheet corrects
CreateExtendedSurface until the parameter line through the side's middle is that long), so each
run-off of the cylinder strip, swept 20 along z, adds 5 x 20: 10 pi/2 20 + 100 = 414.159 for
both. The parabola-length formula (418.18) was truck's.

Ruling d2 (2026-10-30).

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

* kernel: one smooth angle, and a round too small to change the part is refused

The blend's tangent angle is the shaded view's half degree (smooth_edges::ANGLE), one value
for smooth. A constant round lies r (sec(d/2) - 1) inside the crease it replaces (about
r d^2/8); below the edge's tolerance it changes nothing and is refused before SMLib (which
answered SM_ERR_INVALID_INPUT), naming the smallest radius that rounds,
tolerance / (sec(d/2) - 1). d is the least turn along the edge
(tangent_edge::least_angle_along, at tangent_all_along's stations).

Ruling d3 (2026-10-30). The block's tolerance is 2e-5 mm, not 0.1 um: at 1 deg the smallest
radius is 0.526 mm, so R2 and R20 reach SMLib, which still calls an edge within 2 deg smooth
(SmEdge::GetBlendEdgeuse); that test is ignored until SMLib uses 0.5 deg.

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

* Fillet rig: the knife-edge chamfers name SMLib's cause, a cusp's ball radius of 0

smlib_chamfer turns the distance into a ball radius d sqrt((1 - c)/(1 + c)), c the faces'
binormals' dot (capi chamfer_radius); at #851's cusp the binormals agree, the radius is 0 and
the call is SM_ERR_INVALID_INPUT before the fillet executive runs (gdb: chamfer_edges sees
radius 0; the dump's why is empty). SolidCraft hands every chamfer to SMLib, so the
ball-contact chord (the ball of radius d: 3.822 wide at d = 2, 159.2365 removed) is SMLib's to
build.

Ruling d4 (2026-10-30).

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

* kernel tests: a corner round wider than its faces overflows and takes the corner's other edges

R21 on a 20 mm cube's edge builds (6107.529370, closed form, to 2e-12). With R5 on the
corner's other edges the R21 takes the faces they lie on, so the body is R21's alone; SMLib
fails that blend (status 1001, fillet error 4, Unable to Trim Rail Curve) and the test is
ignored until it builds. It replaces the refusal test that expected TooLarge.

Ruling d8 (2026-10-30).

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

* Tests: a 1 degree crease rounds, and the knife edge's chamfers build, on SMLib smlib-batch-4-rulings

With SMLib's smooth limit at half a degree (422e6f1) the 1 degree crease rounds R2 and R20
(the test is no longer ignored), and with a cusp's chamfer the ball of radius d (d231547) the
The 0.5 one stays, for its own cause (the chamfer face left apart from the body). Two corner
cases the same SMLib (replay/1.0.1 and those commits) builds come off too.

Needs SMLIB_REV at that SMLib once it is pushed.

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

* kernel, doc: an overflowing round goes first; an edge it removes is gone, and the fillet says so

SMLib (smlib-batch-4-rulings 0e83cca, SMLIB_ABI_VERSION 5) rounds the edges whose rounds overflow
their faces first and then what is left of the others; smlib_blend_dropping says which picks'
edges the overflow removed whole. The smlib crate binds it (Brep::blend_dropping), the kernel
returns those picks (fillet_removing), and a fillet notes them as its warning: "the edge at
(0, 0, 10) was removed by the larger round, so it is not rounded" (a 50 x 50 x 20 block, R21 on
its bottom front edge with the front left upright: the block is R21's alone, in closed form).

Ruling d8: [21, 5, 5] on a 20 cube builds as R21 alone (6107.529370); the test is no longer
ignored. R25 with R2 at a corner (blend_corner) builds the same way, R25's alone in closed form,
where it was refused. The rig's 1080 notched block outline at R8 builds now and comes off the
known-failing list.

Needs SMLIB_REV at that SMLib once it is pushed.

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

* Fillet rig: the knife edge's 0.5 chamfer builds

SMLib's trim took a fillet meeting its face at more than 78 degrees for one with no side
(smlib-batch-4-rulings 971e567); the chord of the knife edge's 0.5 chamfer meets the flat face at
81 degrees. The case comes off the known-failing list.

Needs SMLIB_REV at that SMLib once it is pushed.

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

* Tests: a 15° tapered text builds, every letter grown, exact where its outline gives it

Owner ruling d6: the letter t of "tres long" was drafted inward by SMLib (its
loop's normal, summed from the cross products of its chords, pointed down;
fixed in SMLib). With the fix every letter grows, and the join builds at 3°
and 15° by the Round rule and by the default rule (tangent for spline
outlines), where before 15° was expected to be refused.

Exact where the outline derives it: a letter's section at height z is its
outline offset by e = z tan a, by the Round rule of area A + P e + C e² while
the offset trims nothing (Steiner; C half each loop's turning less
tan(t/2) - t/2 at each reflex corner), so the letter is
A h + P k h²/2 + C k² h³/3. Each extruded alone, the t at 3° and s, l and o
at 3° and 15° are checked to 1e-6 (SMLib measures them within 1.3e-9; its
mass properties integrate to 1e-8). The concave joins of r, n, g and e are
tighter than e already at 3° (their swallowtails trimmed: r 1.1e-3 under the
formula), the t's by 15° (9.9e-5 over), and on the block the letters grow
into each other, so the whole word is checked for what holds exactly: one
closed body with the block, and half-way up every point of each straight
letter's outline inside the solid (the t drafted inward failed that).

With SMLib's fix (smlib-batch-4-text-loops 82133b2).

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

* Kernel rig: a sloped basin's mitred rim rounds are checked tangent along their contact lines

1080-fillet-sloped-basin-1-mm-rim-r0-5-4-edges asked G1 at every blend seam. Its front wall
leans 19 over 20 and the others 10 over 20, so at each rim corner the two mitred rounds meet the
sloped corner edge at different heights (0.155625 and 0.276393 below the top): the deeper round
runs on past the corner edge and is cut by the plane of the other's wall, a crease by
construction, the gap below the shallower round filled on that plane. The check now measures
the rounds' contact seams, on the top and on each wall, where the rounds are tangent. The
volume is unchanged.

With SMLib smlib-batch-4-mitre (1c15364): the case passes (V 144530.427946 against
144530.427909, 15 faces, sound); before it, only the four corner creases failed the check.

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

* smlib: Brep::classify_point says exactly where a point lies against a body

Binds the fork's smlib_brep_classify_point (SmBrep::Point3DClassify at the body's own zone
tolerance): inside, outside, or on the boundary within that tolerance of a face, an edge or a
vertex. A box and a cylinder just off its curved wall in measure's tests.

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

* kernel: Body::classify_point decides inside, outside or on the boundary exactly

SMLib's point classification at the body's own tolerance, on the exact faces and edges, in
place of a mesh at a chord of 2e-3 of the body's size (about 0.4 mm on a 200 mm part). A point
within the tolerance of a face, edge or vertex is OnBoundary, never decided by a ray. A mesh
body's triangles are its exact shape: on them within the record's tolerance, else by ray
crossings. The kernel's own probes move to it; Body::contains goes once its last callers have.

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

* doc: a hole decides whether it exits at its drill point's shoulder exactly

Material past the depth needs a body with the point inside, air before it needs every body to
have it outside; a point within a body's tolerance of the exit face is neither.

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

* engine tests: probes ask for the exact point class

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

* ui, cli tests: probes ask for the exact point class

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

* kernel: Body::contains, the mesh test, is gone

Every caller asks Body::classify_point now.

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

* kernel tests: the rim chamfers running off their rims build, on SMLib 737c392

chamfer_wider_than_sloped_basin_rim_builds and a_rim_loop_chamfer_running_off_its_rim_builds
waited for SMLib to build the cliff overflow (CloseRemovalRegion refused it,
SM_FILERR_NO_ROOM). The pinned SMLib builds it (eddf0ede4, bba9914b6): both pass on aarch64
Linux and are no longer ignored.

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

* Fillet rig: the cases SMLib 737c392 builds come off the known-failing list

The gate on aarch64 Linux reports 16 fillet cases FIXED with the pinned SMLib: the sloped and
equal-slope basin rim chamfers and rounds past their rims (1080-*, 5835-*), the pocket rim
chamfer past the floor, and the three pocket rim rounds now mitred at the pocket's corners
(every-pocket-*). No other area's gate reports a case fixed.

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

* kernel: the re-blend's removed picks formatted as rustfmt lays them out

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

* kernel: blend_smlib laid out by the 2024 edition's rustfmt

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

* Skills: the tests skill's unit-test example stays under the 3600-character limit

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

* Fillet: the radius-too-large choice in plain nouns

"If too large: Stop and say so / Largest that fits / Narrow where needed"
becomes "Face limits: Exact radius / Largest radius / Narrowed radius", in
every language of the catalog, with the error and the tip that name them.

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

* kernel: a round that would cut through its wall into a hollow is refused

The owner's rule of 2026-10-30: a round running past a rim of its faces
into open air builds; one whose section would remove material through to
a hollow behind its edge (a slit into the hollow, a cut through the part)
is refused as too large, so the fillet names the largest radius that fits.

Decided exactly in the section of a straight convex edge between planar
faces: the round's centre is at depth r below both planes, and it opens
where its arc uncovers a boundary point strictly inside the wedge with no
material beyond it toward the centre (the body's exact point
classification). Looked at: straight edges parallel to the round's (a
hollow's corner), cylinders about parallel axes (a rounded corner), and a
picked concave parallel edge's own round, which fills the corner.

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

* Fillet rig: 1668's R7 past the inside corner is refused at 4 + 2 sqrt2

The round's centre (32 - r, 42 - r) leaves the hollow's corner (30, 40)
outside its circle, opening the wall, where sqrt2 (r - 2) = r: the case
expects the refusal naming 6.828. 2852's R30, refused naming 25.24, now
passes too: both come off the known-failing list.

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

* xtask: known-failing grades a nextest junit report against tests/KNOWN_FAILING_TESTS

`cargo xtask known-failing --junit PATH` reads nextest's junit (a file, or every .xml under a
directory, so all of a run's junit artifacts at once) and fails on a test that failed and the
list does not name, and on a listed test that passed (FIXED: take its line off, as the kernel
rig's gate does). A listed test that did not run is neither, since a pull request tests only the
packages it touches. The list has the rig's KNOWN_FAILING format (`<test> <cause>`, sorted, the
rig's own parser), with `<test>` nextest's `<binary id>::<test name>`; `cargo xtask lists`
checks it is sorted. The list starts empty.

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

* CI: the test jobs grade their junit against tests/KNOWN_FAILING_TESTS

Native Linux tests and the GPU path test shards no longer fail on any failed test: nextest's
"tests failed" exit (100) goes to `cargo xtask known-failing --junit`, which fails on a failure
the list does not name and on a listed test that passes. Any other nextest exit (a build error,
a bad filter) still fails the step at once. The tests skill says to list a failing test with its
cause, in place of ignoring it.

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

* Tests: the known-failing list takes develop's 10 failing tests

Develop 38fd4a4f6 (CI run 41157648345, after the SMLib eaa998a pin) fails 10 cargo tests; each
is listed with the cause its failure message gives. Seven are SMLib refusals or results off
their derived volumes (fillet, chamfer and shell), the cli action matrix and naming corpus
among them; the two corner_g2 tests are the fitted G2 corner patch bulging past the box, which
the explicit G2 corner fixes.

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

* Reference tests: the eight cases that fail on SMLib eaa998a name their cause as known failures

Develop 38fd4a4f6's Reference tests fail 34, 35, 77, 78 and 81 (STEP export finds fillet edges
4.6e-5 to 6.1e-5 off their surfaces against 2e-5), 37 (the apex blend 0.193 off), 80 (the
taper-to-fit area) and 83 (thicken status 1001). Each expected.json names its cause in
known_failure, which the oracle already reads: a known failure that passes fails the job.

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

* Sweep path: a curve of several pieces is turned round whole

The path chain oriented its first piece, not its first curve: a first curve
of several exact pieces (a fit spline, an ellipse, a projected free-form
edge) was never turned round, so a path whose next curve meets it at its
start was refused as "not connected end to end". The chain now orients
curve by curve: the first runs toward the second where they meet at one
end only (else from the end nearest the profile), each next one starts
where the previous ended, with one joint tolerance for both questions.

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

* SMLib: pin 2fc375f (tangency-labs/smlib#71, #72, #73)

A sheet's side is extended exactly the distance asked, or the call says it cannot (#71);
NVIDIA's -Werror build passes again (#72); a body splits by a sheet of several faces as it
is, smlib_split_body_by_sheet (#73). The C API is ABI 6 (the new entry), so the binding
expects 6.

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

* kernel: Extend grows a surface of several faces face by face

A surface body of more than one face (a fit-point spline's spans extruded, #3830) was refused.
Each face with a picked edge is now extended alone by SMLib's extension of its surface, and the
faces are sewn back together, each the face it was. A side that lengthens onto a face meeting it
tangent takes that face along, past its edge continuing the picked one; across a crease the
neighbour grows only when its edge is picked too, otherwise the extension is refused. Faces whose
lengthened sides no longer meet are refused. Up to a target keeps the pieces every grown face
lies in.

3830's extensions now build; its split still fails (the tool point picks one span, and a face
tool cuts with that face's surface alone), so it stays on the list with that reason.

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

* kernel: Split Body by a face of a smooth sheet cuts with the whole sheet

A face tool cut with that face's surface alone, so a face of a surface body made of spans joined
tangent (a fit-point spline extruded, #3830) cut nothing where its span ends inside the body.
Split Body by a face of a surface body now takes the faces joined to it tangentially (the exact
check Extend uses, within the one angle for smooth) and slices with them as they are, through
SMLib's slice by a sheet (smlib_split_body_by_sheet, tangency-labs/smlib 7a01f1b), its sides those
of the picked face. A sheet that only nicks the body cuts nothing, as before; a lone face, or a
face of a solid, still cuts with its surface extended.

3830 comes off the booleans list. Its answer counted the two halves of the cup and not the surface
that cut it, which stays as a body of its own (4879 removes its surface for that reason): three
bodies, the volume unchanged.

Needs SMLib 7a01f1b pinned (SMLIB_REV, prebuilt; its SMLIB_ABI_VERSION 6, which crates/smlib now expects) to link.

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

* Tests: a cylinder strip's run-offs are 100 pi + 100 to 1e-6, SMLib's exact area

The tangent and curvature run-offs of the quarter cylinder r 10 strip, 5 across the surface,
are its section swept 20 along z: exactly 10 pi/2 20 + 5 x 20 = 414.159265. measure is SMLib's
integration on the exact surface (1e-8), not a mesh, so the run-off check is 1e-6 relative and
the 'measured on meshes' notes go. Red until SMLib's extension is solved to its distance
(smlib-extend-converges 590d8a1): eaa998a's curvature run-off is 4.986 long, area 413.882.

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

* kernel: an extension SMLib cannot make exact says so, naming the length asked

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

* Sketch: a control spline can carry its own knots and weights

A sketch spline over control points was always the clamped uniform B-spline
over them, so a free-form edge whose knots are not evenly spaced (or whose
weights are not 1) could only come into a sketch as one curve per Bezier
span. A control spline now may save `nurbs`: its clamped knots and its
weights. Its points, polyline, exact pieces (one a knot span; conics for a
rational quadratic), the solver's distance and curvature, links, parallel
re-projection and inspect.sketch all read them; a loaded design's are
checked. One pending format file (spline-knots).

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

* Sketch projection: a free-form edge comes in as the one exact spline it is

A projected spline edge of several Bezier spans came into the sketch as one
curve per span (#1508), so a G2 round's bottom edge was 16 curves and a path
naming the corner named one span of it (#3604). The kernel now reads such an
edge (a spline, or an elliptical arc of several spans) as the one clamped
B-spline it is over the edge's interval, its knots and weights its own, and
the sketch takes it as one control spline whose ends sit on the edge's
vertices. Planar regions still read edges span by span. The 3604 case builds
with its original path and leaves KNOWN_FAILING.

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

* Fillet rig: blends past an outer rim of their faces build, on SMLib smlib-blends-past-rims

The owner's ruling: a round or chamfer that runs past an outer edge of its face into open air
builds, tangent where it has room and cut by the face beyond where it runs out. SMLib's branch
smlib-blends-past-rims builds them by the rig's analytic answers. Off the known-failing list:
the disc's rim past its 2 mm wall (190), the hole's top rim R12 past the plate's bottom, the
chamfer past the cylinder's bottom (2893), each closing on the wall continued as itself, and
926's chamfer ending on the boss's wall, which now builds to its answer too.

The sloped basins' R4 rim rounds (1080, mitred rounds built together where one overflows) build
to their analytic volumes (to 6e-9), and stay listed with that reason: their continuity check
(g1 at every blend seam) takes in the mitres' creases, G0 by the mitre ruling.
2893-chamfer-two-distances-elliptical-rim is graded as a solid (the owner: it builds) and stays
listed, waiting on the ruling on unequal chamfers along a rim whose angle varies.
5158-fillet-round-just-past-a-10mm-face stays listed with its cause found: the round passes
5e-6 under the box's top back edge, inside the body's tolerance.

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

* Naming: test that a round of a round's end has no seam with that round's faces

The r1 round of a block's top edge, chained onto the end curve of the r2
round of its arc at a cylinder (rig fillets/every-block-cylinder-crease-
top-edge-after-arc-round-r1), ends where it runs into the cylinder. That
edge is no seam of the r1 round: the cylinder is a face of the r2 round,
not of the r1. blend_seams takes it for one today (the r2 round's name
F3:blend:F1:+z|F2:side holds a | of its own), and the rig reads it as a
contact seam at 60 degrees.

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

* Naming: a blend's seams split its edge's name at the | between its two faces

A blend face's name holds the edge it replaced, <face>|<face>, and a side
of that edge can be a blend face whose own name holds a |. Whether a face
is a contact of the blend was read as the edge's name starting or ending
with the face's name next to a |, so the r2 round's own face F2:side read
as a contact of the r1 round F4:blend:F1:+y|F3:blend:F1:+z|F2:side. The
edge's name is now split into its two faces, a blend side read whole as
the two faces of its own edge.

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

* Fillet rig: five refused blends build, off the known-failing list

926 (the chamfer ending on the boss's wall), the oblique cut's r0.1
round, the every-block r1 round (its seam with the cylinder is no
contact seam, after the blend_seams fix), the up-down squares' concave
chamfers and the shelled box's lip chamfer build with SMLib branch
smlib-blend-refusals (f8650b8 and later). 1207 r0.5 passes SMLib's check
on its rails now and stays listed for the next cause: its end rib on the
boss, which starts where the round and the boss are tangent.

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

* Kernel rig: the trough and the pocket whose floors fall to a drain build

SMLib now offsets a vertex where four faces meet (two walls and two facets at the floor's
corners, four facets at the drain) to two vertices joined by a new edge (tangency-labs/smlib
branch smlib-shells). Both volumes match their closed forms. The pocket's lining, hanging from
the top's rim round the pocket, stands apart from the slab's outer walls with the cavity rising
between them to the open top: the case says two shells in the one body.

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

* Kernel rig: the plate with a hole through its arc side shells open at its far end

SMLib's merge now intersects the offsets of an edge's faces where they could not be mated along
it (the hole's offset poking out through the half-round side's; tangency-labs/solidcore#1795):
one closed valid solid with the one face open, as the owner ruled.

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

* Kernel rig: the ring cut frame and the up-down join shell

SMLib's inset now keeps only the regions that are cavity, not the one the offsets close where
they overlap (between the two faces of #545's 0.5 floor, round the up-down join's two empty
octants). The frame's core under the recess is hollowed to a sealed void, so the case says two
shells and its closed form, 21044 mm³.

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

* Kernel rig: #616's lid shells open at a rib's side, with the 0.15 lip

SMLib keeps the skirt top's offset where its loops wind the other way under the opening, the
cavity's floor (the owner's ruling of 2026-10-31: build it; a lip thinner than the wall is no
break-through).

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

* Known failing tests: the action matrix's shells and chamfers as they fail with SMLib batch 7

The ring cut frame's and the up-down join's shells build now, and so do the up-down join's and
modern-plastic's chamfers (SMLib smlib-blend-refusals). #616's lid in the corpus still fails
SMLib's loop check, the shelled box shelled again at an inner wall is unchanged, and the
oblique cut of a cylinder's fillet still fails SmSurfOfRevolution's Test #7: 3 actions of 7.

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

* SMLib: pin 26cc451 (batch 7: rounds past a rim, blend refusals that build, shells)

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

* Known failing tests: the shell after a cut whose end touches an arc side builds on SMLib batch 7

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

* Surface fair test: the energy falls strictly, the face moves, and the edges are read after the fair

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

* Sketch scaling test: the first dimension sets l1 to 40 and scales both joined rectangles about the origin

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

* Frames tests: a pick on a moved component or an instance removes the exact round and bore volumes

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

* Issue 1080 tests: the rib's rounds and the mitred corner chamfers and rounds have their exact volumes

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

* Engine tests: lip_after_boss checks each lip's and groove's exact band, hole_as_deep_as_the_plate each hole's exact bore

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

* Robustness tests: lip_after_boss and hole_as_deep_as_the_plate live once, in the engine's tests.rs with exact volumes

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

* Engine tests: loft_cut goes; loft_spacing's hollow elbow covers its section counts and more

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

* Kernel loft test: the square-to-circle loft has the oracle's exact ruled volume, without printing

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

* Tessellation lens test: the lens's triangles fill exactly the rhombus through its arcs' midpoints

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

* Pattern test: six chamfered holes take exactly five more holes' bore and chamfer rings

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

* Kernel pocket floor fillet test: the corner rounds and the floor loop round add their exact volumes, by Pappus at the corners

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

* Shell regression: the saddle-corner shell opened on the top box's side has its exact volume

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

* SMLib history test: every operand's order is the identity on its record's faces and edges

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

* Boolean history tests: the SMLib module's box-minus-sphere, box-minus-cylinder and box-union-box copies go; the top-level ones check more

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

* Modify history tests: shell, offset and thicken are one test each through the public API, with the volumes, refusals and history the SMLib copies checked

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

* Chamfer history test: the chamfer after each box cut is exactly one face on the painted block too

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

* Grid test: a pole side's face is the five triangles fanned from its one grid node, none degenerate

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

* Kernel tests: the twelve ignored print-only debug tests and surveys go; coplanar_curved_booleans checks the flush-capped cylinder cuts and unions they printed

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

* Kernel: the #883 board fixture generator is an example, not an ignored test

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

* STEP import test: a seam sphere whose meridian stops short of the pole imports as the whole 2 mm ball

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

* Material tests: the nine copies in material_properties go; the persistence file checks the library copy's card and formats 20 and 21 in one loop

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

* Sample test: one loop checks every sample builds cleanly and without a panic; the robustness copy goes

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

* Known failing: the non-antipodal seam sphere import, refused for a loop of no (u, v) area

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

* Tests: rustfmt the board fixture example and the coplanar asserts

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

* Kernel rig: the two 3871 cone cases were byte-for-byte the 258 cases; the 258 cases carry their sources

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

* Kernel rig: a volume or area can be graded as a hand-derived range where analysis only bounds it

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

* Kernel rig: fillets keeps one case per regime; 17 replications of a case in the same regime go

The sloped-basin chamfers and rounds at 0.5 mm rims repeat the 1 mm rim's regimes; two of the
three knife-edge chamfer tips, the r1 and box r3 curvature corners, the r1 tangent corner, the
1 mm pocket-rim and hole-rim chamfers short of the floor and the r1 together-round each repeat a kept case's regime
and grades. #854 moves to 851-chamfer-arc-top-edge-9-5, and the cases that cited a dropped one
cite the kept one or state its formula.

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

* Kernel rig: five cases outside fillets repeat a kept case's regime and go

A square cut is the slanted plane's ordinary crossing; the cam's half turn is the quarter turn's
contact at a symmetric angle; the r15 slab stands inside the same coincidence tolerance as the r10;
the 90 degree unfold and the 40 degree flange repeat the 72 degree and 20 degree cases' formulas.

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

* Kernel rig: 3243 rim and tooth join graded on its volume (rim plus the trimmed tooth less their 0.01 mm overlap)

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

* Kernel rig: 3782 emboss on a cylinder graded on its volume, area, faces and box (the wrapped 4 x 5 badge raised 1 mm)

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

* Kernel rig: 3604 G2 swept-tool cut graded on a volume range (filleted box minus the swept corner section)

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

* Kernel rig: 4505 coil joined through a cylinder wall graded on its volume, area and box (cylinder plus the wire's screw-swept segment outside r 76)

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

* Kernel rig: 4879 window cut through a thickened spline revolve graded on a volume range and its faces

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

* Kernel rig: 4623 join of crossing pleated loft skins graded on its volume and box (both skins less their overlap)

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

* Kernel rig: 831 tapered glyph extrude graded on a volume range and box (the font's S drafted in by 10 deg)

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

* Kernel rig: 2357 ring of 90 cylinder segments graded on its volume and box (the segments less their 90 joint overlaps)

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

* Kernel rig: 4542 fluted wall joined on a revolved base graded on its volume and box (the base by Pappus plus the fit-spline ring)

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

* Kernel rig: 1985 and 2243 STEP-export enclosures graded on their volume and box (the outline less the cavity, plus gusset and standoffs)

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

* Kernel rig: both 4092 loft, ribs and pad unions graded on their volume and box

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

* Kernel rig: 1525 graded on its volume (the convex body's exact 2 mm inward offset), faces and box

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

* Kernel rig: 1494 graded on its volume (box and cylinder less the 1 mm-offset cavity, sharp to rounded concave edges) and box

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

* Kernel rig: 4492 graded on its box and a volume range (the face's 19 mm sweep added, within the block's sides)

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

* Kernel rig: 745 graded on its volume (the end moved 1 mm plus the spline boss's exact area), faces and box

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

* Kernel rig: 4567 graded on its volume (the revolved spline less its revolved 2 mm offset), faces and box

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

* Kernel rig: the up-down squares shell graded on its volume (the columns less the 0.346 mm-offset cavity, sharp to rounded concave edges) and box

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

* Kernel rig: 545 graded on its volume (the frame less its 0.5 mm-offset cavity, sharp to rounded concave edges), its sealed pillar void and box

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

* Kernel rig: the shell-again case graded on its volume (the shelled box less the 0.5 mm-offset cavity in its walls) and box

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

* Kernel rig: the arc-sided plate shell graded on its volume (sharp to rounded at the hole's bottom), faces and box

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

* Kernel rig: 616 graded on its volume (the lid less its 0.5 mm-offset cavity, sharp to rounded concave edges) and box

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

* Kernel rig: 391 graded on its volume (the shelled housing, sharp to rounded at the flange, less the letters' 0.6 mm deboss) and box

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

* Kernel rig: shell/3576 was the ladder's drafted box with rounds shelled, recipe for recipe; the ladder case grades it and carries #3576 and #3573

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

* Kernel rig: the two 851 chamfers of 2 graded on their volume (the cone's sweep, and its union with the front edge's prism)

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

* Kernel rig: the chamfered corner of the lipped shell graded on its volume (the chamfer's triangle through the wall)

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

* Kernel rig: the concave round into the peg's cylinder graded on a volume range (the torus it must add, the band it can't leave)

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

* Kernel rig: the two folded 72 degree trays graded on their volume (flat area x thickness) and the walls' reach

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

* Kernel rig: the flanged rhombus graded on its volume (flat area x thickness, all four reliefs cut) and its walls' ends

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

* Kernel rig: the base with two adjacent flanges and a hole graded on its volume (base, bends and panels folded)

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

* Kernel rig: the flange beside the ledge graded on a volume range (the flange's two readings of its length)

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

* Kernel rig: the two G0 window patches graded on the box the part they close keeps

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

* Kernel rig: the hub's six swept grooves graded on its volume (the revolved hub less each tube's part inside it)

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

* Kernel rig: the cut mirrored loft graded on the box its sections pin

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

* Kernel rig: the three modelled-thread joins and the fine M48 graded on their volume (the basic profile's groove, Pappus)

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

* Kernel rig: the oblique-cut round graded by its seams (tangent, not curvature-continuous); the flanged rhombus says what it tests (its reliefs stay apart at 60 mm)

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

* Kernel rig: 3841 is 168's rim touching its coin along a circle, and 4092's reduced recipe is its report less a flange clear of the failing join; the kept cases carry their sources

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

* Kernel rig: the oblique-cut round keeps its old grade until the new blend algorithm lands

Its seams grade needs the continuity check to skip the body the round is not on. Fillet cases
wait for the new algorithm.

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

* Kernel rig: 1985's enclosure is 78 x 77 (74 and 73 inside, 2 mm walls); its volume 38063.827 by hand

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

* Kernel rig: the three spline walls' face counts wait for the one-wall decision; their volumes grade them

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

* STEP import: a face bounded by seams alone runs its loop the other way, never turns over

A loop that uses each of its edges once each way (a whole torus within its tube and ring seams)
is the same set of uses run backwards, so it says nothing of the face's side. One written
clockwise in (u, v) turned the face over, against its same_sense: the torus came in inside out
with a warning. Such a loop is now reversed and the face keeps its side.

Test: seam_only_torus_imports_as_a_whole_ring, both orders of the two seams, volume 2π²Rr².

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

* STEP import: a face bounded by a slit alone is the whole of its closed surface

A loop that runs out along a chain of edges and straight back (a meridian from a sphere's pole
stopping short of the other, out and back) encloses no (u, v) area, yet on a closed surface it
bounds all of it less a cut of none. The reader only read such a face when the slit already ran
pole to pole (and moved the sphere's frame onto it only then), so the 2 mm ball whose meridian
stops at 2.5 rad was refused for its empty loop.

The slit is now made the face's seams, so its loop bounds the surface's whole domain: on a
sphere, a revolution whose profile ends on its axis, or a spline closed round u that collapses
at both ends, the slit's meridian is run on to both poles, out and back (a sphere's frame put
on the slit first, whichever two points it joins); on a torus, the circle the slit lies along is
closed and the other circle through its start is the second seam. The seams on a sphere are its
own meridians (angle 0 on the equator), so their pcurves are exact lines on its form.

Tests: non_antipodal_seam_sphere_imports_as_a_whole_ball passes (its known-failing line goes);
seam_only_torus_imports_as_a_whole_ring adds a torus bounded by a slit along its tube.

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

* Rig: the test-peg's concave arc and front top edge rounded together stay within the body's box

A new fillet case, report-fillet-concave-arc-and-front-top-edge-together-solver-residues:
the reported part's bar (with the sketch solver's residues) whose sides run tangent into the
cylinder, its concave arc and front top edge rounded r2 together. The plane of the front face
bounds the whole body, so every blend lies within the unblended body's exact box (SMLib's tight
box); graded to 4e-5, the corner patch's own tolerance. On SMLib 2fc375f the corner patch
stands 0.0374 out of the front face (box y min 0.0205): listed in KNOWN_FAILING.

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

* Rig: the test-peg corner at r5 and r7

Two more fillet cases, the reported part's concave arc and front top edge rounded together at
r5 and r7, with the same exact-box containment check (graded to 4e-5, the corner patch's
tolerance). On SMLib 2fc375f the corner patch stands 0.128 (r5) and 0.230 (r7) out of the
bar's front face (box y min -0.0699 and -0.1723): listed in KNOWN_FAILING.

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

* Rig: blends that end where their face runs tangent into another

Fifteen fillet cases on the test-peg's shape with clean numbers: a bar 40 x 16 x 10 whose far
end is a half disc R8, a disc R8 standing 10 on its top, joined, so the front face runs tangent
into the cylinder along x = 40 and the front top edge (convex) meets the concave arc of the top
tangentially there. Each blend ends on a face tangent to the one it rolls on.

- Rounds: both edges together at r2 and r5; one after the other in either order at r2, and at
  r1.5 and r3; the front edge alone as a curvature round, whose end on the cylinder is a
  semicubical cusp. The containment invariant (the unblended body's exact box, to 4e-5) and
  G1 seams, or the analytic volume where the blend has a closed section.
- Chamfers: the front edge alone and the concave arc alone (analytic volumes: the section along
  x up to the wall, the cone's spandrel ring clipped by the sides), both together, and one after
  the other (1 then 2).
- STEP: the peg written to STEP and read back, its front edge rounded (analytic) and both edges
  rounded together.
- A cone: the peg drafted 5 deg, whose front face runs tangent into a cone along a generator,
  its front edge alone and both edges together.

The quadratures are tanh-sinh, converged to 1e-13; SMLib's own volumes of the passing analytic
cases agree to 4e-9. A sphere is left out: a plane meets a sphere tangentially at a point, not
along a seam, so it is not this ending.

On SMLib 2fc375f eight fail, listed in KNOWN_FAILING: every chamfer that ends tangent (status
1001, SmFilletNx1Corner), the two rounds together (the corner patch 0.0374 at r2 and 0.128 at r5
out of the front face, and the rounds meeting with normals 180 degrees apart where their
contact lines on the top cross), the same on the STEP-read peg (status 1001) and on the drafted
peg (its corner refused as unsupported).

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

* Rig: fillet corners graded curvature-continuous, the default to be

Six fillet cases that pick their edges with no type, so they take the fillet's default, which the
owner has ruled is to be curvature-continuous (G2) at every corner, with circular rounds kept as
an option. Each grades every seam of the rounds and of the corner patch G2, on one valid body:

- an oblique convex corner (a block drafted 15 deg);
- a four-edge and a five-edge apex corner (pyramids lofted to a point);
- a pocket's concave corner;
- a mixed corner (a step: one concave edge, two convex);
- a corner beside a cylinder (the top corner of a half cylinder).

All six fail on SMLib 2fc375f, listed in KNOWN_FAILING: today's default is the circular round,
whose seams jump in curvature by 1/r. On the five-edge apex the corner patch also meets two
rounds at 8.45 degrees (G0).

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

* Rig: the test-peg's front chamfer builds

With the SMLib fork's smlib-tangent-ends (a rail's end on its side edge is the rail/edge
crossing), report-peg-front-chamfer-d2 passes. Its line leaves KNOWN_FAILING, for the pin that
carries that fix.

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

* Rig: the curvature-continuous corner cases pick the curvature fillet

The six corner cases added in 3a854104a took the fillet's default type, on the premise that the
default would become curvature-continuous. The default stays the circular round, so each case
now asks for the curvature fillet itself (type curvature: curvature-continuous sections and
corner patches) and keeps its grading: every seam of the rounds and of the corner patch G2, one
valid body. They are renamed from cov-fillet-g2-default-* to cov-fillet-g2-*.

The oblique three-edge corner (a block drafted 15 deg) and the pocket corner pass. Four fail, as
KNOWN_FAILING now says:

- the corner beside a cylinder: the curvature fillet builds its sections only between planar
  faces;
- the four- and five-edge apex corners: refused because the default setback is shorter than
  where the rounds' contact lines meet (3.90 and 3.51 mm from the apex);
- the mixed corner: SMLib sets back a corner only where its edges are all convex or all
  concave.

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

* Rig: the test-peg's concave chamfer, alone and after the front chamfer, builds

With SMLib's tangent end as an event of the fillet's trace (blend-end-event 6d73e4f), the
concave chamfer's rail on the cylinder ends where it crosses the seam along which the front
face runs tangent into the cylinder, continued past the vertex, and its end rib on the front
face is built from there. report-peg-concave-chamfer-d2 and
report-peg-front-chamfer-d1-then-concave-chamfer-d2 pass and leave KNOWN_FAILING. The two
chamfers together still fail, now at the mixed-convexity corner where they meet: its line says
so. Rig against SMLib 0cc48bd: no area regresses (963/993 pass, 961 on a87c502), ladder 168/168,
kernel lib only its listed failures.

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

* Kernel rig: constant rounds of the exact classes, and a tee's round

A plane cutting a cylinder or a cone at an angle, a sphere or a cone coaxial with the other face, a
cone and a cylinder touching one ball, two pipes touching at a point: their rounds' centre lines
are rational, so the rounds are rational surfaces of degrees fixed by the construction ((4, 2),
(2, 2) revolved, (6, 2), (12, 2)). The tee's centre line is not rational; its round is graded on
its volume. Volumes from the divergence theorem over the exact pieces, at rel 1e-6. The six that
SMLib 0cc48bd traces are listed in KNOWN_FAILING with what they come out as.

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

* Kernel rig: the exact-class rounds pass on SMLib's classified exact rounds

The rounds now come as the rational surfaces their classes give, closed ones as two faces split
at their seams, the touching pipes' as four: one splines entry a face, each of the class's degrees.
Their KNOWN_FAILING lines go.

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

* SMLib: pin 7ac037e (the new blend's tangent-end event, zero-speed G2 ends, and its exact-class rounds)

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

* Kernel rig: the fillets known-failing list is the 22 cases that fail on SMLib 7ac037e

Twelve listed cases build on the new blend's end event and exact classes, or were already
built on develop: they come off the list.

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

* Kernel rig: #316's bottom rounds as large as the wall, or larger, shell with a step wall at the opening, not refused

Where the round meets the open bottom the cavity's wall steps down along the bottom's normal, so the
round meets it at 90 degrees and the opening is exactly the picked face. The cases are renamed and
graded on the exact volumes, and come off the shell known-failing list.

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

* Merge fixes: every crate and test target compiles on the merged tree, but for crates/geom's hlr

world_state, view::bounds, home_camera and scene return Result on develop; new
main code takes that with ? or session_world. New fixtures carry the spline's
nurbs. ForeignCopy::of returns Result. occurrence_placement_with walks the path
with its moves, since the placement index holds pending_moves only.

The hlr port is separate and still to come: this commit does not compile as it
stands in crates/geom/src/hlr.

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

* Drawings hidden lines: read develop's B-rep records

Edges come from the record's edge list, cut to their interval (C3::of_edge), and
faces from its face list; a cylinder, cone, sphere or torus chart takes its bounds
from where its NURBS form lies on its label (S3::of_face). A rigidly placed
revolution has main's revolution chart; other placements stay refused. Moving a body
to its centre goes through Brep::moved. Loops are read against the natural normal of
the face's parameters where a chart wraps.

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

* Hidden-line fixtures: build develop's records from the fixtures' data

Fixtures keep their bounded curves and surfaces and their own edge numbers; a private
builder makes the canonical record, and the local prepare puts the edges back in the
fixture's numbering. Assertions are unchanged.

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

* Hidden lines: tests of the bounded edges, face charts and placed revolutions

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

* drawing-hlr doc: what is drawn and refused on develop's records

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

* hlr tests: a placed revolution is refused when its placement is not rigid

The new model has one record for main's Revolution, which was drawn, and its
PlacedRevolution, which was refused. An identity placement is now the former, so
this test's fixture takes a non-rigid one. The refusal it asserts is unchanged.

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

* Hidden lines: a traced intersection is drawn only where its spline lies on both surfaces

A leader off either surface is refused like a curve with no spline to follow. Tests: a traced rim
drawn on the circle and plane within tolerance, the same leader 1 mm off refused, and kernel bodies
with arcs through the production prepare against closed forms.

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

* Hidden lines: kernel bodies through the production prepare against closed forms

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

* Tests from main: the world state is a Result here

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

* A blend's pick measures close edges on their curves, not their polylines

A polyline can be a chord off its edge, more than two edges are apart near a
tangent point. A pick exactly on a ball's rim took the straight edge the rim is
tangent to, and solidcore built a chamfer of that edge with a clean check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
(cherry picked from commit c786db6bf4)

* Merge fixes from the first SMLib test pass

Model cache key covers the crates develop's kernel adds under doc (profile, smlib, step).
A piece taking a name also takes the feature that made it: SMLib lists a cut's pieces in another
order, and the swap left the name on a piece of the wrong feature.
A replaced analysis keeps its status slot. A command with no world picks does not place the model
first, so assembly.mass_properties answers for a flattening placement. FeatureCommon loses the
touching key the merge added; only the contracts that read it take it.
Two of main's tests listed as failing on SMLib, with why.

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

* Merge fixes from the ui test pass and the gates

Main's thread.direction keys go with develop's thread hand, which replaced them. Vietnamese for the
messages develop added while main made vi a complete catalog. CGA and 1-bit themes set develop's
comb and section hatch colours. A ribbon button with a persistent effect is pressed only while its
tool runs, so persistent_ribbon_pressed alone decides how its effect is shown; other analyses stay
pressed while on. Two of main's ui tests listed as failing on SMLib, with why.
Gates: fixed thread tables become slices, one collapsed if, rustfmt.

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

* A silhouette takes its facing from the surface's normals

A flat triangle beside the outline can tip the other way from the surface it approximates, and its
edges joined the outline. On SMLib's sphere that made 192 segments instead of the equator's 64, 100
curves and 95 profiles from the tight fit of #7989. Each corner's normal is used where it is close
to the triangle's own; a mesh without such normals (none, zero, not numbers, or averaged across
creases) keeps the triangle's normal, as before.

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

* Clippy and rustfmt on the merged tree

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

* Stepwise open checks every feature under the session's mode

OpenSteps::start evaluated under the default check mode, and finish kept those results under the session's mode: an each_step or off session opened in steps recomputed nothing. start now takes the mode (the app passes its session's), and a mode that changed before finish evaluates the design again under the new one.

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

* Clippy without SMLib: the planar-sheet edge reader is built only with it

Its one caller is in the smlib-only sheets module, so without the feature it was dead code and
clippy -D warnings refused the kernel.

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

* SMLib: pin d01d7b0 (the new blend's one Gⁿ section generator: the chord, circle and curvature sections, and the variable rounds as the union of their balls)

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

* Kernel rig: the section orders, chord, circle and curvature section, on planes and against a cylinder

Eight fillet cases for the blend's cross-section by its order of contact with the faces, each
graded on its analytic volume at rel 1e-7 and its exact continuity (the order, and not the next):

- a box's top back edge chamfered 3 (G0), rounded r3 (G1) and curvature-rounded 3 (G2): the
  areas cut off the corner s^2/2, (1 - pi/4) s^2 and 113/756 s^2, the last by the default
  thumbweight's definition;
- a pipe R10 dipping into a plate, its concave edge chamfered 2 (the chord between the contacts of
  the ball SMLib rolls for that setback, radius 1), rounded r2 and curvature-rounded 2, each
  section's added area by Green's theorem along the plate, the section and the pipe's arc; the
  order-2 section is built from the faces' feet of the point at distance d from both, which on
  the pipe are not linear in d;
- a boss root curvature-rounded 2, the square corner's section revolved (Pappus);
- a variable round r1 to r5 along a box edge (rel 1e-5): the balls' envelope is a circular cone,
  and the volume is the cone's sections integrated along the edge.

Three fail today and are listed: the two curvature rounds whose faces are not a square corner of
planes (SMLib's quintic blend curve, not the order-2 section: rel 6.5e-7 and 1.2e-5) and the
variable round (great circles, not the envelope's: rel 4.8e-5). The other five pass on SMLib
7ac037e.

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

* Kernel rig: the curvature fillets' expected bodies from the order-2 section at thumbweight 0

SMLIB_BLEND_CURVATURE's section is now the order-2 G^n section at thumbweight 0. Between two
planes square to each other it cuts A = 0.154192655384275 s^2 off the corner, its first moment
about either face 0.0291753295725360 s^3 (Green's theorem on the section): the curvature cases
built on that corner (862, 2708, every-box-edge, every-hole-top-rim, every-l-bracket,
every-block-cylinder-crease, the three-edges-at-bar-corner reports, 3604's fillet) take their
volumes from it by their own methods, the peg's front blend 23.3237146876 (its section along x
up to the wall). Between planes the section's t^3 term vanishes (their feet are linear in the
distance): the box edge case is exactly G3. The boss root is a certified fit, G3 to the plate and
G2 to the wall at Continuity Analysis's tolerances: at least G2. The boss root and the pipe on
the plate pass on SMLib's section and leave the known-failing list.

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

* Curvature fillets: the section's default thumbweight is equal area, so the round's volumes are the circle's

On the round's rails the order-2 section takes off what the ball's circle does: (1 - pi/4) s^2 at a
square corner, first moment 0.0474559669443 s^3 (w = -0.5252123889). The kernel and engine tests
and the curvature rig cases take their volumes from it by their own methods; the pipe on the plate
from its own wedge (70.52878 degrees, w = -0.8544103493). The G2 corner patch's seam test is listed
until the corner step.

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

* Kernel rig: four G2 corners wait for the corner step: their patch meets the new section's rounds G1 at one face seam

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

* Kernel rig: the turn at a corner of two rounds, Mitre (the default) and Swept, at a pocket's floor and a boss's base, by exact volumes

A round's spandrel A = (1 - pi/4) r^2 and its first moment about its wall M = r^3 (5/6 - pi/4):
Mitre adds L A -+ 2 M per edge (cut at, or run on to, the corners' bisector planes), Swept turns
the section about a convex corner's edge, (pi/2) M a corner, and refuses a concave corner rounded
less than the ball. The pocket's tangent chain round its R3 corners is a torus each. Graded to
1e-7, where Mitre and Swept differ by 6.6e-6. The Swept cases are listed until solid.fillet
takes a turn.

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

* Kernel rig: a junction family: oblique wedges 30 to 150 degrees rounded and chamfered (exact), bosses upright and tilted, pipe tees and a Y junction

Four are listed with their causes: the equal-pipe tee (its exact conic-pair rounds meet at the
ellipses' crossings in a corner the old corner code cannot join), the tilted bosses (1001, before the
blend rework too) and the Y junction (refused for room at its triple points).

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

* Variable rounds: graded by the union of their balls (their envelope), not the balls' great circles

The smlib crate's laws and ladder 005, 006, 007 and 012 take the volumes the balls' envelope
removes; the law with a corner is its own test, listed as failing until the corner step.

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

* Kernel tests: variable rounds graded by the union of their balls, the removed volume to 1e-4 of itself

removed_by_balls integrates, in each plane square to a right-angled edge, the corner the union
of the balls leaves (by rays from the edge, the least entry over the balls), cut to the body's
section. The law with a corner through a middle point is its own test, listed as failing.

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

* Kernel tests: a spline radius whose balls fold over one another at its peak is refused

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

* Kernel rig: the variable round on its cone passes (the round is the balls' envelope)

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

* Tests: three rounds of different radii at a corner listed: the corner's transition kinks with the balls' touch circles

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

* Ladder: the variable rounds graded to 1e-6 by the union of their balls; 012 by its law going on past the edge

At the default 1e-3 the great circles' volumes (4e-6 off) passed too. SMLib builds each within
1.6e-7. The golden takes the new volumes and areas; counts unchanged. The G2 corner test listed
as failing passes on SMLib's step 3 and leaves the list.

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

* Reference tests: the variable rounds 77, 78 and 81 pass on SMLib d01d7b0; their known-failure marks come off

The new section builds their edges on the surfaces, so STEP export no longer finds them 4.6e-5
to 6.1e-5 off. develop's Reference tests (60393a718) report all three as passing while marked.

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

* STEP import: a ball's halves whose seams are off each other's quarter lines each form within one turn

A sphere split into two halves along a circle about its axis, each half covering a pole with no
seam, gets a seam per half from the first vertex of its loop to the pole. The circles between the
halves group their frames so they turn together, and when the two seams are not a whole number of
quarters apart no one turn fits both: the group kept its frames, a half's form ran past one turn,
SMLib's trim (SMLib and solidcore alike) across the form's end crossed the face, and the mesh
folded over the ball (a rotor's ball showed torn; its half had 196 times its area in triangles).

When no one turn fits the group, each spilling face is now turned on its own to start on a quarter
line. On the rotor file this also clears three other faces that were filled leniently and a
0.076 mm edge gap; regression test in step_in/tests.rs with the area and volume of each half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz
(cherry picked from commit b3188e005d427043b918267449c2336a1777e54f)

* SMLib: pin 5e8c9c1 (the new blend's corners: Ball and End/Join; exact fold and room checks with fit sizes; overflow in place; the round as its definition)

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

* Kernel tests: a law's dip shows its corner ball's sphere, a peak's two rounds cross (the reasons had them swapped)

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

* Reference tests: the variable rounds 77, 78, 80 and 81 expect the union of their balls

A variable round is the envelope of the balls rolled along the edge, which takes less than a
quarter round in each section. The expectations are now that union, the law continued past the
edge's ends along its end slopes, integrated by Gauss-Legendre panels in x and t; 2, 4 and 8
panels agree to 1e-9 of the body's values, and 77's removed volume matches a brute-force union of
balls to 2.5e-10. The nearest-ball search now scans the balls the ray meets around ball x
(|x - s| < r(s) sqrt(sin 2t)) on their own, which near the faces and where 78's radius closes to 0
are narrower than the global scan's step. Regenerating keeps each case's known_failure. 79, a
constant radius, is unchanged.

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

* Known failing: the law corners' kernel and crate tests and rig 1207 pass on SMLib 48ea616

SMLib builds a variable round's law corners as its balls' union (the corner
ball's zone at a dip, the sides' crossing at a peak): the kernel's corner
through a middle point (39.5410) and the crate's 4 -> 2 -> 4 law (39.5421) pass.
Rig 1207 (the bar's top edge rounded after the boss's arc, r0.5) builds and
grades on the same SMLib; it failed on c820346.

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

* Kernel rig: every fillet and chamfer case rounds its tangent chain

The owner's ruling of 2026-11-01: the rig and the ladder round with tangent_chain true, how a
user's default pick behaves. The 38 fillets-area recipes and the ladder's 07-fillets/016 and 018
that passed false now pass true. The shell recipes keep theirs: a shell's tangent_chain is its
open-face chain, a different feature.

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

* Kernel rig: the partial-selection cases kept as -partial-chain copies, tangent chain off

Where a case's point is one edge of a tangent chain rounded alone, a copy keeps tangent_chain
false as its own case (SMLib rounds exactly the picked edges, ending at the smooth vertex); their
expectations stay as they were, to be re-derived:
- fillets/1207-fillet-box-end-edge-into-unblended-round-partial-chain;
- fillets/every-fillet-after-fillet-curvature-front-then-side-r3-partial-chain;
- fillets/1207-fillet-bar-top-edge-after-boss-arc-round-partial-chain, renamed from
  ...-no-tangent-chain: with the chain on it is exactly 1207-fillet-bar-top-edge-after-boss-arc-round,
  which the rig already has;
- ladder/07-fillets/020 and 021, the r5/r3/r7 and r5/r3/r2 corners (016 and 018) with the r3 on
  its edge alone: with the chain on, the r3 runs on along the r5 round's end arc and down the
  vertical edge the third round was to take.

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

* Kernel rig: the concave arc between the block's tangent sides kept with tangent chain off

fillets/every-block-cylinder-tangent-concave-arc-r2-partial-chain: the pick takes one piece of the
concave half circle, which SMLib splits; with the chain off its other pieces are left out, and the
blend is refused at the piece's end (the base gate's outcome). With the chain on the case rounds
the whole half circle. The copy keeps its expectation, to be re-derived.

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

* Kernel rig: two chains that now build graded by their exact volumes

With the tangent chain on, two cases round more than the edge they pick, and their old
expectations were the single edge's:
- 1207-fillet-box-end-edge-into-unblended-round: the r1 chain is the end face's top edge, the r3
  round's end arc and the back upright; 34 A + (pi/2) A (3 - c) by Pappus on the arc:
  V = 7953.139216 (it built 7953.139216, the old 7957.505 passed only inside 1e-3), 10 faces.
- 851-fillet-top-front-then-front-left-upright: the upright's chain runs up the first round's
  end arc on x = 0 and along the left top edge, so the corner becomes the R2 ball's eighth:
  V0 less the first round's spandrel to the wall (35.344727) less 27 (4 - pi) + 2 pi/3:
  V = 9530.777979 (built 9530.777987), 10 faces.

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

* Kernel rig: a curvature chain round a round's end refused for its size; the concave half circle builds

- every-fillet-after-fillet-curvature-front-then-side-r3: the side edge's chain runs round the
  first curvature round's end curve, whose radius of curvature has a weighted mean of 3 (its
  setback) and is unbounded at the ends, so somewhere below 3: the size-3 blend's contact line
  folds back on itself. Refused, "larger than the faces beside its edges allow", as ladder
  07-fillets/016's r7 on the r5 arc. (It was solid or refused for running on tangentially.)
- every-block-cylinder-tangent-concave-arc-r2-no-tangent-chain: the chain takes every piece of
  the half circle and ends at the convex edges it runs on into; it builds the analytic 19091.9620,
  so the outcome is solid (the refusal alternative was the chain-off one's).

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

* Known failing: two 851 chains whose corner piece is a ball of tube-centre radius 0

851-fillet-arc-then-top-front and 851-fillet-top-front-then-upright: with the tangent chain on,
each R2 chain runs along the end curve of an R2 round on a curved face (the arc round's end on the
front face; the first round's end on the wall), where the rolling ball sits on that round's own
centre line, so the piece is the ball about (39.2538, -19, 8): the three-edge rolling-ball corner.
SMLib fails with status 1001; the same piece on a plane end arc builds (front-left upright).
Expectations unchanged.

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

* Ladder: 016 and 018 refused, their r3 chain takes the vertical edge; 020 and 021 recorded

With the tangent chain on, 016's and 018's r3 runs on along the r5 round's end arc and down the
vertical edge, so the third pick (60, 40, 8) is 3 from both of that round's contact lines and
names no edge: refused, asking for a point along one edge. The corners they were made for are
020 and 021 (the r3 alone), whose golden entries are 016's and 018's (021 measured on f12a101).

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

* Kernel tests: three rounds at a box corner end on one rational patch on the ball's sphere, not an analytic sphere

SMLib's Ball corner is always the rational patch exactly on the sphere (owner ruling, blend
design §23), so the corner counts as an 'other' face beside the three cylinders and six planes.
The removed volume still checks the corner against r³ − πr³/6.

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

* Kernel rig: the curvature round on a pipe on a plate is listed: a seam grades G3 where exactly G2 is wanted

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

* Kernel tests: the box-and-fillet wire fixture records the round's cylinder frame as SMLib now keeps it

The round's cylinder keeps the frame it was made on when it is turned (SMLib's SwapUV no longer
re-derives it): its origin sits at x = −20 on the same axis line, (x, 2, 8) along x, radius 2.
Same cylinder; one number in the record moves.

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

* Known failing: two kernel tests' reasons as they fail on SMLib 5e8c9c1 (a partial selection running into an unblended tangent edge)

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

* Kernel rig: two curvature rounds get 600 s, past the 180 s default, while G2 certification is slow (tangency-labs/smlib#96)

On the CI runners the boss root and the hole rim, both size-2/size-1 curvature rounds, build
correctly but take more than 180 s: the order-2 section's span certificate and the round's
definition are evaluated station by station. The per-case limit is the one the thread cases use.

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

* Lid ring sketch test: find the ring under the pick, not by main's kernel's name

On SMLib the lip join's exact history names the lid underside's ring a piece
of the lid's own start (F14:start#[...]), as the naming rule says for a face
that came from the feature's input; main's kernel named it after the case's
top it rests on (F2:top). The test looked the ring up by that name and so
stopped before the attachment it guards. It now takes the underside piece
under the pick point, which the cap certificate attaches on either kernel.

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

* Viewport culling test: each box's triangles from its mesh, not 12

SMLib meshes a 10 mm box in 108 triangles, so the hard-coded 12 a box stopped
the test before it reached the culling. The expected counts are now each
body's span in the scene.

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

* Timeline chip test: a fillet that really reduces

The R20 fillet with fit reduce on one top edge of a 10 mm box builds on SMLib
without a reduced-radius warning, so the fixture had no warned feature. It
now rounds the four vertical edges at R6, reduced to the full round (5), as
the copy-warning test beside it does, and checks the warning says so.

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

* Oracle: 34-chamfer-then-fillet and 35-fillet-curved-curved pass on SMLib 5e8c9c1 (their STEP edges are on their surfaces)

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

* Known-failing: the shell of a fully rounded box opened at one face fails on SMLib 5e8c9c1 (tangency-labs/smlib#102)

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

* Known-failing: the action matrix's oblique cylinder cut fillet passes on SMLib 5e8c9c1; two shells still fail

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

* Kernel rig: the boss root's curvature round gets the rig's 1800 s cap: on the CI runners, 8 cases at a time, its G2 certification passed 600 s (tangency-labs/smlib#96)

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

* Kernel rig: a case's continuity can ask for an order per seam (seams: [{order, edges, exact}])

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

* Kernel rig: the pipe on a plate's curvature round meets the plate G2 and the pipe G3, 40307.8856312 (option B's order-2 jets, tangency-labs/solidcraft#10845)

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

* Edge names: a seam names its one face once; the curve-id test reads every edge kernel-neutrally

A seam's two sides are the same face, and edge names list each face an edge
bounds once, so SMLib's extruded circle names its side's seam F2:side:c1, with
no separator. The name grammar now says one face also means a seam. The #5858
test no longer counts one '|' an edge: every part of every edge name is a
whole face name of the edge, one per face it bounds, two for the rims and one
only for the side's seam (checked to run bottom to top on the circle).

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

* rustfmt on the merged tree

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

* Known failing on SMLib: two of main's new tests assume truck's box mesh and fillet torus

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

* SMLib: pin f916a8d (a round on a definition face is its definition; the G1 setback fill certified convex, setbacks too short refused naming the least; the G2 certificate halves a span no level can certify at once)

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

* Fillet: a setback too short for its corner is refused naming the least that builds (smlib_blend_fit_size)

SMLib's setback corner takes a G1 fill only once certified convex, and refuses a setback too
short for that as SMLIB_ERR_NO_ROOM with SMLIB_FIT_SETBACK_AT_LEAST, the smallest setback that
builds. The kernel reads it (solidcraft-smlib binds smlib_blend_fit_size) and names it; the
Fillet dialog says it in plain words. Other room refusals keep their words. The r5 box corner
set back 5.5 is refused naming 5.962, which builds.

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

* Kernel tests: three rounds of different radii at a corner meet in SMLib's setback corner

Each round runs whole to its section square to its edge at its setback (the owner's rule, at
least 1.5 r, scaled to the least at which the G1 fill is certified convex), and the hole is the
setback split: one patch per round and the triangle in three quadrilaterals, 15 faces, G1.
Outside the box the sections span the body is the three strips', so its volume is theirs within
that box's. A later r3 round at the mitred corner makes the body all three at once make
(7928.142563938, 15 faces), as the engine and the model-cache tests have it.

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

* Kernel tests: SMLib's own corner between curvature rounds is curvature continuous, as the G2 patch is

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

* Kernel rig: the boss root's and the hole rim's curvature rounds take the default timeout again (37 s and 34 s on the box under the rig's parallel load, tangency-labs/smlib#96)

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

* Agents: model decisions use exact geometry; a mesh is for display, export, previews and mesh bodies

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

* xtask: mesh-decisions, model decisions use exact geometry; today's 176 mesh decision sites are listed and the list only shrinks

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

* Sheet metal: cuts and holes pass over sheets and bends by their exact boxes, never a mesh's

A primitive cut skips the sheets kernel::apart (exact bounds grown by the record tolerance)
finds clear of its tool; the bend check leaves the clear case to kernel::boolean, which answers
it from the same exact boxes with no intersection work. The mesh-box helpers are gone, and their
four lines leave xtask/mesh-decisions.txt (172 sites left).

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

* Agents: AGENTS.md loses a stray merge-conflict marker line at its end

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

* Locales: Vietnamese for the setback-too-short refusal, now that vi is a complete catalog

Main made vi a complete catalog; develop's new message (ui.fillet_error_setback_short) had no vi
entry, so every_complete_catalog_carries_every_english_message failed on the merged tree.

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

* Kernel rig: exact volumes for 1213's bent-tube round and 4782's bar round, rel 1e-6

1213: the loft is a cubic skin through the five sections (ellipses between
them), not a torus; its volume 131601.597487935 by quadrature less the exact
rolling-ball removal 233.662461919 gives 131367.935026 (was a Pappus estimate
on a torus, 131360.148, at rel 5e-4).

4782: the section's area in closed form, 25 pi/3 - 5 sqrt(75) + 25, so
V = 630.2934873 (was a grid estimate, 630.293125).

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

* Kernel rig: the boss root's and the hole rim's curvature rounds take 600 s again: under CI's parallel load they pass the 180 s default (develop 820b7b6)

They take 37 s and 34 s alone on the box, but CI runs 8 cases at once on slower runners. Their
G2 certificate cost is the open item (tangency-labs/smlib#106, moved to solidcore#2344).

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

* After the merge: clippy's auto-deref, and the mesh-decisions list for the merged tree

Develop's list was taken on develop alone. Main's code (tap-marking reach, cut and hole box
checks, join pieces, print faces, placed measuring, selection items, imported mesh payloads) and
the branch's own exact_edges and named_vertex use meshes too; three listed functions were renamed
by main (face_outline and plate_recesses into FaceSlice, paste into build).

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

* After the merge: main's new tests on the SMLib kernel

- Cut To the far side of a curved wall (#8634): the tool stops past the first crossing of the
  face ahead of the profile; SMLib's whole bore is one face that a line crosses twice, so the
  single-crossing rule found none and the cut took nothing.
- Clearing view analyses goes through refresh_display, as main's #10687 made the others, so an
  open feature editor keeps OK and Cancel after Zebra.
- vi, a complete catalog on main, gets the setback-too-short message.
- fillets/KNOWN_FAILING: 4782's cause is now its continuity check (SMLib f916a8d builds the
  expected volume).
- Known failing, as main's tests assume truck: the spline wall's spans as one mesh patch, a
  hole touching an edge at a point leaving one body, and a copy of a copy's triangles matching
  a fresh mesh.

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

* Twisted sweep: circles about a straight path are exact cylinders (solidcore#2184)

A twist about a straight path maps a profile circle centred on the path's
line onto itself, so its swept wall is exactly the cylinder. The skin
through the stations stayed within 3.9e-9 of it (6.6e-9 on the outer wall),
and a boolean with another body on that cylinder failed with status 1001
(solidcore#2184; the kernel's handling of a B-spline face lying on a
cylinder stays open there).

sweep_controlled now builds those walls exactly when the path is lines
along the profile's normal and there is no end scale or rails: a profile
whose every loop is such a circle is swept untwisted, and such holes in
another outer loop are cut as exact cylinders from the skinned sweep of
the rest. Other loops, and an axis-centred arc inside a loop of other
curves, are skinned as before; a failed cut falls back to skinning all.

Regression test in smlib_sweeps_tests.rs: #2184's tubes joined at 10, 16,
18, 20 and 30 degrees give one closed body of 11.5625 pi with cylinder
walls; a twisted hexagon with the bore keeps a cylinder bore.

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

* After the merge: a hole that misses is found by exact boxes; a face is a one-face patch; main's mesh-box helpers gone

- `hole_misses` (main's #10701, through brett's branch) decided from the targets' mesh boxes. It now uses their exact boxes (`Body::exact_bounds`). An unknown box still leaves the hole not known to miss.
- With #10303's two shortcuts and `hole_misses` off the mesh, `body_box`, `mesh_box` and `padded_box` have no caller left, and are deleted.
- `project`'s intersection link passes its one face to `meeting_edges` as a one-element patch. This branch picks one face, not #9009's mesh patch.
- xtask/mesh-decisions.txt drops the six sites that no longer use a mesh here: `apply_traced_with`, `cut_end_past_face`, `beyond_cylinder_reach`, `hole_misses`, `body_box`/`mesh_box`/`padded_box`.

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

* After the merge: split_move's Result reaches its callers; to_local's duplicated move check goes

`frames::split_move` returns a Result on this branch: an unplaceable model is an error, not a silent no-split.
- Move/Copy of a component (main's #9914) propagates it, as its other calls do.
- `to_local` had the solid.move check twice after the merge (brett's Option one and ours). Ours stays, after the no-picks/not-placed return.

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

* After the merge: main's two new tests take world_state's Result, as the rest of their files do

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

* Native B-rep: a ball patch is its own surface kind, read from and written to SMLib's flat kind 6

SMLib's flat arrays now carry the corner of three equal rounds as `SURFACE_BALL_PATCH` (kind 6): its net as a NURBS face's block, with its sphere's centre and radius as its definition (ids 36 and 166). The record reads it as `ball_patch {center, radius}`, a label like the analytic kinds, with the net as its `nurbs`. A plain sphere with a form won't do: the net is not a piece of SMLib's sphere NURBS, so SMLib's `IsNurbSurfaceSphere` would refuse it.

Written back, a ball patch is kind 6 with the same definition, which SMLib takes once its net is certified on that sphere. Elsewhere:
- Offsets classify it as its sphere, so its offsets stay exact and collapse at the centre.
- Face kinds count it as a sphere: fillet_three_edges_at_a_corner's corner is now `sphere`, no longer `other`.
- STEP, which has no ball-patch entity, gets the net as the face's B-spline surface, pcurves included.
- Iso curves and SSI of the label are refused; the face's form is what evaluates.

Test: a_rounded_cubes_ball_patches_go_round_with_their_spheres. The cube's eight corners are ball patches about the corners pulled in by r. Points of each patch lie on its sphere to 1e-12. The patches are kind 6 again after import, with the same definitions bit for bit, and the closed-form volume holds.

Format: pending/ball-patch-faces.rs. Docs: docs/native-brep.md and the rig README's `surface_types` (`ball_patch`).

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

* SMLib: pin v1.1.0-alpha (a18bbe5): the generic blend's step-4 corners, the Ball's sphere patch through the flat arrays, all-target CI

Since f916a8d:
- #120: every target again, plus the warnings fix;
- #121: the Ball's patch as flat kind 6 with its sphere, read by the previous commit;
- #122: tests gated on counts;
- #123: the Mixed corner;
- #124: the End at a smooth vertex;
- #125: four or more rounds, and open setbacks;
- #126: the equal-area thumbweight guard;
- #127: the trunk merge.

The SMLIB_PREBUILT checksums are from the release prebuilt-a18bbe5…, written by `scripts/fetch-smlib.sh --pin`.

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

* smlib export tests: rustfmt's line for the rounded cube's edge midpoints

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

* Kernel rig: the fillet and ladder results on SMLib v1.1.0-alpha, the new failures recorded with their reasons

Fixed (off the known-failing list): the Mixed corner's peg cases and the concave arc with the front top edge (smlib #123), and the 40° boss junction (smlib #124). Eight cases in all.

New failures, recorded as they are, each with its reason. They passed on f916a8d.
- **Six cases grade a blend seam G0 where G1 (or G2) is wanted,** at the end of the round's contact seam, after smlib #124's End at a smooth vertex. Two of them also shift in volume.
- **Four cases come back unsupported** after smlib #125, which leaves setbacks to the Setback's corners only.
- **Ladder 07/012** makes 9 faces, 20 edges and 13 vertices, where its golden had 8, 18 and 12. Its golden entry is the new result, with the reason added.

Whether each change is by design or a defect is being established. A defect is fixed in solidcore, where the End and the Setback are being brought over (#2364, #2365), not in the frozen SMLib.

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

* Kernel ladder: 07/021 and 06/018 recorded as they are on SMLib v1.1.0-alpha, each with its reason

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

* Kernel ladder: a golden case marked unstable is gated on its status, and 06/018 is marked

On SMLib v1.1.0-alpha, 06/018 makes 597 or 594 edges from the same input:
the union's post-process keeps pointers to edges it has deleted. A golden
entry's `unstable` reason makes the gate compare that case's status only
and name it in its output; recording keeps the reason, and only an edit of
the golden removes it. v1.1.0-alpha.1 fixes the cause and removes the mark.

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

* After the merge with main: its new code on develop's exact bounds, types and thread fields

- The sheet cut's cull (#10301) and the tapped wall's (#10227) use each
  body's exact box (`exact_box`), not its display mesh.
- model.threads replies with develop's thread fields: form, hand (right,
  left or both) and starts.
- surface.boundary's schema checks a network curve's continuity as the
  handler does, and words a list entry's error as it does.
- Main's new tests take develop's Hole fields and world_state's Result.
- mesh-decisions: contact_sweep's triangle check is now `depth` (main
  split `hits`).

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

* Known failing: the 14 cargo tests SMLib v1.1.0-alpha fails, with what each sees; two cases that pass now

Develop's CI after the a18bbe5 pin (run 41522001173) fails 14 tests that
passed on f916a8d, and passes two listed ones. Each new line says what the
test sees on v1.1.0-alpha; v1.1.0-alpha.1 fixes the setback hole's cause.
shell_tangent_chain_that_opens_every_face_explains_how_to_keep_walls and
oracle 37-pyramid-apex-blend pass and leave their lists.

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

* Cargo.lock: develop's profile, smlib and step crates at main's 0.30.1

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

* Kernel rig: main's six new sketch-only commands are exempt from a kernel case

sketch.construction_line (and its horizontal and vertical forms) and
sketch.spline.insert_point, remove_point and reverse change only the 2D
sketch.

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

* Cuts leave the bodies their tools can't reach, by exact boxes (#10225 on exact geometry)

- The per-target cut loop skips a body whose exact box lies clear of
  every tool's, as main's version did with mesh boxes. The sheet culls
  (#10226, #10301) and this one now leave the far sheets and bodies alone:
  a_cut_leaves_bodies_it_cannot_reach_as_they_were passes and leaves the
  known-failing list.
- a_cut_still_runs_where_its_tool_touches_grazes_or_meets_a_curved_face:
  a tool 0.5 µm clear of the block is now left out (exact boxes; 0.5 µm is
  far past the bodies' tolerances); touching and 0.1 mm in still cut.
- Known failing, with reasons: main's edge lookup by a stored point
  (#11286, on the display mesh) is not taken; SMLib refuses the
  wrong-way chamfer for its tangent chain first.
- EXEMPT: main's eight new sketch-only commands.

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

* Kernel rig: main's new view, rendering, appearance and settings commands are exempt from a case

62 of the commands main added change no geometry: the view and render
panes, rendering lights and results, appearance tools, settings profiles,
point clouds (drawn as points, never a body), the selection, two sketch
edits, component links and History bar visibility, edit.repeat,
timeline.play, inspect.compare_picture (a picture against the outline),
and component.paste_from_design, which pastes the app's cross-window
clipboard that no command fills. Each joins EXEMPT's first section under
its family's reason.

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

* Kernel rig: construct.plane.axis_point gets construction cases

The plane through a box edge and its far corner, graded against its
closed-form normal (0,-2,3)/sqrt(13) through the origin, and the refusal
of a point on the axis.

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

* Kernel rig: assembly cases for mass properties, align, find limits and moving items between parts

- assembly.mass_properties: two boxes (2 g/cm3) in a rigid group: mass
  0.006 kg, centre (18.333,5,5), inertia diag(0.1, 1.0333, 1.0333) kg mm2
  by the parallel-axis theorem.
- occurrence.align: a block's end face onto a base's top face: a quarter
  turn about y, centre (70,20,20), box (65,15,10)-(75,25,30).
- joint.find_limits: a slider between two stops: min -20, max 10 mm.
- component.move_features, browser.reparent and
  component.move_sketches.to_parts: items moved into a turned and moved
  part keep their world place (volumes, centres and boxes by hand).

The mass case assigns a material it creates, so material.create and
material.assign leave EXEMPT. PATHS names the new command modules.

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

* Kernel rig: export and print face cases in the import area

- file.export_step, export_stl, export_3mf, export_obj: a 30x20x10 box
  written and read back (the meshes converted to a solid): V 6000,
  A 2200, 6 faces, box (5,-10,0)-(35,10,10).
- file.export_urdf: a hinged arm on a grounded base: 3 links, 2 joints,
  and the base's mesh, written in metres, reads back as the
  1 x 0.5 x 0.2 box (V 0.1, A 1.6).
- body.print_face: a cylinder's flat end is accepted, its side refused.

PATHS adds the URDF writer and the print face modules.

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

* claim: #11259

* fix: preserve transferred profile frames when capturing positions

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: brett19 <brett19@gmail.com>
Co-authored-by: jyviko <jyviko@gmail.com>
2026-11-04 21:22:49 +00:00
Homero Thompson 1d387d3f21 Measure: the area of a curved face on the exact surface, not the display mesh (#9086) (#11486)
* Measure: the area of a curved face on the exact surface, not the display mesh (#9086)

* Measure: a non-straight border edge leaves the band to the mesh; fix the Vec3 imports (#9086)

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:22:24 +00:00
github-actions[bot] 51d237829a Regenerate generated files and fold pending files [skip ci] 2026-11-04 21:50:55 +00:00
bherbruck df4f9d3529 Inspect menu: Component Colors is a normal on/off item with a check mark (#11854)
* Inspect menu: Component Colors is a normal on/off item

The Component Colors entry in Inspect > Check was labelled "Enable Component Colors" and
swapped to "Disable Component Colors" with a different icon while on. It is now labelled
"Component Colors" in both states, keeps one icon, and shows a check mark when on, like Draft
Analysis and the other toggles beside it. The command id and parameters are unchanged.

The now-unused "disable" strings and the colored_components_off icon are removed. Other
languages' old "Enable ..." wording is dropped so they fall back to English until retranslated.

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

* Keep the Component Colors label in the fully translated catalogs

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

* Checkable menu rows tell screen readers whether they are on

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

* Georgian: Component Colors loses its Enable/Disable wording

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:12:36 +00:00
takuto-NA ef2f485e15 Extend viewport AO to the existing virtual ground (#3413) (#11979)
* claim: #3413

* feat(gpu): extend viewport AO to the virtual ground

* test(gpu): retain measured AO render attachments

* perf: initialize ground AO pipeline with concurrent jobs

* fix(gpu): size ground AO receiver for the current frame radius

* fix(viewport): retain orbit caches when ground AO visibility changes
2026-11-04 19:42:01 +00:00
github-actions[bot] a43cd99e55 Regenerate generated files and fold pending files [skip ci] 2026-11-04 21:11:47 +00:00
github-actions[bot] 8f26457487 Regenerate generated files and fold pending files [skip ci] 2026-11-04 21:01:46 +00:00
sascha2101 07ef94e102 Hole: drill only the shown bodies when no target is picked (#7520) (#7562)
* claim: #7520

* Hole: drill only the shown bodies when no target is picked (#7520)

A Hole that names no bodies drilled every body in its path, hidden ones
too, and its dialog preview replaced the hidden bodies with drilled ones.
Like a cut (#3961), it now takes the shown bodies of the active component
as its participants while some are hidden, stored so showing the others
later changes nothing. With nothing hidden, or everything, it still drills
every body.

Its face points are named on the bodies it drills first, so a hidden
body's face in the same plane no longer takes the picked face's name.

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

* Hole: drill the picked body though it is hidden (#7520)

Review on #7562: with the picked face's body hidden (picked through Select
Other, or hidden while the dialog is open), the shown-bodies default left it
out, so the hole drilled only shown bodies further along, or nothing, without
a message. The body whose face holds the hole's position now joins the shown
ones, unless a shown body has a face there too (a face shared by a shown and
a hidden body is the shown one's, as a pick in the view takes it).

The test also covers editing a hole made with nothing hidden while a body it
drills is hidden: timeline.redefine stores participants only when the edit
passes them, so the hole keeps its empty scope and the hidden body its bore.

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

* Hole: test that a hole made with a body hidden replays the same (#7520)

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

* Hole: follow the face-name lookup now returning a Result (#7520)

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

* Hole: tests follow main's Vec3 and world_state (#7520)

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

* Hole: format (#7520)

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

* Hole: with every body hidden, drill them all as before (#7520)

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

---------

Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 19:19:18 +00:00
semmelrinde 02f276e296 Report a bug: limit answers by characters and show the room left (#7191) (#7196)
* claim: #7191

* Report a bug: limit answers by characters and show the room left (#7191)

The answers were cut to 2,000 bytes, silently: 666 characters of
Chinese or Japanese. They now keep 2,000 characters, as the text field's
own limit counts them, in the form, in the report and in a crash draft.
Under 200 characters left, an answer says how many there are. The
report's budget grows so three full answers in any script still fit
beside the technical details.

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

* Report a bug: leave the crash draft's description as it was

It is the app's own English text, well under the limit in bytes as in
characters; leaving the line alone keeps it apart from #7189's change.

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

* Locales: Arabic and Hebrew for the new strings

ar and he are complete catalogs on main now (#3458): every new message
needs them. The pending-messages test compares right-to-left messages in
display order once #7532 lands (#7527).

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

* Locales: Vietnamese for the new strings

Vietnamese became a complete catalog on main (#5823), so every new English
message needs it too.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:15:23 +00:00
austin62jake 9602587b0d Navigation: a middle or right drag keeps the mode it started with (#9557) (#9592)
* Claim #9557

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

* Navigation: a middle or right drag keeps the mode it started with (#9557)

The mode of a middle or right drag was worked out again from the keys
held on every frame, so letting go of Shift partway through a Fusion
Shift+middle orbit turned the rest of the drag into a pan. It is now taken
when the drag starts and kept until its button is released, as a left
drag with the mapping's keys already was. Preferences > Navigation > "A
drag keeps the navigation it started with" (on) turns it off.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 19:14:40 +00:00
github-actions[bot] f8a98eed33 Regenerate generated files and fold pending files [skip ci] 2026-11-04 19:59:52 +00:00
github-actions[bot] 7629421375 Regenerate generated files and fold pending files [skip ci] 2026-11-04 19:48:22 +00:00
Homero Thompson d034633265 Desktop: the file chooser says when it is not the desktop's own (#10618)
* test: the file chooser says when it is not the desktop's own (#10159)

rfd falls back to zenity in silence when the XDG portal is not there, and the
user gets a foreign window with no explanation. The host must be able to say so.

RED: the test fails with 'without a D-Bus session bus the file chooser is not the
desktop's own: the user has to be told'.

* fix: say when the file chooser is not the desktop's own (#10159)

rfd opens the file chooser through the XDG desktop portal; without the portal it
falls back to zenity, a window that does not belong to the app, and the app never
says so. The user gets a foreign window and no explanation.

The host now asks the session bus, on a thread of its own, whether the portal is
there, and keeps the answer. When a file is opened and the chooser is not the
desktop's own, the reason goes to the session log (Help > Messages), which is
where a user looks for it.

* fix: the file chooser messages read as notices, not assertions (#10159)

The maintainer asked for two things on the pull request: the strings of the
pending catalogue read like a test assertion ("the user has to be told...")
rather than a notice a person would see, and the doc comment above
desktop_chooser_fallback was there twice.

The three reasons are now plain messages, in every locale of the file: they
say what the chooser is and why, and stop there. The doc comment of the
function is now the only one, and it says what it did.
2026-11-04 19:12:58 +00:00
Yishin Li fc51df3b01 cadmpeg 0.6.7: FreeCAD placements, sphere faces and closed shells come in right (#10382)
Only the version moves: SolidCraft builds against 0.6.7 unchanged. 0.6.7 applies App::Part
and link placements once, writes a sphere's seam ring as a valid loop, and indexes the
lookups that made large assemblies slow to decode. Two real FreeCAD documents that 0.6.3
refused as invalid open with it.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:01:59 +00:00
austin62jake 8f4d7959df Agents and scripts: Parameter Sweep describes its exact parameters and reply (#10977) (#10978)
* claim: #10977

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

* Parameter Sweep: input and output schemas (#10977)

inspect.parameter_sweep gets a typed contract and reply types in
schema/parameter_sweep.rs and leaves unconverted.txt. Each key reads as
the handler reads it, with its message; the reply is built from
ParameterSweep and its item types, with the same JSON as before.

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

* Parameter Sweep schema: a missing parameter reaches the handler's message

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

* parameter_sweep schema: do not publish a null default on the required parameter

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 19:00:58 +00:00
Tury991 338fa197d3 Dimension text: round values to a step (#4896) (#11380)
* claim: #4896

* feat(doc): support dimension text rounding steps (#4896)
2026-11-04 19:00:07 +00:00
SS-EN 727bb23c7e Section Analysis: a body standing on another keeps its own cap, also in CPU pictures (#4622)
Where one body stands on another (a post on a plate), the post's bottom,
seen through the cut, and the plate's top are the same plane. In the CPU
rasterizer (headless pictures without a GPU) the plate's top won that
depth tie and showed through the post's cut. The cut now wins a tie by a
few f32 steps (DEPTH_EPS).

The viewport already draws this right since #6363 and #6728; a GPU
regression test now covers it, both creation orders, orthographic and
perspective, with both depth formats.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:52:15 +00:00
CarelT 8d6c45f640 UI kit: one shared needs-attention mark, used by the agent chat's badge (no behaviour change) (#5424)
* UI kit: one shared needs-attention mark, used by the agent chat's badge

The agent chat's "waiting" badge becomes `ui_kit::attention_mark` (a count, or
a plain dot) in `ui_kit/attention.rs`, so other things that wait for the user
(linked designs that changed, #5354) share one mark. The count is drawn
exactly as before. No change in behaviour.

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

* UI kit: sort the attention re-export

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

* ui_kit attention mark: take the corner inset from Tokens::space_3

The inset came from the agent chat panel as plain numbers, which font_sizes_and_dense_form_metrics_come_from_tokens refuses outside theme::Tokens (review on #5424). space_3 is 3.0, so nothing moves.

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

---------

Co-authored-by: Carel Theron <careltheron@Carels-MacBook-Pro.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 18:52:05 +00:00
Jackson Oursland 6d16fae4ed Render preview: extract the product scene only when what it shows changes (#7465)
The live product preview's signature included the session revision, which
every command moves: a selection click extracted every body and built every
product vertex again (118 ms on a 200-body assembly) before the key comparison
skipped the upload. The signature is now the document and world state (held,
so their addresses are not reused) and every input of body visibility,
including hidden folders and the active sketch.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-04 18:51:42 +00:00
semmelrinde ef92f35b16 Sketch drag: a step solved ahead that solves becomes the sketch, one step per core (#7648) (#7649)
Past a failed step a scripted drag solves the next steps at once and takes
those that fail in a row as tried; the first that solved was then solved
again by the walk. It now becomes the sketch from its solved copy, so no
step is solved twice. A batch holds one step per core (at most 8), and on
one core or in the browser build, where a batch would run its steps one
after another, the walk solves the steps itself. The walk takes the same
steps with the same outcomes, so results and counted solves don't change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:51:32 +00:00
kaltenbrunnermarkus 232a902680 Circular pattern: cutting many copies of a hole is faster (#9007) (#9038)
* claim: #9007

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

* Circular pattern: cut copies of a hole that lie apart in one go, and check missed copies cheaply (#9007)

The check for copies that remove no material intersected every copy with
the whole body; a point test along the hole's axis (and through the middle
of the copy's overlap with the body) now proves most copies take material
and only the rest are intersected. Copies the kernel's coarse box test
takes for meeting are measured on finer meshes and, when clearly apart,
joined with join_apart and cut once.

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

* Format (#9007)

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

* Look for a copy's material through the middle of each of its faces (#9007)

A tool of two pieces (two circles cut at once) turned by 45 degrees has the
middle of its box between the pieces; the lines along the axes through it
miss both, and the copy was intersected. The lines through the middle of
each face's box run along each cylinder's axis.

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

* Circular pattern: cut copies that notch the same round face one by one (#9007)

Copies whose boxes only finer meshes show apart were cut in one go. When
several of them notch the same curved face of the target (grooves along a
bore), the result kept extra lines on that face: a revolved ring with 37
grooves came out in 184 faces instead of the 151 a one-by-one cut leaves,
at the same volume. Such copies are now cut in turn; copies that only
share flat faces (through holes in a tube wall) are still cut at once.

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

* Circular pattern: tell copies apart and find copies that cut nothing on exact shapes (#9007)

The checks this pull request added decided on meshes, which `cargo xtask
mesh-decisions` now refuses. They decide on the exact shapes instead:

- Two tools whose boxes meet are apart when their union comes out in two
  pieces that share no face, edge or vertex (crossing, nested and touching
  tools do not).
- A copy takes material when a point along its axis, the axes of its round
  faces or the middle of the box it shares with a target is inside both, by
  exact point classification; only copies that test cannot place are
  intersected.
- Copies that notch the same round face are no longer cut one by one: cut
  in one go they now leave the same faces as one by one (a revolved bore
  with 37 grooves: 80 faces either way, as on main).

The test of copies that come close now uses 8.5 mm, where they overlap; at
9 mm they are 0.012 mm apart and are cut in one go.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:42:02 +00:00
PaddleStroke cfc4908291 Sheet metal: returns on a notched tray now close at the convex corner (#8823) (#9714)
* claim: #8823

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

* Sheet metal: returns on a notched tray close at the corner away from the notch (#8823)

Closing two returns at a corner was refused when either return already lay
over another flange in the flat pattern, even far from that corner. On a tray
with a notch, the walls on either side of the notch's inner corner both unfold
into the notch, so the return on one of them lies over the other there; the
return's opposite convex corner was then left open ("closed, the flanges would
overlap in the flat pattern") and the two returns ran through each other.

A closure is now refused only when it makes the two returns overlap or adds to
what either already shares with another flange in the flat.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:41:07 +00:00
froglemonade2 1432131e95 English: command labels in title case, other labels in sentence case (#9085) (#10734)
* English: command labels in title case, other labels in sentence case (#9085)

The rest of #9085 after #9194 (the plural and the spelling) and #9907
(the Preferences page names). Command labels are in title case (422 of
430 were already); the seven in sentence case follow, and Fix/UnFix
reads Fix/Unfix. The UI text skill states the rule. The user docs name
the Preferences footer's button by its current label, Restore page
defaults (#10295).

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

* Navigation bar menu test: the echo names the command by its own label

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

* UI text skill: state the case rule in one short sentence, under the 3600 limit

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

* Localization doc: the case rule in full, with examples

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

* Localization doc: the example button is Restore page defaults now

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:39:52 +00:00
CaRLymx 35bc04847a Spanish (es-ES): catch up with en.json (2026-11-04) (#10895)
* i18n: make engine errors translatable

Engine errors (`EngineError::BadParams` and the offset/path errors the kernel
returns) were shown in English regardless of the active locale.

- Add `tr_engine_error`, which maps the known messages to `sketch.error.*`
  keys and returns anything unknown unchanged.
- Normalise the `invalid parameters for `<command>`: ` prefix before matching,
  and interpolate the offset id into the translated message.
- Route `friendly_error` through `tr_engine_error`.
- Avoid redundant status writes in `run_points`/`run_entities`.
- Ship the es-ES pending strings for the sketch tool hints.

* Spanish (es-ES): catch up with en.json

Translate the 734 messages `es.json` was missing and fix `ui.project`
("Proyecto" → "Proyectar", the action behind the sketch project tool).

The catalogue now matches `en.json` key for key (5,591 entries), keeps every
placeholder intact and stays alphabetically sorted. That includes the 61
messages added upstream since the last catch-up (92a9a5e0).

Terminology: "Turntable" → «Plato giratorio», "hover" → «pasar el puntero»,
"card" → «tarjeta».

* i18n: attribute the sketch hints and keep the offset error neutral

The pending sketch hints file adds an asset, so it gets its own row under
attribution/pending rather than editing ATTRIBUTION.md.

The engine reuses the offset-ends error for trim, extend and break, so the
message no longer tells the user to trim: it asks them to edit the source
curves instead.

* i18n: translate shortcut-started tool hints and fix the break message

Shortcuts are handled in logic(), before ui() installs the language scope, so a
tool started by its key stored the English hint even with another language
selected. Install the scope for the whole frame.

The engine has nothing to break, so say so: "nothing to break here".

* i18n: scope a tool start so its hint uses the app language

A direct start can run outside the frame scope (and a shortcut runs in logic),
so the hint was built with the thread default. Open the scope where the tool
starts.

* Spanish (es-ES): translate the 30 new messages

The blur preferences, the dimension step settings and the fillet turn modes
arrived upstream after the last catch-up.

* i18n: scope command errors and use US spelling in the hints

The native menu runs Undo and Redo before the frame scope exists, so an error
from them was translated in the thread default language.

The pending hints mixed British spellings into the English source; the English
catalog is US-spelled, so the fallbacks say center and en-GB keeps centre.
2026-11-04 18:39:43 +00:00
Mateusz Sych 06def2231d Agent chat: tell the agent the design changed by hand since its last reply (#10854) (#11144)
* claim: #10854

* Agent chat: tell the agent the design changed by hand since its last reply (#10854)

The chat remembers the design as the agent last saw it, at the end of a
turn it answered or worked in. When the next message goes and the design
is no longer that one, a note goes before the message for the agent (not
into the conversation, like the note after an interruption): the user
changed the design by hand, which parameters changed from what to what,
which features are new, edited or removed, where the History marker went,
and to read the design again before reusing positions, picks or sizes.

A new chat and a stopped backend forget it, so a session that has seen
nothing gets no note. Each list names at most 12 items and counts the
rest; names and expressions are cut at 60 characters.

* Agent chat docs: the design-changed note asks the agent to read the design again (#10854)

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-04 18:39:09 +00:00
PaddleStroke d39fd6c397 Assistant: discover Mistral Vibe and offer sign-in (#11692) (#11704)
* claim: #11692

* Assistant: discover Mistral Vibe and support its sign-in (#11692)
2026-11-04 18:38:05 +00:00
Brett Lawson 4faf63e48a Loft: profiles picked one after another in one sketch are one section (#11720) (#11906)
* Loft: profiles picked one after another in one sketch are one section (#11720)

The Loft dialog made every picked profile its own section, so two picks in
one sketch (both halves of a split outline, say) became two sections in one
plane and the loft was refused. Consecutive picks in one sketch now form one
section {sketch, profiles: [...]}; a later return to that sketch is still a
section of its own, so the there-and-back loft of #8221 is unchanged.

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

* Loft: an unchanged edit keeps a section of all profiles "all" (#11720)

An edit loads a section of "all" as each profile of its sketch; now that
those picks make one section, OK saved them as indices. Indices fill a
letter's counters that "all" keeps as holes, so a later rebuild could
change the loft. Like Extrude, Revolve and Emboss, a section that was
"all" stays "all" while the picks are unchanged and cover every profile.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:37:41 +00:00
takuto-NA 91ecea8cfd Properties: account for sheet perforations (#6025) (#6028)
* claim: #6025

* fix(engine): account for sheet perforations in physical properties

* fix(inspect): retain kernel topology counts for sheet bodies
2026-11-04 18:30:31 +00:00
PaddleStroke c8c0c28fc8 Point clouds: keep every PTS point within the import limit (#11093) (#11717)
* claim: #11093

* Point clouds: count point rows before thinning PTS imports

* Point clouds: exclude invalid coordinates from text sampling
2026-11-04 18:27:21 +00:00
PaddleStroke f91db06904 Sketch Project: links to old numbered faces can be recovered (#11666) (#11707)
* claim: #11666

* Sketch: recover legacy references on original whole faces
2026-11-04 18:24:02 +00:00
Andrzej Błasik 9df9d1fbdc fix(smlib): NURBS weights compare within rounding of the coordinates' size (#11974)
same_nurbs compared the weights of a face's NURBS form and the rebuilt
analytic surface's NURBS within an absolute 1e-12. A weight is found from
the control points, so its rounding grows with the coordinates: 3.8e-12 at
coordinates of about 320 on a cone. The face was refused with "a NURBS form
that is not its analytic surface". Weights now compare within
1e-12 * max(1, |coordinates|), as same_nurbs_curve already does for points.
2026-11-04 18:23:53 +00:00
rugbymauri 8068e8f4b8 Preferences: the theme strip's arrows page by a screenful of themes (#11434) (#11600)
* claim: #11434

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

* Preferences: the theme arrows move by the themes that fit in view

The previous and next arrows beside the theme cards in Preferences >
Appearance moved the row by one card per click, although their hover
text says "Previous themes" and "Next themes". With 26 themes and about
six cards in view, reaching the last theme took some twenty clicks.

The shared carousel now moves by the number of cards that fit at the
current width, so one click shows the next screenful. The last page
ends on the last card, the first page starts on the first, and the
arrow at an end stays dimmed and inert. The step follows the window
width, and the row still opens on the chosen theme.

Fixes #11434

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-11-04 18:21:39 +00:00
PaddleStroke 863b0777da Sketch: dragging a constrained line by its middle no longer stalls (#11696) (#11714)
* claim: #11696

* Sketch: move constrained lines that stall during dragging
2026-11-04 18:19:46 +00:00
austin62jake 80889762ce Agents and scripts: Configure, Configuration Column, Row and Rule describe their exact parameters and reply (#11895) (#11896)
* claim: #11895

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

* Configuration table: input and output schemas for Configure, Configuration Column, Row and Rule (#11895)

describe_command now gives the exact parameters and reply of the four
commands that change a design's configuration table, and the warn and
strict parameter checks use them. The handlers read their parameters
from the raw JSON, so the new contracts in schema/config.rs read each
key as its handler does, with its message: lists of names as
params::string_list (whose json_brief and MAX_STRING_LIST become
pub(crate)), exactly one column target, faces picked or a face column's
key, a row's cells, and rules (whose schema describes them; the handler
keeps its own check). What only the design can answer stays the
handler's. All four reply with ConfigTableReply, as config.table does.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 18:19:35 +00:00
rugbymauri a3f293c987 Error messages: an out-of-range value names the field that is wrong (#9625) (#9684)
* claim: #9625

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

* Name the value at fault when a hole, sheet rule, joint or extrude is refused (#9625)

Several refusals were right but named a check the input had passed, so
the user changed the wrong field:

- solid.hole: an unknown `fit` was reported as an unknown bolt size; it
  now says the fit must be close, normal or loose. A countersink angle
  out of range was reported as a countersink too narrow; the angle and
  the width are now checked apart. A `diameter` number out of range was
  reported as missing; it now gives the range, as other parameters do.
- Sheet rules: a thickness of 10000 mm or more said "must be positive";
  it now gives both limits. The K-factor and the bend radius are named
  on their own, and the message no longer implies a radius of 0 is
  refused.
- joint.drive: a number out of range said "numbers or expressions"; it
  now gives the range.
- Extrude: a distance past the kernel's limit said `end = 1000000000`;
  it now says the extrusion is too long and gives the limit.

Fixes #9625

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

* Refuse a too-long extrusion before the kernel and match the countersink floor (#9625)

The kernel is paused, and its `finite()` check also guards a revolve's
angle and a cylinder's and a torus's sizes, so its message must not say
"extrusion". crates/kernel is back as it was; the doc evaluator now
refuses an extrude (plain, tapered, or redefined from the Edit Extrude
dialog) whose end lies 10000000 mm or more from the sketch plane,
mirroring the kernel's limit, and says so. A cylinder of radius 2e7
keeps the kernel's own message.

The countersink check refused 0.1° while the message said "more than
0°". The floor stays (the cone grows as 1 / tan of the half angle) and
the message now names it: more than 0.115°.

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-11-04 18:18:14 +00:00
PaddleStroke e27d17c421 Assistant: offer the Mistral AI endpoint in Add Agent (#11246) (#11715)
* claim: #11246

* Assistant: offer the Mistral AI API endpoint (#11246)
2026-11-04 18:16:34 +00:00
github-actions[bot] 52bc27aaf6 Regenerate generated files and fold pending files [skip ci] 2026-11-04 18:48:25 +00:00
github-actions[bot] c357b1565e Regenerate generated files and fold pending files [skip ci] 2026-11-04 18:40:09 +00:00
cronos de9a2bc846 Shaded with hidden edges: optionally select edges through faces (#11841)
* Allow optional hidden-edge picking in Shaded with hidden edges

Keep visible-only picking as the default. Cover the preference, hover cache,
section and body visibility, and Fillet inputs in the existing UI tests.
Use platform-absolute fixture paths in the Windows CI argument-parser test.

* Respect section caps and visible-edge ties in hidden-edge picking

* Keep section caps as occluders behind nearer kept faces
2026-11-04 18:13:40 +00:00
Mateusz Sych 6b6bd26648 Agent chat: a prompt's bubble is as tall as its message (#11882) (#11883)
* claim: #11882

* Agent chat: a prompt's bubble is as tall as its message (#11882)

The prompt's Copy message row (#10741) was laid out right to left and centred
across its height. egui gives such a row all the height left in the
conversation's view, so a prompt near the top of a conversation stretched
down over the empty view, with Copy message floating in the middle. The row
is top-aligned now.

* Agent chat: keep the test's harness bound (#11882)

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-04 18:13:19 +00:00
ABDULLAH 8239b914ba Arabic: rim term in the Lip help, Up/Down keys in the dimension step preferences, frame-rate note (#11870)
* Arabic: rim term and the shell-first step in the Lip help

* Arabic: Up/Down keys in the dimension step preferences, frame-rate note matches the English
2026-11-04 18:12:49 +00:00
Janiz-zzz e95946ac82 i18n(fi): translate the 822 messages Finnish lacks (#12036)
Added as assets/locales/pending/finnish-catch-up-2026-11.json so it
folds after merge: help texts, the redesigned Preferences (sketch,
navigation, dimension values, panel blur), fillet corner, setback and
turn options, threads, import report, browser and viewport messages.
cargo xtask i18n lists nothing missing for fi.

Co-authored-by: Janiz-zzz <267742026+Janiz-zzz@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:11:29 +00:00
cronos 46ce12b7f0 perf(ui): reuse assembly rendering caches during orbit (#2889) 2026-11-04 18:06:34 +00:00
austin62jake aba00427fa Render shots: input and output schemas for Duplicate, Rename and Remove (#11450) (#11451)
The handler's strict parameter type moves to schema/render_shots.rs as
ShotParams, with descriptions and JsonSchema, and becomes the contract of all
three commands; the handler reads the same type. It builds ShotChange instead
of a json! literal, and takes its operation as a ShotOperation enum.

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 18:02:53 +00:00
alfonsotames cccf8c1ece Snap points: a flat face offers its corners, edge middles and every hole's centre (#11476) (#11477)
* Snap points: a flat face offers its corners, edge middles and every hole's centre (#11476)

A flat face now offers its whole outline as snap points, read from the
exact faces and edges: every corner, the middle of every straight edge
and the centre of every round edge, holes included, as well as the centre
of its area. Corners on a closed circle's seam, or where the kernel split
one circle into pieces, are left out.

snap_points_from gives each point with where it came from (SnapSource):
the face it was picked on, the face's outward normal and a point inside
its edge, so a tool can keep the point by name (joint.create's edge,
corner and on) and turn a frame to the face. SnapPoint itself does not
change, so code that builds one keeps compiling.

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

* Snap points: a round face's end centres say which way their end faces (#11476)

The centres at a cylinder's or cone's two ends now carry a normal in their
SnapSource: the axis pointing out of that end, which is the normal of the
face a hole opens onto or of a pin's end. A joint origin taken from one can
then lie on that face, as the centres a flat face offers already do.

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

* Snap points: a hole centred on its face keeps its own point and rim (#11476)

Snap points were one point when they lay at one place, whatever their kind,
so a hole centred on its face was lost behind the face's centre, and with it
the rim its point keeps. Now only points of one kind are merged: the face's
centre and the hole's centre both stay, and only the hole's has its rim.

SnapSource::normal's doc now says it is also the axis out of a cylinder's or
cone's end.

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

* Snap points: a washer's two rims keep a centre each (#11476)

Centres at one place are one point only when they are on one circle: the
inner and outer rims of a washer face now keep a centre each, with its own
rim, while the pieces the kernel split one circle into are still one point.

SnapSource::normal's doc now says that at a round face's end it is the
axis out of that end of the face, which at a blind hole's bottom points
into the material below, not out of it.

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

* Snap points: rims are told apart by their radii's size, not their place (#11476)

Two centres at one place were one circle when their rims' radii differed by
less than the place's tolerance, which grows with the distance from the
origin: 60 m out, rims of 4 and 4.05 counted as one and one lost its point.
The radii are now compared with a tolerance from the radii themselves.

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

* Snap points: nearby points stay apart far from the origin (#11476)

Points of one kind at one place were merged within a tolerance of 1e-6 of
their distance from the origin, 0.06 mm at 60 m, so two small holes there
whose centres are 0.05 apart lost one centre. Places now use 1e-9 of that
distance (0.06 µm at 60 m): well above the rounding of coordinates and of a
circle fitted to an imported edge, so a point found twice is still one, and
far below any feature. The radii keep their own tolerance.

The merge is its own function, so its test can give it points directly.

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

---------

Co-authored-by: Alfonso Tamés <mufufu@Mufufu-MacBook-Pro.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:02:44 +00:00
Jackson Oursland fb80454368 User guide: window tour gets a numbered screenshot and a navigation clip (#10323)
* Book: a numbered window screenshot and a navigation clip, from a capture script

The window tour now opens with a screenshot of the real window, its parts numbered in the
order the page describes them, and a short looping clip of orbit, pan, zoom and Home.

examples/demo/doc_shots.py makes both, and any later figure: each shot is a function that
starts a fresh app (its own settings) under Xvfb, drives it over the control channel with
the demo's Demo driver, and writes a still (numbered callouts, palette-optimised PNG) or an
H.264 clip (the drawn pointer of overlay.py, short button labels, idle cut by trim_idle.py)
into docs/book/src/media/. CONTRIBUTING.md says how to redo a figure when the UI changes.

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

* doc_shots: clips keep a moment on each result; the window tour again on 0.29.0

trim_idle.py takes --hold S, the seconds of each idle stretch to keep (default 0.1, as before,
so the README demo is unchanged). The guide's clips keep 0.8 s, so a reader can see what each
step did before the next begins. The window screenshot and the navigation clip are made again
with it, on SolidCraft 0.29.0.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:33:39 +00:00
Dahak Builds f92e16fa25 Keep orbit gizmo drag tracking past the window edge (#9943) (#9978)
* Claim #9943: keep orbit gizmo drag tracking past the window edge

* Warp the orbit gizmo drag back to the window centre at the edge (#9943)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-04 17:16:33 +00:00
Dahak Builds dcbedbd805 Don't blindly reopen a design that stopped the last session (#5064) (#10443)
A design that crashes or hangs the app while it is being reopened was
reopened blindly every launch, bricking the app each time. A launch that
reopens designs now marks them in the recovery folder; the marker is
cleared as each design finishes opening. A launch that finds the marker
left behind skips those designs once and says which one the last session
stopped on, so the app starts usable and the loop breaks.

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-04 17:16:15 +00:00
Dahak Builds 9bb216a357 Ribbon: only switch the tab when a command actually starts (#10540) (#10549)
* claim: #10540

* Ribbon: only switch the tab when a command actually starts (#10540)

select_command_tab ran before the command's own checks, so a command that
only reports a missing parameter or nothing to work on still moved the
ribbon. The ribbon now moves when the command starts and a refused command
puts the tab back, as a disabled command already never leaves it. View
display toggles and choices menus keep the ribbon's tab however they are
started, matching F6, the navigation bar and the view cube.

Written by Qwen.

* rustfmt (#10540)

Written by Qwen.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-04 17:16:09 +00:00
github-actions[bot] fd9b3fd9f2 Regenerate generated files and fold pending files [skip ci] 2026-11-04 18:00:54 +00:00
Mateusz Sych 6488a540c9 Windows: a lost graphics device restarts the app instead of crashing (#10240) (#11109)
* claim: #10240

* Lost GPU: a loss met by a queue write restarts the app instead of crashing (#10240)

The per-frame probe (#6528) finds a device the driver reset before the
frame's uploads, but the driver resets a device at any moment: a reset
that lands after the probe is still met first by a queue write or the
present, and wgpu-core unwraps the driver's Lost there
(PendingWrites::activate) instead of losing the device. That panic
ended the app and wrote a crash report.

- egui-wgpu: the painter runs each frame under DeviceLost::survive. A
  panic the frame meets is taken for the loss once the probe then finds
  the device lost (the probe's path loses the device and runs its lost
  callback); the painter draws nothing more and the app restarts as for
  any lost device, keeping unsaved changes for recovery. Any other panic
  carries on unwinding.
- Crash reports: a panic inside wgpu-core that unwraps Lost is the
  device loss, not a bug, and is not reported. A command that meets it
  fails; the next frame's probe restarts the app.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-04 17:15:58 +00:00
froglemonade2 3e70ad9876 View navigator: right-click to choose Axes, Cube or a Custom body (#10266)
* View navigator: a custom body as the navigator, and a right-click menu to switch types

A right-click anywhere on the view navigator opens a menu with its three types: Axes, Cube and
Custom. Custom opens the Custom View Navigator panel (a feature panel with OK and Cancel): pick a
body in the view, its texture (Themed: the cube's colours; Modeled: the body's own appearance and
the view's edge colour), and whether the cube's rotation arrows and Home button go around it. The
navigator previews the panel's choices while it is open; Cancel leaves it as it was.

The custom navigator draws the body at the cube's size through a small depth-buffered raster, so
hidden faces stay hidden; hovering a face highlights it, a click looks straight at it (and frames
the model with navigator_fit on), a drag orbits. Until a body is chosen it draws the cube.

The body is kept as a packed snapshot (16-bit, base64) with the preferences and in settings
profiles, so it stays across runs and designs and travels with an exported profile. Bodies over
10,000 triangles, and long or flat ones (thinnest width under a fifth of the widest), are refused.

New command view.navigator (style, body, texture, arrows, home) does the same without opening
anything; new preferences navigator_texture, navigator_arrows and navigator_home, and a Custom
choice for navigator_style. Defaults are unchanged.

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

* Custom view navigator: a hovered face in Modeled takes the view's selection colours

In the Modeled texture the face under the pointer is filled and outlined the way the view selects
a face of the model (sel_face, sel_edge), not in the cube's hover blue and accent, which Themed
keeps.

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

* Custom view navigator: a hovered face in Modeled lights up as the view's mouse-over does

The face under the pointer keeps its own colour, a little lighter (hover_face_lift) and still
shaded, with its edges in the view's mouse-over edge colour (hover_edge), as the 3D view shows a
face under the pointer; not the selection colours. Themed keeps the cube's hover blue and accent.

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

* Custom view navigator: rotation arrows and Home on by default

navigator_arrows and navigator_home start on, so a new custom navigator has the cube's arrows and
Home button around it; the Custom View Navigator panel turns either off.

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

* Custom view navigator: the strings in every complete language, and test fixes

- The new messages in Arabic, Hebrew and Vietnamese (complete catalogs, like Japanese and Chinese).
- The navigator's raster goes through one colour helper, listed with its reason among the files
  allowed colour literals (the design's appearance shaded into pixels).
- nav_settings joins the requested-icon manifest (the view.navigator command's icon).
- A visible edge piece is kept only when the stretch seen with it, across the edge's segments, is
  at least two samples long: a hidden edge no longer shows a sliver at a silhouette corner.
- The navigator layout test treats Custom without a body as the cube it draws; the panel and
  size tests no longer assume an empty undo list, the style before opening, or a coarse sphere.

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

* Custom view navigator: profiles carry its texture, arrows and Home

Settings profiles keep a fixed set of preference keys (#9907), which the custom navigator's newer
settings are not part of. They travel in the profile's own `view_navigator` entry, next to its
body (`view_gizmo`): captured, checked (a value this version can't read is left out) and applied,
leaving the fixed key set unchanged. The cube's arrow inversion (#10286) applies to the custom
navigator's arrows too, through the shared arrows().

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

* Custom view navigator: follow main's Vec3 move and fallible world_state

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

* Custom view navigator: list view.navigator in the rig's EXEMPT, refuse a stored body that can't save again, right-click without a widget over the navigator

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

* Custom view navigator: keep the body unless picked again, save only a profile that reads back, no navigator menu through a popup

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

* Custom view navigator: refuse a stored body with a triangle past its points

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

* Custom view navigator: a stored body meets the triangle and aspect limits a picked one does

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

* Custom view navigator: a body at the 5:1 limit stays inside it once packed

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:12:27 +00:00
Igor Bronovskyi 30842efb22 Ukrainian: translate the 174 messages main gained after the 3 November sync, panel layout and the redesigned preferences included (#11064)
* i18n(uk): translate the 80 messages main gained after batch 11

* i18n(uk): translate the messages main gained after 2 November

Add Ukrainian for the 158 English messages upstream added since the last
sync, and update 10 existing translations whose English text changed.

The new messages go in assets/locales/pending/uk-sync-2-nov.json, as the
locale files are not edited for new messages. The changed English text is
updated in assets/locales/uk.json, because a pending file cannot change an
existing message.

* i18n(uk): translate the messages main gained after the 3 November sync

Add Ukrainian for the 174 English messages upstream added since the last
sync, and update 56 existing translations whose English text changed.

The new messages go in assets/locales/pending/uk-sync-2-nov.json, as the
locale files are not edited for new messages. The changed English text is
updated in assets/locales/uk.json, because a pending file cannot change an
existing message.

* i18n(uk): apply CodeRabbit review fixes

Use the catalog's established wording: 'вибрати' in the keyboard hint,
name the linked component in the double-click help, and use the command
labels 'Упор', 'Бобишка' and 'Клацкове з'єднання' in the placement group.

* i18n(uk): use "Защіпкове з'єднання" for Snap Fit

The catalog called the plastic snap-fit joint "Клацкове з'єднання", a
calque of "snap". Use the standard "Защіпкове з'єднання" and
"защіпковий гачок" in the command label, help, sample description and
the design tools placement group. The drag hint already said "защіпку".

* i18n(uk): replace calques and outdated terms

Use "збіг" for coincident, "виступ" for lip, "ступиця" for hub, "шаблон" for preset, and "наддискретизація" for supersampling. Render and panel host texts now say "рендерер" or "середовище"/"вікно" instead of "хост".
2026-11-04 17:05:44 +00:00
Colin Faulkingham 549aef17a5 Changing anti-aliasing no longer resets the saved window system (#10116) (#10147)
* claim: #10116

* fix: preserve the saved window-system preference at startup

* fix: preserve graphics preferences in safe mode
2026-11-04 16:57:30 +00:00
Daniel 4d738cd210 Sketch: an ellipse is drawn with its axes, and D measures its half-axes (#9291) (#9292)
* Sketch: an ellipse comes with its axes, and D measures its half-axes

An ellipse could only be sized by its minor radius, drawn like a circle's
radius, and there was no way to give its full axes. sketch.ellipse now also
adds the major and minor axes as construction lines through the centre.
The major axis runs from the major end to the point opposite it; the minor
one is perpendicular to it, ends on the ellipse, and both are bisected by
the centre. That adds six coordinates and six equations, so the ellipse
keeps its five degrees of freedom, and it needs no new constraint kind and
no format change.

With the axes in place every ellipse size is an ordinary dimension. D on an
axis line gives its length, the whole axis. D on the outline gives a half-
axis, the distance from the centre to an axis end, chosen the way a line
chooses horizontal or vertical but in the ellipse's own frame: text pulled
across the major axis measures along it (the major half-axis), text pulled
out past an end of it measures across it (the minor one). The end on the
text's side is used. Near the boundary between the two, a dimension being
placed keeps the half-axis it had (sketch.dimension's previous_axis, which
the Dimension tool sends), so the preview does not flip. A half-axis and
its whole axis determine each other, so the second becomes a driven
dimension through the existing redundancy path.

Deleting the ellipse leaves its axes, as deleting a Center Rectangle's
edges leaves its construction diagonals. Ellipses without axes, from
existing designs, keep the minor radius as before, and type radius still
gives it.

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

* Guide: an ellipse's axes and half-axis dimensions

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

* Sketch: test that an ellipse's half-axis preview keeps its axis near the boundary

The Dimension tool sends the half-axis its last preview chose, so near the
boundary between the major and the minor half-axis the preview keeps it.
This covers that path from the tool, from both sides of the boundary.

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

* Sketch: an ellipse's axes select and drag with it, as a rectangle's diagonals do

A double-click on a Center Rectangle's edge or diagonal selects its edges
and both diagonals, and dragging a diagonal moves the rectangle without
changing it, because its lines share corner points. An ellipse's axes are
held by constraints instead, so a double-click selected only the ellipse
(and Delete then left the axes behind), and dragging the major axis moved
the ellipse but stretched its minor half-axis.

Sketch::ellipse_axes now finds an ellipse's construction axes (the
detection sketch.dimension already used, moved into the sketch crate).
Sketch::chain treats an ellipse and its two axes as joined, so a
double-click on any of them selects all three. Dragging either axis moves
the ellipse and both axes as one, keeping both half-axes. Dragging the
outline still sets the minor half-axis.

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

* Sketch: a corner test reads its line's id instead of assuming l1

sketch.ellipse now adds two axis lines, which take the first line ids, so
the extend test reads the id of the line it draws from the reply.

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

* Sketch: an ellipse's axes cut a trim, as a Center Rectangle's diagonals do

Trim, extend and break take every other curve as a cutter, construction
lines included, so an ellipse's construction axes stop them as a Center
Rectangle's diagonals do. A test now states it for both shapes, and the
guide says it. The freeform cutter test keeps the axes it used to delete
and asserts the piece a trim removes: from where the major axis crosses
the circle to where the ellipse does.

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

* Sketch: test dragging an ellipse's outline with its axes present

Dragging the outline sets the minor half-axis, and the minor axis line,
held on the ellipse at one end, follows it; the centre and the major
half-axis stay.

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

* Sketch: other dimension types on an ellipse with axes keep the minor radius

Only auto, major and minor give a half-axis on an ellipse drawn with its
axes. Any other type (radius, diameter, horizontal and the rest) gives
what it gives on an ellipse without axes, as the parameter doc says,
instead of quietly becoming a half-axis chosen by the text. The arm for
a missing text_at is dropped: with no text the rule already picks the
minor half-axis.

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

* Sketch: test an ellipse's half-axis dead zone from the major side too

The tests only showed a previous minor half-axis being kept near the
boundary; the point they used on the major side is major by geometry
alone. A point inside the band where geometry says minor now shows a
previous major half-axis kept, in the engine and in the Dimension
tool's preview.

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

* Sketch: a placed ellipse half-axis is the one its preview showed

On an ellipse already sized, a half-axis is added as a driven dimension
on the click, and that command left out the half-axis the preview kept
near the boundary, so the click could place the other one. It now
passes it as the preview does.

The remembered half-axis also comes only from a draft whose text was
placed (one without text is the minor half-axis by default and biased
the first placement), is kept per sketch as well as per picks, and is
forgotten once a dimension is placed or the picks are cleared.

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

* Sketch: say how an ellipse's axes are recognised and what that costs

The axes are read from the constraints, with no mark in the design, so
the doc comment says which lines count, that deleting the midpoint or
point-on-curve constraint leaves an ellipse without axes, and that each
lookup scans curves times constraints.

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

* Sketch: an ellipse with one axis deleted still picks its half-axis by the text

With one construction axis deleted, D on the ellipse fell back to the minor radius wherever the
text was pulled, because the half-axis choice needed both axes. It now reads what is left of
them and chooses as before: the major half-axis goes to the major end (also from the far side,
when the point opposite is gone with its line), the minor one to its axis end, or as the minor
radius when the minor axis is the one deleted. An ellipse with neither axis keeps the minor
radius.

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

* Sketch: the one-axis half-axis test checks what is measured to

The test for an ellipse with one axis deleted checked only the reply's axis and value, which
both ends of an axis give alike. It now checks the dimension's kind and end: the axis end on
the text's side, the major end from either side once the point opposite is gone, a radius once
the minor axis is. With the major axis deleted and no text the reply names the minor axis and
measures to its end, where it used to be a radius with no axis named.

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

* Sketch: an ellipse's minor radius is drawn as its half-axis, without R

A radius on an ellipse is the minor half-axis, and it was drawn as a circle's radius: one
leader from the centre towards the text, marked R, which measures nothing a reader can see.
It is now drawn as the distance from the centre to the minor axis end on the text's side,
the way the half-axis is drawn while the ellipse has its axes, and labelled with the length
alone. This is what D gives once the minor axis line is deleted, and on an ellipse with no
axes.

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

* Sketch: an ellipse's half-axis keeps a radius's stored text

The half-axis frame read the stored text from the middle of the half-axis, so a radius placed
on an ellipse in an existing design would show its text somewhere else. The text is stored from
the centre again, as a radius's always was, and the side drawn to follows it. An unplaced one
sits where a linear dimension to the minor axis end puts its text, and stacked texts step out
from the centre as a radius's do. A view test reads the painted label, so the drawing cannot
go back to R unnoticed.

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

* Sketch: an ellipse moved, turned or scaled takes its axes along

Move, Sketch Transform and Scale moved only the curves picked, so an ellipse moved without its
axes left them where they were: its centre no longer halved them, the minor end left the
ellipse, and the sketch stayed unsolved. A Center Rectangle's diagonals share its corners and
always go along; an ellipse's axes share only the major end. The axes now go with the ellipse,
as they do when it is dragged. Copy is unchanged: it copies no constraints, for a rectangle
either.

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

* Sketch: a preference draws ellipses without their axes, as before

Drawing an ellipse with its construction axes changes what the Ellipse tool makes out of the
box, so the old ellipse stays one setting away: Preferences › Sketch › Draw ellipses with their
axes, on by default. Off, the tool draws the ellipse alone. sketch.ellipse takes axes: false
for the same, and keeps drawing the axes when it is not given.

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

* Sketch: sketch.ellipse refuses an axes value that is not true or false

A string such as "false" was read as not given, so the axes were drawn; it is now refused as
the text commands refuse a non-boolean bold or italic. The move test also covers an ellipse
whose minor axis was deleted: the major axis left goes along.

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

* Sketch: D picks a half-axis on an ellipse without axes too

An ellipse with no axis lines (both deleted, a mirrored copy, one from an older design) gave the
minor radius wherever the text was pulled, so its major half-axis could not be dimensioned on
it. The half-axis is now chosen by where the text is on every ellipse: the major one to the
ellipse's own major end, the minor one as its minor radius. With no text it is the minor
radius, as before.

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

* Sketch: with one half-axis dimensioned, D on the ellipse gives the other

D on an ellipse chose the half-axis by where the text was pulled even when that half-axis was
already held by a dimension, so the second D usually guessed the one already there and was
refused as over-constrained. With a driving dimension already on one half-axis (to an axis
end, the minor radius, or the whole axis along its line or between its ends), D with no type
now gives the other, in either order and with or without the axis lines. With both held it
chooses as before; an explicit major or minor is kept.

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

* Sketch: the held half-axis rule is documented and its cases tested

The Dimension command's parameters and the guide now say that with one half-axis held, D on
the ellipse gives the other. Tests cover the whole axis held as a distance between its ends
and as its line's length with the other axis deleted, and check that a mirrored ellipse has
no axes. The placement test undoes its first dimension, so its second half still tests where
the text is rather than the held half-axis.

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

* Sketch: Transform previews an ellipse with its axes

OK in Sketch Transform moves, turns and scales an ellipse's axes with it, but the window drew
where OK would leave only the picked curves, so the axes stayed put in the preview until OK. An
ellipse picked alone is now drawn with the axis lines it has.

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

* Sketch: the typed-radius ellipse test counts the ellipse's axes

Main's #10282 test drew an ellipse with typed radii and expected one curve; with the
ellipse_axes preference on by default the tool also draws the two axes.

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

* Sketch: an ellipse's remaining axis still drags and selects it

With one axis deleted, dragging the other axis did not hold the ellipse (with the minor axis
left, the drag squashed it), and a double-click on the ellipse or that axis selected only the
one curve. The drag and Sketch::chain now read whichever axes are left through
ellipse_axis_lines, as Transform and Move already do.

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

* Sketch: drop the unreachable third arm of the held half-axis check

A distance from the centre to the major end or the point opposite it is already matched by the first arm, so the third arm and its major_end closure never ran.

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

* Sketch: D on an ellipse's axis line is the whole axis wherever the value is pulled

An axis line took the free line's rule, so on a turned ellipse a value pulled beside or above the box the axis spans gave its horizontal or vertical distance. An ellipse's axis is a construction axis, so auto now always gives its length, which the held half-axis check already counts.

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

* Sketch: an ellipse's axis is never a side of a Fillet or Chamfer corner

A line drawn from an ellipse's major end shares that point with the major axis, so Fillet or Chamfer there took the two as a corner (two lines skip the construction filter) and rounded the line into the axis. The axes are left out of a corner, picked by its point or as a and b.

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

* Sketch: test the move reply, extend and the one-axis Transform preview for ellipse axes

The reply of sketch.move, sketch.transform and sketch.scale lists the axis lines, Extend stops at an axis, and Sketch Transform previews an ellipse with the one axis left after the other was deleted.

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

* Sketch: with Draw ellipses with their axes off, D on an ellipse is its minor radius as before

The preference brought back the old drawing only; D on an ellipse had changed with no way back. sketch.dimension takes half_axes (default true, refused when not true or false); false makes auto on an ellipse the minor radius wherever the text is. The Dimension tool and the context menu's Sketch Dimension pass it from the preference, and with it off a radius on an ellipse is drawn and labelled R as before.

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

* Sketch: read an ellipse's axes in one pass over the constraints

ellipse_axis_lines scanned every constraint for every curve, so 500 ellipses with their axes (1500 curves, 2000 constraints) took 1.6 s to read and Move on all of them 2.0 s. It now collects the lines the centre bisects first: 3 ms and 0.36 s.

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

* Sketch: the dimension and corner parameters say how an ellipse's axis lines take part

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

* Preferences: Draw ellipses with their axes says D gives the minor radius when off

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

* Sketch: ask only the ellipses centred on a line's midpoint whether it is their axis

is_ellipse_axis read every ellipse's axes to answer for one line. An axis is bisected by its ellipse's centre, so it now asks only the ellipses centred on a point that bisects the line. The answer is the same.

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

* Sketch: a horizontal or vertical dimension across a level major axis holds it for D on the ellipse

half_axes_held counted a distance, a length and a radius, but not a horizontal or vertical dimension across the major axis, which is its length when the axis lies that way. D on the outline could then take the held major half-axis and be refused as determined; it now gives the minor one. Across a slanted axis such a dimension is only a projection and does not count.

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

* Dimension: a draft on an ellipse is drawn with the axes preference it was made with

The frame read the preference as it is now, while the dimension added is the draft's.

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

* Sketch: a horizontal or vertical dimension across an upright minor axis holds it too

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

* Sketch: drop the unused Sketch::ellipse_axes; ellipse_axis_lines says how the axes are read

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

* Sketch: an ellipse's own axes do not cut it, so Trim takes a lone ellipse whole and stops at real crossings

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

* Sketch: Fillet and Chamfer offer no corner at an ellipse's axis end, as the engine takes none

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

* Sketch: a deleted ellipse leaves its centre on its axes, as a Center Rectangle's centre stays

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

* Sketch: ellipse axis docs say what they do, and the corner picker tests a curve's ends before its axis role

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

* Sketch: the trim-at-a-crossing test checks that part of the ellipse is left

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

* Sketch: an ellipse axis moved, turned or scaled on its own takes its ellipse along, as a drag does

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

* Sketch: a selection dragged by an ellipse axis takes the ellipse along, as the axis alone does

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

* Docs: the Sketch preferences page lists Draw ellipses with their axes

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

* Sketch: the ellipse axes help text says D gives the minor radius, drawn and labelled R as before, wherever the value is pulled

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:54:46 +00:00
github-actions[bot] 1cfe77bb85 Regenerate generated files and fold pending files [skip ci] 2026-11-04 17:13:13 +00:00
Nathan Dane e3f99de8af Onyx: a tactile stone-grey theme with broad subtle blur (#10338)
* Onyx: add the finished stone-grey theme and document its blur range

* Onyx: distinguish grey construction lines from gold dimensions
2026-11-04 16:35:28 +00:00
ReDead 40b1b70dec Shell: pick a body from the Browser for a closed hollow (#794) (#11441)
* claim: #794

Co-authored-by: ReDead <ReDead@users.noreply.github.com>

* Shell: pick a body from the Browser for a closed hollow (#794)

Faces/Body accepted only faces, so a Browser body click never filled the
Shell dialog. Accept bodies, build faces:[] with that body, restore empty-face
shells as a body pick, and keep the chip/highlights on the body itself.

Co-authored-by: ReDead <ReDead@users.noreply.github.com>

* Shell: refuse faces that sit on a different body than the Browser-picked one

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

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:01:35 +00:00
github-actions[bot] 0862f1679a Regenerate generated files and fold pending files [skip ci] 2026-11-04 16:25:53 +00:00
austin62jake 2cd54b6af9 Agents and scripts: the standard part commands describe their exact parameters and reply (#11003) (#11004)
* claim: #11003

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

* Standard parts: input and output schemas (#11003)

parts.library, parts.insert, parts.fastener and parts.resize get typed
contracts and reply types in schema/parts.rs and leave unconverted.txt.
Each key reads as the handler reads it, with its message; the replies
are built from PartsLibrary, InsertedPart and ResizedPart, with the same
JSON as before.

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

* Standard parts schema: Vec3 comes from solidcore_record

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

* Standard parts schema: hole picks must start with a list

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

* parts.insert schema: the first hole pick must be a point, not any list

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 15:30:52 +00:00
N3MI-DG c9c063620a Hole: Bridged option for counterbores (#11103) (#11104)
* claim: #11103

* Hole: Bridge option for counterbores (#11103)

A counterbore hole takes an optional `bridge` length: two 3D-printing
layers that thick below the counterbore's floor, a slot along X across
the counterbore and then a square, both 0.02 mm wider than the hole, so
a printer bridges the step instead of sagging into it. Empty (the
default) keeps a plain counterbore.

The clearance keeps the bore, cut straight through both layers as for a
plain counterbore, clear of their sides: at exactly the hole's width it
ran tangent along them and broke through. The command and the geometry
refuse a layer that is not a positive length, layers that don't fit in
the hole below the counterbore, and a bridge on any other hole type.

The Hole dialog shows a Bridge field below Drill Point for counterbores.

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

* Hole: translate the Bridge label for the complete catalogs (#11103)

Arabic, Hebrew, Japanese, Vietnamese and Chinese carry every English
message, so `cargo xtask i18n --check` needs the new label in each.

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

* Hole: bridged counterbores refuse layers past a through hole's exit, too narrow a counterbore and a malformed bridge (#11103)

- Through all, the second bridge layer must end inside a body (exact point classification),
  short of the exit face; the tool's reach is no measure of the material.
- A bridged counterbore must be at least 0.04 mm wider than the hole, so the slot's ends have
  room on its wall (no square root of a negative number reaches the extrude).
- `solid.hole` refuses a `bridge` that is given but not a length expression (`true`, `[]`, `""`)
  instead of making a plain counterbore; `null` still leaves it out.
- Loop2 and Seg2 now come from solidcore_record (main moved them).
- The "nothing saved" check looks for the `"bridge"` key: `format_pending` names
  `bridged-counterbore` in every design.

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

* Unit defaults test: solid.hole has the bridge field too

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 15:24:15 +00:00
Nathan Dane 9060abc83e Sliders: keep the parent material clear beneath the track (#10375) 2026-11-04 15:17:54 +00:00
austin62jake bec762fb2b Agents and scripts: the render library browsing commands describe their exact parameters and reply (#11443) (#11444)
* claim: #11443

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

* Render library: input and output schemas for List, Describe, Targets and Preview (#11443)

Describe, Targets and Preview read their strict parameter types from
schema/render_library.rs, which become their contracts. List keeps its two
readers (the physical profiles' takes availability: null, the other
catalogs' doesn't); its contract takes what either takes. The handlers build
LibraryPage, LibraryResource, LibraryTargets and LibraryPreview instead of
json! literals; a catalog resource is read into ResourceSummary through a
small Listed reader. The physical profiles' replies keep their JSON, which the
schemas describe.

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

* Render library schema: the list says which availability each catalog takes

render.library.list has two readers: the physical profiles' takes
availability all, usable or null; the other catalogs' (and a call
without kind, which lists surfaces) take all, usable, specification or
blocked, but not null. The schema now says so with an anyOf on kind.
The type still takes what either reader takes: the readers check the
query and the page before availability, so refusing it in the type
would change which message a call with two mistakes gets in strict.
A new test checks that the schema takes ten calls exactly when the
handler does.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 14:50:44 +00:00
leValdemar19 826fce68eb Assembly: Move Occurrence says when the part's joints put it back (#11723) (#11791)
* claim: #11723

* Assembly: Move Occurrence says when the part's joints put it back (#11723)

A part that rigid joints hold to something grounded was moved, then put
back by the joint solve after the command, while occurrence.move still
replied with the moved transform. occurrence.move now solves the joints
before it replies. When they put the part back, the reply gives where the
part is, adds reverted: true and a warning that names the joint holding it
and what to change instead. A free chain of parts follows the move as before.

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

* Move Occurrence: the warning names the joint for a part in a repeated assembly too (#11723)

A part inside an assembly placed more than once has no path of its own, so
the reverted-move warning asked for one instead of naming the joint. It now
looks at the placement the caller named, or else the first one.

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

* Move Occurrence: list the part's joints when no single joint holds it back (#11723)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:39:17 +00:00
SS-EN 73f50d7bb4 Viewport: hidden parts no longer draw their joint-origin triads (#11294)
* Viewport: hidden occurrences draw no joint-origin triads

A named joint origin's triad (red/green/blue axes, white dot) was drawn for every
origin, so hidden inserted hardware still showed one at its placement. It now
follows the occurrence's visibility (and its parents') and the Joints folder of
the design and of the placement.

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

* Viewport: a copy's joint-origin triad follows that copy's visibility

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:24:55 +00:00
github-actions[bot] d1927aac25 Regenerate generated files and fold pending files [skip ci] 2026-11-04 13:33:45 +00:00
Brett Lawson b224ad6dae Kernel rig: the Lip refusal case matches the reworded message (#10871) (#11937)
#10871 reworded Lip's refusal on a face that is not a shelled rim to
"Lip needs the rim of a shelled body: ...", but the plastic rig case
cov-lip-on-a-face-without-an-opening-refused still looked for "pick the
rim face of a shelled body". The case now matches the new message, so the
kernel rig's plastic shard passes again on main.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:49:40 +00:00
bherbruck ca10e02573 Russian: correct the plural of degrees of freedom and make two notes match English (#11856)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:42:56 +00:00
bherbruck 5500681d6a Brand kit: logo files and a short brand guide in assets/branding (#9117)
Adds assets/branding/logo with the static mark (colour, one-colour
black and white), transparent 512/1024/2048 px PNGs, an outlined
wordmark and a horizontal lockup, all drawn from the app icon's
blue cutout layer. assets/branding/README.md covers use, colours,
clear space, minimum size and the name.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:36:16 +00:00
bherbruck fabeaa329a Desktop app: closing a design stops its kernel work, and designs build the same on any kernel (#11771)
* Desktop: closing a design stops its kernel work; same result on any kernel

A design's jobs now run under a stop token that closing or replacing the
design fires: an operation under it refuses to start, and one running ends
at solidcore's next check. The kernel it ran on is not used again. Calls
that answer from a body itself always finish, since a stopped call can leave
what it borrows half-changed, and what fills a body's shared cells is not
stopped.

Every kernel call starts clean (the same random rays, no stations from
earlier calls), so a design builds the same on any kernel of the pool, down
to its B-rep records. The pool is bounded: at most 8 kernels wait between
calls, and at 256 running calls a new call fails rather than grow or wait.

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

* Stop tokens: a copy never stops its design; a stopped job can't decide a refusal

A copy replacing its own design stopped the token it shares with the design
it came from, so that design's later work would be refused. Only the design
itself stops it now. Which face a refusal names no longer depends on whether
the job that found it had been stopped: the refusal is kept with the body,
and a stopped job's probes were all refused, which named the wrong face.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:35:39 +00:00
PeppaWutz 8f67c7fb95 Sketch: constraint icons stay while the sketch fills the view (#10518) (#11399)
* Sketch: constraint icons stay while the sketch fills the view (#10518)

Constraint glyphs hid as soon as a millimetre of the sketch plane drew smaller than two pixels
(#8980), whatever the sketch's size, so a 500 mm sketch zoomed to fit lost every glyph although
it still filled the view. Below that scale the glyphs now hide only while their anchors span
less than twenty glyphs (300 px) on screen: a small sketch far away still loses them, a large
one that fills the view keeps them. The preference "Hide constraint glyphs when zoomed out" is
unchanged; off, glyphs stay at every zoom as before.

Test: constraint_glyphs_hide_when_zoomed_out adds a 500 mm sketch that keeps its glyphs zoomed
to fit and loses them far away; glyph_zoom_tests covers the span threshold.

* Sketch: glyph span measures only drawn glyphs and stops at the threshold (#10518)

The span check now walks the same constraints the glyph pass draws (one MidPoint glyph per shared midpoint, a pattern's constraints once) and returns as soon as the bounds reach MIN_GLYPH_SPAN_PX, so a large sketch zoomed out costs no more than before.

* Sketch: filter glyph constraints lazily, midpoints in a set (#10518)

* Sketch: measure the sketch, not its glyph anchors, before hiding glyphs (#10518)

* Sketch: collapse the midpoint check into one if (#10518)
2026-11-04 12:06:26 +00:00
github-actions[bot] 6bca7d2854 Regenerate generated files and fold pending files [skip ci] 2026-11-04 08:13:26 +00:00
Frank eeb686f164 Measure: a circle and a line now show 'Line to centre' instead of 'Center to center' (#11524)
* Measure: a circle and a line show 'Line to centre' instead of 'Centre to centre'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Measure: US spelling for Line to center, UK in en-GB

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 07:38:21 +00:00
Nathan Dane 9e3e403b3f Browser and History: keep row tools working when either panel is hidden (#11303) (#11416)
* claim: #11303

* Keep Browser and History row interactions independent

* Browser tests: use solidcore_record::Vec3 after the layer split

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 07:15:40 +00:00
dudedagger42 0231eec4c6 Nix flake: zenity on the app's PATH, for file dialogs without a portal (#2177) (#5183)
rfd opens file dialogs through the XDG desktop portal and falls back to zenity
when there is none. The flake's package didn't bring zenity, so on a system
without a portal or zenity, Open and Save showed nothing. The wrapper now adds
nixpkgs' zenity at the end of PATH, after the system's own.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:53:37 +00:00
erman 69f202f1c8 Measure: show complete point coordinates (#11069) (#11085)
* Measure: show complete point coordinates (#11069)

Render measurement item details as wrapping text instead of fixed-height cells. Keep the existing values, units and precision. Add a regression test in the existing measurement UI test module.

Local validation: the regression test compiled, but the Windows GNU test binary crashes with 0xc0000005 even for --list, before tests run. Runtime verification and visual evidence remain pending; this contribution is a draft.

* Keep repository LF line endings in the coordinate fix
2026-11-04 06:53:04 +00:00
austin62jake 1cbfb889d5 Agents and scripts: Compare Bodies describes its exact parameters and reply (#10994) (#10995)
* claim: #10994

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

* Compare Bodies: input and output schemas (#10994)

inspect.compare gets a typed contract and reply types in
schema/compare.rs and leaves unconverted.txt. Each key reads as the
handler reads it, with its message; `color` checks each colour with the
handler's own color_param, now pub(crate) and re-exported from cmd. The
reply is built from Comparison and its parts, with the same JSON.

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

* Compare: drop the json import the typed reply no longer uses

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

* Compare schema: colour strings carry the hex pattern

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 06:45:10 +00:00
thocsmith ee3cc32525 Settings profiles: export and import stay inside an agent's folders (#11362) (#11390)
* claim: #11362

* Settings profiles: export and import go through vfs, inside an agent's folders (#11362)

settings.profile.export and settings.profile.import read and wrote their
path with std::fs, so the folder scope an MCP request's allow_paths sets
up never saw it: an agent could write a profile anywhere, or probe any
path. Both now go through vfs (len, read_to_string, write), as file.export
and ui.screenshot do. requests_with_allow_paths_stay_inside_them covers
export and import inside and outside the agent's folders.

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

---------

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:28:11 +00:00
Colin Faulkingham 954232cef1 AI client setup: preserve configuration files when a write fails (#11363) (#11410)
* claim: #11363

* AI client setup: replace configuration files atomically (#11363)
2026-11-04 06:27:37 +00:00
github-actions[bot] 9092df9b97 Regenerate generated files and fold pending files [skip ci] 2026-11-04 07:25:51 +00:00
semmelrinde c1a23f8f97 Section Analysis: the drag arrow is back when the dialog is reopened (#11423) (#11432)
Edit reopens the dialog from the section's own geometry (Show viewport
section status, on by default): the plane is kept as numbers and nothing
is picked. The arrow was placed from the first pick only, so it was gone.
With nothing picked, it now stands on the kept plane, its tip at the cut.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:11:46 +00:00
austin62jake 0fc4cd2747 Agents and scripts: the render stage commands (Ground, Backdrop, Clear) describe their exact parameters and reply (#11455) (#11456)
* claim: #11455

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

* Render stage: input and output schemas for Ground, Backdrop and Clear (#11455)

The three handlers' strict parameter types move to schema/render_stage.rs as
GroundParams, BackdropParams and ClearParams, with descriptions and
JsonSchema, and become the contracts; the handlers read the same types. The
handlers build the shared StageSaved reply instead of json! literals, and the
saved setup is serialized with an error instead of json!'s panic path.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 06:11:09 +00:00
leValdemar19 6d83f95fba Isolate a body: other components are hidden as components, in one step (#7896) (#7951)
* claim: #7896

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

* Isolate a body: other components are hidden as components, in one step (#7896)

Isolating a body hid every other body one by one: browser.visibility with `items` writes only
hidden_bodies, so every other component's Browser row kept an open eye and normal text, and
its eye could not bring it back (the first click hid an already invisible component).

browser.visibility takes `isolate: true` with `items` (bodies), as it does with `paths`. The
picked bodies' own placements stay open, their other bodies (and the root's) are hidden one by
one, and every other placement is hidden as a placement. Isolate in the Browser and the view
runs that one command, so it is also one undo step instead of two.

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

* Isolate a body: a hidden Bodies folder or group it is in shows again (#7896)

Isolating a body inside a hidden Bodies folder or group left that folder or
group hidden, so the picked body stayed hidden. Isolate now shows the
folders and groups (with the groups they are in) that hold the picked
bodies; their other bodies are hidden one by one as before.

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

* Isolate a body: look for hidden folders only when some are hidden, and find body owners once (#7896)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:06:23 +00:00
austin62jake bf625dff05 Box, Sphere and Torus: clicking a point of a sketch places them there (#9403) (#9405)
* Box, Sphere and Torus: a sketch point picks where they go (#9403)

The Point input of the Box, Sphere and Torus windows took a vertex, a point on an edge or face
and the origin, but not a sketch's points, so a sphere could not be centred on a point drawn in a
sketch. It now takes the points of every shown sketch (POINTS | EXTERNAL_POINTS, as Measure
does): a click fills X, Y and Z with where the sketch puts the point in the world, and a point
selected before the window opens places the primitive there, as a vertex does. The input's chip
names the point and its sketch as Select Other does ("Point 2 · Marks").

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

* Box, Sphere and Torus: the sketch point chip label for every complete locale

Main's i18n check wants each message in the complete catalogs (ar, he, ja,
vi, zh); the label has no words, so each carries the same "{point} · {sketch}".
With main's new selection kinds and this one, every Sel variant has its arm,
so the catch-all arm is unreachable (clippy) and goes.

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

* Primitive at a sketch point: a point picked in the sketch being finished still places it

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 06:03:39 +00:00
semmelrinde ed3510a91f Typed schemas for the design properties commands (part of #2829) (#10920)
document.metadata, document.metadata.set and document.metadata.remove
get input and output schemas: contracts in schema/metadata.rs, checked
in front of the unchanged handlers, and reply types the handlers now
build their replies from. Their lines leave unconverted.txt.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:03:21 +00:00
github-actions[bot] 8f0130a61b Regenerate generated files and fold pending files [skip ci] 2026-11-04 06:39:31 +00:00
github-actions[bot] 72df682af7 Regenerate generated files and fold pending files [skip ci] 2026-11-04 06:27:43 +00:00
Davit Matchakhelidze 41a450be7c i18n: add Georgian (ka) interface language (#11501)
A Georgian catalog (assets/locales/ka.json) carrying every English message,
registered in crates/ui-egui/src/i18n.rs, with a row in the new-language test,
a pending attribution file and a terminology note in docs/localization.md.
The catalog is Mkhedruli only; egui's built-in Hack already draws it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:00:45 +00:00
HiroKenEz b227924be9 Render: the Cancel button keeps its full label during a render (#11222) (#11241)
* claim: #11222

* Render: the progress row keeps Cancel whole (#11222)

The progress card is an egui area, laid out within last frame's size. When
the status grew (9 % to 10 %, a longer phase or name), the caption and the
Cancel button were truncated, down to "..." with a long shot name.

progress_action now takes the name and the status apart and gives each part
an explicit width from the room the caller offers (the canvas for the
floating card): Cancel and the status always show whole, a long name is cut
with an ellipsis (full name on hover), then the meter narrows.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:00:31 +00:00
Mateusz Sych 28b47059bb Agent chat: a text selection dragged past the edge scrolls the conversation (#11527) (#11533)
* claim: #11527

* Agent chat: a text selection dragged past the edge scrolls the conversation (#11527)

Holding a text selection near or past the top or bottom of the conversation
scrolls it, faster the further out, so a long answer can be selected in one
drag. egui scrolls a label's selection only for the label under the cursor, and
not at all for a table, whose label sits in its own horizontal scroll area.

egui also leaves a label out of the text selection while it is scrolled out of
view: a selection whose start had scrolled away was dropped, and Copy missed
the hidden text. The conversation's labels now take part out of view too,
clipped to nothing, so they keep their end of the selection and give their
text to Copy without being drawn or moving the selection.

* Agent chat: only a drag towards the edge scrolls the conversation (#11527)

A press on a label creates an empty selection on the press frame, so a click,
a double click or a held press on the line by the edge scrolled the
conversation, and so did the start of a drag from there inwards. Scrolling now
waits until the pointer has moved more than 8 points towards that edge since
the press.

* Agent chat: approval titles and pending messages keep a selection scrolled away

They were plain selectable labels, so a selection that started in one was dropped
once its label scrolled out of view, as in the rest of #11527. They now use the
same off-screen-aware label as the conversation's text.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-04 06:00:28 +00:00
Mateusz Sych a19a32ddd7 Interference: Compute checks in the background, so the window no longer freezes (#11483) (#11541)
* claim: #11483

* Interference: intersect and mesh each overlap on a kernel worker (#11483)

Compute in Inspect > Interference intersected each overlapping pair and
meshed the shared volume on the window thread. Its stack is 1 MiB on
Windows, too small for the kernel's recursion, so the app could close
without a crash message. Each pair now runs through
kernel::map_with_local_pools, on a worker with the kernel's 8 MiB stack,
as inspect.interference and inspect.clearance already do.

* Interference: Compute checks off the window thread (#11483)

On 0.30.0 the report's two parts took the window thread 95 s (x64) to
154 s (x86) in the overlap boolean, and the window stopped responding
until the app was closed. Compute now runs inspect.clearance and the
shared volumes on a copy of the session, on a kernel thread, when the
app runs commands off the window thread (as Find Limits does). The
dialog shows the elapsed seconds with Cancel meanwhile, and drops the
reply of a design changed since.

* Interference: Cancel on its own row while Compute checks (#11483)

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-11-04 06:00:15 +00:00
Homero Thompson 606e264f6d UI: the Dependents button of the INSPECT HISTORY ribbon no longer fails without a feature (#11642) 2026-11-04 05:59:52 +00:00
semmelrinde d25f8198fa Pieces of a cut keep their names when the design is evaluated again (#11658)
A cut that leaves a body in pieces names them after their faces
(`order_lumps`): the piece whose least face name sorts first takes the
first name, and so on. It moved the names but left the pieces where the
kernel put them. The session then pinned the feature's body names in
that state order, and the next full evaluation (opening the design, or
editing a parameter upstream) handed them out again in a different
order: two pieces swapped names, and a later feature that named one met
the other. Found by a local model fuzzer.

The pieces now also take the family's places in name order, so the
names pinned are the order the next evaluation hands them out in. A
design saved before keeps the names a fresh evaluation gave it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:59:42 +00:00
Hainlein4711 464f4ac748 Intel OpenGL: restore the grid and sketch axes (#8053) (#11573)
* claim: #8053

* Intel OpenGL: restore the grid and sketch axes
2026-11-04 05:49:15 +00:00
semmelrinde 03b5248dc1 Sketch: Line and Construction Line refuse a second point next to the first (#11595)
A through-point 0.0000001 mm from the start made an infinite line whose
direction nothing held, and a Line that short had both ends connected to
the origin, a line of length zero. Both now refuse points closer than the
distance at which a new point is connected to an existing one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:49:05 +00:00
Colin Faulkingham f9dd77e383 Configurations: reject invalid suppression values when saving rows (#9088) (#11571)
* claim: #9088

* Configurations: reject invalid suppression values before saving rows (#9088)
2026-11-04 05:39:04 +00:00
semmelrinde 4afd24d29f Design tabs: the tooltip says when a saved design has unsaved changes (#11408) (#11590)
* Design tabs: the tooltip says when a saved design has unsaved changes (#11408)

A tab marks unsaved changes with a bullet after the design's name, and a
long name cut short by the tab hides it. The tab's tooltip, which says
where the design is saved, now adds "Unsaved changes" below for a saved
design with changes not saved yet. A design never saved already says
"Not saved yet". The new string is in a pending locale file, with ten
translations taken from the same words elsewhere in the catalogs.

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

* Design tabs: the unsaved line in every complete catalog (#11408)

Arabic, Hebrew and Vietnamese are complete catalogs, which must carry
every English message: they get the words they already use for
"Unsaved changes" elsewhere.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:29:03 +00:00
kaltenbrunnermarkus 8a74336713 Tests: the autosave recovery test no longer fails at random on a busy machine (#11815)
* claim: Recovery: the save test waits for the autosave worker before it checks

* Recovery: the save test waits for the autosave worker before it checks

saving_drops_the_entry_and_a_clean_close_leaves_nothing marked the
session saved right after the tick that started an autosave. Under load
the worker was still writing, so the next tick found the write running,
took nothing up and dropped nothing; the file landed afterwards and the
check that it is gone failed (seen in CI on #9021).

The test now ticks, bounded, until the autosave has caught up
(Autosaver::caught_up), as the app's own frames would, before it marks
the session saved. A small helper in the test module does this. The
app's behaviour is unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:22:20 +00:00
Colin Faulkingham 5e4c52a7cb Sketch Transform: keep the applied rectangle at the previewed rotation and size (#11617) (#11620)
* claim: #11617

* Sketch Transform: preserve dimensioned geometry on apply
2026-11-04 05:21:27 +00:00
github-actions[bot] 77e0e11775 Regenerate generated files and fold pending files [skip ci] 2026-11-04 05:42:40 +00:00
Lars Jansen 7bd66b7c06 Sketch dimensions: enter the value in a small window with step arrows (#7246) (#7251)
* claim: #7246

* feat(sketch): enter a dimension's value in a small window with step arrows (#7246)

* Dimension window: fit the layer split, and keep the wheel off the camera over the window

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:12:31 +00:00
Daniel 86db05e538 Sketch: regression test for a Line started on a snap point (#11171) (#11322)
* claim: #11171

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

* Sketch: test that a line starts on a snap point of an existing shape (#11171)

The Line tool's first click on a line's midpoint, a circle's centre or edge,
a rectangle's edge or a point part-way along a line starts the line there and
ties its start to that shape. In 0.30.0 and 0.30.1 the start was dropped on the
next hover (fixed by #11184); this covers each shape the reports name.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:11:14 +00:00
ABDULLAH d912f3533b Arabic: better wording for Appearance, Graphics, Assistant, Layout, Sketch and Navigation preferences, thread drill notes and other messages (#11358)
* Arabic: viewport term in the Appearance section heading, desktop app badge, deeper-overlap note in Find Limits

* Arabic: drop the stray direction mark and match the Refresh wording in the old Node.js note

* Arabic: tanween in the thread fit hint, tap drill wording, crease term in continuity, spline label and setback wording in the fillet help

* Arabic: sweep and boolean terms in the body check note

* Arabic: badge term for the status chips note and summary wording for Fold items in Graphics preferences

* Arabic: tanween, catalog terms and clearer labels in the Assistant, Design and Tools preferences

* Arabic: tanween in the shortcut key prompt, Look At wording in the Assistant follow help

* Arabic: complete the Several choice for bodies folders and use a verb form for the dialogs Open row

* Arabic: orbit style term, Select Other and center-point arc names in Sketch preferences, place-from-origin wording, tanween in the radial menu help, tap makers in the tap drill notes

* Arabic: graphics device term in the blur notes, boss and turn wording in the fillet corner option
2026-11-04 05:10:29 +00:00
kaltenbrunnermarkus 9d7bbb7fa9 Tests: two Properties tests no longer fail at random on a busy machine (#11750)
* claim: Properties: hold the measuring worker in tests until the card is checked

* Properties: hold the measuring worker in tests until the card is checked

open_object starts the measurement on a worker and looks for the reply
once without waiting. Under load the window side could be held up
between the two, the reply was already in, and two Properties tests saw
the card filled where it should still be measuring (about 0.3 % and 4 %
of runs with 18 tests at a time).

measure_beside now runs a hold before sending the reply. In tests it
waits on the app's existing apply_gate, which feature evaluation already
uses for the same purpose; outside tests it does nothing. Both tests set
the gate, check the card is measuring, then let the worker go.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:08:36 +00:00
leValdemar19 6312c2760a MCP list_commands: ask for commands by id prefix or panel, ids and labels only, or a page at a time (#11726) (#11788)
* claim: #11726

* MCP list_commands: ask for commands by id prefix or panel, ids and labels only, or a page at a time (#11726)

list_commands answered with the whole catalog, every key of 575 commands
(about 350 KB), and could only narrow it by a substring. It now also takes
`prefix` (start of the id), `panel` (a whole panel or tab name), `compact`
(id and label), `fields` (the keys to keep) and `limit`/`offset` (pages, as
{commands, total, offset, next_offset}). With none of them the reply is the
same as before. A batch step naming list_commands narrows the same way, and
a bad argument is an error that names it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 05:07:29 +00:00
SilentWolf75 3a23d21795 Lip: clearer guidance when the face is not a shelled rim (#10437) (#10871)
* claim: #10437

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

* Lip: say Shell first when the face is not a rim (#10437)

A solid box top left OK disabled with a vague shelled-body hint. Name Shell and the rim-around-an-opening requirement in the refusal, the dialog tip, and the command help.

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

* Lip: keep the dialog hint as it was, match the new refusal text in the test

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 04:42:08 +00:00
austin62jake e199f9aeff Agents and scripts: Match Surface describes its exact parameters and reply (#10981) (#10982)
* claim: #10981

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

* Match Surface: input and output schemas (#10981)

surface.match gets a typed contract in schema/surface_match.rs and the
shared FeatureResult as its output, and leaves unconverted.txt. Each key
reads as the handler reads it, with its message; like solid.scale it
declares `name` and `participants` itself, since a match makes no body.

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

* Follow main's kernel merge: world_state and the shared continuity reader

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

* Surface match schema: Vec3 comes from solidcore_record now

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 04:14:11 +00:00
austin62jake e06d6e36d8 Agents and scripts: the material card checks describe their exact parameters and reply (#11036) (#11037)
* claim: #11036

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

* Physical material property commands: input and output schemas (#11036)

material.properties.validate and material.properties.directional get
typed contracts and reply types in schema/material_properties.rs and
leave unconverted.txt. The card stays the handler's to check; the
direction has the handler's messages. The replies mirror the document's
Validation and DirectionalModulus, destructured in full.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 04:08:15 +00:00
Kai Evans 9d1ad9f0b9 Feature editor: OK accepts the joint placements a roll back re-solves (#11094) (#11095)
* Feature editor: a roll back's re-solved placements are not a changed design

Rolling the marker solves the joints again and stores where that puts each occurrence, so the
editor's pinned original differed from its source there and OK was refused with "the original
editor model does not match its source design". The check now takes the placements as the roll
leaves them.

* Feature editor: also accept the ratio and lift of coupled joints that rolling the marker works out

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 03:56:46 +00:00
austin62jake d39a2d28db Agents and scripts: the clearance check describes its exact parameters and reply (#11051) (#11052)
* claim: #11051

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

* Clearance: input and output schemas (#11051)

inspect.clearance gets a typed contract and reply types in
schema/clearance.rs and leaves unconverted.txt. Each key reads as the
handler reads it, with its message; MAX_CLEARANCE and MAX_NEAREST move
to the schema module so both read one limit. The reply is built from
Clearance and its item types, with the same JSON as before.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-04 03:44:59 +00:00
github-actions[bot] 1e649653ef Regenerate generated files and fold pending files [skip ci] 2026-11-04 03:44:17 +00:00
bherbruck 6988eb0043 CodeRabbit: flag changes to existing defaults (#11832)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 03:15:51 +00:00
Tyler Chiriboga 23c2244307 Inspect: center of mass reports the same volume as measure (#6173) (#11262)
* claim: #6173

* Inspect: center of mass reports the same volume as measure (#6173)

inspect.center_of_mass measured each body's display mesh, whose flat
facets cut curved faces short, so its volume and centre differed from
inspect.measure's (a sphere of radius 10: 4183.10 vs 4188.49 mm3). It now
takes both from the same cached kernel measurement Measure uses, and like
Measure it leaves out bodies that enclose no volume, counting them in
open_bodies_skipped.

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

* Centre of mass tests: read the skipped list that main now returns

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

---------

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 03:13:56 +00:00
semmelrinde a52ee850aa 15 languages: translate the 21 messages main gained after batch 4 (batch 5) (#8573)
* 15 languages: translate the 21 messages main gained after batch 4 (batch 5)

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

* Pending locales: drop the strings the catalogs already have

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-04 03:08:32 +00:00
bherbruck 65d23a78a5 Preferences: the 3D mouse speed readout shows × instead of garbled text (#11785)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 02:46:30 +00:00
bherbruck 481687fff3 Modeled threads: the thread runs down to a flat face with no uncut block, so a nut can bottom out (#11782)
* Modeled threads: run down to a flat shoulder so a nut can bottom out

An external thread running the whole face stopped with its groove clear of
a shoulder, leaving up to a turn uncut above it. The groove now runs on past
the plane of the face's end and is trimmed there, so its root reaches the
shoulder at every angle and nothing past the plane is cut.

A shoulder rising back along the face keeps the square stop, as do internal
threads, tapers, runouts and lengths stopping short. Where the kernel refuses
the trimmed cut, the groove also keeps the square stop and the feature warns.

Closes #11610

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

* Kernel ladder: record the thread cases that now reach their shoulder

Six cases gain the faces of the trimmed last turn; every other case is
unchanged apart from its timing.

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

* Thread at a shoulder: review fixes, and a test of the fallback

The shoulder check starts just off the face, as the free-end check does, so
a slope rising from the face counts. A groove the trim leaves nothing of now
falls back to the square stop too. A test forces the fallback and reads its
warning. The ladder's order and report go back to main's, changed only for
the cases this moved.

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

* Thread at a shoulder: check all the radii the groove cuts

The check for a shoulder rising along the face looked a fixed distance out,
which falls short of the groove on an undersized shaft. It now covers the
groove's reach. A test reads how each kind of shoulder is classified.

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

* Thread at a shoulder: test the trim that leaves nothing of the groove

The test now reaches the path where the half-space keeps nothing, which is
the one that must fall back to the square stop.

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

* Thread at a shoulder: say so when the shoulder can't be checked

If the kernel can't make the rising-shoulder check, the groove keeps the square
stop as before, and the feature now warns that its last turn is left uncut.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 02:43:36 +00:00
github-actions[bot] 46a9cf80b8 Regenerate generated files and fold pending files [skip ci] 2026-11-04 01:45:15 +00:00
jyviko f74c6605c4 Fillet: Mitre or Swept corners where two rounds meet (#11498)
* SMLib wrapper: a swept or mitred turn for constant rounds (Turn)

Brep::blend_dropping takes a Turn: Mitre (the default, as before) or Swept,
which sets solidcore's FilletSession::set_swept_turns before the corners are
made. A swept turn solidcore refuses at CreateFilletCorners comes back as
Error::TurnRefused with its reason and place (NoRoom at a concave side edge,
else CornerUnsupported). Swept is for Blend::Round only.

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

* Kernel: fillet rounds turn in a mitre or swept (FilletTurn)

fillet_removing, fillet_max, fillet_max_each and fillet_each take a
FilletTurn and hand it to SMLib, through the reblend of earlier rounds at a
later corner too. A swept corner solidcore does not build is refused as
invalid input with its reason and place: no smaller radius builds it.

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

* Design: a fillet saves its turn, mitre (default) or swept

FeatureKind::Fillet gets `turn` (FilletTurn: mitre, the default and not
saved, or swept), one pending format file. Evaluation hands it to the kernel,
pattern copies included, and refuses a swept turn with a style, fit, corner
or setbacks the kernel's swept corner does not take (SWEPT_TAKES).

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

* solid.fillet: the turn parameter, and the command's input and output schemas

`turn: mitre|swept` on solid.fillet, refused when unknown or with options a
swept corner does not take. solid.fillet now has its contract (FilletParams)
and reply type (FilletResult), registered with checked!, and leaves
schema/unconverted.txt.

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

* Fillet dialog: a Turns choice, Mitre or Swept

A Constant radius fillet's Turns row sends `turn: swept`; choosing a narrowed
radius or a curvature corner puts it back to Mitre, and Swept puts those back.
Its tooltip says what each does where two rounds turn round a corner edge.
New strings in a pending locale file (en, de, ja, ko, zh); glossary entry.

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

* Fillet turn: the schema tests' fixes and the complete catalogs' strings

The no-contract example in the schema tests is solid.chamfer now that
solid.fillet has one; the reply test checks the mid-radii form against the
contract (solidcore refuses that law's corner yet, #430) instead of building
it; Arabic, Hebrew and Vietnamese, complete catalogs, get the new strings.

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

* Fillet schema test: variable and spline forms checked against the contract

solidcore fails a variable round on a box edge at this pin (status 1001, as
variable_radius_on_a_box_edge), so the reply test checks those forms against
the contract instead of building them.

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

* Fillet turn: the swept rig cases pass; the MCP no-contract example is solid.chamfer

cov-fillet-turn-swept-boss-base-r1 and
cov-fillet-turn-swept-pocket-sharp-corner-refused-r1 pass now that
solid.fillet takes `turn`: off the rig's known-failing list. The MCP bridge
test's example of a command without a contract was solid.fillet, which has
one now.

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:36:00 -05:00
github-actions[bot] 215cc17c52 Regenerate generated files and fold pending files [skip ci] 2026-11-04 01:33:39 +00:00
Nathan Dane c5bc849913 Blur: keep themed backing and notice actions accessible (#9810) 2026-11-04 00:52:09 +00:00
leValdemar19 6178d1a45e Selection fields: remove one picked item from the dialog (#3683) (#4232)
* claim: #3683

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

* Selection fields: remove one picked item from the dialog (#3683)

A dialog field that takes several picks (edges for Fillet, Chamfer or
Flange, faces, bodies) showed "N selected" with one x that cleared them
all. It now has the kit's disclosure arrow beside that x: open, it lists
the picked items by name, each with its own remove button (the rows
Sketch Modify dialogs already used), in a short scroll area. Removing an
item works like picking it again to toggle it off: the hand-picked edges
of a Fillet and the explicit profile choice of Extrude, Revolve and
Emboss follow. The x still clears everything and folds the list.

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

* Selection fields: hovering a picked row lights its item; every multi field lists alike (#3683)

Review follow-up. Hovering a row of a dialog's picked-items list, or its
remove button, lights that item in the view with the normal hover shading
(and not as picked): faces, edges, vertices, profiles, planes, axes and
sketch geometry as the hover, bodies, components and features as their
Browser and timeline rows light them.

Every multi-pick field now has the same folded list: Sketch Modify fields
lose their always-open list for the shared toggle, and Shell with Tangent
Chain lists the faces its chip counts (removing a face its chain brought
takes out the pick that brought it). Removing a row works as picking the
item again: a rim or surface split into pieces is one row and goes whole.
Tests cover removing a row from Combine, Loft rails, Sweep path, Blend To
edges, Rigid Group components and pattern features: the preview's
commands follow.

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

* Selection fields: build on main's fallible world state; Sketch Mirror's label test opens the list (#3683)

The world state is now a Result on main: the remove and hover paths treat a
failed one as nothing to remove or light. The Sketch Mirror label test from
#10770 opens the picked list first, as this field now lists its picks behind
the arrow like every other multi-pick field.

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

* Selection fields: removing a Tangent Chain row takes out the pick that brought it (#3683)

With Tangent Chain on, a Fillet's list shows every edge of each picked
edge's chain. Removing one of those rows left the picked edge behind, so
ticking Tangent Chain off and on brought the chain back. The row now goes as
picking it again would: with the picks whose chain holds it and their whole
chains. A Shell row whose model can't be built goes on its own instead of
silently staying.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 23:45:06 +00:00
jyviko e12afd11cc Ellipse and spline extrudes make one wall; coplanar faces never lose their names to a budget (#11687)
* Planar sources: decide coplanar faces by support group, no budgets that drop sources

map_planar_sources kept two budgets, `metadata` (2 MB) and `identity_bytes`
(16 MB), charged by each face's source descriptor and by every extra
candidate on its plane. When one ran out, faces silently lost their source
and with it their persistent name: a 300-hole plate whose top a comb of
grooves cuts into 62 strips lost the cap source from strip ~47 on.

The budgets bounded the real cost: each result face looked at every
candidate on its plane, faces x candidates when many faces are coplanar.
The candidates are now grouped by support once (`same_support` against
each group's first plane, through the existing PlaneIndex), each group's
chosen source, ambiguity and inner flag decided once, and the faces
without a role together with the competing tool faces grouped the same
way. Each result face then looks up its plane's few groups. The decision
is the old one: blocked by an unknown or competing support, else the
first candidate on the plane unless another candidate there has a
different plane, source or role.

Work: grouping looks at each candidate's index window once per group
(each candidate is assigned on its group's first look), and each face
looks at the group representatives near its plane, so the planes looked
at grow with faces + candidates. A counter (`looked`) checks it: n
pieces of one cap and n faces on its plane take exactly 2n looks (n*n
before), 3n with n blocking supports.

Tests: the holed plate keeps the cap source on all 62 strips and the
bottom (fails on the old code: face 352 lost its source); the existing
planar source tests pass.

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

* Extrude: an ellipse or a spline sketch curve sweeps one wall

An extruded ellipse had four side faces (`F2:side:e1#1` to `#4`), one
per conic quarter, and a spline one per span: the profile holds such a
curve as several exact segments and SMLib got each as its own curve.

The Extrude feature's builder (`extrude_whole_sides`) now takes a
`one_curve` test from the caller. Each run of free-form segments of one
degree that it says are one curve goes to SMLib as one B-spline: the
Béziers' control points in a row, knots repeated the degree times at
each joint, each span as long as keeps the speed through the joint the
same (so the curve is C1 there and SMLib makes one face over it, as it
would not over a C0 parametrisation), weights scaled to meet. Exact: the
same curve, reparametrised. A loop starts at a run's start, so no run
wraps round it. The wall's history lists every piece, and its
construction source is its first piece's (as joined collinear lines).

The document says which segments are one curve: ExactSketchCurves now
also holds each free-form sketch curve's exact segments
(`find_free_form`: a segment that is one of them, either way round), and
the profile roles use it, so the wall is named `side:<curve>` from its
history rather than by the curve nearest its mesh centre.

Saved designs rebuild with these walls renamed (`F2:side:e1#k` ->
`F2:side:e1`); accepted, no alias kept.

Tests: kernel `pieces_of_one_curve_extrude_into_one_wall` (an ellipse in
four conics is 3 faces, its volume pi a b h to 1e-6, the wall generated by
all four pieces; a 3-span spline closed by lines is one wall; the same
solid as a face per piece); doc
`an_extruded_ellipse_or_spline_is_one_wall_named_by_its_curve` (one
`F2:side:e1`, no piece names, the same names on a second build and after
reopening).

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

* Tests: a tray's far rim name is checked on the exact B-rep face

The tray test took the point `sheet:...:rim.far` finds as right when it
lay within 1e-3 mm of the strip z = 30, 199 <= y <= 200: a slack sized to
the mesh. It now asserts on the face the name finds, on exact geometry:
the point's distance to that face's surface (FaceSurface::nearest) is
within rounding at the part's scale (1e-12 x its size), and the point is
inside the face's boundary, counted by crossings of its record edges
(each asserted to be a line, the rim being planar).

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

* Tests: delete tests/naming/, the records no test reads since the naming corpus stopped comparing against them

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

* Viewport test: smooth wall joins drawn from eight G2 spline walls

The PR extrudes each spline or ellipse sketch curve as one wall, so the
offset-ellipse ring the test built has no joins between faces any more.
The test now extrudes a circle drawn as eight cubic splines: eight walls
meeting with one curvature along eight upright edges (24 edges in all).
It still checks that those joins are left out of the shaded view, drawn
in the wireframe and selectable as edges.

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

* Kernel ladder: 009 and 012 record the one-wall extrude (an extruded spline or ellipse sweeps one wall)

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

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:27:14 +00:00
898 changed files with 67338 additions and 23333 deletions
+20
View File
@@ -0,0 +1,20 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
reviews:
auto_review:
# 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.
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.
pre_merge_checks:
custom_checks:
- 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.
+7 -5
View File
@@ -3,13 +3,13 @@
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
## Start here
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Act when the code, docs and rules settle a question; ask only when it changes what users see. Local `plan/` notes are optional.
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Local `plan/` notes are optional.
## Hard rules (what enforces each)
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
- Never crash: no unwrap/expect/panic/unsafe in non-test code (unsafe allowlist in crash-safety), errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
- 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`.
- New kernel cases go to solidcore: `cargo xtask lists`.
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
@@ -21,12 +21,14 @@ 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, and no second PR for an issue a lower-numbered one closes: `pr-checks.yml`.
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line: `pr-checks.yml`.
- 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, never force-push `main`, no pushes just to catch up with `main`: `skills/solidcraft-contributing/SKILL.md`.
- 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`.
- Code, comments, commit messages and docs are in English, always: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
- Independent work runs in parallel, dependent work stays sequential: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
- Show observable changes with real screenshots or a recording, captured yourself.
- Run `cargo xtask ci` before every commit, without switching branches while it runs. `cargo xtask skills` keeps this file and `skills/` honest.
- Run `cargo xtask ci` before every commit. `cargo xtask skills` keeps this file and `skills/` honest.
## Working on X: read the skill first
| Working on | Read |
+47 -4
View File
@@ -317,7 +317,6 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/icons/ui/design/svg/model/circle_ttt.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/coil.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/colored_components.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/colored_components_off.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/combine.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/component.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
@@ -784,6 +783,51 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
| `assets/locales/pending/sketch-offset-instances.json` | SolidCraft contributors | Original offset instance-count label and translations | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/settings_profile.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original analytic head and shoulder geometry for settings profiles | MIT OR Apache-2.0 |
| tests/kernel-cases/import/4561-convert-box-mesh-with-a-zero-area-triangle/box-degenerate.stl | SolidCraft contributors | synthetic ASCII STL written by hand: a box with one zero-area triangle, for the kernel regression case of tangency-labs/solidcraft#4561 | MIT OR Apache-2.0 |
| assets/locales/pending/theme-blur-ui.json | SolidCraft contributors | Original blur controls, notices and translations | MIT OR Apache-2.0 |
| crates/ui-egui/tests/fixtures/theme-blur.json | SolidCraft contributors | Original theme for blur option scenarios and manager review | MIT OR Apache-2.0 |
| assets/locales/ka.json | David Machakhelidze (devius) | Original Georgian translation of SolidCraft's English catalog, written with Claude Code from the meaning of each English string; terminology and doubtful strings decided by a native Georgian speaker; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-mark.svg | SolidCraft contributors | static SolidCraft mark: the five faces and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg (the final frame of assets/styles/web/loading-logo.svg), cropped to the mark's bounds; original artwork | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-mark-mono-black.svg | SolidCraft contributors | one-colour (black) version of assets/branding/logo/solidcraft-mark.svg, same paths; original artwork | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-mark-mono-white.svg | SolidCraft contributors | one-colour (white) version of assets/branding/logo/solidcraft-mark.svg, same paths; original artwork | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-mark-512.png | SolidCraft contributors | transparent 512 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-mark-1024.png | SolidCraft contributors | transparent 1024 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-mark-2048.png | SolidCraft contributors | transparent 2048 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-wordmark.svg | SolidCraft contributors | the name SolidCraft set like the loading logo's wordmark (bold, +0.02 em tracking, #15499e) in IBM Plex Sans Bold from assets/fonts/ibm-plex-sans (SIL OFL 1.1, IBM Corp.), outlined to paths; no font file is embedded | MIT OR Apache-2.0 |
| assets/branding/logo/solidcraft-lockup-horizontal.svg | SolidCraft contributors | assets/branding/logo/solidcraft-mark.svg beside assets/branding/logo/solidcraft-wordmark.svg, in the proportions of assets/branding/web/source/og.png; original artwork | MIT OR Apache-2.0 |
| assets/styles/ui/themes/onyx.json | SolidCraft contributors | original Onyx stone-grey palette, option overlays and semantic icon-role mapping; existing SolidCraft artwork is unchanged | MIT OR Apache-2.0 |
| docs/book/src/media/ui-tour/window.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`solidcraft --sample`, default dark theme, software renderer, 1280x800) with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/ui-tour/navigate.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (`--sample`, default dark theme, software renderer) orbiting, panning and zooming, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| assets/locales/pending/sketch-tool-hints.json | carlymx | Original sketch tool hints and engine error translations | MIT OR Apache-2.0 |
| assets/locales/pending/primitive-pick-field-tips.json | SolidCraft contributors | Original primitive pick-field tooltip translations | MIT OR Apache-2.0 |
| assets/locales/pending/ribbon-panel-hover.json | SolidCraft contributors | Original ribbon panel menu preference labels and translations | MIT OR Apache-2.0 |
| docs/screenshots/box-insert-designer.gif | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (2.5× playback, 800 px wide) | MIT OR Apache-2.0 |
| docs/screenshots/box-insert-designer.mp4 | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (1280×720) | MIT OR Apache-2.0 |
| assets/locales/pending/agent-http-consent.json | SolidCraft contributors | Original endpoint-specific HTTP consent messages and translations | MIT OR Apache-2.0 |
| assets/locales/pending/ground-shadow-placement.json | SolidCraft contributors | Original ground-shadow placement preference messages and translations | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/components.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making components from two original hinge-leaf bodies, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/joint.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) creating a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/drag.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dragging a jointed hinge leaf, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/drive.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) driving a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/extrude.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) extruding a sketch's profile, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/shell.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) shelling a block, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/fillet.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) filleting four edges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/edit.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) editing a feature from the timeline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/hole.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) placing a hole, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/pattern.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) patterning a hole around an axis, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/parameters/dialog.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer) showing the Parameters dialog, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/files/menu.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`--sample`, default dark theme, software renderer) showing the File menu, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/getting-started/start.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing the start page with its built-in sample thumbnails, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/getting-started/sample.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) opening the built-in Sample Plate from the start page, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sheet-metal/base.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making a sheet metal base flange, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sheet-metal/flange.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) adding sheet metal edge flanges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sheet-metal/flat.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) showing a sheet metal flat pattern, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/create.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) starting a sketch on the Top plane, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/draw.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) drawing a rectangle and a circle with typed values, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/dimension.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dimensioning a sketch until it is fully constrained, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/parts.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing a fully constrained sketch, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/offset.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) offsetting a sketch outline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/trim.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) trimming a notch in a sketch, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| assets/locales/ca.json | carlymx | Catalan translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
## Code adapted from sibling projects
@@ -835,7 +879,7 @@ into THIRD-PARTY-LICENSES.txt when `main` regenerates it.
## FreeCAD and Rhino import library
FreeCAD `.FCStd` and Rhino `.3dm` files are read with [cadmpeg](https://github.com/cadmpeg/cadmpeg)
0.6.3 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
0.6.7 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
`cadmpeg-container`, `cadmpeg-codec-freecad`, `cadmpeg-codec-rhino` and `cadmpeg-codec-step`
in native and browser builds (`crates/io/src/native_cad.rs`). Only those open-format codecs are built: no
default features, so cadmpeg's codecs for commercial CAD formats and its ShapeManager/ACIS
@@ -853,11 +897,10 @@ copied into SolidCraft.
| Directory | Source | Licence | Changes |
|---|---|---|---|
| vendor/accesskit_macos | accesskit_macos 0.26.3, https://github.com/AccessKit/accesskit (The AccessKit contributors) | MIT OR Apache-2.0 | the subclassing adapter and its tests from 0.27.2: dropping the adapter keeps AppKit's key-value observers (vendor/README.md) |
## Vendored renderer compatibility fix
`vendor/egui-wgpu` is egui-wgpu 0.36.2 from https://github.com/emilk/egui
`vendor/egui-wgpu` is egui-wgpu 0.37.0 from https://github.com/emilk/egui
(MIT OR Apache-2.0; both original licence texts are included). Rust sources and
its original `egui.wgsl` shader are retained; no images or other artwork are copied.
SolidCraft's narrow patch selects a safe 1x MSAA path on the chosen OpenGL adapter
+12
View File
@@ -88,6 +88,8 @@ The [architecture page](docs/architecture.md) has the layer table. The short ver
- `crates/geom`: vectors, planes, meshes, measures.
- `crates/sketch`: our own constraint solver, plus profile finding.
- `crates/dynamics`: multibody dynamics: rigid bodies, joint constraints, gravity and springs,
integrated in time (RFC #9719). It depends only on `geom`.
- `crates/kernel`: the B-rep kernel boundary. It wraps [truck](https://github.com/ricosjp/truck),
and it's the only crate allowed to touch truck. Fillets, chamfers, shell/offset, Delete Face,
Split Face, STEP/IGES read and write, and face provenance are our own code here. Patched
@@ -472,6 +474,16 @@ the same area), first resolve the command registry and source conflicts, then ru
The generator tests cover stable ordering and additions that leave existing lines
unchanged. The initial switch to sorted rows is a one-time larger diff.
## Pictures in the user guide
The user guide's screenshots and short clips (`docs/book/src/media/`) are captured from the real
app by `examples/demo/doc_shots.py`, one function per figure. Each run starts a fresh app with its
own settings, so your preferences never show. When a change makes a figure out of date, run
`python3 examples/demo/doc_shots.py <page or shot>` (it needs Xvfb, ffmpeg, Pillow and a release
build) and commit the new file. A new figure is a new function there, an ATTRIBUTION row in
`attribution/pending/`, and a `<video>` (clips) or image link on the page. Keep a still under
about 250 KB and a clip under about 1 MB.
## Licence
By contributing you agree that your contribution is licensed as MIT OR Apache-2.0, like the rest
Generated
+194 -177
View File
@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "accesskit"
version = "0.24.1"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320"
checksum = "4e883d1d28c5fd5568c79ed2d2bd48d9f2fd3945c8974ed0f53690ceddb051be"
dependencies = [
"enumn",
"serde",
@@ -15,12 +15,12 @@ dependencies = [
[[package]]
name = "accesskit_atspi_common"
version = "0.18.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954"
checksum = "24f5baf62e34a4a6c30e3d697e4782f821a7337049c44c3b40576a27dfbb580d"
dependencies = [
"accesskit",
"accesskit_consumer 0.36.0",
"accesskit_consumer",
"atspi-common",
"phf",
"serde",
@@ -29,41 +29,37 @@ dependencies = [
[[package]]
name = "accesskit_consumer"
version = "0.35.0"
version = "0.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10"
checksum = "f3da8f27636015191b2264ee1df681503170a632cafd832fdc1edfdd4e3a2fd3"
dependencies = [
"accesskit",
"hashbrown 0.16.1",
"hashbrown 0.17.1",
]
[[package]]
name = "accesskit_consumer"
version = "0.36.0"
name = "accesskit_ios"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60"
checksum = "a5280724673bbfa8b30a838c013500b719392989be3b97c5a9964062c252c232"
dependencies = [
"accesskit",
"hashbrown 0.16.1",
]
[[package]]
name = "accesskit_consumer"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245"
dependencies = [
"accesskit",
"hashbrown 0.16.1",
"accesskit_consumer",
"hashbrown 0.17.1",
"objc2 0.5.3",
"objc2-foundation 0.2.2",
"objc2-ui-kit 0.2.2",
]
[[package]]
name = "accesskit_macos"
version = "0.26.3"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123828ceeec755b05255e3976cb0852993202de98a0626c97dd8d34246991f84"
dependencies = [
"accesskit",
"accesskit_consumer 0.38.0",
"hashbrown 0.16.1",
"accesskit_consumer",
"hashbrown 0.17.1",
"objc2 0.5.3",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
@@ -71,9 +67,9 @@ dependencies = [
[[package]]
name = "accesskit_unix"
version = "0.21.1"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c"
checksum = "30d5192871228c54f1b0fb4a85cef000e11a3804f25d2165f14afa0daa67f3f6"
dependencies = [
"accesskit",
"accesskit_atspi_common",
@@ -89,13 +85,13 @@ dependencies = [
[[package]]
name = "accesskit_windows"
version = "0.32.1"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21"
checksum = "498668b90da9995302a0055aaecad4756932333da2ddef6baee1c00844c430fc"
dependencies = [
"accesskit",
"accesskit_consumer 0.35.0",
"hashbrown 0.16.1",
"accesskit_consumer",
"hashbrown 0.17.1",
"static_assertions",
"windows",
"windows-core",
@@ -103,11 +99,12 @@ dependencies = [
[[package]]
name = "accesskit_winit"
version = "0.32.2"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313"
checksum = "cd4ab4437b520ff03088fbe48676a11e7d44a929535e64b31432c1073e48dbcc"
dependencies = [
"accesskit",
"accesskit_ios",
"accesskit_macos",
"accesskit_unix",
"accesskit_windows",
@@ -203,6 +200,7 @@ dependencies = [
"parking_lot",
"percent-encoding",
"windows-sys 0.60.2",
"wl-clipboard-rs",
"x11rb",
]
@@ -564,9 +562,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadmpeg-codec-freecad"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c4a8fa61748cbb3ce80838b00a8cebfe553460583941f5aba6e2b97c16b4d37"
checksum = "6fe0ed1935ace2c203b87107a064818dec2bb2ec2f250c4ad121767f3da75d1b"
dependencies = [
"cadmpeg-container",
"cadmpeg-core",
@@ -580,9 +578,9 @@ dependencies = [
[[package]]
name = "cadmpeg-codec-rhino"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7af41440d4c1d69e88e612de02cc3c9bad79d03429531e640ac924fef53c6bf"
checksum = "7dd2b76bd1d9301c949fbd731fd22cff9d37d10d1f481c754b557c2187bd0171"
dependencies = [
"cadmpeg-container",
"cadmpeg-core",
@@ -599,9 +597,9 @@ dependencies = [
[[package]]
name = "cadmpeg-codec-step"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e1b6d17cf26b31f59f2edab8ef5c29d2a33216c581fd913f012ad4869bd559"
checksum = "f3cbce33ed681a28b62d8b01ae64cca2b01f11f31b9a3e5e2455b9ffde915712"
dependencies = [
"base64",
"cadmpeg-container",
@@ -616,9 +614,9 @@ dependencies = [
[[package]]
name = "cadmpeg-container"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab7bcef48ac7480b4917820272d85cd2c9533e17904e15b0e4d22f19a6045bd"
checksum = "03f0d1c22b875294c5fbf4a4738aec8f8c0a4ad035eb28ece404f1dec21f371a"
dependencies = [
"cadmpeg-core",
"crc32fast",
@@ -628,9 +626,9 @@ dependencies = [
[[package]]
name = "cadmpeg-core"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86bb7a40521832504bb1f5953a1c706b2c52aad73bf6d84e1b35e5cb0beae3f"
checksum = "795bdc6e2a10ecc0627138874c17e2948a3925cd22c6c6b0c2e4f6621d92f89c"
dependencies = [
"flate2",
"memchr",
@@ -643,9 +641,9 @@ dependencies = [
[[package]]
name = "cadmpeg-ir"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b570a1a8c579e407a9fd7bb10fa1900f8827f1d578b8fcb4ea2a056b5f323c72"
checksum = "5592036516aaf2574b159a86a3d1ae872dc245261de86e4c0ce6773fed00a249"
dependencies = [
"base64",
"cadmpeg-core",
@@ -658,9 +656,9 @@ dependencies = [
[[package]]
name = "cadmpeg-registry"
version = "0.6.3"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2be23469a9bca0b684f1811118fffb581ebd32ded08e6cc8b0716a41f8c1377"
checksum = "fc163233d5f664337267192848505854de6aeed4843181adf87f58baddcee9a0"
dependencies = [
"cadmpeg-codec-freecad",
"cadmpeg-codec-rhino",
@@ -687,42 +685,18 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "calloop"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e"
dependencies = [
"bitflags 2.13.2",
"polling",
"rustix 1.1.5",
"tracing",
]
[[package]]
name = "calloop-wayland-source"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [
"calloop 0.13.0",
"calloop",
"rustix 0.38.44",
"wayland-backend",
"wayland-client",
]
[[package]]
name = "calloop-wayland-source"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
dependencies = [
"calloop 0.14.5",
"rustix 1.1.5",
"wayland-backend",
"wayland-client",
]
[[package]]
name = "cbc"
version = "0.1.2"
@@ -1123,9 +1097,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "ecolor"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34"
checksum = "31bcbef0d4263a63eb32c4834e5d711613035ce1333468e107d928923e21e6e2"
dependencies = [
"bytemuck",
"emath",
@@ -1134,9 +1108,9 @@ dependencies = [
[[package]]
name = "eframe"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2"
checksum = "9c5642501a65ca657d05f00c643a5c14ff2346cb3818032bfe23d58e1f8e5f84"
dependencies = [
"ahash",
"bytemuck",
@@ -1159,6 +1133,7 @@ dependencies = [
"profiling",
"raw-window-handle",
"ron",
"send_wrapper",
"serde",
"static_assertions",
"wasm-bindgen",
@@ -1171,9 +1146,9 @@ dependencies = [
[[package]]
name = "egui"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba"
checksum = "bec9144f49b7a28b5f395d3147e325b4fe3e77ba686eac11d1bdf0a7da3f375f"
dependencies = [
"accesskit",
"ahash",
@@ -1193,7 +1168,7 @@ dependencies = [
[[package]]
name = "egui-wgpu"
version = "0.36.2"
version = "0.37.0"
dependencies = [
"ahash",
"bytemuck",
@@ -1211,32 +1186,34 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc"
checksum = "e4e449f4c4a2a60c7ccb4ba7fa4078417bfe1f9ddc03b5e05ce75748e33a70de"
dependencies = [
"accesskit_winit",
"arboard",
"block2 0.6.2",
"bytemuck",
"egui",
"log",
"objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
"objc2-ui-kit 0.3.2",
"profiling",
"raw-window-handle",
"serde",
"smithay-clipboard",
"web-time",
"webbrowser",
"windows-sys 0.61.2",
"winit",
]
[[package]]
name = "egui_glow"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "606ed4167daf341e5753d703285c4c1d562324b2c07aa8f405cd7d066fe58f3b"
checksum = "9275ca5cc35a6b7c94e0e8cedf22542f66cb0cf45ed8ab2657c78ff49fec7268"
dependencies = [
"bytemuck",
"egui",
@@ -1254,9 +1231,9 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
[[package]]
name = "emath"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1"
checksum = "e73ce90f54750be8672c02be0743fe47530e6f251e066f0ee2869b59eb297af5"
dependencies = [
"bytemuck",
"serde",
@@ -1330,9 +1307,9 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3"
checksum = "97bac86ae3dde93fc3951e790951caea739ff39a6c89d909cdaf1d9278dbb1bd"
dependencies = [
"ahash",
"bytemuck",
@@ -1350,15 +1327,16 @@ dependencies = [
"skrifa",
"smallvec",
"unicode-general-category",
"unicode-properties",
"unicode-segmentation",
"vello_cpu",
]
[[package]]
name = "epaint_default_fonts"
version = "0.36.2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13"
checksum = "cfc1cc505827f4c23eedc297975a0fd038268d41293f389dd8ff3b574aefab19"
[[package]]
name = "equivalent"
@@ -1444,6 +1422,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flate2"
version = "1.1.10"
@@ -1455,6 +1439,12 @@ dependencies = [
"zlib-rs",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -1651,12 +1641,12 @@ dependencies = [
[[package]]
name = "glifo"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a"
checksum = "282a26c1e23de04bdab3e34a21b6f877a479b96737792516b9b8b8f69b6661be"
dependencies = [
"bytemuck",
"foldhash",
"foldhash 0.2.0",
"hashbrown 0.17.1",
"log",
"peniko",
@@ -1826,6 +1816,15 @@ dependencies = [
"smallvec",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
@@ -1834,7 +1833,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
"foldhash 0.2.0",
]
[[package]]
@@ -1843,7 +1842,7 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"foldhash",
"foldhash 0.2.0",
]
[[package]]
@@ -2500,6 +2499,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "num"
version = "0.4.3"
@@ -2956,6 +2964,16 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "os_pipe"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "parking"
version = "2.2.1"
@@ -3005,10 +3023,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "phf"
version = "0.13.1"
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap",
]
[[package]]
name = "phf"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
dependencies = [
"phf_macros",
"phf_shared",
@@ -3017,9 +3046,9 @@ dependencies = [
[[package]]
name = "phf_generator"
version = "0.13.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
dependencies = [
"fastrand",
"phf_shared",
@@ -3027,9 +3056,9 @@ dependencies = [
[[package]]
name = "phf_macros"
version = "0.13.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085"
dependencies = [
"phf_generator",
"phf_shared",
@@ -3040,9 +3069,9 @@ dependencies = [
[[package]]
name = "phf_shared"
version = "0.13.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
dependencies = [
"siphasher",
]
@@ -3762,6 +3791,12 @@ version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "send_wrapper"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "serde"
version = "1.0.229"
@@ -3986,8 +4021,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [
"bitflags 2.13.2",
"calloop 0.13.0",
"calloop-wayland-source 0.3.0",
"calloop",
"calloop-wayland-source",
"cursor-icon",
"libc",
"log",
@@ -4004,44 +4039,6 @@ dependencies = [
"xkeysym",
]
[[package]]
name = "smithay-client-toolkit"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
dependencies = [
"bitflags 2.13.2",
"calloop 0.14.5",
"calloop-wayland-source 0.4.1",
"cursor-icon",
"libc",
"log",
"memmap2",
"rustix 1.1.5",
"thiserror 2.0.21",
"wayland-backend",
"wayland-client",
"wayland-csd-frame",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-experimental",
"wayland-protocols-misc",
"wayland-protocols-wlr",
"wayland-scanner",
"xkeysym",
]
[[package]]
name = "smithay-clipboard"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
dependencies = [
"libc",
"smithay-client-toolkit 0.20.0",
"wayland-backend",
]
[[package]]
name = "smol_str"
version = "0.2.2"
@@ -4102,6 +4099,7 @@ dependencies = [
"ureq",
"windows-sys 0.61.2",
"winit",
"winreg",
"x11rb",
"zbus 5.19.0",
]
@@ -4152,6 +4150,17 @@ dependencies = [
"web-time",
]
[[package]]
name = "solidcraft-dynamics"
version = "0.40.0-rc.2"
dependencies = [
"serde",
"serde_json",
"solidcore-record",
"solidcraft-geom",
"thiserror 2.0.21",
]
[[package]]
name = "solidcraft-engine"
version = "0.40.0-rc.2"
@@ -4651,7 +4660,6 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -4677,6 +4685,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tree_magic_mini"
version = "3.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
dependencies = [
"memchr",
"nom",
"petgraph",
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
@@ -4751,6 +4770,12 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "unicode-properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993ad287eb20e8ba755312801cfe1413e0f25d604075863acaddca10b04c1833"
[[package]]
name = "unicode-segmentation"
version = "1.13.3"
@@ -4834,9 +4859,9 @@ dependencies = [
[[package]]
name = "vello_common"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2"
checksum = "bbb2141a2bca6e6d598e471fd4d1d7eed8e020aad6a28187edda07f091a325dd"
dependencies = [
"bytemuck",
"fearless_simd",
@@ -4849,9 +4874,9 @@ dependencies = [
[[package]]
name = "vello_cpu"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea"
checksum = "ace506ca414548966fcf3c283be94eb1cad8254de488791ee698bf438b0890a9"
dependencies = [
"bytemuck",
"glifo",
@@ -5026,32 +5051,6 @@ dependencies = [
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-experimental"
version = "20250721.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
dependencies = [
"bitflags 2.13.2",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-misc"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
dependencies = [
"bitflags 2.13.2",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-plasma"
version = "0.3.12"
@@ -5629,7 +5628,7 @@ dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
"bytemuck",
"calloop 0.13.0",
"calloop",
"cfg_aliases",
"concurrent-queue",
"core-foundation 0.9.4",
@@ -5650,7 +5649,7 @@ dependencies = [
"raw-window-handle",
"redox_syscall 0.4.1",
"rustix 0.38.44",
"smithay-client-toolkit 0.19.2",
"smithay-client-toolkit",
"smol_str",
"tracing",
"unicode-segmentation",
@@ -5693,6 +5692,24 @@ version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wl-clipboard-rs"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "959c7ac3b8afde864f7a7448ac16f29b2b83350b7eccbf77fb781c3999aa6848"
dependencies = [
"libc",
"log",
"os_pipe",
"rustix 1.1.5",
"thiserror 2.0.21",
"tree_magic_mini",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-protocols-wlr",
]
[[package]]
name = "writeable"
version = "0.6.4"
+9 -10
View File
@@ -22,6 +22,7 @@ solidcraft-kernel = { path = "crates/kernel" }
solidcraft-smlib = { path = "crates/smlib" }
solidcraft-step = { path = "crates/step" }
solidcraft-render = { path = "crates/render" }
solidcraft-dynamics = { path = "crates/dynamics" }
solidcraft-gpu = { path = "crates/gpu" }
solidcraft-doc = { path = "crates/doc" }
solidcraft-io = { path = "crates/io" }
@@ -81,9 +82,9 @@ gethostname = "1"
# Pinned: its IR and codec APIs change between minor versions. Open formats only: no default
# features, so cadmpeg's codecs for commercial formats (f3d, sldprt, inventor, catia, creo, nx)
# and its ShapeManager/ACIS `sat` codec are not compiled in. `step` is the writer we restate through.
cadmpeg-core = "=0.6.3"
cadmpeg-ir = "=0.6.3"
cadmpeg-registry = { version = "=0.6.3", default-features = false, features = ["fcstd", "rhino", "step"] }
cadmpeg-core = "=0.6.7"
cadmpeg-ir = "=0.6.7"
cadmpeg-registry = { version = "=0.6.7", default-features = false, features = ["fcstd", "rhino", "step"] }
# fonts (sketch text outlines; MIT OR Apache-2.0, pure Rust)
ttf-parser = { version = "0.25", default-features = false, features = ["std", "variable-fonts"] }
@@ -102,11 +103,12 @@ tiny-skia = { version = "0.12", default-features = false, features = ["std", "si
toml = "1"
# ui
egui = "0.36"
eframe = { version = "0.36", default-features = false, features = ["persistence"] }
# default_fonts: the built-in Hack is the UI's symbol fallback (#2955); 0.37 no longer turns it on.
egui = { version = "0.37", features = ["default_fonts"] }
eframe = { version = "0.37", default-features = false, features = ["persistence"] }
# Instant/SystemTime that also work in the browser (std panics on wasm32-unknown-unknown).
web-time = "1"
egui-wgpu = "0.36"
egui-wgpu = "0.37"
# the wgpu egui-wgpu uses, for compute (crates/gpu); backends come from the apps' features
wgpu = { version = "30", default-features = false, features = ["std", "wgsl"] }
naga = { version = "30", default-features = false }
@@ -161,12 +163,9 @@ debug = "line-tables-only"
egui-wgpu = { path = "vendor/egui-wgpu" }
# Deliver trackpad pinch and rotation gestures on Wayland (zwp_pointer_gestures_v1), which
# winit 0.30 never binds and eframe 0.36 still requires winit 0.30.
# winit 0.30 never binds and eframe 0.37 still requires winit 0.30.
winit = { path = "vendor/winit" }
# macOS: dropping the accessibility adapter leaves AppKit's key-value observers intact (#7535).
accesskit_macos = { path = "vendor/accesskit_macos" }
# Mutation testing (cargo-mutants, docs/mutation-testing.md): the ci profile with incremental
# builds, since every mutant rebuilds the crates above the one it changes.
[profile.mutants]
+6
View File
@@ -225,6 +225,12 @@ completed drawings publish atomically. The browser still exports synchronously.
## Render foundation
- Wood surface images (#1345): native CPU and GPU opaque rendering share physical
repeat frames and filtered mip chains. Imported maps retain exact bytes and
declared provenance in the design, reopen offline, and expose grain-angle
controls. Scalar face appearances suppress body maps. Planar projection and
CPU encoding/optical limits remain explicit; see [portable surfaces](docs/render-portable-surfaces-and-stages.md).
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
- Bounded linear film accumulation (#1696) supplies caller-admitted mean/variance storage and complete-pass views; actual progressive consumers remain separate. See [the film contract](docs/render-film.md).
+288 -129
View File
@@ -1,18 +1,17 @@
Third-party software in SolidCraft
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 537 crates below.
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 540 crates below.
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
accesskit 0.24.1 (MIT OR Apache-2.0)
accesskit_atspi_common 0.18.1 (MIT OR Apache-2.0)
accesskit_consumer 0.35.0 (MIT OR Apache-2.0)
accesskit_consumer 0.36.0 (MIT OR Apache-2.0)
accesskit_consumer 0.38.0 (MIT OR Apache-2.0)
accesskit_macos 0.26.3 (MIT OR Apache-2.0)
accesskit_unix 0.21.1 (MIT OR Apache-2.0)
accesskit_windows 0.32.1 (MIT OR Apache-2.0)
accesskit_winit 0.32.2 (Apache-2.0)
accesskit 0.25.2 (MIT OR Apache-2.0)
accesskit_atspi_common 0.21.1 (MIT OR Apache-2.0)
accesskit_consumer 0.39.2 (MIT OR Apache-2.0)
accesskit_ios 0.2.2 (MIT OR Apache-2.0)
accesskit_macos 0.27.2 (MIT OR Apache-2.0)
accesskit_unix 0.24.1 (MIT OR Apache-2.0)
accesskit_windows 0.35.2 (MIT OR Apache-2.0)
accesskit_winit 0.34.2 (Apache-2.0)
adler2 2.0.1 (0BSD OR MIT OR Apache-2.0)
aes 0.8.4 (MIT OR Apache-2.0)
ahash 0.8.12 (MIT OR Apache-2.0)
@@ -57,17 +56,15 @@ bytemuck_derive 1.12.1 (Zlib OR Apache-2.0 OR MIT)
byteorder 1.5.0 (Unlicense OR MIT)
byteorder-lite 0.1.0 (Unlicense OR MIT)
bytes 1.12.1 (MIT)
cadmpeg-codec-freecad 0.6.3 (Apache-2.0)
cadmpeg-codec-rhino 0.6.3 (Apache-2.0)
cadmpeg-codec-step 0.6.3 (Apache-2.0)
cadmpeg-container 0.6.3 (Apache-2.0)
cadmpeg-core 0.6.3 (Apache-2.0)
cadmpeg-ir 0.6.3 (Apache-2.0)
cadmpeg-registry 0.6.3 (Apache-2.0)
cadmpeg-codec-freecad 0.6.7 (Apache-2.0)
cadmpeg-codec-rhino 0.6.7 (Apache-2.0)
cadmpeg-codec-step 0.6.7 (Apache-2.0)
cadmpeg-container 0.6.7 (Apache-2.0)
cadmpeg-core 0.6.7 (Apache-2.0)
cadmpeg-ir 0.6.7 (Apache-2.0)
cadmpeg-registry 0.6.7 (Apache-2.0)
calloop 0.13.0 (MIT)
calloop 0.14.5 (MIT)
calloop-wayland-source 0.3.0 (MIT)
calloop-wayland-source 0.4.1 (MIT)
cbc 0.1.2 (MIT OR Apache-2.0)
cfg-if 1.0.5 (MIT OR Apache-2.0)
cgl 0.3.2 (MIT / Apache-2.0)
@@ -109,21 +106,21 @@ document-features 0.2.12 (MIT OR Apache-2.0)
downcast-rs 1.2.1 (MIT/Apache-2.0)
dpi 0.1.2 (Apache-2.0 AND MIT)
dyn-clone 1.0.20 (MIT OR Apache-2.0)
ecolor 0.36.2 (MIT OR Apache-2.0)
eframe 0.36.2 (MIT OR Apache-2.0)
egui 0.36.2 (MIT OR Apache-2.0)
egui-wgpu 0.36.2 (MIT OR Apache-2.0)
egui-winit 0.36.2 (MIT OR Apache-2.0)
egui_glow 0.36.2 (MIT OR Apache-2.0)
ecolor 0.37.0 (MIT OR Apache-2.0)
eframe 0.37.0 (MIT OR Apache-2.0)
egui 0.37.0 (MIT OR Apache-2.0)
egui-wgpu 0.37.0 (MIT OR Apache-2.0)
egui-winit 0.37.0 (MIT OR Apache-2.0)
egui_glow 0.37.0 (MIT OR Apache-2.0)
either 1.19.0 (MIT OR Apache-2.0)
emath 0.36.2 (MIT OR Apache-2.0)
emath 0.37.0 (MIT OR Apache-2.0)
encoding_rs 0.8.42 ((Apache-2.0 OR MIT) AND BSD-3-Clause)
endi 1.1.1 (MIT)
enumflags2 0.7.12 (MIT OR Apache-2.0)
enumflags2_derive 0.7.12 (MIT OR Apache-2.0)
enumn 0.1.14 (MIT OR Apache-2.0)
epaint 0.36.2 (MIT OR Apache-2.0)
epaint_default_fonts 0.36.2 ((MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0)
epaint 0.37.0 (MIT OR Apache-2.0)
epaint_default_fonts 0.37.0 ((MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0)
equivalent 1.0.2 (Apache-2.0 OR MIT)
errno 0.3.14 (MIT OR Apache-2.0)
error-code 3.4.0 (BSL-1.0)
@@ -134,7 +131,9 @@ fastrand 2.5.0 (Apache-2.0 OR MIT)
fax 0.2.7 (MIT)
fdeflate 0.3.7 (MIT OR Apache-2.0)
fearless_simd 0.4.1 (Apache-2.0 OR MIT)
fixedbitset 0.5.7 (MIT OR Apache-2.0)
flate2 1.1.10 (MIT OR Apache-2.0)
foldhash 0.1.5 (Zlib)
foldhash 0.2.0 (Zlib)
font-types 0.12.6 (MIT OR Apache-2.0)
foreign-types 0.5.0 (MIT/Apache-2.0)
@@ -154,7 +153,7 @@ getrandom 0.2.17 (MIT OR Apache-2.0)
getrandom 0.3.4 (MIT OR Apache-2.0)
getrandom 0.4.3 (MIT OR Apache-2.0)
gif 0.14.2 (MIT OR Apache-2.0)
glifo 0.2.0 (Apache-2.0 OR MIT)
glifo 0.3.0 (Apache-2.0 OR MIT)
glow 0.17.0 (MIT OR Apache-2.0 OR Zlib)
gltf 1.4.1 (MIT OR Apache-2.0)
gltf-derive 1.4.1 (MIT OR Apache-2.0)
@@ -168,6 +167,7 @@ gpu-allocator 0.28.0 (MIT OR Apache-2.0)
guillotiere 0.7.0 (MIT/Apache-2.0)
half 2.7.1 (MIT OR Apache-2.0)
harfrust 0.12.0 (MIT)
hashbrown 0.15.5 (MIT OR Apache-2.0)
hashbrown 0.16.1 (MIT OR Apache-2.0)
hashbrown 0.17.1 (MIT OR Apache-2.0)
hermit-abi 0.5.3 (MIT OR Apache-2.0)
@@ -234,6 +234,7 @@ ndk-sys 0.6.0+11769913 (MIT OR Apache-2.0)
nix 0.29.0 (MIT)
nix 0.31.3 (MIT)
nohash-hasher 0.2.0 (Apache-2.0 OR MIT)
nom 8.0.0 (MIT)
num 0.4.3 (MIT OR Apache-2.0)
num-bigint 0.4.8 (MIT OR Apache-2.0)
num-complex 0.4.6 (MIT OR Apache-2.0)
@@ -275,15 +276,17 @@ orbclient 0.3.55 (MIT)
ordered-float 2.10.1 (MIT)
ordered-float 5.5.0 (MIT)
ordered-stream 0.2.0 (MIT OR Apache-2.0)
os_pipe 1.2.3 (MIT)
parking 2.2.1 (Apache-2.0 OR MIT)
parking_lot 0.12.5 (MIT OR Apache-2.0)
parking_lot_core 0.9.12 (MIT OR Apache-2.0)
peniko 0.6.1 (Apache-2.0 OR MIT)
percent-encoding 2.3.2 (MIT OR Apache-2.0)
phf 0.13.1 (MIT)
phf_generator 0.13.1 (MIT)
phf_macros 0.13.1 (MIT)
phf_shared 0.13.1 (MIT)
petgraph 0.8.3 (MIT OR Apache-2.0)
phf 0.14.0 (MIT)
phf_generator 0.14.0 (MIT)
phf_macros 0.14.0 (MIT)
phf_shared 0.14.0 (MIT)
pin-project 1.1.13 (Apache-2.0 OR MIT)
pin-project-internal 1.1.13 (Apache-2.0 OR MIT)
pin-project-lite 0.2.17 (Apache-2.0 OR MIT)
@@ -352,6 +355,7 @@ security-framework 3.7.0 (MIT OR Apache-2.0)
security-framework-sys 2.17.0 (MIT OR Apache-2.0)
self_cell 1.3.0 (Apache-2.0 OR GPL-2.0-only)
semver 1.0.28 (MIT OR Apache-2.0)
send_wrapper 0.6.0 (MIT/Apache-2.0)
serde 1.0.229 (MIT OR Apache-2.0)
serde-value 0.7.0 (MIT)
serde_core 1.0.229 (MIT OR Apache-2.0)
@@ -375,8 +379,6 @@ slab 0.4.12 (MIT)
slotmap 1.1.1 (Zlib)
smallvec 1.16.2 (MIT OR Apache-2.0)
smithay-client-toolkit 0.19.2 (MIT)
smithay-client-toolkit 0.20.0 (MIT)
smithay-clipboard 0.7.3 (MIT)
smol_str 0.2.2 (MIT OR Apache-2.0)
solidcore 1.0.1 (Apache-2.0)
solidcore-record 0.1.0 (Apache-2.0)
@@ -409,6 +411,7 @@ toml_writer 1.1.3+spec-1.1.0 (MIT OR Apache-2.0)
tracing 0.1.44 (MIT)
tracing-attributes 0.1.31 (MIT)
tracing-core 0.1.36 (MIT)
tree_magic_mini 3.2.2 (MIT)
ttf-parser 0.25.1 (MIT OR Apache-2.0)
twox-hash 2.1.5 (MIT)
type-map 0.5.1 (MIT/Apache-2.0)
@@ -419,6 +422,7 @@ uds_windows 1.2.1 (MIT)
unicode-bidi 0.3.18 (MIT OR Apache-2.0)
unicode-general-category 1.1.0 (Apache-2.0)
unicode-ident 1.0.26 ((MIT OR Apache-2.0) AND Unicode-3.0)
unicode-properties 0.1.5 ((MIT OR Apache-2.0) AND Unicode-3.0)
unicode-segmentation 1.13.3 (MIT OR Apache-2.0)
unicode-width 0.2.2 (MIT OR Apache-2.0)
untrusted 0.9.0 (ISC)
@@ -428,8 +432,8 @@ url 2.5.8 (MIT OR Apache-2.0)
utf8-zero 0.8.1 (MIT OR Apache-2.0)
utf8_iter 1.0.4 (Apache-2.0 OR MIT)
uuid 1.27.0 (Apache-2.0 OR MIT)
vello_common 0.1.0 (Apache-2.0 OR MIT)
vello_cpu 0.1.0 (Apache-2.0 OR MIT)
vello_common 0.2.0 (Apache-2.0 OR MIT)
vello_cpu 0.2.0 (Apache-2.0 OR MIT)
wasi 0.11.1+wasi-snapshot-preview1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
wasip2 1.0.4+wasi-0.2.12 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
wasm-bindgen 0.2.129 (MIT OR Apache-2.0)
@@ -442,8 +446,6 @@ wayland-client 0.31.15 (MIT)
wayland-csd-frame 0.3.0 (MIT)
wayland-cursor 0.31.14 (MIT)
wayland-protocols 0.32.13 (MIT)
wayland-protocols-experimental 20250721.0.1 (MIT)
wayland-protocols-misc 0.3.12 (MIT)
wayland-protocols-plasma 0.3.12 (MIT)
wayland-protocols-wlr 0.3.12 (MIT)
wayland-scanner 0.31.11 (MIT)
@@ -500,6 +502,7 @@ winit 0.30.13 (Apache-2.0)
winnow 1.0.4 (MIT)
winreg 0.56.0 (MIT)
wit-bindgen 0.57.1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
wl-clipboard-rs 0.9.5 (MIT OR Apache-2.0)
writeable 0.6.4 (Unicode-3.0)
x11-dl 2.21.0 (MIT)
x11rb 0.13.2 (MIT OR Apache-2.0)
@@ -570,13 +573,14 @@ num_enum 0.7.6, num_enum_derive 0.7.6, once_cell 1.21.4, ordered-stream 0.2.0, p
pin-project 1.1.13, pin-project-internal 1.1.13, pin-project-lite 0.2.17, piper 0.2.5, polling
3.11.0, portable-atomic 1.15.0, portable-atomic-util 0.2.8, proc-macro-crate 3.5.0, proc-macro2
1.0.107, quote 1.0.47, ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 1.1.0, rustc-hash 2.1.3,
rustix 0.38.44, rustix 1.1.5, rustversion 1.0.23, semver 1.0.28, serde 1.0.229, serde_core 1.0.229,
serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json 1.0.151, serde_path_to_error 0.1.20,
serde_repr 0.1.21, simd_cesu8 1.2.0, smol_str 0.2.2, syn 2.0.119, syn 3.0.6, thiserror 1.0.69,
thiserror 2.0.21, thiserror-impl 1.0.69, thiserror-impl 2.0.21, typed-path 0.13.0, typeid 1.0.3,
unicode-ident 1.0.26, utf8-zero 0.8.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12,
wit-bindgen 0.57.1, x11-dl 2.21.0, xdg-home 1.3.0, zbus-lockstep 0.5.2, zbus-lockstep-macros 0.5.2,
zbus_names 3.0.0, zmij 1.0.23, zvariant_utils 2.1.0, zvariant_utils 4.2.0
rustix 0.38.44, rustix 1.1.5, rustversion 1.0.23, semver 1.0.28, send_wrapper 0.6.0, serde 1.0.229,
serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json 1.0.151,
serde_path_to_error 0.1.20, serde_repr 0.1.21, simd_cesu8 1.2.0, smol_str 0.2.2, syn 2.0.119, syn
3.0.6, thiserror 1.0.69, thiserror 2.0.21, thiserror-impl 1.0.69, thiserror-impl 2.0.21, typed-path
0.13.0, typeid 1.0.3, unicode-ident 1.0.26, utf8-zero 0.8.1, wasi 0.11.1+wasi-snapshot-preview1,
wasip2 1.0.4+wasi-0.2.12, wit-bindgen 0.57.1, x11-dl 2.21.0, xdg-home 1.3.0, zbus-lockstep 0.5.2,
zbus-lockstep-macros 0.5.2, zbus_names 3.0.0, zmij 1.0.23, zvariant_utils 2.1.0, zvariant_utils
4.2.0
================================================================================
Permission is hereby granted, free of charge, to any
@@ -1086,25 +1090,26 @@ ahash 0.8.12, arrayvec 0.7.8, async-channel 2.5.0, async-executor 1.14.0, async-
0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, core_detect
1.0.0, crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils
0.8.23, dbus-secret-service 4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14,
euclid 0.22.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10,
form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow 0.17.0, gltf 1.4.1,
gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, httparse
1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.2, itertools 0.15.0, js-sys 0.3.106,
keyboard-types 0.8.3, khronos-egl 6.0.0, lazy_static 1.5.1, linux-raw-sys 0.12.1, linux-raw-sys
0.4.15, lock_api 0.4.14, log 0.4.34, muda 0.21.2, num 0.4.3, num-bigint 0.4.8, num-complex 0.4.6,
num-integer 0.1.47, num-iter 0.1.46, num-rational 0.4.2, num-traits 0.2.19, once_cell 1.21.4,
openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5, parking_lot_core
0.9.12, percent-encoding 2.3.2, piper 0.2.5, plain 0.2.3, png 0.18.1, polling 3.11.0, pollster
0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core 1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, ron
0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix 0.38.44, rustix 1.1.5, rustls 0.23.46,
rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard 1.2.0, secret-service 4.0.0,
security-framework 2.11.1, security-framework 3.7.0, security-framework-sys 2.17.0,
signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.16.2, smol_str 0.2.2, stable_deref_trait
1.2.1, tempfile 3.27.0, ttf-parser 0.25.1, unicode-bidi 0.3.18, unicode-segmentation 1.13.3,
unicode-width 0.2.2, url 2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2
1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129,
wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12,
wit-bindgen 0.57.1
euclid 0.22.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, fixedbitset
0.5.7, flate2 1.1.10, form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow
0.17.0, gltf 1.4.1, gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.15.5, hashbrown 0.16.1,
hashbrown 0.17.1, hermit-abi 0.5.3, httparse 1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap
2.14.2, itertools 0.15.0, js-sys 0.3.106, keyboard-types 0.8.3, khronos-egl 6.0.0, lazy_static
1.5.1, linux-raw-sys 0.12.1, linux-raw-sys 0.4.15, lock_api 0.4.14, log 0.4.34, muda 0.21.2, num
0.4.3, num-bigint 0.4.8, num-complex 0.4.6, num-integer 0.1.47, num-iter 0.1.46, num-rational 0.4.2,
num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1,
parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, petgraph 0.8.3, piper 0.2.5,
plain 0.2.3, png 0.18.1, polling 3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core
1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, ron 0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix
0.38.44, rustix 1.1.5, rustls 0.23.46, rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard
1.2.0, secret-service 4.0.0, security-framework 2.11.1, security-framework 3.7.0,
security-framework-sys 2.17.0, send_wrapper 0.6.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0,
smallvec 1.16.2, smol_str 0.2.2, stable_deref_trait 1.2.1, tempfile 3.27.0, ttf-parser 0.25.1,
unicode-bidi 0.3.18, unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2, url
2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen
0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129, wasm-bindgen-macro-support
0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12, wit-bindgen 0.57.1,
wl-clipboard-rs 0.9.5
================================================================================
Apache License
@@ -1310,7 +1315,7 @@ See the License for the specific language governing permissions and
limitations under the License.
================================================================================
allocator-api2 0.2.21, async-trait 0.1.92, dyn-clone 1.0.20, enumn 0.1.14, glifo 0.2.0, itoa 1.0.18,
allocator-api2 0.2.21, async-trait 0.1.92, dyn-clone 1.0.20, enumn 0.1.14, glifo 0.3.0, itoa 1.0.18,
libc 0.2.190, litrs 1.0.0, num_enum 0.7.6, num_enum_derive 0.7.6, proc-macro2 1.0.107, quote 1.0.47,
ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 2.1.3, rustversion 1.0.23, semver 1.0.28, serde
1.0.229, serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json
@@ -1530,7 +1535,7 @@ android-activity 0.6.1, arboard 3.6.1, color 0.3.3, fdeflate 0.3.7, fearless_sim
image 0.25.10, image-webp 0.2.4, linebender_resource_handle 0.1.1, miniz_oxide 0.8.9, miniz_oxide
0.9.1, naga 30.0.1, naga-types 30.0.1, pin-project 1.1.13, pin-project-internal 1.1.13,
pin-project-lite 0.2.17, portable-atomic 1.15.0, portable-atomic-util 0.2.8, range-alloc 0.1.5,
raw-window-handle 0.6.2, simdutf8 0.1.5, vello_common 0.1.0, vello_cpu 0.1.0, wgpu 30.0.1, wgpu-core
raw-window-handle 0.6.2, simdutf8 0.1.5, vello_common 0.2.0, vello_cpu 0.2.0, wgpu 30.0.1, wgpu-core
30.0.1, wgpu-core-deps-apple 30.0.1, wgpu-core-deps-emscripten 30.0.1, wgpu-core-deps-wasm 30.0.1,
wgpu-core-deps-windows-linux-android 30.0.1, wgpu-hal 30.0.1, wgpu-naga-bridge 30.0.1, wgpu-types
30.0.1
@@ -2588,7 +2593,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
bit-set 0.10.0, bit-vec 0.9.1, downcast-rs 1.2.1, egui-wgpu 0.36.2, presser 0.3.1
bit-set 0.10.0, bit-vec 0.9.1, downcast-rs 1.2.1, egui-wgpu 0.37.0, presser 0.3.1
================================================================================
Apache License
@@ -3134,7 +3139,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
calloop 0.13.0, calloop 0.14.5, smithay-client-toolkit 0.19.2, smithay-client-toolkit 0.20.0
calloop 0.13.0, smithay-client-toolkit 0.19.2
================================================================================
Copyright (c) 2018 Victor Berger
@@ -3158,7 +3163,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================================================
calloop-wayland-source 0.3.0, calloop-wayland-source 0.4.1
calloop-wayland-source 0.3.0
================================================================================
Copyright (c) 2023 Kirill Chibisov
@@ -5231,7 +5236,7 @@ have been licensed under extremely permissive terms.
------------------------------------------------------------------------------
================================================================================
egui-wgpu 0.36.2
egui-wgpu 0.37.0
================================================================================
Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
@@ -5261,7 +5266,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
either 1.19.0, itertools 0.15.0
either 1.19.0, itertools 0.15.0, petgraph 0.8.3
================================================================================
Copyright (c) 2015
@@ -5709,6 +5714,36 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
fixedbitset 0.5.7
================================================================================
Copyright (c) 2015-2017
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
flate2 1.1.10
================================================================================
@@ -5740,7 +5775,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
foldhash 0.2.0
foldhash 0.1.5, foldhash 0.2.0
================================================================================
Copyright (c) 2024 Orson Peters
@@ -6511,7 +6546,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
glifo 0.2.0
glifo 0.3.0
================================================================================
Copyright 2025 the Vello Authors
@@ -7138,7 +7173,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
hashbrown 0.16.1, hashbrown 0.17.1
hashbrown 0.15.5, hashbrown 0.16.1, hashbrown 0.17.1
================================================================================
Copyright (c) 2016 Amanieu d'Antras
@@ -9459,6 +9494,31 @@ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
nom 8.0.0
================================================================================
Copyright (c) 2014-2019 Geoffroy Couprie
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
num_enum 0.7.6, num_enum_derive 0.7.6
================================================================================
@@ -9547,6 +9607,30 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
os_pipe 1.2.3
================================================================================
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================================================
parking 2.2.1
================================================================================
@@ -9562,7 +9646,7 @@ option. All files in the project carrying such notice may not be
copied, modified, or distributed except according to those terms.
================================================================================
phf 0.13.1, phf_generator 0.13.1, phf_macros 0.13.1, phf_shared 0.13.1
phf 0.14.0, phf_generator 0.14.0, phf_macros 0.14.0, phf_shared 0.14.0
================================================================================
The MIT License (MIT)
@@ -12377,30 +12461,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
smithay-clipboard 0.7.3
================================================================================
Copyright (c) 2018 Lucas Timmins & Victor Berger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================================================
solidcore 1.0.1
================================================================================
@@ -12973,6 +13033,32 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
tree_magic_mini 3.2.2
================================================================================
MIT License
Copyright (c) 2017 Aaron Hancock
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
ttf-parser 0.25.1
================================================================================
@@ -13795,7 +13881,7 @@ carrying such notice may not be copied, modified, or distributed except
according to those terms.
================================================================================
unicode-bidi 0.3.18, unicode-segmentation 1.13.3, unicode-width 0.2.2
unicode-bidi 0.3.18, unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2
================================================================================
Copyright (c) 2015 The Rust Project Developers
@@ -13869,7 +13955,51 @@ dealings in these Data Files or Software without prior written
authorization of the copyright holder.
================================================================================
unicode-segmentation 1.13.3, unicode-width 0.2.2
unicode-properties 0.1.5
================================================================================
UNICODE LICENSE V3
COPYRIGHT AND PERMISSION NOTICE
Copyright © 1991-2026 Unicode, Inc.
NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.
================================================================================
unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2
================================================================================
Licensed under the Apache License, Version 2.0
@@ -14015,7 +14145,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
vello_common 0.1.0, vello_cpu 0.1.0
vello_common 0.2.0, vello_cpu 0.2.0
================================================================================
Copyright 2020 the Vello Authors
@@ -14046,8 +14176,8 @@ DEALINGS IN THE SOFTWARE.
================================================================================
wayland-backend 0.3.17, wayland-client 0.31.15, wayland-cursor 0.31.14, wayland-protocols 0.32.13,
wayland-protocols-experimental 20250721.0.1, wayland-protocols-misc 0.3.12, wayland-protocols-plasma
0.3.12, wayland-protocols-wlr 0.3.12, wayland-scanner 0.31.11, wayland-sys 0.31.11
wayland-protocols-plasma 0.3.12, wayland-protocols-wlr 0.3.12, wayland-scanner 0.31.11, wayland-sys
0.31.11
================================================================================
Copyright (c) 2015 Elinor Berger
@@ -14942,6 +15072,36 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================================================
wl-clipboard-rs 0.9.5
================================================================================
Copyright (c) 2019 Ivan Molodetskikh
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
x11rb 0.13.2, x11rb-protocol 0.13.2
================================================================================
@@ -15967,35 +16127,34 @@ Crates whose package carries no licence file (their licence is named in Cargo.to
texts are as in SolidCraft's own LICENSE-MIT and LICENSE-APACHE)
================================================================================
accesskit 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_atspi_common 0.18.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_consumer 0.35.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_consumer 0.36.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_consumer 0.38.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_macos 0.26.3: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_unix 0.21.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_windows 0.32.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_winit 0.32.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit 0.25.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_atspi_common 0.21.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_consumer 0.39.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_ios 0.2.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_macos 0.27.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_unix 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_windows 0.35.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
accesskit_winit 0.34.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
block2 0.5.1: MIT (https://github.com/madsmtm/objc2)
block2 0.6.2: MIT (https://github.com/madsmtm/objc2)
cadmpeg-codec-freecad 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-rhino 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-step 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-container 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-core 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-ir 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-registry 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-freecad 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-rhino 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-step 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-container 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-core 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-ir 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-registry 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
clipboard-win 5.4.1: BSL-1.0 (https://github.com/DoumanAsh/clipboard-win)
dispatch 0.2.0: MIT (http://github.com/SSheldon/rust-dispatch)
dispatch2 0.3.1: Zlib OR Apache-2.0 OR MIT (https://github.com/madsmtm/objc2)
ecolor 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui)
eframe 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/eframe)
egui 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui)
egui-winit 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui-winit)
egui_glow 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui_glow)
emath 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/emath)
epaint 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/epaint)
epaint_default_fonts 0.36.2: (MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0 (https://github.com/emilk/egui/tree/main/crates/epaint_default_fonts)
ecolor 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui)
eframe 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/eframe)
egui 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui)
egui-winit 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui-winit)
egui_glow 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui_glow)
emath 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/emath)
epaint 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/epaint)
epaint_default_fonts 0.37.0: (MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0 (https://github.com/emilk/egui/tree/main/crates/epaint_default_fonts)
jni 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
jni-macros 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
jni-sys-macros 0.4.1: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-sys)
+2 -2
View File
@@ -11,7 +11,7 @@ use std::process::{Command, ExitCode};
const TARGET: &str = "solidcraft-cli";
fn main() -> ExitCode {
eprintln!("solvecraft-cli is deprecated and will be removed in a later release: use solidcraft-cli");
solidcraft_engine::note!("solvecraft-cli is deprecated and will be removed in a later release: use solidcraft-cli");
let sibling = std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|dir| dir.join(format!("{TARGET}{}", std::env::consts::EXE_SUFFIX))))
@@ -23,7 +23,7 @@ fn main() -> ExitCode {
None => ExitCode::FAILURE,
},
Err(e) => {
eprintln!("solvecraft-cli: could not run {}: {e}", program.to_string_lossy());
solidcraft_engine::note!("solvecraft-cli: could not run {}: {e}", program.to_string_lossy());
ExitCode::FAILURE
}
}
+9 -6
View File
@@ -33,8 +33,8 @@ pub fn one(args: &[String]) -> Result<(), String> {
std::panic::set_hook(Box::new(|_| {
PANICS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}));
eprintln!("design {seed}");
let run = fuzz::run_with(seed, steps, &mut |st| eprintln!("step {}", serde_json::to_string(st).unwrap_or_default()));
solidcraft_engine::note!("design {seed}");
let run = fuzz::run_with(seed, steps, &mut |st| solidcraft_engine::note!("step {}", serde_json::to_string(st).unwrap_or_default()));
let mut design = json!({
"outcome": run.outcome,
"steps": run.steps.iter().map(|s| s.kind).collect::<Vec<_>>(),
@@ -45,13 +45,13 @@ pub fn one(args: &[String]) -> Result<(), String> {
if run.outcome.is_bug() {
design["script"] = run.script();
if minimise {
eprintln!("minimise");
solidcraft_engine::note!("minimise");
let m = fuzz::minimise(&run);
design["minimal"] = json!({"outcome": m.outcome, "steps": m.steps.iter().map(|s| s.kind).collect::<Vec<_>>(), "script": m.script()});
}
}
let (a, b) = solidcraft_engine::fuzz::boolean_shapes(seed);
eprintln!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default());
solidcraft_engine::note!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default());
let b = boolean_case(seed);
let bp = PANICS.swap(0, std::sync::atomic::Ordering::SeqCst);
println!("{}", json!({"seed": seed, "design": design, "boolean": b, "boolean_panics": bp}));
@@ -162,11 +162,14 @@ fn record(v: &Value, out_dir: Option<&str>) {
let d = &v["design"];
let bug = matches!(d["outcome"]["kind"].as_str(), Some("failed" | "panic" | "invalid" | "volume"));
if bug || v.get("timeout").is_some() || v.get("crash").is_some() {
eprintln!("seed {seed}: {}", if bug { format!("{} {}", d["outcome"]["kind"], d["outcome"]["detail"]) } else { format!("{v}") });
solidcraft_engine::note!(
"seed {seed}: {}",
if bug { format!("{} {}", d["outcome"]["kind"], d["outcome"]["detail"]) } else { format!("{v}") }
);
}
let bk = v["boolean"]["outcome"].as_str().unwrap_or("ok");
if bk != "ok" {
eprintln!("seed {seed}: boolean {bk} {}", v["boolean"]["detail"]);
solidcraft_engine::note!("seed {seed}: boolean {bk} {}", v["boolean"]["detail"]);
}
if let Some(dir) = out_dir {
use std::io::Write;
+25 -17
View File
@@ -18,7 +18,7 @@
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
//! solidcraft-cli kernel-cases [--json] [--dumps=DIR] [--validation=MODE] <case-dir>... the kernel regression rig (tests/kernel-cases)
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
//! [--param-check off|warn|strict]
//! [--param-check off|warn|strict] [--compact]
//! MCP server on stdio (docs/mcp.md)
//! solidcraft-cli mcp setup [--dry-run] [--client NAME] [--live] (alias: mcp install) add it to OpenCode, Claude Desktop, Claude Code, Cursor
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
@@ -91,7 +91,7 @@ const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [-
const USAGE_FUZZ: &str =
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
/// `mcp`'s lines in the usage.
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu]\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu] [--compact]\n (--compact, SOLIDCRAFT_MCP_COMPACT=1 or the app's Preferences › AI (with --connect): compact tool output for\n agents: inspect_design summarises sketches and takes `sketches` by name and a `bodies` filter, and tool JSON\n is not pretty-printed; off by default)\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
/// `crashes`'s lines in the usage.
const USAGE_CRASHES: &str = " solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
/// `licences`'s lines in the usage.
@@ -154,7 +154,7 @@ fn main() -> ExitCode {
}
};
if let Err(e) = max_file_size(&mut args) {
eprintln!("error: {e}");
solidcraft_engine::note!("error: {e}");
return ExitCode::FAILURE;
}
let r = match args.first().map(String::as_str) {
@@ -235,7 +235,7 @@ fn main() -> ExitCode {
match r {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e}");
solidcraft_engine::note!("error: {e}");
ExitCode::FAILURE
}
}
@@ -351,7 +351,7 @@ fn run_script_warning(s: &mut Session, path: &str, script: &Value) -> Result<Vec
/// Step `step`'s `param_warnings` on stderr, one per line, named after the script at `path`.
fn print_param_warnings(path: &str, step: usize, r: &Value) {
for w in r.get("param_warnings").and_then(Value::as_array).into_iter().flatten() {
eprintln!("{path}: step {}: {}", step + 1, w.as_str().unwrap_or_default());
solidcraft_engine::note!("{path}: step {}: {}", step + 1, w.as_str().unwrap_or_default());
}
}
@@ -598,10 +598,10 @@ fn cmd_script(args: &[String]) -> Result<(), String> {
let out = flag(args, "--out");
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
for what in r["omitted"].as_array().into_iter().flatten().filter_map(Value::as_str) {
eprintln!("note: the script leaves out {what}");
solidcraft_engine::note!("note: the script leaves out {what}");
}
match out {
Some(out) => eprintln!("{out}: {} steps", r["steps"]),
Some(out) => solidcraft_engine::note!("{out}: {} steps", r["steps"]),
None => println!("{}", pretty(&r["script"])),
}
Ok(())
@@ -632,7 +632,7 @@ fn cmd_gpu(args: &[String]) -> Result<(), String> {
);
match s.lands_on {
Some(solidcraft_gpu::select::Landing::Cpu) => {
eprintln!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
solidcraft_engine::note!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
Ok(())
}
Some(_) => Ok(()),
@@ -709,7 +709,7 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
std::fs::write(out, &png.bytes).map_err(|e| format!("{out}: {e}"))?;
let mut reply = json!({"path": out, "width": w, "height": h, "bytes": png.bytes.len(), "renderer": png.renderer});
if let Some(w) = solidcraft_engine::capture::warning(png.renderer) {
eprintln!("warning: {w}");
solidcraft_engine::note!("warning: {w}");
reply["warning"] = json!(w);
}
println!("{}", pretty(&reply));
@@ -792,11 +792,15 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
return Err("mcp: --render-gpu installs a headless image host; a connected application owns its own device".into());
}
install_renderer();
let options = solidcraft_mcp::Options::from_args_and_env(args).map_err(|e| format!("mcp: {e}"))?;
if options.compact {
eprintln!("solidcraft mcp: compact output (inspect_design summarises sketches; tool JSON is not pretty-printed)");
}
let scope = (!args.iter().any(|a| a == "--allow-any-path"))
.then(|| solidcraft_mcp::agent_scope(flags(args, "--allow-path").into_iter().map(std::path::PathBuf::from)));
if let Some(s) = &scope {
let roots: Vec<String> = s.roots().iter().map(|r| r.display().to_string()).collect();
eprintln!(
solidcraft_engine::note!(
"solidcraft mcp: files limited to {} and the folders of designs opened or saved here (--allow-path DIR adds one, --allow-any-path lifts the limit)",
roots.join(", ")
);
@@ -804,7 +808,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
.and_then(|p| std::path::Path::new(p).parent())
.map(|d| if d.as_os_str().is_empty() { std::path::Path::new(".") } else { d });
if let Some(why) = design_dir.and_then(solidcraft_engine::io::vfs::design_folder_refusal) {
eprintln!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
solidcraft_engine::note!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
}
}
// How calls that break a command's parameter contract are treated, headless or bridged.
@@ -818,7 +822,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
// Serve without the app too: the tools connect once it runs, and say it isn't until then.
let mut remote = solidcraft_mcp::Remote::new(addr, flag(args, "--token")).with_param_check(param_check);
if let Err(e) = remote.try_connect() {
eprintln!(
solidcraft_engine::note!(
"solidcraft mcp: no SolidCraft app at {addr} yet ({e}); start it with `solidcraft --control {port}` \
(or SOLIDCRAFT_CONTROL_PORT={port}), tool calls connect once it runs"
);
@@ -835,9 +839,9 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
(None, None) => {
let auto = !args.iter().any(|a| a == "--headless");
if auto && let Some(port) = discover_app() {
eprintln!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
solidcraft_engine::note!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
let remote = solidcraft_mcp::Remote::new(&format!("127.0.0.1:{port}"), flag(args, "--token")).with_param_check(param_check);
return serve_mcp(Box::new(remote), scope);
return serve_mcp(Box::new(remote), scope, options);
}
let mut headless = solidcraft_mcp::Headless::default();
headless.session.param_check = param_check;
@@ -852,13 +856,17 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
}
}
};
serve_mcp(backend, scope)
serve_mcp(backend, scope, options)
}
fn serve_mcp(backend: Box<dyn solidcraft_mcp::Backend>, scope: Option<solidcraft_engine::io::vfs::PathScope>) -> Result<(), String> {
fn serve_mcp(
backend: Box<dyn solidcraft_mcp::Backend>,
scope: Option<solidcraft_engine::io::vfs::PathScope>,
options: solidcraft_mcp::Options,
) -> Result<(), String> {
let stdin = std::io::stdin();
let stdout = std::io::stdout();
solidcraft_mcp::Server::new(backend).with_path_scope(scope).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
solidcraft_mcp::Server::new(backend).with_path_scope(scope).with_options(options).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
}
/// The port of the running desktop app: the one its discovery file names, if something listens
+23
View File
@@ -188,3 +188,26 @@ fn max_file_size_raises_the_limit_and_refuses_bad_sizes() {
assert!(!output.status.success() && !stderr.contains("too large"), "{stderr}");
let _ = std::fs::remove_dir_all(&dir);
}
/// #10111: a line for a stderr whose reader has gone (the pipe closed before it, as an agent may
/// leave the MCP server's, and the app that one starts) is dropped, and the command ends as it
/// would with stderr open: here a script that isn't there fails with 1, where `eprintln!`
/// panicked with "failed printing to stderr" (101).
#[test]
fn a_closed_stderr_does_not_stop_the_cli() {
for _ in 0..5 {
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
.args(["run", "no-such-script.json"])
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.unwrap();
drop(child.stderr.take());
assert_eq!(child.wait().unwrap().code(), Some(1));
}
// The old name's alias writes its deprecation notice first: an agent still configured with
// `solvecraft-cli mcp` must get the server, not that panic.
let mut child = Command::new(env!("CARGO_BIN_EXE_solvecraft-cli")).arg("--help").stdout(Stdio::null()).stderr(Stdio::piped()).spawn().unwrap();
drop(child.stderr.take());
assert_eq!(child.wait().unwrap().code(), Some(0));
}
+48
View File
@@ -0,0 +1,48 @@
//! `mcp --compact` and `SOLIDCRAFT_MCP_COMPACT` turn on compact tool output; without them the
//! server's tools are as they always were.
use std::io::Write;
use std::process::{Command, Stdio};
use serde_json::{Value, json};
/// `inspect_design`'s input schema as `solidcraft-cli mcp` with `args` and `env` lists it, or
/// the server's stderr when it does not start.
fn inspect_schema(args: &[&str], env: Option<&str>) -> Result<Value, String> {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
// --headless: never bridge to (and read the preference of) an app the developer has running.
cmd.args(["mcp", "--headless", "--allow-any-path"]).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
match env {
Some(v) => cmd.env("SOLIDCRAFT_MCP_COMPACT", v),
None => cmd.env_remove("SOLIDCRAFT_MCP_COMPACT"),
};
let mut child = cmd.spawn().unwrap();
{
let mut stdin = child.stdin.take().unwrap();
// The server may refuse to start and close stdin first.
let _ = writeln!(stdin, "{}", json!({"jsonrpc": "2.0", "id": 1, "method": "tools/list"}));
}
let out = child.wait_with_output().unwrap();
if !out.status.success() {
return Err(String::from_utf8_lossy(&out.stderr).into_owned());
}
let reply: Value = serde_json::from_slice(&out.stdout).unwrap();
let tools = reply["result"]["tools"].as_array().cloned().unwrap_or_default();
Ok(tools.into_iter().find(|t| t["name"] == "inspect_design").map(|t| t["inputSchema"].clone()).unwrap_or(Value::Null))
}
#[test]
fn compact_output_is_off_unless_asked() {
let plain = inspect_schema(&[], None).unwrap();
assert_eq!(plain["properties"]["sketches"]["type"], "boolean", "{plain}");
assert!(plain["properties"].get("bodies").is_none(), "{plain}");
assert_eq!(inspect_schema(&[], Some("0")).unwrap(), plain);
for (args, env) in [(&["--compact"][..], None), (&[][..], Some("1")), (&[][..], Some("true"))] {
let compact = inspect_schema(args, env).unwrap();
assert!(compact["properties"]["bodies"].is_object(), "{args:?} {env:?}: {compact}");
}
let e = inspect_schema(&[], Some("maybe")).unwrap_err();
assert!(e.contains("SOLIDCRAFT_MCP_COMPACT"), "{e}");
}
+34 -13
View File
@@ -60,15 +60,25 @@ fn mcp_connect_serves_before_the_app_runs_and_reaches_it_once_it_does() {
let listener = TcpListener::bind(&addr).unwrap();
let app = std::thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
let mut line = String::new();
BufReader::new(stream.try_clone().unwrap()).read_line(&mut line).unwrap();
let req: Value = serde_json::from_str(&line).unwrap();
let reply = if req["token"] == json!(token) {
json!({"id": req["id"], "ok": true, "result": {"bodies": [], "method": req["method"]}})
} else {
json!({"id": req["id"], "ok": false, "error": format!("unauthorized: token {}", req["token"])})
};
writeln!(&stream, "{reply}").unwrap();
let mut lines = BufReader::new(stream.try_clone().unwrap());
loop {
let mut line = String::new();
lines.read_line(&mut line).unwrap();
let req: Value = serde_json::from_str(&line).unwrap();
// The server first asks for the agents' output options (the compact preference: off).
let options = req["method"] == "mcp.options";
let reply = if req["token"] != json!(token) {
json!({"id": req["id"], "ok": false, "error": format!("unauthorized: token {}", req["token"])})
} else if options {
json!({"id": req["id"], "ok": true, "result": {"compact": false}})
} else {
json!({"id": req["id"], "ok": true, "result": {"bodies": [], "method": req["method"]}})
};
writeln!(&stream, "{reply}").unwrap();
if !options {
break;
}
}
});
let r = ask(4, "tools/call", measure);
assert_ne!(r["result"]["isError"], true, "{r}");
@@ -111,10 +121,21 @@ fn mcp_without_arguments_finds_the_running_app() {
break (stream, line);
}
};
let req: Value = serde_json::from_str(&line).unwrap();
let ok = req["token"] == json!(token);
let reply = json!({"id": req["id"], "ok": ok, "result": {"bodies": [], "method": req["method"]}, "error": "unauthorized"});
writeln!(&stream, "{reply}").unwrap();
let mut lines = BufReader::new(stream.try_clone().unwrap());
let mut line = line;
// The server first asks for the agents' output options (the compact preference: off).
loop {
let req: Value = serde_json::from_str(&line).unwrap();
let ok = req["token"] == json!(token);
let options = req["method"] == "mcp.options";
let result = if options { json!({"compact": false}) } else { json!({"bodies": [], "method": req["method"]}) };
writeln!(&stream, "{}", json!({"id": req["id"], "ok": ok, "result": result, "error": "unauthorized"})).unwrap();
if !options {
break;
}
line.clear();
lines.read_line(&mut line).unwrap();
}
});
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
.args(["mcp", "--allow-any-path"])
+1 -1
View File
@@ -25,6 +25,7 @@ solidcraft-engine = { workspace = true }
solidcraft-ui-egui = { workspace = true }
egui = { workspace = true }
log = { workspace = true }
serde_json = { workspace = true }
url = "2"
percent-encoding = "2"
@@ -33,7 +34,6 @@ eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
serde_json = { workspace = true }
web-sys = { version = "0.3", features = [
"Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "Location", "Navigator", "StorageManager",
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemGetDirectoryOptions", "FileSystemWritableFileStream",
+4 -1
View File
@@ -553,7 +553,7 @@ mod web {
/// Open a design remembered from an earlier session (a recent one, after a reload): ask once
/// for access to the file (call in the click), read it into the engine's files and queue it
/// to open. A file that is gone is forgotten and dropped from the recent designs.
pub fn restore(files: &Rc<Files>, path: &str) {
pub fn restore(files: &Rc<Files>, path: &str, on_opened: Option<Box<dyn FnOnce()>>) {
let Some((id, name)) = parse(path) else { return };
let (files, path, id, name) = (files.clone(), path.to_string(), id.to_string(), name.to_string());
spawn_local(async move {
@@ -568,6 +568,9 @@ mod web {
files.opened.borrow_mut().push(path);
files.wake();
touch(&id).await;
if let Some(done) = on_opened {
done();
}
}
Err(e) => {
let gone = e.contains("NotFoundError") || e.contains("could not be found") || e.contains("no longer remembers");
+175 -5
View File
@@ -5,7 +5,8 @@
//! and go out as downloads (exports, and "Download design"). Where the File System Access API
//! exists a design also lives on the computer's disk, saved and opened through the browser's file
//! dialogs (`disk.rs`). An autosave of unsaved work is kept in OPFS and offered again after a
//! reload or a crash.
//! reload or a crash. With "keep unsaved work in this browser" on, each open design's unsaved
//! changes are kept there instead, one copy per design (`keep.rs`, #10220).
use std::cell::{Cell, RefCell};
use std::rc::Rc;
@@ -13,7 +14,7 @@ use std::rc::Rc;
use js_sys::{Array, Function, Object, Reflect, Uint8Array};
use solidcraft_engine::io::vfs;
use crate::disk;
use crate::{disk, keep};
use wasm_bindgen::{JsCast as _, JsValue, closure::Closure};
use wasm_bindgen_futures::{JsFuture, spawn_local};
use web_sys::{
@@ -79,6 +80,16 @@ pub struct Files {
pub recovery: RefCell<Option<(String, Option<String>)>>,
/// The app's context, to wake it when a browser call finishes.
pub ctx: RefCell<Option<egui::Context>>,
/// This page's id: the copies it keeps are named after it (`keep::entry_id`).
pub keep_page: RefCell<String>,
/// Kept copies stored by this page, and the design revision each holds once its write ended.
pub kept: RefCell<std::collections::HashMap<String, Option<u64>>>,
/// Kept copies being written or removed now (one browser call at a time per copy).
pub keeping: RefCell<std::collections::HashSet<String>>,
/// When (ms) the last write of a copy failed, to wait before trying again (`keep::may_write`).
pub kept_failed: RefCell<std::collections::HashMap<String, f64>>,
/// Copies earlier pages left, newer than their design's last save: offered at start.
pub keep_offered: RefCell<Vec<keep::Entry>>,
}
impl Files {
@@ -288,6 +299,9 @@ pub fn start(files: &Rc<Files>) {
let _ = JsFuture::from(p).await;
});
}
let page = keep::page_id(js_sys::Date::now(), js_sys::Math::random());
hold_page_lock(&page);
*files.keep_page.borrow_mut() = page;
let files = files.clone();
spawn_local(async move {
match disk::known_ids().await {
@@ -300,6 +314,7 @@ pub fn start(files: &Rc<Files>) {
files.say("This page can't use the browser's file storage (it needs https or localhost): designs are kept in local storage, a few MB at most.", false);
}
let mut entries = Vec::new();
let kept = kept_copies(&all);
let has_recovery = all.iter().any(|(n, _, _)| n == RECOVERY);
let legacy = (!has_recovery).then(|| LEGACY_RECOVERY.into_iter().find(|l| all.iter().any(|(n, _, _)| n == l))).flatten();
for (name, bytes, modified) in all {
@@ -315,7 +330,7 @@ pub fn start(files: &Rc<Files>) {
*files.recovery.borrow_mut() = Some((n, meta["path"].as_str().map(str::to_string)));
continue;
}
if name != RECOVERY {
if name != RECOVERY && keep::parse_name(&name).is_none() {
entries.push(Entry { name: name.clone(), size: bytes.len() as u64, modified });
}
vfs::insert(&format!("{OPFS}{name}"), bytes);
@@ -326,6 +341,7 @@ pub fn start(files: &Rc<Files>) {
if !vfs::exists(&format!("{OPFS}{RECOVERY}")) {
*files.recovery.borrow_mut() = None;
}
sort_kept(&files, kept, &live_pages().await);
}
Err(e) => files.say(format!("browser storage: {e}"), true),
}
@@ -343,7 +359,7 @@ pub fn flush(files: &Rc<Files>) {
{
let mut e = files.entries.borrow_mut();
e.retain(|x| x.name != name);
if name != RECOVERY && name != RECOVERY_META {
if name != RECOVERY && name != RECOVERY_META && keep::parse_name(&name).is_none() {
e.push(Entry { name: name.clone(), size: bytes.len() as u64, modified: js_sys::Date::now() });
e.sort_by_key(|a| a.name.to_lowercase());
}
@@ -473,7 +489,15 @@ pub fn recovery_path() -> String {
/// A recovery file lives in OPFS, but its relative links belong to the original design's
/// folder (which can be on disk). Resolve them before Open sees the temporary file location.
pub fn prepare_recovery(original_path: Option<&str>) -> Result<String, String> {
let path = recovery_path();
prepare_copy(recovery_path(), original_path)
}
/// The same for a kept copy (#10220).
pub fn prepare_kept(id: &str, original_path: Option<&str>) -> Result<String, String> {
prepare_copy(format!("{OPFS}{}", keep::design_file(id)), original_path)
}
fn prepare_copy(path: String, original_path: Option<&str>) -> Result<String, String> {
let bytes = vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| e.to_string())?;
let mut doc = solidcraft_engine::io::read_design(&bytes).map_err(|e| e.to_string())?;
solidcraft_engine::resolve_design_links(&mut doc, original_path);
@@ -646,3 +670,149 @@ pub fn take_drops(files: &Rc<Files>, raw: &mut egui::RawInput, theme: bool) {
});
}
}
/// Hold this page's browser lock while it runs, so other tabs know its kept copies are in use
/// (`keep.rs`). Browsers without the Web Locks API hold none; their copies are then offered by
/// any later page.
fn hold_page_lock(page: &str) {
let Some(locks) = web_locks() else { return };
let Ok(request) = Reflect::get(&locks, &"request".into()).and_then(|f| f.dyn_into::<Function>().map_err(JsValue::from)) else { return };
// A promise that never settles: the lock is let go only when the page goes.
let hold = Closure::<dyn FnMut(JsValue) -> js_sys::Promise>::new(|_lock: JsValue| js_sys::Promise::new(&mut |_, _| {}));
let name = format!("{}{page}", keep::LOCK_PREFIX);
if let Err(e) = request.call2(&locks, &name.into(), hold.as_ref().unchecked_ref()) {
log::info!("keep: no page lock: {}", err(e));
}
// The callback lives as long as the page.
hold.forget();
}
fn web_locks() -> Option<JsValue> {
let nav = web_sys::window()?.navigator();
Reflect::get(&nav, &"locks".into()).ok().filter(|l| l.is_object())
}
/// The pages still open (in this browser profile) that hold their lock.
async fn live_pages() -> Vec<String> {
let query = async {
let locks = web_locks().ok_or("no Web Locks")?;
let f: Function = Reflect::get(&locks, &"query".into()).map_err(err)?.dyn_into().map_err(|_| "no query".to_string())?;
let p: js_sys::Promise = f.call0(&locks).map_err(err)?.dyn_into().map_err(|_| "no promise".to_string())?;
let state = JsFuture::from(p).await.map_err(err)?;
let held: Array = Reflect::get(&state, &"held".into()).map_err(err)?.dyn_into().map_err(|_| "no list".to_string())?;
Ok::<_, String>(
held.iter()
.take(10_000)
.filter_map(|l| Reflect::get(&l, &"name".into()).ok()?.as_string())
.filter_map(|n| n.strip_prefix(keep::LOCK_PREFIX).map(str::to_string))
.collect(),
)
};
query.await.unwrap_or_else(|e| {
log::info!("keep: open pages unknown ({e})");
Vec::new()
})
}
/// The kept copies among the stored files: (copy, when its design's file was last written), and
/// the ids of designs stored without their metadata.
struct Found {
copies: Vec<(keep::Entry, Option<f64>)>,
orphans: Vec<String>,
}
fn kept_copies(all: &[(String, Vec<u8>, f64)]) -> Found {
let modified = |path: &str| path.strip_prefix(OPFS).and_then(|n| all.iter().find(|(name, _, _)| name == n)).map(|(_, _, m)| *m);
let mut copies = Vec::new();
let mut designs = Vec::new();
for (name, bytes, _) in all {
match keep::parse_name(name) {
Some((id, keep::Part::Meta)) => {
if let Some(e) = keep::Entry::parse(id, bytes) {
let saved = e.path.as_deref().and_then(modified);
copies.push((e, saved));
}
}
Some((id, keep::Part::Design)) => designs.push(id.to_string()),
None => {}
}
}
// A copy is whole only with its design.
copies.retain(|(e, _)| designs.contains(&e.id));
let orphans = designs.into_iter().filter(|id| !copies.iter().any(|(e, _)| &e.id == id)).collect();
Found { copies, orphans }
}
/// Offer the copies earlier pages left that hold unsaved work, and remove those their design's
/// later save made stale (and parts without the rest), leaving open pages' copies alone.
fn sort_kept(files: &Rc<Files>, found: Found, live: &[String]) {
let mut offered = Vec::new();
let mut gone = Vec::new();
for (e, saved) in found.copies {
match keep::fate(&e, live, saved) {
keep::Fate::Offer => offered.push(e),
keep::Fate::Drop => gone.push(e.id),
keep::Fate::Leave => {}
}
}
gone.extend(found.orphans.into_iter().filter(|id| !keep::page_of(id).is_some_and(|p| live.iter().any(|l| l == p))));
keep::order_offered(&mut offered);
*files.keep_offered.borrow_mut() = offered;
for id in gone {
remove_kept(files, &id);
}
}
/// The path (in the engine's files) a kept copy opens from.
pub fn kept_path(id: &str) -> String {
format!("{OPFS}{}", keep::design_file(id))
}
/// Write the copy of a design with unsaved changes (`bytes`, described by `entry`) that holds
/// its revision `revision`. Its metadata goes last, so a copy is never offered half written.
pub fn write_kept(files: &Rc<Files>, entry: &keep::Entry, bytes: Vec<u8>, revision: u64) {
let id = entry.id.clone();
if !files.keeping.borrow_mut().insert(id.clone()) {
return;
}
files.kept.borrow_mut().entry(id.clone()).or_insert(None);
let meta = entry.to_json();
let files = files.clone();
spawn_local(async move {
let r = async {
write_file(&keep::design_file(&id), &bytes).await?;
write_file(&keep::meta_file(&id), meta.as_bytes()).await
};
match r.await {
Ok(()) => {
files.kept.borrow_mut().insert(id.clone(), Some(revision));
files.kept_failed.borrow_mut().remove(&id);
}
Err(e) => {
files.kept_failed.borrow_mut().insert(id.clone(), js_sys::Date::now());
files.say(format!("keeping unsaved work in this browser: {e}"), true)
}
}
files.keeping.borrow_mut().remove(&id);
files.wake();
});
}
/// Remove kept copy `id` (its design was saved, closed, discarded or restored).
pub fn remove_kept(files: &Rc<Files>, id: &str) {
let id = id.to_string();
if !files.keeping.borrow_mut().insert(id.clone()) {
return;
}
let _ = vfs::remove(&kept_path(&id));
files.kept_failed.borrow_mut().remove(&id);
let files = files.clone();
spawn_local(async move {
// The metadata first: what's left without it is never offered.
let _ = remove_file(&keep::meta_file(&id)).await;
let _ = remove_file(&keep::design_file(&id)).await;
files.kept.borrow_mut().remove(&id);
files.keeping.borrow_mut().remove(&id);
files.wake();
});
}
+360
View File
@@ -0,0 +1,360 @@
//! Keep unsaved work in this browser (#10220): the decisions, in plain Rust (tested natively).
//!
//! With Preferences › General › "Keep unsaved work in this browser instead of asking when the tab
//! closes" on, each open design with unsaved changes has a copy in the browser's private storage:
//! `keep-<id>.sc` (the design) and `keep-<id>.json` (its name, its file and when it last
//! changed). The copy is written shortly after each change ([`due`]) and again when the page is
//! hidden, so it is already current when the tab closes; the page then doesn't ask through the
//! browser's "leave site?" box ([`asks_before_unload`]). A design that is saved, closed or
//! discarded has its copy removed. The next visit offers the copies newer than their design's
//! last save ([`fate`]) in SolidCraft's own window, answered with [`plan`].
//!
//! An id is `<page>-<n>`: the page that wrote it (each page load has its own) and the design's
//! number in that page. A page holds a browser lock named after it while it runs, so another tab
//! never offers, or removes, the copies of a page that is still open.
#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
use serde_json::{Value, json};
/// The start of every kept file's name.
pub const PREFIX: &str = "keep-";
/// The browser lock a page holds while it runs: this, then its page id.
pub const LOCK_PREFIX: &str = "solidcraft.keep.";
/// A copy is written once the design has been still this long (ms)…
pub const QUIET_MS: f64 = 800.0;
/// …or at the latest this long after the first change it doesn't hold yet (ms).
pub const MAX_WAIT_MS: f64 = 4000.0;
/// The most copies offered at once (the rest wait for the next visit).
pub const MAX_OFFERED: usize = 32;
const MAX_ID: usize = 64;
const MAX_NAME: usize = 256;
const MAX_PATH: usize = 4096;
/// Copies' metadata is small: a damaged or hostile one is not read.
pub const MAX_META_BYTES: usize = 64 * 1024;
/// Ids are short and safe in a file name: ASCII letters, digits and dashes, with a page part.
pub fn valid_id(id: &str) -> bool {
!id.is_empty() && id.len() <= MAX_ID && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') && page_of(id).is_some()
}
/// The page that wrote copy `id`.
pub fn page_of(id: &str) -> Option<&str> {
id.rsplit_once('-').map(|(page, _)| page).filter(|p| !p.is_empty())
}
/// This page's id, from the time it started (ms since 1970) and a random number in 0..1:
/// letters and digits only.
pub fn page_id(now_ms: f64, random: f64) -> String {
let now = if now_ms.is_finite() && now_ms > 0.0 { now_ms.min(1e15) as u64 } else { 0 };
let random = if random.is_finite() { (random.clamp(0.0, 1.0) * f64::from(0xff_ffff)) as u64 } else { 0 };
format!("{now:x}{random:06x}")
}
/// The copy of design `uid` (`Session::uid`) open in page `page`.
pub fn entry_id(page: &str, uid: u64) -> String {
format!("{page}-{uid}")
}
/// The stored file names of copy `id`: its design and its metadata.
pub fn design_file(id: &str) -> String {
format!("{PREFIX}{id}.{}", solidcraft_engine::io::DESIGN_EXT)
}
pub fn meta_file(id: &str) -> String {
format!("{PREFIX}{id}.json")
}
/// Which part of a copy a stored file is.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Part {
Design,
Meta,
}
/// A stored file's copy id and part; `None` for every other file.
pub fn parse_name(name: &str) -> Option<(&str, Part)> {
let rest = name.strip_prefix(PREFIX)?;
let (id, part) = if let Some(id) = rest.strip_suffix(".json") {
(id, Part::Meta)
} else {
(rest.strip_suffix(solidcraft_engine::io::DESIGN_EXT)?.strip_suffix('.')?, Part::Design)
};
valid_id(id).then_some((id, part))
}
/// A kept copy, as its metadata describes it.
#[derive(Clone, Debug, PartialEq)]
pub struct Entry {
pub id: String,
/// The design's name.
pub name: String,
/// Its file (`None`: never saved).
pub path: Option<String>,
/// When the design last changed (ms since 1970).
pub changed_at: f64,
}
impl Entry {
pub fn to_json(&self) -> String {
json!({"name": self.name, "path": self.path, "changed_at": self.changed_at}).to_string()
}
/// Read copy `id`'s metadata. Anything malformed, oversized or not finite is no copy.
pub fn parse(id: &str, bytes: &[u8]) -> Option<Entry> {
if !valid_id(id) || bytes.len() > MAX_META_BYTES {
return None;
}
let v: Value = serde_json::from_slice(bytes).ok()?;
let name = v.get("name")?.as_str()?.chars().take(MAX_NAME).collect::<String>();
let path = match v.get("path") {
None | Some(Value::Null) => None,
Some(p) => Some(p.as_str().filter(|p| !p.is_empty() && p.len() <= MAX_PATH)?.to_string()),
};
let changed_at = v.get("changed_at")?.as_f64().filter(|t| t.is_finite() && *t >= 0.0)?;
Some(Entry { id: id.to_string(), name, path, changed_at })
}
}
/// Whether a copy that last changed at `changed_at` holds work its design's file doesn't:
/// the file was last written at `saved_at` (ms since 1970; `None` or unknown: never saved, or
/// the time can't be read, so the copy is kept rather than lost).
pub fn newer_than_save(changed_at: f64, saved_at: Option<f64>) -> bool {
match saved_at {
Some(saved) if saved.is_finite() && saved > 0.0 => changed_at > saved,
_ => true,
}
}
/// What the next visit does with a copy it finds.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Fate {
/// Offer it: "Restore unsaved design…?".
Offer,
/// Its design was saved after it: remove it without asking.
Drop,
/// Its page is still open (another tab): leave it alone.
Leave,
}
/// The copy `entry`, whose design's file was last written at `saved_at`, while the pages
/// `live` are open.
pub fn fate(entry: &Entry, live: &[String], saved_at: Option<f64>) -> Fate {
if page_of(&entry.id).is_some_and(|p| live.iter().any(|l| l == p)) {
Fate::Leave
} else if newer_than_save(entry.changed_at, saved_at) {
Fate::Offer
} else {
Fate::Drop
}
}
/// Copies to offer first: the most recently changed, at most [`MAX_OFFERED`].
pub fn order_offered(entries: &mut Vec<Entry>) {
entries.sort_by(|a, b| b.changed_at.total_cmp(&a.changed_at).then_with(|| a.id.cmp(&b.id)));
entries.truncate(MAX_OFFERED);
}
/// Whether a design's copy is due to be written at `now`: the design last changed at
/// `last_change`, and the copy has lacked a change since `first_unwritten` (all ms).
pub fn due(now: f64, first_unwritten: f64, last_change: f64) -> bool {
now - last_change >= QUIET_MS || now - first_unwritten >= MAX_WAIT_MS
}
/// How long after a failed write of a design's copy it isn't tried again (ms).
pub const RETRY_MS: f64 = 30_000.0;
/// Whether a copy whose write last failed at `failed_at` (ms; `None`: it hasn't) may be written
/// at `now`: not before [`RETRY_MS`] has passed, so a full or denied store isn't hit every tick.
pub fn may_write(now: f64, failed_at: Option<f64>) -> bool {
failed_at.is_none_or(|t| !(now >= t) || now - t >= RETRY_MS)
}
/// Whether `wanted` is the design now open, given the open design's path.
pub fn is_open(open: Option<&str>, wanted: &str) -> bool {
open == Some(wanted)
}
/// Whether leaving the page asks through the browser's "leave site?" box: there is unsaved work,
/// and either the user still wants to be asked or it isn't all kept in the browser yet (with the
/// keep preference off it never is, so there the box always stays).
pub fn asks_before_unload(persists: bool, keep: bool, also_ask: bool, any_unsaved: bool, all_kept: bool) -> bool {
persists && any_unsaved && (also_ask || !(keep && all_kept))
}
/// An answer in the "Restore unsaved design?" window.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Answer {
/// Open the copy, with its unsaved changes, as the design it was (saving goes to its file).
Restore,
/// Open the design's file as last saved; the copy goes.
OpenSaved,
/// The copy goes.
Discard,
}
/// What an answer does.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Plan {
/// Open the copy (and, once it is open, give it back its name and file).
pub open_copy: bool,
/// Open this saved file.
pub open_saved: Option<String>,
/// Remove the copy.
pub drop_copy: bool,
}
/// What `answer` does to copy `entry`; `saved_exists`: its file can still be opened. `None`: the
/// answer isn't offered for it (Open saved version of a design never saved, or gone).
pub fn plan(answer: Answer, entry: &Entry, saved_exists: bool) -> Option<Plan> {
match answer {
Answer::Restore => Some(Plan { open_copy: true, open_saved: None, drop_copy: true }),
Answer::OpenSaved => {
let path = entry.path.clone().filter(|_| saved_exists)?;
Some(Plan { open_copy: false, open_saved: Some(path), drop_copy: true })
}
Answer::Discard => Some(Plan { open_copy: false, open_saved: None, drop_copy: true }),
}
}
/// The answers the window offers for `entry`, in order.
pub fn answers(entry: &Entry, saved_exists: bool) -> Vec<Answer> {
[Answer::Restore, Answer::OpenSaved, Answer::Discard].into_iter().filter(|a| plan(*a, entry, saved_exists).is_some()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(id: &str, changed_at: f64, path: Option<&str>) -> Entry {
Entry { id: id.into(), name: "Bracket".into(), path: path.map(str::to_string), changed_at }
}
#[test]
fn a_failed_write_waits_before_the_next_try() {
assert!(may_write(1000.0, None));
assert!(!may_write(1000.0 + RETRY_MS - 1.0, Some(1000.0)));
assert!(may_write(1000.0 + RETRY_MS, Some(1000.0)));
// A clock that went back doesn't block for good.
assert!(may_write(10.0, Some(1000.0)));
}
#[test]
fn a_design_counts_as_opened_only_at_its_own_path() {
assert!(is_open(Some("a.sc"), "a.sc"));
assert!(!is_open(Some("b.sc"), "a.sc"));
assert!(!is_open(None, "a.sc"));
}
#[test]
fn copies_are_named_by_page_and_design_and_nothing_else_reads_as_one() {
let page = page_id(1_700_000_000_000.0, 0.5);
assert!(page.chars().all(|c| c.is_ascii_hexdigit()));
let id = entry_id(&page, 7);
assert!(valid_id(&id));
assert_eq!(page_of(&id), Some(page.as_str()));
assert_eq!(parse_name(&design_file(&id)), Some((id.as_str(), Part::Design)));
assert_eq!(parse_name(&meta_file(&id)), Some((id.as_str(), Part::Meta)));
for other in ["part.sc", "recovery.sc", "recovery.json", "keep-.sc", "keep-x.sc", "keep-a/b-1.sc", "keep-a-1.step", "keep-a-1sc"] {
assert_eq!(parse_name(other), None, "{other}");
}
// Hostile clocks still give a usable id.
for (now, random) in [(f64::NAN, f64::NAN), (f64::INFINITY, -1.0), (-5.0, 9.0)] {
assert!(valid_id(&entry_id(&page_id(now, random), 1)));
}
}
#[test]
fn metadata_round_trips_and_hostile_metadata_is_no_copy() {
let e = entry("a1-3", 1234.0, Some("/opfs/Bracket.sc"));
assert_eq!(Entry::parse("a1-3", e.to_json().as_bytes()), Some(e.clone()));
let never_saved = entry("a1-4", 99.0, None);
assert_eq!(Entry::parse("a1-4", never_saved.to_json().as_bytes()), Some(never_saved));
for bad in [
"",
"[]",
r#"{"name":"x","changed_at":"soon"}"#,
r#"{"name":"x","changed_at":-1}"#,
r#"{"name":"x","changed_at":1e999}"#,
r#"{"name":"x","path":7,"changed_at":1}"#,
r#"{"name":"x","path":"","changed_at":1}"#,
r#"{"changed_at":1}"#,
] {
assert_eq!(Entry::parse("a1-3", bad.as_bytes()), None, "{bad}");
}
assert_eq!(Entry::parse("../x", e.to_json().as_bytes()), None);
assert_eq!(Entry::parse("a1-3", &vec![b' '; MAX_META_BYTES + 1]), None);
let long = json!({"name": "n".repeat(10_000), "changed_at": 1}).to_string();
assert_eq!(Entry::parse("a1-3", long.as_bytes()).map(|e| e.name.len()), Some(MAX_NAME));
}
#[test]
fn a_copy_newer_than_the_last_save_is_offered_an_older_one_dropped_an_open_pages_left() {
let e = entry("p1-1", 2000.0, Some("/opfs/Bracket.sc"));
// Changed after the file was last written: offered.
assert_eq!(fate(&e, &[], Some(1000.0)), Fate::Offer);
// Saved since (in this tab or another): nothing to offer.
assert_eq!(fate(&e, &[], Some(2000.0)), Fate::Drop);
assert_eq!(fate(&e, &[], Some(3000.0)), Fate::Drop);
// Never saved, or the save time can't be read: kept rather than lost.
for saved in [None, Some(0.0), Some(f64::NAN), Some(f64::INFINITY), Some(-4.0)] {
assert_eq!(fate(&e, &[], saved), Fate::Offer, "{saved:?}");
}
// Its page is still open in another tab: neither offered nor removed.
assert_eq!(fate(&e, &["p1".into()], Some(1000.0)), Fate::Leave);
assert_eq!(fate(&e, &["p1".into()], Some(3000.0)), Fate::Leave);
assert_eq!(fate(&e, &["p2".into()], Some(1000.0)), Fate::Offer);
}
#[test]
fn the_newest_copies_are_offered_first_and_at_most_a_windowful() {
let mut list: Vec<Entry> = (0..40).map(|i| entry(&format!("p-{i}"), f64::from(i), None)).collect();
order_offered(&mut list);
assert_eq!(list.len(), MAX_OFFERED);
assert_eq!(list.first().map(|e| e.id.as_str()), Some("p-39"));
}
#[test]
fn a_copy_is_written_once_the_design_is_still_or_after_the_longest_wait() {
// Still changing, not waited long: not yet.
assert!(!due(1000.0, 900.0, 900.0));
// Still for QUIET_MS: due.
assert!(due(900.0 + QUIET_MS, 900.0, 900.0));
// Changing all the time: due after MAX_WAIT_MS anyway.
assert!(!due(5000.0, 2000.0, 4900.0));
assert!(due(2000.0 + MAX_WAIT_MS, 2000.0, 2000.0 + MAX_WAIT_MS - 1.0));
}
#[test]
fn the_browser_asks_before_unload_unless_told_not_to_and_every_copy_is_current() {
// Keeping off: exactly as before, unsaved work asks, whatever the second option says.
assert!(asks_before_unload(true, false, true, true, true));
assert!(asks_before_unload(true, false, true, true, false));
assert!(asks_before_unload(true, false, false, true, true));
// Keeping on, asking on (the default): both, the box and the copy.
assert!(asks_before_unload(true, true, true, true, true));
// Keeping on, asking off, every unsaved design kept: no browser box.
assert!(!asks_before_unload(true, true, false, true, true));
// Keeping on, asking off, a copy still being written (or failed): the browser still asks.
assert!(asks_before_unload(true, true, false, true, false));
// Nothing unsaved, or an embedded page that keeps nothing: never.
assert!(!asks_before_unload(true, true, true, false, false));
assert!(!asks_before_unload(true, false, true, false, false));
assert!(!asks_before_unload(false, false, true, true, false));
}
#[test]
fn restore_open_saved_and_discard_each_do_one_thing_and_always_remove_the_copy() {
let saved = entry("p-1", 5.0, Some("/opfs/Bracket.sc"));
assert_eq!(plan(Answer::Restore, &saved, true), Some(Plan { open_copy: true, open_saved: None, drop_copy: true }));
assert_eq!(
plan(Answer::OpenSaved, &saved, true),
Some(Plan { open_copy: false, open_saved: Some("/opfs/Bracket.sc".into()), drop_copy: true })
);
assert_eq!(plan(Answer::Discard, &saved, true), Some(Plan { open_copy: false, open_saved: None, drop_copy: true }));
assert_eq!(answers(&saved, true), [Answer::Restore, Answer::OpenSaved, Answer::Discard]);
// A design never saved, or whose file is gone, has no saved version to open.
let never = entry("p-2", 5.0, None);
assert_eq!(plan(Answer::OpenSaved, &never, true), None);
assert_eq!(plan(Answer::OpenSaved, &saved, false), None);
assert_eq!(answers(&never, false), [Answer::Restore, Answer::Discard]);
}
}
+1 -1
View File
@@ -48,7 +48,7 @@ impl Download {
}
if solidcraft_ui_egui::documents::any_dirty(app) {
let mut answer = None;
let closed = ui_kit::Window::new(egui::Id::new("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
let closed = ui_kit::Window::new(egui::Id::unique("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
.required()
.show_body(ctx, |ui| {
ui.label(solidcraft_ui_egui::i18n::tr("web.open_link_confirm"));
+283 -15
View File
@@ -3,7 +3,8 @@
//! Runs the same [`solidcraft_ui_egui::SolveApp`] as the desktop app through eframe's web runner
//! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from this
//! directory (output in `dist/web`). URL flags: `?webgl` forces WebGL2 (remembered until
//! `?webgpu`), `?sample` opens the sample part.
//! `?webgpu`), `?sample` opens the sample part, `?embed=editor` runs it as an editing embed in a
//! host page (no application bar or start page; nothing kept in the browser; #8517).
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#[cfg(target_arch = "wasm32")]
@@ -18,6 +19,8 @@ mod easyprint;
mod files;
#[cfg(target_arch = "wasm32")]
mod install;
#[cfg(any(target_arch = "wasm32", test))]
mod keep;
#[cfg(target_arch = "wasm32")]
mod linked;
#[cfg(any(target_arch = "wasm32", test))]
@@ -42,6 +45,8 @@ fn query_flag(query: &str, name: &str) -> bool {
struct Launch {
open: Option<String>,
embed: bool,
/// `?embed=editor` (#8517): an editing embed in a host page that keeps the files itself.
editor: bool,
}
#[cfg(any(target_arch = "wasm32", test))]
@@ -52,16 +57,19 @@ impl Launch {
for (key, value) in url::form_urlencoded::parse(query.trim_start_matches('?').as_bytes()) {
match key.as_ref() {
"open" if launch.open.is_none() => launch.open = Some(value.into_owned()),
"embed" if value == "editor" => launch.editor = true,
"embed" => embed = true,
_ => {}
}
}
launch.embed = embed && launch.open.is_some();
launch.embed = embed && launch.open.is_some() && !launch.editor;
launch
}
/// The browser's own storage (designs, autosave, recovery, preferences) is used: not when
/// embedded, where the host keeps what is kept.
fn persists(&self) -> bool {
!self.embed
!self.embed && !self.editor
}
fn full_app_url(&self, mut url: url::Url) -> String {
@@ -171,12 +179,14 @@ mod web {
use solidcraft_engine::Session;
use solidcraft_engine::io::vfs;
use solidcraft_ui_egui::ui_kit::BackingTooltipExt as _;
use solidcraft_ui_egui::{Services, SolveApp, ui_kit};
use wasm_bindgen::JsCast as _;
use crate::api::Api;
use crate::disk::{self, SaveTarget};
use crate::files::{self, Files, Intent};
use crate::keep;
use crate::space_mouse::SpaceMouse;
const CANVAS_ID: &str = "solidcraft_canvas";
@@ -236,7 +246,9 @@ mod web {
}
/// The browser's "leave site? changes you made may not be saved" prompt while a design has
/// unsaved changes (the page can't show its own dialog when it is being closed).
/// unsaved changes (the page can't show its own dialog when it is being closed). With "keep
/// unsaved work in this browser" on and "still ask" off, there is nothing to ask once every
/// unsaved design's copy is current (`keep::asks_before_unload`, #10220).
fn ask_before_unload(window: &web_sys::Window) {
let on_unload = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::BeforeUnloadEvent)>::new(|e: web_sys::BeforeUnloadEvent| {
if UNSAVED.with(std::cell::Cell::get) {
@@ -249,6 +261,50 @@ mod web {
on_unload.forget();
}
/// With "keep unsaved work in this browser" on, the page brings every unsaved design's copy up
/// to date when it is hidden or goes (`pagehide`, `visibilitychange`): the browser may not
/// wait for that write, so the copies are written shortly after each change as well
/// (`Shell::keep_tick`, #10220).
fn keep_when_hidden(window: &web_sys::Window, runner: &eframe::WebRunner) {
let keep_now = {
let runner = runner.clone();
move || {
if let Some(mut shell) = runner.app_mut::<Shell>() {
shell.keep_tick(None, true);
}
}
};
let on_hide = {
let keep_now = keep_now.clone();
wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::Event)>::new(move |_: web_sys::Event| keep_now())
};
let on_visibility = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::Event)>::new(move |_: web_sys::Event| {
let hidden = web_sys::window()
.and_then(|w| w.document())
.and_then(|d| js_sys::Reflect::get(&d, &"visibilityState".into()).ok())
.and_then(|v| v.as_string())
.is_some_and(|v| v == "hidden");
if hidden {
keep_now();
}
});
let _ = window.add_event_listener_with_callback("pagehide", on_hide.as_ref().unchecked_ref());
if let Some(document) = window.document() {
let _ = document.add_event_listener_with_callback("visibilitychange", on_visibility.as_ref().unchecked_ref());
}
// The listeners live as long as the page.
on_hide.forget();
on_visibility.forget();
}
/// One open design followed for its kept copy: its revision, when that last changed, and since
/// when its copy has lacked a change (`None`: current, or being written).
struct Track {
revision: u64,
last_change: f64,
first_unwritten: Option<f64>,
}
/// eframe cancels the browser's action for every Tab it gets, so the browser couldn't switch
/// tabs while the app has the keyboard. Its tab-switching keys stop here, on the way down to
/// the canvas, before eframe sees them; the browser then acts on them as usual.
@@ -321,6 +377,10 @@ mod web {
/// The settings this tab wrote or loaded last, and those another tab wrote since (#8644).
prefs_writer: crate::PrefsWriter,
other_tab_prefs: PrefsInbox,
/// The open designs followed for their kept copy, by `Session::uid` (#10220).
keep_track: std::collections::HashMap<u64, Track>,
/// Every unsaved design's kept copy is current (and the preference is on).
all_kept: bool,
}
impl Shell {
@@ -435,8 +495,14 @@ mod web {
self.sync_recents();
}
let now = ctx.input(|i| i.time);
// With "keep unsaved work in this browser" on, each design's own copy takes over from
// the single autosave (#10220).
if self.app.preferences.keep_unsaved_in_browser && self.autosaved.is_some() {
files::autosave(&self.files, None);
self.autosaved = None;
}
// Unsaved work is kept (at most every AUTOSAVE_S); saved work needs no autosave.
if self.files.ready.get() && self.files.recovery.borrow().is_none() {
if self.files.ready.get() && self.files.recovery.borrow().is_none() && !self.app.preferences.keep_unsaved_in_browser {
let s = &self.app.session;
let changed = s.is_dirty() && self.autosaved != Some(s.revision);
let wait = AUTOSAVE_S - (now - self.autosaved_at);
@@ -455,6 +521,175 @@ mod web {
if !self.files.ready.get() || !self.files.incoming.borrow().is_empty() {
ctx.request_repaint_after(std::time::Duration::from_millis(100));
}
self.all_kept = self.keep_tick(Some(ctx), false);
}
/// Keep each open design's unsaved changes in the browser, when the preference asks for
/// it: a design's copy is written once it has been still a moment (`keep::due`), or at
/// once with `now` (the page is being hidden); a copy whose design is saved or closed, or
/// with the preference off, goes. Answers whether every unsaved design's copy is current.
fn keep_tick(&mut self, ctx: Option<&egui::Context>, now: bool) -> bool {
if !self.launch.persists() || !self.files.ready.get() {
return false;
}
let Shell { app, files, keep_track, .. } = self;
let on = app.preferences.keep_unsaved_in_browser;
let time = js_sys::Date::now();
let page = files.keep_page.borrow().clone();
let mut wanted = std::collections::HashSet::new();
let mut all_kept = true;
let mut wake: Option<f64> = None;
let mut error = None;
let mut soon = |ms: f64| wake = Some(wake.map_or(ms, |w: f64| w.min(ms)));
if on {
for s in solidcraft_ui_egui::documents::open_sessions(app).into_iter().filter(|s| s.is_dirty()) {
let id = keep::entry_id(&page, s.uid);
wanted.insert(s.uid);
let t = keep_track.entry(s.uid).or_insert(Track { revision: s.revision, last_change: time, first_unwritten: None });
if t.revision != s.revision {
t.revision = s.revision;
t.last_change = time;
}
if files.kept.borrow().get(&id).copied().flatten() == Some(s.revision) {
t.first_unwritten = None;
continue;
}
all_kept = false;
if files.keeping.borrow().contains(&id) {
soon(100.0);
continue;
}
let first = *t.first_unwritten.get_or_insert(time);
let failed = files.kept_failed.borrow().get(&id).copied();
if !keep::may_write(time, failed) {
// The last write failed: try again after a while.
soon(keep::RETRY_MS);
continue;
}
if now || keep::due(time, first, t.last_change) {
match solidcraft_engine::io::write_design_container(&s.design()) {
Ok(bytes) => {
let entry = keep::Entry { id, name: s.doc.name.clone(), path: s.path.clone(), changed_at: t.last_change };
files::write_kept(files, &entry, bytes, s.revision);
t.first_unwritten = None;
}
Err(e) => {
files.kept_failed.borrow_mut().insert(id, time);
error = Some(format!("keeping unsaved work in this browser: {e}"));
}
}
soon(100.0);
} else {
soon((keep::QUIET_MS - (time - t.last_change)).min(keep::MAX_WAIT_MS - (time - first)).max(16.0));
}
}
}
// This page's copies no design needs any more: saved, closed, or the preference off.
let stale: Vec<String> = files
.kept
.borrow()
.keys()
.filter(|id| keep::page_of(id) == Some(page.as_str()))
.filter(|id| !wanted.iter().any(|uid| keep::entry_id(&page, *uid) == **id))
.cloned()
.collect();
for id in stale {
files::remove_kept(files, &id);
soon(100.0);
}
keep_track.retain(|uid, _| wanted.contains(uid));
if let Some(e) = error {
app.set_status(e, true);
}
if let (Some(ctx), Some(ms)) = (ctx, wake) {
ctx.request_repaint_after(std::time::Duration::from_secs_f64(ms / 1000.0));
}
on && all_kept
}
/// Offer the copies an earlier visit kept (#10220): "Restore unsaved design: <name>,
/// changed <time>?", one window for all of them, each with Restore, Open saved version
/// (when it has a file) and Discard.
fn keep_prompt(&mut self, ctx: &egui::Context) {
let offered = self.files.keep_offered.borrow().clone();
if offered.is_empty() || !self.files.ready.get() {
return;
}
let now = (js_sys::Date::now() / 1000.0) as u64;
let rows: Vec<_> = offered
.iter()
.map(|e| solidcraft_ui_egui::recovery_ui::BrowserCopy {
name: &e.name,
when: solidcraft_ui_egui::recovery_ui::ago_between(now, (e.changed_at / 1000.0) as u64),
open_saved: keep::answers(e, self.saved_exists(e)).contains(&keep::Answer::OpenSaved),
})
.collect();
let act = solidcraft_ui_egui::recovery_ui::browser_copies_window(ctx, &self.app.preferences.language, &rows);
let Some((index, answer)) = act else { return };
let Some(entry) = offered.get(index) else { return };
let answer = match answer {
solidcraft_ui_egui::recovery_ui::CopyAnswer::Restore => keep::Answer::Restore,
solidcraft_ui_egui::recovery_ui::CopyAnswer::OpenSaved => keep::Answer::OpenSaved,
solidcraft_ui_egui::recovery_ui::CopyAnswer::Discard => keep::Answer::Discard,
};
self.keep_answer(entry, answer);
}
/// Whether a kept copy's design file can still be opened: in the browser's storage, or a
/// file on the computer this browser remembers.
fn saved_exists(&self, e: &keep::Entry) -> bool {
e.path.as_deref().is_some_and(|p| vfs::exists(p) || disk::parse(p).is_some_and(|(id, _)| self.files.disk_known.borrow().contains(id)))
}
/// Act on an answer in the "Restore unsaved design?" window (`keep::plan`).
fn keep_answer(&mut self, entry: &keep::Entry, answer: keep::Answer) {
let Some(plan) = keep::plan(answer, entry, self.saved_exists(entry)) else { return };
if plan.open_copy {
let copy = match files::prepare_kept(&entry.id, entry.path.as_deref()) {
Ok(copy) => copy,
Err(error) => {
self.app.set_status(error, true);
return;
}
};
self.app.open_path(&copy);
if self.app.session.path.as_deref() != Some(copy.as_str()) {
// Not opened (the status says why): it stays offered.
return;
}
// It is its own design again (saving goes to its file), with its changes unsaved.
self.app.session.path = entry.path.clone();
self.app.session.doc_mut().name = entry.name.clone();
self.app.session.mark_unsaved();
self.app.home.open = false;
}
if let Some(path) = &plan.open_saved {
if vfs::exists(path) {
self.app.open_path(path);
if !keep::is_open(self.app.session.path.as_deref(), path) {
// Not opened (the status says why): the copy stays offered.
return;
}
} else {
// A file on the computer: the browser asks for access again, in this click.
// The copy goes once the file has opened, not before: if access is refused,
// the picker is dismissed or the file is gone, it stays offered.
let (files, id) = (self.files.clone(), entry.id.clone());
disk::restore(
&self.files,
path,
Some(Box::new(move || {
files.keep_offered.borrow_mut().retain(|e| e.id != id);
files::remove_kept(&files, &id);
})),
);
return;
}
}
if plan.drop_copy {
self.files.keep_offered.borrow_mut().retain(|e| e.id != entry.id);
files::remove_kept(&self.files, &entry.id);
}
}
/// The start page's recent designs are the stored ones: the remembered order first (those
@@ -491,7 +726,7 @@ mod web {
return;
}
let mut act: Option<(&'static str, String)> = None;
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_files"), "folder", "Files in this browser")
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::unique("sc_browser_files"), "folder", "Files in this browser")
// Room for long names; the kit still caps the window at the browser's width.
.cards(4.0)
.show_body(ctx, |ui| {
@@ -500,7 +735,7 @@ mod web {
files::pick_from_disk(&self.files, Intent::Open);
}
if ui_kit::button(ui, "Insert from computer…", ui_kit::Button::Normal)
.on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_drawings_hint"))
.backing_on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_drawings_hint"))
.clicked()
{
files::pick_from_disk(&self.files, Intent::Insert);
@@ -554,7 +789,7 @@ mod web {
let design = solidcraft_engine::io::is_design_path(&e.name);
if !design
&& ui_kit::button(ui, "Insert", ui_kit::Button::Small)
.on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_stored_drawings_hint"))
.backing_on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_stored_drawings_hint"))
.clicked()
{
act = Some(("insert", e.name.clone()));
@@ -564,7 +799,7 @@ mod web {
self.rename_focus = true;
}
if ui_kit::button(ui, "Save to computer…", ui_kit::Button::Small)
.on_hover_text("A copy in a file you choose; the stored design stays here")
.backing_on_hover_text("A copy in a file you choose; the stored design stays here")
.clicked()
{
act = Some(("save_copy", e.name.clone()));
@@ -655,7 +890,7 @@ mod web {
fn recovery_prompt(&mut self, ctx: &egui::Context) {
let Some((name, path)) = self.files.recovery.borrow().clone() else { return };
let mut answer = None;
let _ = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_recovery"), "restore", "Recover unsaved work?")
let _ = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::unique("sc_browser_recovery"), "restore", "Recover unsaved work?")
.required()
.show_body(ctx, |ui| {
ui.label(format!("“{name}” had unsaved changes when this page last closed."));
@@ -691,6 +926,13 @@ mod web {
files::autosave(&self.files, None);
}
/// Whether leaving the page asks through the browser's own box (`keep::asks_before_unload`).
fn asks_before_unload(&self) -> bool {
let any = solidcraft_ui_egui::documents::any_dirty(&self.app);
let prefs = &self.app.preferences;
keep::asks_before_unload(self.launch.persists(), prefs.keep_unsaved_in_browser, prefs.keep_unsaved_also_asks, any, self.all_kept)
}
/// Whether the shell already has the on-demand face the live language needs.
fn script_font_ready(&self) -> bool {
match solidcraft_ui_egui::fonts::web_script_font_kind(solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language)) {
@@ -802,7 +1044,7 @@ mod web {
self.api.poll();
self.space_mouse.logic(&mut self.app, ctx);
self.files_tick(ctx);
UNSAVED.with(|u| u.set(solidcraft_ui_egui::documents::any_dirty(&self.app)));
UNSAVED.with(|u| u.set(self.asks_before_unload()));
DESIGN_SHOWN.with(|d| d.set(solidcraft_ui_egui::toolbar::design_shown(&self.app)));
}
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
@@ -822,7 +1064,7 @@ mod web {
}
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
self.linked.tick(&mut self.app, ui.ctx());
UNSAVED.with(|u| u.set(self.launch.persists() && solidcraft_ui_egui::documents::any_dirty(&self.app)));
UNSAVED.with(|u| u.set(self.asks_before_unload()));
if self.launch.embed {
ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Chrome).edge("embed_header", ui_kit::Edge::Top).show(ui, |ui| {
if ui_kit::button(ui, solidcraft_ui_egui::i18n::tr("web.open_full"), ui_kit::Button::Normal).clicked()
@@ -837,10 +1079,15 @@ mod web {
if self.launch.persists() {
self.file_panel(&ctx);
self.recovery_prompt(&ctx);
self.keep_prompt(&ctx);
}
self.watch_text_for_font(&ctx);
self.store_prefs(&ctx);
}
#[cfg(target_arch = "wasm32")]
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
Some(&mut *self)
}
fn persist_egui_memory(&self) -> bool {
// eframe keeps this under one fixed key: only stable uses it, so a pre-release never rewrites it.
self.launch.persists() && crate::channel::current().is_empty()
@@ -915,7 +1162,8 @@ mod web {
{
create.instance_descriptor.backends = eframe::wgpu::Backends::GL;
}
let result = eframe::WebRunner::new()
let runner = eframe::WebRunner::new();
let result = runner
.start(
canvas,
options,
@@ -927,7 +1175,7 @@ mod web {
}
let (panel, opener, saver, shown) = (files.clone(), files.clone(), files.clone(), files.clone());
let (restorable, restorer) = (files.clone(), files.clone());
let services = if launch.embed { Services::default() } else { Services {
let services = if !launch.persists() { Services::default() } else { Services {
// Open shows the browser's open dialog for a design where it has one
// (the file keeps its handle, so Save writes back to it), else the
// browser's files window. A command that reads another kind of file
@@ -959,7 +1207,7 @@ mod web {
can_restore: Some(Box::new(move |path: &str| {
disk::parse(path).is_some_and(|(id, _)| restorable.disk_known.borrow().contains(id))
})),
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path))),
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path, None))),
download_design: Some(Box::new(files::download)),
easyprint_upload: Some(std::sync::Arc::new(crate::easyprint::upload)),
browser_files: Some(Box::new(move || shown.panel.set(true))),
@@ -971,6 +1219,7 @@ mod web {
let (sender, receiver) = std::sync::mpsc::channel();
let mut app = SolveApp::new(Session::default(), services).with_control(receiver);
app.embedded = launch.embed;
app.embed_editor = launch.editor;
// Listening before reading: settings another tab writes from now on are
// newer than those read here.
let other_tab_prefs = PrefsInbox::default();
@@ -1025,10 +1274,18 @@ mod web {
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
prefs_writer: crate::PrefsWriter::default(),
other_tab_prefs,
keep_track: Default::default(),
all_kept: false,
}))
}),
)
.await;
if result.is_ok()
&& crate::Launch::parse(&query()).persists()
&& let Some(window) = web_sys::window()
{
keep_when_hidden(&window, &runner);
}
if let Some(el) = document.get_element_by_id(LOADING_ID) {
match result {
Ok(()) => dismiss_loading(el),
@@ -1081,6 +1338,17 @@ mod tests {
assert!(full.fragment().is_none());
}
#[test]
fn embed_editor_needs_no_design_link_and_keeps_nothing_in_the_browser() {
let editor = Launch::parse("?embed=editor");
assert!(editor.editor && !editor.embed && !editor.persists());
let linked = Launch::parse("?embed=editor&open=https://example.org/a.sc");
assert!(linked.editor && !linked.embed, "an editing embed may open a linked design, still editable");
let viewer = Launch::parse("?embed=viewer&open=https://example.org/a.sc");
assert!(viewer.embed && !viewer.editor, "any other value is the viewer embed, as before");
assert!(!Launch::parse("?other=editor").editor);
}
#[test]
fn design_links_refuse_hostile_schemes_credentials_and_sizes() {
assert!(design_url("https://example.org/arm.solidcraft?download=1", "http://solidcraft.io").is_ok());
+2 -1
View File
@@ -81,6 +81,7 @@ windows-sys = { version = "0.61", features = [
"Win32_UI_HiDpi",
"Win32_UI_WindowsAndMessaging",
] }
winreg = "0.56"
[target.'cfg(target_os = "macos")'.dependencies]
muda = { version = "0.21.2", default-features = false }
@@ -92,7 +93,7 @@ hidapi = { version = "2.6.7", default-features = false, features = ["macos-share
# Files opened from Finder and the Dock (src/open_documents.rs), and the fullscreen title-bar
# inset (src/main.rs); all already come with eframe and rfd.
objc2 = "0.6"
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSResponder", "NSWindow"] }
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSAccessibility", "NSApplication", "NSResponder", "NSWindow", "NSWorkspace"] }
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "block2", "NSArray", "NSNotification", "NSOperation", "NSProcessInfo", "NSString", "NSURL"] }
block2 = "0.6"
# Native crash capture (src/native_crash_macos.rs): the signal handler, the faulting module
+13 -9
View File
@@ -68,7 +68,9 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
Ok(l) => {
if let Some(port) = listen_on_listener(l, token.clone(), tx, ctx, false) {
if matches!(listen_on, control_auth::Listen::Default) {
eprintln!("solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)");
solidcraft_engine::note!(
"solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)"
);
}
// The discovery file: where `solidcraft-cli mcp` finds this app with no arguments.
match control_auth::write_current_port(port) {
@@ -77,14 +79,14 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
*p = Some((port, token));
}
}
Err(e) => eprintln!("solidcraft: could not write the control discovery file ({e})"),
Err(e) => solidcraft_engine::note!("solidcraft: could not write the control discovery file ({e})"),
}
}
}
Err(e) => eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
Err(e) => solidcraft_engine::note!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
}
}
None => eprintln!("solidcraft: control server not started: no random token available"),
None => solidcraft_engine::note!("solidcraft: control server not started: no random token available"),
}
rx
}
@@ -95,13 +97,13 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
/// requests go to `tx`, where the chat's gate reads them; one may wait long for the user's Allow.
pub fn listen_agent(tx: Sender<ControlRequest>, ctx: egui::Context) -> Option<u16> {
let Some(token) = new_token() else {
eprintln!("solidcraft: agent chat endpoint not started: no random token available");
solidcraft_engine::note!("solidcraft: agent chat endpoint not started: no random token available");
return None;
};
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
Ok(l) => l,
Err(e) => {
eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}");
solidcraft_engine::note!("solidcraft: control server failed to bind 127.0.0.1: {e}");
return None;
}
};
@@ -115,19 +117,21 @@ fn listen_on_listener(listener: TcpListener, token: String, tx: Sender<ControlRe
match control_auth::token_path(port) {
Some(path) => match control_auth::write_token_file(&path, &token) {
Ok(()) => {
eprintln!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
solidcraft_engine::note!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
if let Ok(mut files) = TOKEN_FILES.lock() {
files.push((path, token.clone()));
}
}
Err(e) => eprintln!(
Err(e) => solidcraft_engine::note!(
"solidcraft: control server listening on 127.0.0.1:{port}, but its token file {} could not be written ({e}); \
clients need SOLIDCRAFT_CONTROL_TOKEN",
path.display()
),
},
None => {
eprintln!("solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN")
solidcraft_engine::note!(
"solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN"
)
}
}
let token = std::sync::Arc::new(token);
+100
View File
@@ -0,0 +1,100 @@
//! Is the file chooser that opens the desktop's own? (#10159)
//!
//! `rfd` opens the file chooser through the XDG desktop portal; without the portal it falls
//! back to zenity, a window that does not belong to the app and that the app never mentions.
//! The desktop's own chooser is the portal one, so the app has to know when it is not the one
//! that opens, and say so in the session log.
//!
//! The session bus is asked once, on a thread of its own: a bus that answers late must not
//! hold the window. What it answered is kept, and read when a file is opened.
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
/// The portal's bus name: the desktop's own file chooser lives behind it.
const PORTAL: &str = "org.freedesktop.portal.Desktop";
/// The bus has not answered yet.
const UNKNOWN: u8 = 0;
/// There is no session bus to reach the portal on.
const NO_SESSION_BUS: u8 = 1;
/// The bus is there but the portal does not answer on it.
const NO_PORTAL_SERVICE: u8 = 2;
/// The bus is there and the portal answers on it.
const PORTAL_THERE: u8 = 3;
/// What the session bus said about the portal.
static BUS: AtomicU8 = AtomicU8::new(UNKNOWN);
/// A file chooser is opening: whether it is the desktop's own is to be said.
static OPENING: AtomicBool = AtomicBool::new(false);
/// Ask the session bus, on a thread of its own, whether the portal is there.
pub fn start(ctx: &egui::Context) {
let thread_ctx = ctx.clone();
let probe = move || {
let answer = match session_bus() {
None => NO_SESSION_BUS,
Some(connection) => match portal_on(&connection) {
true => PORTAL_THERE,
false => NO_PORTAL_SERVICE,
},
};
BUS.store(answer, Ordering::Relaxed);
// The answer is in: a file chooser waiting for it can be told now.
thread_ctx.request_repaint();
};
if let Err(e) = std::thread::Builder::new().name("desktop-chooser".into()).spawn(probe) {
log::warn!("file chooser: no thread to ask the session bus: {e}");
// Nothing will answer: the desktop's own chooser cannot be told apart from the start.
BUS.store(PORTAL_THERE, Ordering::Relaxed);
}
}
/// The session bus, when there is one to talk to.
fn session_bus() -> Option<zbus::blocking::Connection> {
zbus::blocking::Connection::session().ok()
}
/// Whether the portal answers on the bus: it is either running on it or waiting to be started.
fn portal_on(connection: &zbus::blocking::Connection) -> bool {
let Ok(bus) = zbus::blocking::fdo::DBusProxy::new(connection) else { return false };
let named = |names: Vec<zbus::names::OwnedBusName>| names.iter().any(|name| name.as_str() == PORTAL);
// A portal that is running is on the bus; one that is not running yet is still there when
// the bus can start it, which is what the portal's service file is for.
match (bus.list_names().map(named), bus.list_activatable_names().map(named)) {
(Ok(running), Ok(startable)) => running || startable,
(Ok(running), Err(_)) => running,
(Err(_), Ok(startable)) => startable,
// The bus does not answer: nothing answers on it.
(Err(_), Err(_)) => false,
}
}
/// A file chooser is opening: ask for a frame, so that the reason it is not the desktop's own
/// is said (#10159).
pub fn note_opening(ctx: &egui::Context) {
OPENING.store(true, Ordering::Relaxed);
ctx.request_repaint();
}
/// The reason the file chooser that is opening is not the desktop's own, if it is not.
///
/// The decision is the one the test states: without a session bus there is no bus to reach the
/// portal on; with a bus but no portal on it, `rfd` falls back to zenity; and under XWayland the
/// window is an X11 one while the session is a Wayland one, which the portal refuses to parent
/// its chooser to.
pub fn take_reason() -> Option<&'static str> {
if !OPENING.load(Ordering::Relaxed) {
return None;
}
let (dbus_session, portal_service) = match BUS.load(Ordering::Relaxed) {
NO_SESSION_BUS => (false, false),
NO_PORTAL_SERVICE => (true, false),
PORTAL_THERE => (true, true),
// The bus has not answered yet: wait for it, and say nothing yet.
_ => return None,
};
OPENING.store(false, Ordering::Relaxed);
let wayland_display = std::env::var_os("WAYLAND_DISPLAY").is_some();
let x11_display = super::window_system_now() == Some("x11");
super::desktop_chooser_fallback(dbus_session, portal_service, wayland_display, x11_display)
}
@@ -0,0 +1,63 @@
//! Cached accessibility signal, observed only while a theme offers blur. No UI-thread IO.
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU8, Ordering},
};
#[derive(Default)]
struct Reading {
needed: AtomicBool,
started: AtomicBool,
value: AtomicU8,
}
pub fn cached(ctx: &egui::Context) -> Box<dyn Fn(bool) -> Option<bool>> {
let state = Arc::new(Reading::default());
let ctx = ctx.clone();
Box::new(move |needed| {
state.needed.store(needed, Ordering::Relaxed);
if needed && !state.started.swap(true, Ordering::Relaxed) {
let weak = Arc::downgrade(&state);
let ctx = ctx.clone();
if let Err(error) = std::thread::Builder::new().name("desktop-transparency".into()).spawn(move || {
while let Some(state) = weak.upgrade() {
if state.needed.load(Ordering::Relaxed) {
let code = match read() {
Some(true) => 1,
Some(false) => 2,
None => 0,
};
if state.value.swap(code, Ordering::Relaxed) != code {
ctx.request_repaint();
}
}
drop(state);
std::thread::sleep(std::time::Duration::from_secs(5));
}
}) {
log::warn!("desktop transparency: could not observe accessibility setting: {error}");
}
}
match state.value.load(Ordering::Relaxed) {
1 => Some(true),
2 => Some(false),
_ => None,
}
})
}
#[cfg(target_os = "windows")]
fn read() -> Option<bool> {
let key =
winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER).open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize").ok()?;
match key.get_value::<u32, _>("EnableTransparency").ok()? {
0 => Some(true),
1 => Some(false),
_ => None,
}
}
#[cfg(target_os = "macos")]
fn read() -> Option<bool> {
objc2::rc::autoreleasepool(|_| Some(objc2_app_kit::NSWorkspace::sharedWorkspace().accessibilityDisplayShouldReduceTransparency()))
}
+1 -1
View File
@@ -112,7 +112,7 @@ pub fn restart(software: bool, lost_at: u64, reason: &str) {
pub fn end() -> ! {
let watchdog = std::thread::Builder::new().name("solidcraft-exit".into()).spawn(|| {
std::thread::sleep(END_GRACE);
eprintln!("SolidCraft graphics: still closing after the device was lost; aborting");
solidcraft_engine::note!("SolidCraft graphics: still closing after the device was lost; aborting");
std::process::abort();
});
if let Err(e) = watchdog {
+235 -24
View File
@@ -23,7 +23,11 @@ mod app_icon;
mod come_forward;
mod control_server;
#[cfg(target_os = "linux")]
mod desktop_chooser;
#[cfg(target_os = "linux")]
mod desktop_theme;
#[cfg(any(target_os = "windows", target_os = "macos"))]
mod desktop_transparency;
mod device_lost;
#[cfg(target_os = "windows")]
mod native_crash;
@@ -75,7 +79,7 @@ fn mark(step: &str) {
static START: std::sync::OnceLock<(std::time::Instant, bool)> = std::sync::OnceLock::new();
let (start, on) = START.get_or_init(|| (std::time::Instant::now(), solidcraft_engine::geom::env::var_os("PROFILE_STARTUP").is_some()));
if *on {
eprintln!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
solidcraft_engine::note!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
}
}
@@ -240,6 +244,12 @@ impl eframe::App for App {
menu.tick(&mut self.solve, ctx);
}
self.solve.logic(ctx);
// The file chooser that opened is not the desktop's own: the user has to be told (#10159).
#[cfg(target_os = "linux")]
if let Some(reason) = desktop_chooser::take_reason() {
let text = self.solve.tr(reason).to_string();
self.solve.set_status(text, false);
}
self.dialog_folder.design(self.solve.session.path.as_deref());
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
if !self.solve.file_picker_busy() {
@@ -293,6 +303,8 @@ impl eframe::App for App {
log::warn!("a picture was still being drawn as the app closed");
}
solidcraft_ui_egui::recovery_ui::close(&mut self.solve);
// A GPU path tracer setup still running did not stop the app (#9236).
solidcraft_ui_egui::render_workspace::close(&mut self.solve);
solidcraft_ui_egui::open_elsewhere::close(&mut self.solve);
// Before a restart (`Drop for App`) starts the next instance, which would otherwise get
// nothing from the 3D mouse driver (`solidcraft_3dx_client::stop`).
@@ -356,7 +368,7 @@ impl App {
let Some(gpu) = self.gpu.as_mut().filter(|g| g.lost_at.is_none() && g.lost.is_lost()) else { return };
let reason = gpu.lost.reason().unwrap_or_default();
log::error!("graphics: the device was lost ({reason}); closing to start again");
eprintln!("SolidCraft graphics: the device was lost ({reason}); starting again");
solidcraft_engine::note!("SolidCraft graphics: the device was lost ({reason}); starting again");
solidcraft_engine::crash::phase(&format!("graphics device lost: {reason}"));
gpu.lost_at = Some(device_lost::now_secs());
gpu.reason = reason;
@@ -469,12 +481,19 @@ fn fullscreen_content_insets(raw: &mut egui::RawInput, title_height: f32, zoom:
fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services {
let parent = rfd::AsyncFileDialog::new().set_parent(cc);
// The file chooser that opens may not be the desktop's own: the user is told when it does
// not (#10159).
#[cfg(target_os = "linux")]
let chooser_ctx = cc.egui_ctx.clone();
let open_parent = parent.clone();
let program_parent = parent.clone();
let file_parent = parent.clone();
let (open_folder, file_folder, save_folder) = (folder.clone(), folder.clone(), folder.clone());
Services {
pick_open: Some(Box::new(move |first: &[&str]| {
// The chooser that opens may not be the desktop's own (#10159).
#[cfg(target_os = "linux")]
desktop_chooser::note_opening(&chooser_ctx);
// The file type the command reads comes first, so the picker opens on it (#1801).
let mut dialog = open_folder.start(open_parent.clone());
if !first.is_empty() {
@@ -539,6 +558,10 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
desktop_dark: Some(desktop_theme::start(&cc.egui_ctx)),
#[cfg(not(target_os = "linux"))]
desktop_dark: None,
#[cfg(any(target_os = "windows", target_os = "macos"))]
reduce_transparency: Some(desktop_transparency::cached(&cc.egui_ctx)),
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
reduce_transparency: None,
http_get: Some(std::sync::Arc::new(http_get)),
http_download: Some(std::sync::Arc::new(http_download)),
// Already an installed app: only the browser build offers to install.
@@ -779,6 +802,28 @@ fn graphics() -> Option<serde_json::Value> {
graphics_path().and_then(|p| std::fs::read_to_string(p).ok()).and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
}
fn restore_preferences(app: &mut SolveApp, stored_ui: Option<&str>, saved_graphics: Option<&serde_json::Value>, safe_mode: bool) {
if let Some(p) = stored_ui.filter(|_| !safe_mode) {
app.load_prefs(p);
}
// Even safe mode edits the real graphics.json. Keep its choices in Preferences so an
// anti-aliasing change cannot erase them; safe mode's renderer still uses its own defaults.
restore_graphics_preferences(app, saved_graphics);
}
/// Restore startup choices after loading the separately saved UI preferences.
fn restore_graphics_preferences(app: &mut SolveApp, saved: Option<&serde_json::Value>) {
app.preferences.graphics_adapter = saved.and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str)).unwrap_or_default().to_string();
// graphics.json chooses the window before app.ron is loaded. Reflect that saved choice,
// not the current session's window system (which an environment override may change).
app.preferences.window_system = saved
.and_then(|v| v.get("window_system").and_then(serde_json::Value::as_str))
.map(str::trim)
.filter(|choice| matches!(*choice, "wayland" | "x11"))
.unwrap_or("auto")
.to_string();
}
/// The graphics backend preference (`graphics.json`'s `backend`: `auto`, `dx12`, `vulkan`, `gl`).
fn backend_preference() -> Option<String> {
graphics().and_then(|v| v.get("backend").and_then(serde_json::Value::as_str).map(str::to_string))
@@ -948,7 +993,7 @@ fn main() -> eframe::Result {
if let Some(value) = max_file_size {
let applied = startup::max_file_size(value.as_deref()).and_then(|bytes| solidcraft_engine::io::limits::set_override(Some(bytes)));
if let Err(error) = applied {
eprintln!("SolidCraft: {error}");
solidcraft_engine::note!("SolidCraft: {error}");
return Err(eframe::Error::AppCreation(std::io::Error::other(error).into()));
}
}
@@ -980,7 +1025,7 @@ fn main() -> eframe::Result {
let fallback_note = std::env::var(startup::FALLBACK_NOTE_ENV).ok().map(|s| s.chars().take(4096).collect::<String>());
if let Some(note) = &fallback_note {
log::warn!("{note}");
eprintln!("SolidCraft: {note}");
solidcraft_engine::note!("SolidCraft: {note}");
notes.push(note.clone());
}
let saved_samples = if safe_mode { None } else { msaa() };
@@ -997,6 +1042,8 @@ fn main() -> eframe::Result {
solidcraft_engine::crash::install(dir, build)
})
.unwrap_or_default();
// The recovery folder also holds the reopen guard marker (#5064); read once, reused below.
let recovery_dir = solidcraft_engine::recovery::default_dir();
// A crash in native code (a graphics driver) says where it happened: the exception or signal
// and the module on Windows and macOS, and on every system the startup step the app was in.
#[cfg(target_os = "windows")]
@@ -1079,6 +1126,10 @@ fn main() -> eframe::Result {
solidcraft_ui_egui::fonts::load_system_fonts_in_background(&cc.egui_ctx);
mark("fonts");
let dialog_folder = DialogFolder::default();
// The file chooser that opens may not be the desktop's own: the session bus is asked
// now, so the answer is there when a file is opened (#10159).
#[cfg(target_os = "linux")]
desktop_chooser::start(&cc.egui_ctx);
let mut app = SolveApp::new(Session::default(), services(cc, &dialog_folder));
app.apply_off_thread = Some(solidcraft_ui_egui::dialogs::APPLY_AT_ONCE);
app.custom_titlebar = custom_titlebar;
@@ -1087,21 +1138,20 @@ fn main() -> eframe::Result {
// Windows: the taskbar button shows the mark even when slow first frames keep the
// taskbar waiting for the window's icon.
if !native_frame::set_class_icon(cc) {
eprintln!("SolidCraft window: no class icon; the taskbar may show a generic one");
solidcraft_engine::note!("SolidCraft window: no class icon; the taskbar may show a generic one");
}
// The OS hit test offers the resize border, at the window's DPI, from the first
// message (#1381).
app.native_resize_border = custom_titlebar && native_frame::install(cc);
if custom_titlebar && !app.native_resize_border {
eprintln!("SolidCraft window: no native resize border; egui edge zones instead");
solidcraft_engine::note!("SolidCraft window: no native resize border; egui edge zones instead");
}
}
app.integrated_titlebar = cfg!(target_os = "macos");
app.cpu_meshing = safe_mode;
app.graphics_fallback = fallback_note.clone();
if let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))).filter(|_| !safe_mode) {
app.load_prefs(&p);
}
let stored_ui = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k)));
restore_preferences(&mut app, stored_ui.as_deref(), graphics().as_ref(), safe_mode);
if let Some(path) = &theme_file
&& let Err(e) = solidcraft_ui_egui::prefs::follow_theme_file_for_run(&mut app, path)
{
@@ -1114,10 +1164,6 @@ fn main() -> eframe::Result {
lost_at: None,
reason: String::new(),
});
if !safe_mode {
app.preferences.graphics_adapter =
graphics().and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str).map(str::to_string)).unwrap_or_default();
}
if let Some(rs) = &cc.wgpu_render_state {
app.services.graphics_adapters = rs
.available_adapters
@@ -1129,9 +1175,10 @@ fn main() -> eframe::Result {
.collect();
let info = rs.adapter.get_info();
if !app.preferences.graphics_adapter.is_empty() && !info.name.eq_ignore_ascii_case(app.preferences.graphics_adapter.as_str()) {
eprintln!(
solidcraft_engine::note!(
"SolidCraft graphics: preferred adapter {:?} was not used; using {:?} instead",
app.preferences.graphics_adapter, info.name
app.preferences.graphics_adapter,
info.name
);
}
let samples = scene_msaa_request(saved_samples);
@@ -1139,7 +1186,7 @@ fn main() -> eframe::Result {
eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth)
});
let scene_samples = scene_samples(info.backend, u32::from(samples), &supported);
eprintln!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
solidcraft_engine::note!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
solidcraft_engine::crash::phase("viewport pipelines (shader compile)");
let scene_samples = app.set_wgpu_split(rs, depth_bits, STENCIL_BITS, scene_samples);
app.msaa_supported = supported;
@@ -1190,8 +1237,25 @@ fn main() -> eframe::Result {
};
// No design given: the designs open at the last quit, when the preference asks (#5542).
// Not when driven over the control channel, whose clients expect a fresh design.
let files =
if files.is_empty() && !sample && control_port.is_none() { solidcraft_ui_egui::documents::designs_to_reopen(&app) } else { files };
// A design that stopped the last session while it was being reopened is skipped this
// time, so one bad file cannot brick every launch (#5064).
app.reopen_guard_dir = recovery_dir.clone();
let files = if files.is_empty() && !sample && control_port.is_none() {
let (reopen, skipped) = solidcraft_ui_egui::documents::reopen_plan(&app, recovery_dir.as_deref());
if !skipped.is_empty() {
let names: Vec<String> = skipped
.iter()
.map(|p| std::path::Path::new(p).file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_else(|| p.clone()))
.collect();
notes.push(format!(
"The last session stopped while reopening {}. It was not reopened this time; open it yourself if you need it.",
names.join(", ")
));
}
reopen
} else {
files
};
for f in &files {
app.open_path_async(f);
}
@@ -1207,9 +1271,9 @@ fn main() -> eframe::Result {
app.home.show(rev);
}
// Autosave, and offer what crashed sessions left behind.
if let Some(dir) = solidcraft_engine::recovery::default_dir() {
if let Some(dir) = recovery_dir.as_ref() {
let every = if app.autosave_minutes > 0.0 { app.autosave_minutes * 60.0 } else { f64::MAX / 4.0 };
solidcraft_ui_egui::recovery_ui::start(&mut app, &dir, std::time::Duration::from_secs_f64(every.min(1e9)));
solidcraft_ui_egui::recovery_ui::start(&mut app, dir, std::time::Duration::from_secs_f64(every.min(1e9)));
// Safe mode keeps autosaving but offers nothing: the entries wait for a normal start.
if safe_mode {
app.recoverable.clear();
@@ -1283,7 +1347,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
let retry = std::env::var(startup::RETRY_BACKEND_ENV).ok();
let preference = if cpu { preference } else { retry.as_deref().or(preference) };
if let Some(backends) = startup::choose_backends(cfg!(target_os = "windows"), env_set, cpu, preference, startup::has_hardware_adapter) {
eprintln!("SolidCraft graphics: starting {backends:?}");
solidcraft_engine::note!("SolidCraft graphics: starting {backends:?}");
setup.instance_descriptor.backends = backends;
}
let intel_mac = cfg!(all(target_os = "macos", target_arch = "x86_64"));
@@ -1309,7 +1373,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
/// event loop or a failed device; each retry advances through the APIs, then software once.
fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backends, settings: Option<&std::path::Path>) -> eframe::Result {
log::error!("the window could not open: {error}");
eprintln!("SolidCraft: the window could not open: {error}");
solidcraft_engine::note!("SolidCraft: the window could not open: {error}");
// This failure was handled, so neither the marker nor the journal should call it a crash.
if let Some(dir) = settings {
startup::started(dir);
@@ -1322,7 +1386,7 @@ fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backen
let previous = std::env::var(startup::FALLBACK_NOTE_ENV).unwrap_or_default();
let note = startup::fallback_note(&previous, failed, next, &error.to_string());
log::warn!("{note}");
eprintln!("SolidCraft: {note}");
solidcraft_engine::note!("SolidCraft: {note}");
let mut command = std::process::Command::new(exe);
command.args(std::env::args_os().skip(1)).env(startup::FALLBACK_NOTE_ENV, note);
if next == "cpu" {
@@ -1397,6 +1461,47 @@ fn prefer_x11(display: bool, wayland_display: bool, desktop: Option<&str>, wayla
display && !(niri && wayland_display)
}
#[cfg(all(unix, not(target_os = "macos")))]
const CHOOSER_NO_SESSION_BUS: &str = "file.open.portal_fallback.no_session_bus";
#[cfg(all(unix, not(target_os = "macos")))]
const CHOOSER_NO_PORTAL_SERVICE: &str = "file.open.portal_fallback.no_portal_service";
#[cfg(all(unix, not(target_os = "macos")))]
const CHOOSER_XWAYLAND: &str = "file.open.portal_fallback.xwayland";
/// Whether the chooser that opens is NOT the desktop's own, and why.
///
/// `rfd` opens the file chooser through the XDG desktop portal; when the portal is not there it
/// falls back to zenity, which opens a window that does not belong to SolidCraft and that the
/// application never mentions. The application has to know that: this decision is what the warning
/// is built on.
///
/// * `dbus_session`: the session bus is there (`DBUS_SESSION_BUS_ADDRESS`).
/// * `portal_service`: the file chooser portal answers on it.
/// * `wayland_display`: a Wayland session runs (`WAYLAND_DISPLAY`).
/// * `x11_display`: the window is an X11 one (`DISPLAY`, or XWayland).
///
/// `None` means the desktop's own chooser is the one that opens and there is nothing to say;
/// `Some` carries the reason to report.
#[cfg(all(unix, not(target_os = "macos")))]
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn desktop_chooser_fallback(dbus_session: bool, portal_service: bool, wayland_display: bool, x11_display: bool) -> Option<&'static str> {
// Without a session bus there is no bus to reach the portal on, so the chooser is not the
// desktop's own whatever else is there.
if !dbus_session {
return Some(CHOOSER_NO_SESSION_BUS);
}
// The bus is there but the portal does not answer on it: `rfd` falls back to zenity.
if !portal_service {
return Some(CHOOSER_NO_PORTAL_SERVICE);
}
// Under XWayland the window is an X11 one while the session is a Wayland one, and the portal
// refuses to parent its chooser to an X11 window: `rfd` falls back to zenity.
if wayland_display && x11_display {
return Some(CHOOSER_XWAYLAND);
}
None
}
#[cfg(all(test, unix, not(target_os = "macos")))]
mod backend_tests {
use super::prefer_x11;
@@ -1432,6 +1537,30 @@ mod backend_tests {
assert!(prefer_x11(true, true, Some("niri"), over(Some("x11"))));
}
/// The file chooser must say when it is NOT the desktop's own: `rfd` falls back to zenity
/// when the XDG portal is not there, and the user gets a foreign window with no explanation.
#[test]
fn the_file_picker_says_when_it_is_not_the_desktops_own_chooser() {
use super::desktop_chooser_fallback;
// The normal case: the portal is there, the desktop's own chooser opens, nothing to say.
assert_eq!(desktop_chooser_fallback(true, true, true, false), None);
// No session bus: the portal can not be reached at all, so `rfd` falls back to zenity.
assert!(
desktop_chooser_fallback(false, true, true, false).is_some(),
"without a D-Bus session bus the file chooser is not the desktop's own: the user has to be told"
);
// The bus is there but the portal service is not: same fallback, same warning.
assert!(
desktop_chooser_fallback(true, false, true, false).is_some(),
"without the desktop portal service the file chooser is not the desktop's own: the user has to be told"
);
// XWayland: the parent window is an X11 one and the portal refuses it, so `rfd` falls back.
assert!(
desktop_chooser_fallback(true, true, true, true).is_some(),
"under XWayland the portal rejects the parent window: the user has to be told"
);
}
#[test]
fn the_desktops_title_bar_only_when_asked_for() {
use super::titlebar_choice;
@@ -1663,7 +1792,7 @@ mod viewport_tests {
viewport.fullscreen = Some(fullscreen);
viewport.native_pixels_per_point = Some(retina);
super::fullscreen_content_insets(&mut raw, 32.0, zoom);
let _ = ctx.run_ui(raw, |ui| super::content_ui(app, ui));
ctx.run_ui(raw, |ui| super::content_ui(app, ui)).textures_delta.clear();
};
// Enter, leave, and enter again: no stale inset when returning to a normal window.
for fullscreen in [false, true, false, true] {
@@ -1709,6 +1838,88 @@ mod viewport_tests {
#[cfg(test)]
mod msaa_tests {
/// Safe mode uses default UI settings but edits the real graphics.json for the next start.
#[test]
fn safe_mode_preserves_graphics_choices_when_changing_antialiasing() {
use serde_json::{Value, json};
use solidcraft_ui_egui::scenario::Harness;
for choice in ["wayland", "x11"] {
let mut h = Harness::new();
let path = h.dir.join("graphics.json");
let saved = json!({"window_system": choice, "adapter": "Selected GPU", "backend": "vulkan", "msaa": 8});
std::fs::write(&path, saved.to_string()).unwrap();
h.app.services.graphics_path = Some(path.to_string_lossy().into_owned());
let defaults = h.app.preferences.clone();
let stored_ui = json!({"preferences": {"window_system": "auto", "graphics_adapter": "Stale GPU", "msaa": 2,
"display_quality": "fine"}})
.to_string();
super::restore_preferences(&mut h.app, Some(&stored_ui), Some(&saved), true);
assert_eq!(h.app.preferences.window_system, choice);
assert_eq!(h.app.preferences.graphics_adapter, "Selected GPU");
assert_eq!(h.app.preferences.msaa, defaults.msaa, "safe mode ignores saved anti-aliasing");
assert_eq!(h.app.preferences.display_quality, defaults.display_quality, "safe mode ignores saved UI settings");
assert_eq!(serde_json::from_str::<Value>(&std::fs::read_to_string(&path).unwrap()).unwrap(), saved, "startup does not write settings");
assert_eq!(h.call("ui.prefs", json!({"open": "Graphics"}))["ok"], true);
assert_eq!(h.call("ui.prefs", json!({"msaa": 1}))["ok"], true);
assert_eq!(h.call("ui.prefs", json!({"button": "ok"}))["ok"], true);
let after: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(after, json!({"window_system": choice, "adapter": "Selected GPU", "backend": "vulkan", "msaa": 1}));
let mut normal = Harness::new();
super::restore_preferences(&mut normal.app, Some(&stored_ui), Some(&after), false);
assert_eq!(normal.app.preferences.window_system, choice);
assert_eq!(normal.app.preferences.graphics_adapter, "Selected GPU");
assert_eq!(normal.app.preferences.display_quality, "fine", "normal startup still restores UI settings");
}
}
#[test]
fn missing_or_invalid_startup_window_system_uses_automatic() {
use serde_json::json;
for saved in [None, Some(json!(null)), Some(json!({})), Some(json!({"window_system": 7})), Some(json!({"window_system": "unknown"}))] {
let mut app = super::SolveApp::new(super::Session::default(), super::Services::default());
app.preferences.window_system = "wayland".into();
super::restore_graphics_preferences(&mut app, saved.as_ref());
assert_eq!(app.preferences.window_system, "auto", "{saved:?}");
}
}
/// A manual graphics.json choice wins over stale app.ron preferences and survives an
/// unrelated graphics edit (#10116), including on a fresh launch with no saved UI state.
#[test]
fn startup_window_system_survives_changing_antialiasing() {
use serde_json::{Value, json};
use solidcraft_ui_egui::scenario::Harness;
for choice in ["wayland", "x11", "auto", " wayland ", " x11 "] {
for stale in [None, Some("auto"), Some("wayland"), Some("x11")] {
let mut h = Harness::new();
if let Some(stale) = stale {
h.app.load_prefs(&json!({"preferences": {"window_system": stale}}).to_string());
}
let path = h.dir.join("graphics.json");
std::fs::write(&path, json!({"window_system": choice, "backend": "vulkan", "adapter": "Selected GPU", "msaa": 4}).to_string())
.unwrap();
h.app.services.graphics_path = Some(path.to_string_lossy().into_owned());
let saved: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
super::restore_graphics_preferences(&mut h.app, Some(&saved));
let result = h.call("ui.prefs", json!({"msaa": 2}));
assert_eq!(result["ok"], true, "{result}");
let after: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(after["window_system"], choice.trim(), "saved choice {choice:?}, stale UI choice {stale:?}");
assert_eq!(result["result"]["window_system"], choice.trim());
assert_eq!(after["msaa"], 2);
assert_eq!(after["backend"], "vulkan");
assert_eq!(after["adapter"], "Selected GPU");
}
}
}
#[test]
fn first_launch_adopts_the_previous_release_settings() {
let d = std::env::temp_dir().join(format!("solidcraft-adopt-settings-{}", std::process::id()));
+1 -1
View File
@@ -434,7 +434,7 @@ mod tests {
let mut app = SolveApp::new(solidcraft_engine::Session::default(), solidcraft_ui_egui::Services::default());
app.home.open = false;
let ctx = Context::default();
let _ = ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |_| {});
ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |_| {}).textures_delta.clear();
let mut bridge = NavBridge { shared: Arc::new(Mutex::new(Shared::default())) };
let queue = |bridge: &NavBridge| {
let mut shared = bridge.shared.lock().unwrap();
+1
View File
@@ -1914,6 +1914,7 @@ mod tests {
fn wheel() -> egui::Event {
egui::Event::MouseWheel {
source: egui::MouseWheelSource::Unknown,
unit: egui::MouseWheelUnit::Line,
delta: egui::vec2(0.0, -1.0),
modifiers: Default::default(),
+23 -17
View File
@@ -40,10 +40,10 @@ fn connect_to(path: &Path) -> Option<UnixStream> {
/// The axes of one record, or `None` for a record that is not motion.
///
/// The daemon's frame is right-handed with x to the right, y away from the user and z up; ours is
/// the device's raw one, x right, y towards the user and z down, so y and z (and the rotations
/// about them) change sign. With nothing set in `spnavcfg` the two give the same motion; what
/// the user swaps or inverts there then carries over.
/// The daemon reports x to the right, y up and z away from the user; our raw HID frame is x
/// right, y towards the user and z down. Swap and negate y/z and ry/rz to match that frame.
/// With nothing set in `spnavcfg` the two readers give the same motion; what the user swaps
/// or inverts there then carries over.
fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
let mut data = [0_i32; 8];
for (slot, bytes) in data.iter_mut().zip(record.as_chunks::<4>().0) {
@@ -51,8 +51,8 @@ fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
}
let [kind, x, y, z, rx, ry, rz, _period] = data;
(kind == 0).then(|| Axes {
translation: [unit(x), unit(-i64::from(y)), unit(-i64::from(z))],
rotation: [unit(rx), unit(-i64::from(ry)), unit(-i64::from(rz))],
translation: [unit(x), unit(-i64::from(z)), unit(-i64::from(y))],
rotation: [unit(rx), unit(-i64::from(rz)), unit(-i64::from(ry))],
})
}
@@ -149,22 +149,28 @@ mod tests {
#[test]
fn motion_records_become_axes_in_the_devices_raw_frame() {
// Full deflection of the daemon's x, y and z is the device's full scale, and y and z flip.
assert_eq!(decode(&record([0, 500, 500, 500, 0, 0, 0, 8])), Some(Axes { translation: [350, -350, -350], rotation: [0; 3] }));
// Rotations follow translations: right-handed in both frames, so ry and rz flip as y and z do.
assert_eq!(decode(&record([0, 0, 0, 0, 250, 250, 250, 8])), Some(Axes { translation: [0; 3], rotation: [175, -175, -175] }));
// Each daemon axis reaches only its matching raw HID axis, in both directions.
for (daemon_axis, raw_axis, sign) in [(0, 0, 1), (1, 2, -1), (2, 1, -1), (3, 3, 1), (4, 5, -1), (5, 4, -1)] {
for value in [-500, 500] {
let mut data = [0; 8];
data[daemon_axis + 1] = value;
let mut expected = [0; 6];
expected[raw_axis] = sign * (value * 350 / 500) as i16;
assert_eq!(decode(&record(data)), Some(Axes::from(expected)), "daemon axis {daemon_axis}, value {value}");
}
}
// Rest is a motion record of zeros.
assert_eq!(decode(&record([0, 0, 0, 0, 0, 0, 0, 1200])), Some(Axes::default()));
}
#[test]
fn what_spnavcfg_swaps_reaches_the_view() {
// The daemon sends a push away as y. With Y and Z swapped in spnavcfg it sends the same push
// as z, and the view gets a lift (raw z) instead of the zoom (raw y).
let before = decode(&record([0, 0, 500, 0, 0, 0, 0, 8])).map(|axes| axes.translation);
let after = decode(&record([0, 0, 0, 500, 0, 0, 0, 8])).map(|axes| axes.translation);
assert_eq!(before, Some([0, -350, 0]));
assert_eq!(after, Some([0, 0, -350]));
// A push away is daemon z (raw y); swapping Y/Z makes it a lift (raw z).
// The same swap changes a sideways tilt (daemon rz, raw ry) into a twist (raw rz).
let before = decode(&record([0, 0, 0, 500, 0, 0, 250, 8]));
let after = decode(&record([0, 0, 500, 0, 0, 250, 0, 8]));
assert_eq!(before, Some(Axes { translation: [0, -350, 0], rotation: [0, -175, 0] }));
assert_eq!(after, Some(Axes { translation: [0, 0, -350], rotation: [0, 0, -175] }));
}
#[test]
@@ -179,7 +185,7 @@ mod tests {
// Nothing overflows on the extremes of `i32`.
let extreme = decode(&record([0, i32::MIN, i32::MIN, i32::MAX, i32::MAX, i32::MIN, i32::MIN, -1]));
assert_eq!(extreme.map(|axes| axes.translation[0]), Some(i16::MIN));
assert_eq!(extreme.map(|axes| axes.translation[1]), Some(i16::MAX));
assert_eq!(extreme.map(|axes| axes.translation), Some([i16::MIN, i16::MIN, i16::MAX]));
assert_eq!(extreme.map(|axes| axes.rotation[0]), Some(i16::MAX));
}
+11 -6
View File
@@ -311,13 +311,18 @@ pub const SYSTEM_VERSION_COMPAT_ENV: &str = "SYSTEM_VERSION_COMPAT";
#[cfg(target_os = "macos")]
pub const OCLP_NVRAM_KEY: &str = "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:OCLP-Version";
/// Where macOS keeps its `nvram` tool.
#[cfg(target_os = "macos")]
const NVRAM_TOOL: &str = "/usr/sbin/nvram";
/// `true` if the OCLP NVRAM key is present on this system.
///
/// Runs `nvram <key>` and treats exit 0 as present. Any error (missing binary,
/// permission denied, key absent) returns `false` — this is a best-effort probe.
/// Runs `/usr/sbin/nvram <key>` and treats exit 0 as present. The full path keeps a `PATH` that
/// lacks `/usr/sbin`, or lists a look-alike first, from changing the answer. Any error (missing
/// binary, permission denied, key absent) returns `false`: this is a best-effort probe.
#[cfg(target_os = "macos")]
pub fn oclp_nvram_present() -> bool {
std::process::Command::new("nvram").arg(OCLP_NVRAM_KEY).output().map(|o| o.status.success()).unwrap_or(false)
std::process::Command::new(NVRAM_TOOL).arg(OCLP_NVRAM_KEY).output().map(|o| o.status.success()).unwrap_or(false)
}
/// Whether to re-launch with `SYSTEM_VERSION_COMPAT=1`: it is not set yet and OCLP shows through
@@ -334,8 +339,8 @@ fn compat_wanted(compat_set: bool, nvram: impl FnOnce() -> bool) -> bool {
///
/// `SYSTEM_VERSION_COMPAT=1` must be present **before** the process starts: setting it
/// with `std::env::set_var` inside `main` is too late because the macOS frameworks have
/// already queried the OS version by then. Re-launching is the only safe approach that
/// works without `unsafe` (a true `execve` replacement would require it).
/// already queried the OS version by then, so the program starts itself again with the variable
/// set and waits for that child.
///
/// Returns `Ok(false)` immediately when:
/// - `SYSTEM_VERSION_COMPAT` is already set (re-launched process, no loop), or
@@ -352,7 +357,7 @@ pub fn relaunch_with_version_compat() -> Result<bool, String> {
return Ok(false);
}
log::info!("startup: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
eprintln!("SolidCraft: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
solidcraft_engine::note!("SolidCraft: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
let exe = std::env::current_exe().map_err(|e| format!("could not find own executable: {e}"))?;
let status = std::process::Command::new(&exe)
.args(std::env::args_os().skip(1))
+1
View File
@@ -17,6 +17,7 @@ assets/
ui/design/tools/ # authoring bases, checks and included review app
ui/source/ # Rust viewport glyphs, markers and navigation
branding/web/source/og.png # shared social preview card
branding/logo/ # print/web logo files; guide in branding/README.md
styles/ui/palettes.rs # light/dark defaults
styles/web/loading.css # web loading/canvas styles
appearances/library.rs # built-in visual presets
+73
View File
@@ -0,0 +1,73 @@
# SolidCraft brand kit
Logo files for stickers, shirts, the web and social posts. Everything in `logo/` is drawn
from the app icon (`assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg`),
which is also the last frame of the loading animation (`assets/styles/web/loading-logo.svg`).
The SVGs are plain paths: no scripts, fonts, animation or external links, so any printer can use them.
## Files
| File | Use it for |
|---|---|
| `logo/solidcraft-mark.svg` | The mark in full colour. First choice for stickers, print and the web. |
| `logo/solidcraft-mark-mono-black.svg` | One-colour print on light material: screen-printed shirts, stamps, engraving, vinyl. |
| `logo/solidcraft-mark-mono-white.svg` | One-colour print on dark material, such as a white print on a dark shirt. |
| `logo/solidcraft-mark-512.png`, `-1024.png`, `-2048.png` | Transparent square PNGs for places that don't take SVG: avatars, social posts, slides, print shops that ask for a bitmap (send the 2048). |
| `logo/solidcraft-wordmark.svg` | The name on its own, outlined to paths. |
| `logo/solidcraft-lockup-horizontal.svg` | Mark and name side by side: headers, banners, shirt fronts, wide stickers. |
| `web/source/og.png` | The existing link preview card for the docs site. |
The gaps between the faces are part of the logo. In the colour and one-colour files they are
transparent, so the material shows through. For a die-cut sticker, cut around the outer shape
and let the backing colour (white is best) fill the gaps.
## Colours
The mark's five faces each use a two-stop gradient from the icon source:
| Face | From | To |
|---|---|---|
| Top | `#51adff` | `#2b83f1` |
| Left | `#258df5` | `#1461da` |
| Right | `#1b66cf` | `#124596` |
| Lower step (top) | `#459fff` | `#2379e7` |
| Upper step (inner side) | `#1d63c6` | `#15499e` |
Other colours, all already used in the project:
| Colour | Hex | Where it comes from |
|---|---|---|
| Wordmark blue | `#15499e` | loading logo `--word` |
| Interface accent | `#2678da` | light theme `accent` token (`assets/styles/ui/palettes.rs`) |
| Light background | `#e8ebf0` | web loading screen (`assets/styles/web/loading.css`) |
| Dark background | `#1f232b` | dark theme `file_background` token |
| Black / white | `#000000` / `#ffffff` | one-colour versions only |
If a printer can't do gradients, use the one-colour files, or fill every face with `#2678da`.
## Clear space and size
- Keep empty space around the mark of at least one fifth of its height on every side. Treat
the lockup the same way, measured from the mark.
- Smallest mark: 16 px tall on screen, 12 mm tall in print. Below that the gaps fill in.
- Smallest lockup: 120 px wide on screen, 40 mm wide in print.
## Don'ts
- Don't recolour it outside the colours above.
- Don't stretch, squash, rotate, skew or crop it. Scale it evenly.
- Don't add shadows, outlines, glows, bevels or other effects.
- Don't fill or close the gaps between the faces, or move the faces apart.
- Don't rearrange the lockup, retype the name in another font, or put the mark on a busy
background where it loses contrast.
## Name and logo
The SolidCraft code and these files are licensed MIT OR Apache-2.0 (see the [README](../../README.md#licence)),
so you may copy and change them. The name SolidCraft and this logo identify the SolidCraft
project. Use them to refer to the project, for example in an article, a talk, or a sticker for a
meetup. If you publish a fork or a modified build, give it its own name and logo so people
don't mistake it for SolidCraft.
The wordmark is set in IBM Plex Sans Bold (SIL Open Font License 1.1, the interface font in
`assets/fonts/ibm-plex-sans/`) and outlined, so you don't need the font to print it.
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 2748.71 904.436" width="2749" height="904" role="img"><title>SolidCraft</title><defs><linearGradient id="sc1" x1="619.14" y1="179.734" x2="694.687" y2="396.761" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d63c6"/><stop offset="1" stop-color="#15499e"/></linearGradient><linearGradient id="sc2" x1="619.14" y1="413.244" x2="790.839" y2="605.547" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#459fff"/><stop offset="1" stop-color="#2379e7"/></linearGradient><linearGradient id="sc3" x1="632.876" y1="399.508" x2="783.971" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b66cf"/><stop offset="1" stop-color="#124596"/></linearGradient><linearGradient id="sc4" x1="207.063" y1="303.357" x2="426.837" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#258df5"/><stop offset="1" stop-color="#1461da"/></linearGradient><linearGradient id="sc5" x1="385.63" y1="53.364" x2="522.989" y2="510.769" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#51adff"/><stop offset="1" stop-color="#2b83f1"/></linearGradient></defs><g><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z" fill="url(#sc1)"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z" fill="url(#sc2)"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z" fill="url(#sc3)"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z" fill="url(#sc4)"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z" fill="url(#sc5)"/></g><path fill="#15499e" transform="translate(1092.78 632.20) scale(0.36281)" d="M303 12Q212 12 147.5 -20Q83 -52 38 -101L138 -202Q173 -162 217.5 -142Q262 -122 313 -122Q369 -122 396 -145Q423 -168 423 -206Q423 -225 416.5 -240.5Q410 -256 392.5 -266.5Q375 -277 342 -281L273 -290Q200 -300 152.5 -326.5Q105 -353 82 -396.5Q59 -440 59 -498Q59 -561 90.5 -608.5Q122 -656 180.5 -683Q239 -710 321 -710Q400 -710 460 -684.5Q520 -659 563 -612L462 -510Q437 -539 401.5 -557.5Q366 -576 312 -576Q261 -576 236 -558Q211 -540 211 -508Q211 -484 219.5 -469Q228 -454 246.5 -446Q265 -438 294 -433L363 -422Q435 -411 482 -385Q529 -359 552 -317Q575 -275 575 -215Q575 -148 543.5 -97Q512 -46 451 -17Q390 12 303 12ZM926 12Q849 12 794 -21.5Q739 -55 709.5 -117Q680 -179 680 -263Q680 -348 709.5 -409Q739 -470 794 -503.5Q849 -537 926 -537Q1003 -537 1058 -503.5Q1113 -470 1142.5 -409Q1172 -348 1172 -263Q1172 -179 1142.5 -117Q1113 -55 1058 -21.5Q1003 12 926 12ZM926 -105Q970 -105 994 -132.5Q1018 -160 1018 -209V-316Q1018 -366 994 -393Q970 -420 926 -420Q883 -420 858.5 -393Q834 -366 834 -316V-209Q834 -160 858.5 -132.5Q883 -105 926 -105ZM1511 0H1444Q1368 0 1332.5 -37Q1297 -74 1297 -144V-740H1445V-115H1511ZM1694 -581Q1649 -581 1628.5 -602Q1608 -623 1608 -654V-676Q1608 -708 1628.5 -728.5Q1649 -749 1694 -749Q1739 -749 1759.5 -728.5Q1780 -708 1780 -676V-654Q1780 -623 1759.5 -602Q1739 -581 1694 -581ZM1620 0V-525H1768V0ZM2248 0V-88H2243Q2232 -45 2192.5 -16.5Q2153 12 2102 12Q2036 12 1991 -19.5Q1946 -51 1923 -112.5Q1900 -174 1900 -263Q1900 -352 1923 -413Q1946 -474 1991 -505.5Q2036 -537 2102 -537Q2153 -537 2192.5 -509Q2232 -481 2243 -437H2248V-740H2396V0ZM2151 -108Q2179 -108 2201 -117Q2223 -126 2235.5 -143.5Q2248 -161 2248 -185V-340Q2248 -364 2235.5 -381.5Q2223 -399 2201 -408Q2179 -417 2151 -417Q2109 -417 2081.5 -387.5Q2054 -358 2054 -308V-217Q2054 -168 2081.5 -138Q2109 -108 2151 -108ZM2844 12Q2750 12 2680.5 -28Q2611 -68 2573 -147Q2535 -226 2535 -344Q2535 -462 2573 -543.5Q2611 -625 2680.5 -667.5Q2750 -710 2844 -710Q2940 -710 3003.5 -670Q3067 -630 3105 -546L2974 -478Q2961 -522 2930.5 -548.5Q2900 -575 2844 -575Q2777 -575 2736.5 -531.5Q2696 -488 2696 -405V-293Q2696 -211 2736.5 -167Q2777 -123 2844 -123Q2900 -123 2933.5 -153.5Q2967 -184 2983 -227L3107 -155Q3068 -76 3004 -32Q2940 12 2844 12ZM3373 0H3225V-525H3373V-411H3378Q3384 -440 3400 -466Q3416 -492 3443.5 -508.5Q3471 -525 3512 -525H3538V-387H3501Q3458 -387 3429.5 -379.5Q3401 -372 3387 -355Q3373 -338 3373 -307ZM4105 0H4023Q3996 0 3972.5 -13.5Q3949 -27 3935 -53.5Q3921 -80 3921 -117V-130L3953 -92H3917Q3905 -41 3864 -14.5Q3823 12 3763 12Q3684 12 3642 -30.5Q3600 -73 3600 -141Q3600 -197 3627.5 -233Q3655 -269 3705.5 -287Q3756 -305 3825 -305H3908V-338Q3908 -376 3888 -398.5Q3868 -421 3821 -421Q3777 -421 3751 -402Q3725 -383 3708 -359L3620 -437Q3652 -484 3701 -510.5Q3750 -537 3832 -537Q3943 -537 3999.5 -487.5Q4056 -438 4056 -345V-115H4105ZM3908 -221H3836Q3793 -221 3771 -206Q3749 -191 3749 -162V-147Q3749 -119 3767 -105Q3785 -91 3818 -91Q3843 -91 3863 -98Q3883 -105 3895.5 -120Q3908 -135 3908 -159ZM4250 0V-410H4176V-525H4250V-587Q4250 -663 4290.5 -701.5Q4331 -740 4407 -740H4503V-625H4398V-525H4503V-410H4398V0ZM4877 0H4789Q4713 0 4673 -39Q4633 -78 4633 -153V-410H4559V-525H4596Q4626 -525 4637 -539.5Q4648 -554 4648 -581V-667H4781V-525H4885V-410H4781V-115H4877Z"/></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><g fill="#000000"><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z"/></g></svg>

After

Width:  |  Height:  |  Size: 612 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><g fill="#ffffff"><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z"/></g></svg>

After

Width:  |  Height:  |  Size: 612 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><defs><linearGradient id="sc1" x1="619.14" y1="179.734" x2="694.687" y2="396.761" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d63c6"/><stop offset="1" stop-color="#15499e"/></linearGradient><linearGradient id="sc2" x1="619.14" y1="413.244" x2="790.839" y2="605.547" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#459fff"/><stop offset="1" stop-color="#2379e7"/></linearGradient><linearGradient id="sc3" x1="632.876" y1="399.508" x2="783.971" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b66cf"/><stop offset="1" stop-color="#124596"/></linearGradient><linearGradient id="sc4" x1="207.063" y1="303.357" x2="426.837" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#258df5"/><stop offset="1" stop-color="#1461da"/></linearGradient><linearGradient id="sc5" x1="385.63" y1="53.364" x2="522.989" y2="510.769" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#51adff"/><stop offset="1" stop-color="#2b83f1"/></linearGradient></defs><g><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z" fill="url(#sc1)"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z" fill="url(#sc2)"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z" fill="url(#sc3)"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z" fill="url(#sc4)"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z" fill="url(#sc5)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="38.0 -749.0 4847.0 761.0" width="4847" height="761" role="img"><title>SolidCraft</title><path fill="#15499e" d="M303 12Q212 12 147.5 -20Q83 -52 38 -101L138 -202Q173 -162 217.5 -142Q262 -122 313 -122Q369 -122 396 -145Q423 -168 423 -206Q423 -225 416.5 -240.5Q410 -256 392.5 -266.5Q375 -277 342 -281L273 -290Q200 -300 152.5 -326.5Q105 -353 82 -396.5Q59 -440 59 -498Q59 -561 90.5 -608.5Q122 -656 180.5 -683Q239 -710 321 -710Q400 -710 460 -684.5Q520 -659 563 -612L462 -510Q437 -539 401.5 -557.5Q366 -576 312 -576Q261 -576 236 -558Q211 -540 211 -508Q211 -484 219.5 -469Q228 -454 246.5 -446Q265 -438 294 -433L363 -422Q435 -411 482 -385Q529 -359 552 -317Q575 -275 575 -215Q575 -148 543.5 -97Q512 -46 451 -17Q390 12 303 12ZM926 12Q849 12 794 -21.5Q739 -55 709.5 -117Q680 -179 680 -263Q680 -348 709.5 -409Q739 -470 794 -503.5Q849 -537 926 -537Q1003 -537 1058 -503.5Q1113 -470 1142.5 -409Q1172 -348 1172 -263Q1172 -179 1142.5 -117Q1113 -55 1058 -21.5Q1003 12 926 12ZM926 -105Q970 -105 994 -132.5Q1018 -160 1018 -209V-316Q1018 -366 994 -393Q970 -420 926 -420Q883 -420 858.5 -393Q834 -366 834 -316V-209Q834 -160 858.5 -132.5Q883 -105 926 -105ZM1511 0H1444Q1368 0 1332.5 -37Q1297 -74 1297 -144V-740H1445V-115H1511ZM1694 -581Q1649 -581 1628.5 -602Q1608 -623 1608 -654V-676Q1608 -708 1628.5 -728.5Q1649 -749 1694 -749Q1739 -749 1759.5 -728.5Q1780 -708 1780 -676V-654Q1780 -623 1759.5 -602Q1739 -581 1694 -581ZM1620 0V-525H1768V0ZM2248 0V-88H2243Q2232 -45 2192.5 -16.5Q2153 12 2102 12Q2036 12 1991 -19.5Q1946 -51 1923 -112.5Q1900 -174 1900 -263Q1900 -352 1923 -413Q1946 -474 1991 -505.5Q2036 -537 2102 -537Q2153 -537 2192.5 -509Q2232 -481 2243 -437H2248V-740H2396V0ZM2151 -108Q2179 -108 2201 -117Q2223 -126 2235.5 -143.5Q2248 -161 2248 -185V-340Q2248 -364 2235.5 -381.5Q2223 -399 2201 -408Q2179 -417 2151 -417Q2109 -417 2081.5 -387.5Q2054 -358 2054 -308V-217Q2054 -168 2081.5 -138Q2109 -108 2151 -108ZM2844 12Q2750 12 2680.5 -28Q2611 -68 2573 -147Q2535 -226 2535 -344Q2535 -462 2573 -543.5Q2611 -625 2680.5 -667.5Q2750 -710 2844 -710Q2940 -710 3003.5 -670Q3067 -630 3105 -546L2974 -478Q2961 -522 2930.5 -548.5Q2900 -575 2844 -575Q2777 -575 2736.5 -531.5Q2696 -488 2696 -405V-293Q2696 -211 2736.5 -167Q2777 -123 2844 -123Q2900 -123 2933.5 -153.5Q2967 -184 2983 -227L3107 -155Q3068 -76 3004 -32Q2940 12 2844 12ZM3373 0H3225V-525H3373V-411H3378Q3384 -440 3400 -466Q3416 -492 3443.5 -508.5Q3471 -525 3512 -525H3538V-387H3501Q3458 -387 3429.5 -379.5Q3401 -372 3387 -355Q3373 -338 3373 -307ZM4105 0H4023Q3996 0 3972.5 -13.5Q3949 -27 3935 -53.5Q3921 -80 3921 -117V-130L3953 -92H3917Q3905 -41 3864 -14.5Q3823 12 3763 12Q3684 12 3642 -30.5Q3600 -73 3600 -141Q3600 -197 3627.5 -233Q3655 -269 3705.5 -287Q3756 -305 3825 -305H3908V-338Q3908 -376 3888 -398.5Q3868 -421 3821 -421Q3777 -421 3751 -402Q3725 -383 3708 -359L3620 -437Q3652 -484 3701 -510.5Q3750 -537 3832 -537Q3943 -537 3999.5 -487.5Q4056 -438 4056 -345V-115H4105ZM3908 -221H3836Q3793 -221 3771 -206Q3749 -191 3749 -162V-147Q3749 -119 3767 -105Q3785 -91 3818 -91Q3843 -91 3863 -98Q3883 -105 3895.5 -120Q3908 -135 3908 -159ZM4250 0V-410H4176V-525H4250V-587Q4250 -663 4290.5 -701.5Q4331 -740 4407 -740H4503V-625H4398V-525H4503V-410H4398V0ZM4877 0H4789Q4713 0 4673 -39Q4633 -78 4633 -153V-410H4559V-525H4596Q4626 -525 4637 -539.5Q4648 -554 4648 -581V-667H4781V-525H4885V-410H4781V-115H4877Z"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+1 -1
View File
@@ -60,7 +60,6 @@ circle_ttt false
close false
coil false
colored_components false
colored_components_off false
combine false
compare false
component false
@@ -161,6 +160,7 @@ min_radius false
mirror false
motion_link false
move false
nav_settings false
new false
normal false
offset false
@@ -1,21 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<title>Disable component colors</title>
<path d="M9.235 17.706L15.059 20.882L15.059 48.941L9.235 44.926Z" fill="#328bd3"/>
<path d="M35.176 9.235L15.059 20.882L15.059 48.941L36.765 35.139Z" fill="#236ead"/>
<path d="M29.046 5L35.176 8.176L15.196 19.718L9.066 16.541Z" fill="#b9ddf8"/>
<path d="M29.046 5.529L35.176 8.706L35.176 36.235L15.059 49.471L9.235 45.765L9.066 17.071Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.066 17.6L15.059 20.882L34.647 9.235" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.059 20.882L15.059 47.067" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M32.222 18.595L44.949 25.944L44.949 51.122L32.222 43.774Z" fill="#328bd3"/>
<path d="M54.934 20.173L44.949 25.944L44.949 51.122L54.934 45.352Z" fill="#236ead"/>
<path d="M42.059 12.412L54.934 20.173L44.949 25.944L32 17.706Z" fill="#b9ddf8"/>
<path d="M41.159 11.766L54.934 20.173L54.934 45.352L44.949 51.122L31.164 42.715L31.164 17.536Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M32.222 18.595L44.949 25.944L54.934 20.173" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M44.949 25.944L44.949 51.122" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.299 39.062L31.015 46.411L31.015 59L18.299 51.652Z" fill="#328bd3"/>
<path d="M41.921 40.121L31.015 46.411L31.015 59L41.921 52.711Z" fill="#236ead"/>
<path d="M29.194 32.773L41.921 40.121L31.015 46.411L18.299 39.062Z" fill="#b9ddf8"/>
<path d="M29.194 32.773L41.921 40.121L41.921 52.711L31.015 59L18.299 51.652L18.299 39.062Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.299 39.062L31.015 46.411L41.921 40.121" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.015 46.411L31.015 59" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

-11
View File
@@ -79,17 +79,6 @@ pub(crate) fn snap_hint(painter: &egui::Painter, c: Pos2, kind: SnapKind, col: C
painter.circle_filled(c, 1.3, col);
}
/// A placed hole's drag handle: a ring round the hole's centre with a cross through it, filled
/// while it is hovered or dragged.
pub(crate) fn hole_handle(painter: &egui::Painter, c: Pos2, col: Color32, hot: bool) {
let ic = Tokens::get().icons;
let r = 6.0;
painter.circle_filled(c, r + 3.5, ic.marker_halo);
painter.circle(c, r, if hot { col } else { col.gamma_multiply(0.35) }, Stroke::new(1.6, col));
let ink = Stroke::new(1.3, if hot { ic.highlight } else { col });
painter.line_segment([c - vec2(r + 3.0, 0.0), c + vec2(r + 3.0, 0.0)], ink);
painter.line_segment([c - vec2(0.0, r + 3.0), c + vec2(0.0, r + 3.0)], ink);
}
pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
Some(match name {
"Coincident" => "c_coincident",
+1 -2
View File
@@ -55,8 +55,7 @@ pub(crate) fn face_text(painter: &egui::Painter, face: &Facet, ink: Color32, fon
painter.ctx().add_plugin(LabelAtlas);
let text = Shape::Text(egui::epaint::TextShape::new(Pos2::ZERO, galley, ink));
let atlas = painter.ctx().fonts(|f| f.font_image_size());
let primitives =
painter.ctx().tessellate(vec![egui::epaint::ClippedShape { clip_rect: Rect::EVERYTHING, shape: text }], painter.ctx().pixels_per_point());
let primitives = painter.ctx().tessellate(vec![egui::epaint::ClippedShape::new(Rect::EVERYTHING, text)], painter.ctx().pixels_per_point());
for primitive in primitives {
if let egui::epaint::Primitive::Mesh(mut mesh) = primitive.primitive {
mesh.texture_id = LABEL_PIXELS;
+200 -44
View File
@@ -85,6 +85,7 @@
"Bottom Right": "أسفل اليمين",
"Boundary": "الحد",
"Bounding box": "الصندوق المحيط",
"Bridge": "جسر",
"CHECK": "تحقق",
"COMPONENTS": "المكوّنات",
"CONFIGURATIONS": "التكوينات",
@@ -628,6 +629,8 @@
"Suppress Selected Folders": "تعطيل المجلدات المحددة",
"Suppressed": "مُعطّل",
"Surface": "السطح",
"Surface or face": "سطح أو وجه",
"Surface or faces": "سطح أو أوجه",
"Surface, or faces to turn over": "السطح، أو الأوجه المراد قلبها",
"Surfaces": "الأسطح",
"Swap Ends": "تبديل الطرفين",
@@ -641,6 +644,7 @@
"Target": "الهدف",
"Target Body": "الجسم الهدف",
"Target Face (optional)": "الوجه الهدف (اختياري)",
"Target edges": "الحواف المستهدفة",
"Tear": "تمزيق",
"Technical details": "التفاصيل التقنية",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "الرسم المتقطع دليل مستوى محفوظ. حدد وجهًا جديدًا لإصلاح الارتباط.",
@@ -648,6 +652,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "يجب أن يكون الاتجاه محورًا أو منحنى رسم مستقيمًا يقع في مستوى الرسم",
"The mirror line cannot also be mirrored": "لا يمكن عكس خط المرآة نفسه",
"The mirror line must be a straight curve of this sketch, not an origin axis": "يجب أن يكون خط المرآة منحنى مستقيمًا من هذا الرسم، وليس أحد محاور نقطة الأصل",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "محور الأصل عمودي على مستوى الرسم، لذا ليس له خط في المستوى",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "نسخة المؤلف المحفوظة مفقودة. حدد موضع مؤلف صالحًا قبل إعادة الربط.",
"Then": "ثم",
"These features will be deleted:": "ستُحذف هذه الميزات:",
@@ -739,27 +744,32 @@
"a picked edge's body is gone": "جسم الحافة المحددة لم يعد موجودًا",
"add a key for a joint first": "أضف أولًا مفتاحًا لمفصل",
"ai.connected_toast": "أصبح SolidCraft متاحًا في {clients}. أعد تشغيلها لاستخدامه (التفضيلات > المساعد).",
"ai.http.allow": "السماح باتصال HTTP غير آمن",
"ai.http.confirm": "السماح لهذه النقطة الطرفية",
"ai.http.warning": "سيُرسَل مفتاح API والمحادثة دون تشفير. يمكن لأي شخص لديه وصول إلى الشبكة قراءتهما أو تغييرهما. استخدم هذا الخيار فقط على شبكة تثق بها.",
"ai.prefs.allow_control": "السماح لوكلاء الذكاء الاصطناعي بالاتصال",
"ai.prefs.allow_control_note": "قناة محلية (MCP). لا شيء يغادر هذا الحاسوب.\n\nيفتح قناة تحكم على هذا الحاسوب فقط (127.0.0.1، المنفذ 27878 أو أول منفذ متاح بعده). على البرنامج الذي يتصل أن يقدّم أيضًا رمزًا سريًا لا يقرؤه إلا حساب المستخدم الخاص بك، ويُنشأ من جديد عند كل تشغيل. يُطبّق في المرة القادمة التي يبدأ فيها SolidCraft. عند الإيقاف: لا شيء يستمع ما لم تشغّل SolidCraft مع --control.",
"ai.prefs.availability": "التوفر",
"ai.prefs.background_commands": "تشغيل الأوامر البطيئة في الخلفية",
"ai.prefs.connect_apps": "إضافة SolidCraft إلى تطبيقات الذكاء الاصطناعي",
"ai.prefs.connect_apps_note": "إيقافه يزيل الإدخالات مجدداً.\n\nعند بدء تشغيل SolidCraft، يُضاف كخادم MCP إلى تطبيقات الذكاء الاصطناعي الموجودة على هذا الحاسوب (OpenCode وClaude Desktop وClaude Code وCursor وCodex وGemini CLI وCopilot CLI وWindsurf وZed وVS Code)، ليتمكن الوكيل من استخدامه دون أي إعداد. لا يُضاف إلا مُدخل solidcraft أو يُحدَّث مساره؛ ويُترك المُدخل الذي غيّرته كما هو، ويُنسخ الملف احتياطيًا أولًا. عند الإيقاف تُزال المُدخلات التي أضافها. الأمر «المساعدة > ربط مساعدي الذكاء الاصطناعي…» يفعل ذلك يدويًا.",
"ai.prefs.connect_apps_note": "إيقافه يزيل الإدخالات مجددًا.\n\nعند بدء تشغيل SolidCraft، يُضاف كخادم MCP إلى تطبيقات الذكاء الاصطناعي الموجودة على هذا الحاسوب (OpenCode وClaude Desktop وClaude Code وCursor وCodex وGemini CLI وCopilot CLI وWindsurf وZed وVS Code)، ليتمكن الوكيل من استخدامه دون أي إعداد. لا يُضاف إلا مُدخل solidcraft أو يُحدَّث مساره؛ ويُترك المُدخل الذي غيّرته كما هو، ويُنسخ الملف احتياطيًا أولًا. عند الإيقاف تُزال المُدخلات التي أضافها. الأمر «المساعدة > ربط مساعدي الذكاء الاصطناعي…» يفعل ذلك يدويًا.",
"ai.prefs.connect_at_start": "توصيل آخر وكيل عند البدء",
"ai.prefs.connect_at_start_note": "يبدأ في الخلفية الوكيل الذي اتصلت به آخر مرة (أو الوكيل الافتراضي) في كل مرة يبدأ فيها SolidCraft، ليكون جاهزًا للمحادثة. لا يحدث ذلك عند بدء SolidCraft مع --control. إذا تعذّر الاتصال، يعرض المساعد «فشل الاتصال» وزر «إعادة المحاولة».",
"ai.prefs.follow_camera": "اتباع الكاميرا",
"ai.prefs.follow_camera": "تتبّع الكاميرا",
"ai.prefs.follow_camera_off": "تتبّع الكاميرا التلقائي متوقف.",
"ai.prefs.follow_camera_on": "تتجه الكاميرا نحو الرسومات التي ينشئها الوكيل.",
"ai.prefs.instructions": "تعليمات مخصصة",
"ai.prefs.instructions_note": "تُرسَل إلى الوكيل في بداية كل محادثة، بعد تعليمات SolidCraft الخاصة: طريقتك المفضلة في العمل والقواعد التي يجب الالتزام بها. يسري أي تغيير من المحادثة التالية (اتصال أو محادثة جديدة).",
"ai.prefs.intro": "وكلاء الذكاء الاصطناعي في SolidCraft.",
"ai.prefs.make_default": "تعيين كافتراضي",
"ai.prefs.make_default": "تعيين افتراضيًا",
"ai.prefs.no_agents": "لا يوجد وكلاء بعد. أضف وكيلًا أو حدّث القائمة للعثور على الوكلاء المثبّتين.",
"ai.prefs.no_saved_approvals": "لا توجد موافقات محفوظة.",
"ai.prefs.open_at_start": "فتح المحادثة عند البدء",
"ai.prefs.open_at_start_note": "يعرض لوحة المساعد (أو نافذتها المستقلة) في كل مرة يبدأ فيها SolidCraft. لا يحدث ذلك عند بدء SolidCraft مع --control.",
"ai.prefs.override_all_warning": "قد يحفظ هذا الوكيل التصميم أو يكتب فوقه أو يصدّره أو يستبدله دون أن يسأل.",
"ai.prefs.permission_all": "فعل كل شيء",
"ai.prefs.permission_all_summary": "لا يسأل أبداً",
"ai.prefs.permission_ask": "السؤال أولاً",
"ai.prefs.permission_all": "تنفيذ كل شيء",
"ai.prefs.permission_all_summary": "لا يسأل أبدًا",
"ai.prefs.permission_ask": "السؤال أولًا",
"ai.prefs.permission_ask_summary": "يسأل عن كل تغيير",
"ai.prefs.permission_edits": "إجراء تعديلات",
"ai.prefs.permission_edits_summary": "يعدّل بحرية ويسأل عن الباقي",
@@ -963,8 +973,7 @@
"cmd.inspect_analysis": "تحليل العرض",
"cmd.inspect_center_of_mass": "مركز الكتلة",
"cmd.inspect_clearance": "الخلوص",
"cmd.inspect_colored_components": "تفعيل ألوان المكوّنات",
"cmd.inspect_colored_components_disable": "تعطيل ألوان المكوّنات",
"cmd.inspect_colored_components": "ألوان المكوّنات",
"cmd.inspect_compare": "مقارنة الأجسام",
"cmd.inspect_compare_picture": "مقارنة بصورة مرجعية",
"cmd.inspect_continuity": "تحليل الاستمرارية",
@@ -1081,6 +1090,9 @@
"cmd.render.compare.divider": "تحريك فاصل مقارنة التصيير",
"cmd.render.result.discard": "تجاهل نتيجة التصيير",
"cmd.render.result.select": "تحديد نتيجة التصيير",
"cmd.render.surface.import": "استيراد خرائط السطح",
"cmd.render.texture.list": "عرض صور السطح المضمّنة",
"cmd.render.texture.remove": "إزالة صورة السطح المضمّنة",
"cmd.render_aging_describe": "تقادم السطح بالطبقات",
"cmd.render_backends": "محركات التصيير",
"cmd.render_batch": "تصيير كل اللقطات",
@@ -1296,6 +1308,7 @@
"cmd.timeline_stop": "إيقاف السجل",
"cmd.timeline_suppress": "تعطيل الميزة",
"cmd.ui.assistant": "إظهار المساعد أو إخفاؤه",
"cmd.view.navigator": "متصفح العرض المخصص",
"cmd.view.render_browser": "متصفح التصيير",
"cmd.view.render_dock": "نقل لوحة التصيير",
"cmd.view.render_focus": "عرض تركيز التصيير",
@@ -1323,6 +1336,7 @@
"cmd.view_previous_tab": "علامة التبويب السابقة",
"cmd.view_rename": "إعادة تسمية عرض مسمّى",
"cmd.view_render_frame": "أدلة إطار التصيير",
"cmd.view_ribbon_tabs_only": "علامات تبويب الشريط فقط",
"cmd.view_right": "العرض الأيمن",
"cmd.view_save": "عرض مسمّى جديد",
"cmd.view_shots_strip": "شريط اللقطات",
@@ -1394,15 +1408,22 @@
"dialog.computing": "جارٍ الحساب… {seconds} ث",
"dialog.could_not_be_checked": "تعذّر فحصه",
"dialog.custom_axis": "↖ محور مخصص",
"dialog.cylinder_face_tip": "وجه النموذج أو رأسه الذي تقوم عليه الأسطوانة: انقر هنا ثم انقر عليه في العرض. إذا تُرك فارغًا، تحدد X وY وZ موضعها.",
"dialog.dimension_insert_hint": "انقر على بُعد قائد آخر لإدراج اسمه",
"dialog.dimension_name": "البُعد: {name}",
"dialog.dimension_step_down": "إنقاص بمقدار {step}",
"dialog.dimension_step_expression": "يتغير الرقم فقط بالخطوات؛ يبقى التعبير كما كُتب",
"dialog.dimension_step_up": "زيادة بمقدار {step}",
"dialog.edit_title": "تعديل {title}",
"dialog.no_shown_body": "لا يوجد جسم ظاهر لتغييره: أظهر جسمًا، أو اختر «جسم جديد»",
"dialog.pattern_sketch_not_object": "اختر أجسامًا أو ميزات: الرسم ليس أيًّا منهما. كرّر منحنياته بأداة النمط أو المرآة داخل الرسم، أو ابثقه أولًا.",
"dialog.primitive_point_tip": "نقطة تملأ X وY وZ: انقر هنا ثم على رأس أو حافة أو وجه من النموذج، أو على نقطة من رسم، أو على نقطة الأصل.",
"dialog.reference_edge_tip": "حافة مستقيمة من الوجه المختار تُقاس منها المسافة أدناه: انقر هنا ثم على الحافة.",
"dialog.select": "↖ تحديد",
"dialog.selected_count": "↖ {count} محدد",
"dialog.selection_constraint": "قيد {id}",
"dialog.selection_external_curve": "{sketch} · منحنى {id}",
"dialog.selection_external_point": "{point} · {sketch}",
"dialog.selection_guidance": "حدد مُدخلًا لتغييره. انقر على العناصر المحددة لإزالتها؛ Ctrl/Cmd مع النقر يبدّل تحديدها أيضًا. راجع المعاينة ثم اضغط موافق.",
"dialog.selection_midpoint": "نقطة المنتصف · {id}",
"dialog.selection_profile": "{sketch} · مقطع {number}",
@@ -1420,6 +1441,7 @@
"document.read_only_save": "فُتح هذا التصميم للقراءة فقط: احفظه كملف جديد",
"document.save_changes": "حفظ التغييرات على {name}؟",
"document.save_copy": "حفظ نسخة…",
"document.tab_unsaved_changes": "تغييرات غير محفوظة",
"document.unsaved_changes": "في {name} تغييرات غير محفوظة. إن لم تحفظها فستضيع.",
"drive.rigid": "{joint} مفصل صلب: يثبّت أجزاءه في مكانها، لذا لا يوجد ما يمكن تحريكه. لتتحرك الأجزاء، غيّر نوعه في «تعديل المفصل»: «دوراني» للدوران، «منزلق» للانزلاق، «أسطواني» لكليهما.",
"drive.stopped_by_contact": "توقف عند {value}: سيخترق {a} الجسم {b}",
@@ -1440,10 +1462,16 @@
"each rail's curves must be in one sketch": "يجب أن تكون منحنيات كل دليل في رسم واحد",
"feature computation ended without a result": "انتهى حساب الميزة دون نتيجة",
"file.open.drawing_filter": "التصاميم وملفات CAD والشبكات والرسومات",
"file.open.portal_fallback.no_portal_service": "منتقي الملفات الذي فُتح ليس تابعًا لسطح المكتب: خدمة منتقي الملفات في سطح المكتب غير متاحة.",
"file.open.portal_fallback.no_session_bus": "منتقي الملفات الذي فُتح ليس تابعًا لسطح المكتب: لا يوجد ناقل جلسات D-Bus.",
"file.open.portal_fallback.xwayland": "منتقي الملفات الذي فُتح ليس تابعًا لسطح المكتب: في XWayland ترفض البوابة النافذة الأصلية.",
"file_load.built": "بُني حتى الآن: {built} من {total} ميزة.",
"file_load.converting": "تحويل الجسم الصلب {done} من {total}",
"file_load.inserted": "تم إدراج {name}",
"file_load.inserting": "إدراج الهندسة",
"file_load.link_item": "{name} ({done} من {total})",
"file_load.opened": "تم فتح {name}",
"file_load.opened_in_tab": "تم فتح {name} في علامة تبويب جديدة",
"file_load.opening": "فتح التصميم",
"file_load.preparing_thumbnail": "تجهيز الصورة المصغّرة...",
"file_load.preparing_view": "تجهيز العرض...",
@@ -1600,7 +1628,7 @@
"help.parts.resize": "يغيّر مقاس قطعة قياسية مدرجة أو طولها ويعيد بناءها، وتبقيها مفاصلها مثبتة في مكانها. استخدمه عندما يتبين أن برغيًا أطول من اللازم أو أن ثقبًا يحتاج إلى مقاس آخر.",
"help.plastic.assign_rule": "يعيّن قاعدة بلاستيك لأجسام، فتأخذ ميزاتها البلاستيكية ميلها وخلوصها منها كقيم افتراضية. استخدمه لإعداد قطعة مقولبة مرة واحدة لكل ميزاتها.",
"help.plastic.boss": "يضيف نتوءًا لمسمار على وجه: عمودًا مع ثقب اختياري وميل وتقويس عند الجذر وأضلاع تقوية. استخدمه لنقاط البراغي في الهياكل المقولبة أو المطبوعة.",
"help.plastic.lip": "يضيف شفة أو أخدودًا مطابقًا على طول حافة جسم مفرَّغ. استخدمه ليتمركز نصفا الهيكل أحدهما على الآخر ويُغلقا بإحكام.",
"help.plastic.lip": "يضيف شفة أو أخدودًا مطابقًا على طول إطار جسم مفرَّغ (فرّغ الجسم بفتح أحد وجوهه أولًا، ثم اختر الإطار المحيط بتلك الفتحة). استخدمه ليتمركز نصفا الهيكل أحدهما على الآخر ويُغلقا بإحكام.",
"help.plastic.manage_rules": "ينشئ قواعد البلاستيك ويعدّلها: المادة وسماكة الجدار وأنصاف الأقطار والخلوص والميل. استخدمها للحفاظ على اتساق ميزات القطعة المقولبة.",
"help.plastic.rest": "يضيف مسندًا على وجه: وسادة مستديرة أو مستطيلة، مصمتة أو مجوفة، مع ميل. استخدمه كدعامة أو مُباعِد في هيكل مقولب.",
"help.plastic.snap_fit": "يضيف خطاف وصلة كبس على وجه، بطوله وسماكته ونتوء التثبيت. استخدمه لتثبيت القطع معًا بلا براغي.",
@@ -1679,7 +1707,7 @@
"help.sketch.insert_svg": "يقرأ مسارات مخطط SVG إلى رسم بالمقياس والدوران والموضع الذي تحدده. استخدمه لتحويل شعار أو محيط متجهي إلى منحنيات يمكنك بثقها.",
"help.sketch.inspect": "يفتح تقريرًا عن الرسم المفتوح: عدد درجات الحرية المتبقية وهل هو مقيد بالكامل. استخدمه لمعرفة ما لا يزال يتحرك قبل إنهاء الرسم.",
"help.sketch.intersect": "يضيف المنحنيات أو النقاط التي تقطع فيها أجسام أو أوجه أو حواف أو رسوم أخرى مستوى الرسم. تبقى مرتبطة وتتبع النموذج عندما يتغير.",
"help.sketch.intersection_curve": "ينشئ منحنيات ثلاثية الأبعاد حيث يلتقي جسمان أو وجهان، مرتبطة بهما. استخدمها مسارات أو قضبان توجيه تتبع النموذج.",
"help.sketch.intersection_curve": "ينشئ منحنيات ثلاثية الأبعاد حيث يلتقي جسمان أو وجهان أو منحنيات رسوم أخرى، مرتبطة بهما. استخدمها مسارات أو قضبان توجيه تتبع النموذج.",
"help.sketch.isoparametric_curve": "ينشئ منحنى ثلاثي الأبعاد على اتجاه U أو V للوجه عبر نقطة تختارها، مرتبطًا بالوجه. استخدمه مسارًا أو خط تقسيم يتبع السطح.",
"help.sketch.line": "يرسم خطوطًا متصلة نقطة بنقطة، كسلسلة مفتوحة أو محيط مغلق. هو الأداة الأساسية لمعظم المقاطع.",
"help.sketch.line.midpoint": "يرسم خطًا من منتصفه إلى أحد طرفيه، ويعكس الطرف الآخر. استخدمه للخطوط المتمركزة على نقطة، مثل حافة متناظرة.",
@@ -1785,6 +1813,7 @@
"help.view.navigate": "يدير العرض أو يُزيحه أو يغيّر تكبيره بخطوات، مثل ربع دورة إلى اليسار. استخدمه لتحريك الكاميرا بدقة بدلًا من السحب.",
"help.view.navigation_bar": "يُظهر شريط التنقل أسفل منطقة العرض أو يخفيه. أخفِه لمساحة أكبر، وأظهره للوصول إلى أدوات العرض بالفأرة.",
"help.view.rename": "يمنح عرضًا مسمّى محفوظًا اسمًا جديدًا. يبقى العرض نفسه كما هو.",
"help.view.ribbon_tabs_only": "يطوي الشريط إلى صف علامات التبويب، فتحصل منطقة العرض على المساحة التي كانت تشغلها لوحاته. انقر علامة تبويب لإظهار لوحاتها فوق منطقة العرض؛ واختر هذا الأمر مرة أخرى لإبقاء الشريط كاملًا ظاهرًا.",
"help.view.save": "يحفظ موضع الكاميرا الحالي كعرض مسمّى في التصميم. استخدمه للعودة إلى الزاوية نفسها لاحقًا، مثلًا للقطات الشاشة.",
"help.view.spin": "يدوّر العرض حول النموذج باستمرار حتى النقرة أو المفتاح التالي. استخدمه لعرض النموذج من جميع الجهات.",
"help.view.tool": "يثبّت أداة كاميرا، التدوير أو الإزاحة أو التكبير، بحيث يحرّك السحب بزر الفأرة الأيسر العرض حتى توقفها. استخدمه على لوحة اللمس أو بدون زر فأرة أوسط.",
@@ -1918,7 +1947,7 @@
"joint.limit_loop": "{side} {value}: لا يمكن لحلقة مغلقة أن تنغلق بعده",
"joint.limit_range": "{side}: لا تلامس ضمن {range}، تُرك كما كان",
"joint.limit_turn": "{side} {value}: لا تلامس ضمن دورة واحدة",
"joint.limits_overlapping": "متداخلة بالفعل هنا: {pairs}. لا توقف البحث إلا إذا تداخلت أعمق.",
"joint.limits_overlapping": "متداخلة بالفعل هنا: {pairs}. لا توقف البحث إلا إذا ازداد تداخلها.",
"joint.one_body": "كلا الاختيارين على {body}. اختر الثاني على قطعة أخرى.",
"joint.one_component": "هذه القطع أجسام لمكوّن واحد: {bodies}. المفصل يحرّك مكوّنًا مقابل آخر، لذا تحتاج كل قطعة إلى مكوّن خاص بها.",
"joint.rigid_group_one_component": "هذه الأجسام وعددها {count} كلها في مكوّن واحد (أو في التصميم نفسه): استخدم {command} أولًا.",
@@ -1933,7 +1962,7 @@
"joint_move.fine": "الخطوة بعيدًا عن المحور",
"joint_move.fixed": "لا يملك هذا الجزء محور مفصل قابلًا للحركة.",
"joint_move.help": "تحريك — اسحب سهمًا أو قوسًا. يلغى السحب الحالي بمفتاح Esc.",
"joint_move.legacy_hint": "إيقاف: لوحة التحريك السابقة.",
"joint_move.legacy_hint": "عند الإيقاف: لوحة التحريك السابقة.",
"joint_move.precision": "اسحب خارج الدليل المتقطع للخطوة الدقيقة.",
"joint_move.rotation": "الدوران من نقطة البداية",
"joint_move.select": "اختر وجهًا أو جزءًا واحدًا من التجميع، ثم اختر تحريك.",
@@ -1999,6 +2028,7 @@
"material.selection.mixed": "مواد مختلفة",
"measure.at": "عند",
"measure.item": "العنصر {number}",
"measure.line_to_centre": "من الخط إلى المركز",
"measure.quick_copied": "تم نسخ القياس",
"measure.quick_copy": "انقر لنسخ القياس",
"measure.quick_edges": "{count} حواف",
@@ -2029,6 +2059,8 @@
"nav.blender.help": "السحب بالزر الأوسط يدوّر العرض، وShift مع الأوسط يُزيحه، وCtrl مع الأوسط يكبّره؛ والسحب بالأيمن لا يحرك الكاميرا",
"nav.creo.help": "السحب بالزر الأوسط يدوّر العرض، وShift مع الأوسط يُزيحه، وCtrl مع السحب العمودي بالأوسط يكبّره أو يصغّره (السحب لأسفل للتكبير)؛ وتدوير العجلة يكبّره أو يصغّره",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "يستمر السحب بالزر الأوسط أو الأيمن في التدوير أو التحريك أو التكبير كما بدأ حتى تحرير الزر، مهما ضُغطت المفاتيح أو حُرّرت في أثناء ذلك. عند الإيقاف: تقرر المفاتيح المضغوطة في كل لحظة من السحب.",
"nav.drag_keeps_mode.label": "يحتفظ السحب بطريقة التنقل التي بدأ بها",
"nav.free_orbit.help": "الفأرة والفأرة ثلاثية الأبعاد تدوّران العرض عبر القطبين وتحافظان على اللف. يُعطَّل التقاط المحاور بـ Shift.",
"nav.free_orbit.label": "تدوير حر",
"nav.free_orbit.snap_help": "الفأرة والفأرة ثلاثية الأبعاد تدوّران العرض عبر القطبين وتحافظان على اللف. اضغط مطولًا على Shift أثناء سحب التدوير لالتقاط اتجاه العرض واللف.",
@@ -2240,7 +2272,7 @@
"prefs.anti_aliasing_amd_dx12": "يستخدم تنعيم الحواف 1× لأن فحص بدء التشغيل لم يرسم شيئًا مع أخذ العينات المتعددة ونجح عند 1×. يمكنك اختيار إعداد آخر أعلاه؛ أعد التشغيل لتطبيقه إن نجح الفحص.",
"prefs.anti_aliasing_note": "ينعّم الحواف المسننة.",
"prefs.appearance.colour_vision_help": "يغيّر الألوان ذات الدلالة: حالات الرسم والمحاور والثنيات والتحليلات والأيقونات.",
"prefs.appearance.desktop_only": "لسطح المكتب فقط",
"prefs.appearance.desktop_only": "لتطبيق سطح المكتب فقط",
"prefs.appearance.follow_help": "يعيد التحميل عندما تعيد أداة أخرى كتابة الملف.",
"prefs.appearance.interface_scale": "مقياس الواجهة",
"prefs.appearance.next_themes": "السمات التالية",
@@ -2250,11 +2282,11 @@
"prefs.appearance.theme_count": "{count} سمات",
"prefs.appearance.theme_own": "ألوان السمة",
"prefs.appearance.viewport_colours": "ألوان العرض ثلاثي الأبعاد",
"prefs.appearance.viewport_logo": "منفذ العرض والشعار",
"prefs.appearance.viewport_logo": "منطقة العرض والشعار",
"prefs.applies_on_restart": "يُطبَّق عند إعادة التشغيل",
"prefs.assistant": "المساعد",
"prefs.assistant_connecting": "الاتصال",
"prefs.assistant_follow_help": "تحرّك عروض Look At للرسم المنظور مع المؤشر.",
"prefs.assistant_follow_help": "النظر إلى الرسومات يحرّك العرض مع المؤشر.",
"prefs.assistant_limit": "الحد",
"prefs.assistant_limited": "محدود",
"prefs.assistant_pointer_speed": "سرعة المؤشر",
@@ -2262,13 +2294,24 @@
"prefs.assistant_seconds_aria": "الثواني",
"prefs.assistant_use_care": "استخدم بحذر",
"prefs.auto_operation": "اختيار العملية تلقائيًا",
"prefs.auto_operation_note": "ضم أو جسم جديد أو قطع، حسب ما تلامسه المادة.",
"prefs.auto_operation_note": "ضم أو جسم جديد أو قص، حسب ما تلامسه المادة.",
"prefs.axes": "المحاور",
"prefs.axis": "المحور",
"prefs.background_choose": "اختيار صورة…",
"prefs.background_image": "الصورة",
"prefs.background_unreadable": "تعذرت قراءة صورة الخلفية",
"prefs.blur_cost": "تكلفة العرض النسبية. زيادة الأشرطة تعني عادةً مزيدًا من العمل.",
"prefs.blur_device": "غير متاح على جهاز الرسوميات أو مسار العرض هذا",
"prefs.blur_guard": "خُفّضت جودة التمويه للحفاظ على استجابة الإطارات. لم يتغير نصف القطر.",
"prefs.blur_off": "إيقاف",
"prefs.blur_reduce_transparency": "معطّل بسبب إعداد تقليل الشفافية في النظام",
"prefs.blur_software": "غير متاح مع الرسوميات البرمجية (gpu-software)",
"prefs.blur_source": "غير متاح في عرض مساحة العمل هذا",
"prefs.blur_web": "غير متاح في مسار عرض الويب هذا",
"prefs.bodies_folders": "مجلدات الأجسام",
"prefs.bodies_folders_always": "دائمًا",
"prefs.bodies_folders_never": "أبدًا",
"prefs.bodies_folders_several": "عدة",
"prefs.bodies_folders_several": "عند التعدد",
"prefs.bottom": "أسفل",
"prefs.bottom_centre": "أسفل الوسط",
"prefs.bottom_right": "أسفل اليمين",
@@ -2278,6 +2321,8 @@
"prefs.browser_eye_left": "أيقونة الإظهار بجانب سهم الطي",
"prefs.browser_eye_left_note": "تعرض صفوف المستعرض أيقونة الإظهار/الإخفاء بجانب سهم الطي كما كانت من قبل، بدلًا من عمود عند نهاية الصفوف.",
"prefs.browser_reveal_on_hover": "إظهار عند مرور المؤشر",
"prefs.browser_rows_start_closed": "صفوف المستعرض تبدأ مغلقة",
"prefs.browser_rows_start_closed_note": "تبدأ المواضع والمجلدات في المستعرض مغلقة، فيعرض التصميم الذي يضم مكونات كثيرة صفوفه العليا. يُفتح كل صف بسهمه أو بـ«توسيع الكل» أو بـ«البحث في المستعرض» الذي يفتح الصفوف المؤدية إلى الجسم فقط، ويغلق من جديد الصفوف التي فتحها البحث السابق. اختيار جسم في العرض ثلاثي الأبعاد يترك المستعرض كما هو. عند إيقافه تبدأ المجلدات مفتوحة، وتبدأ المواضع كما يحدّد خيار «المكوّنات مفتوحة من البداية»، ويفتح الاختيار في العرض ثلاثي الأبعاد المستعرضَ حتى صف الجسم، كما كان من قبل.",
"prefs.browser_show_note": "«ظاهر» يُبقي المستعرض على الشاشة. «إظهار عند مرور المؤشر» يُخفيه حتى يمر المؤشر فوق مكانه أو فوق الشريط الرفيع عند حافته اليسرى، ويبقى مفتوحًا ما دام المؤشر داخله أو كانت قائمة منه مفتوحة.",
"prefs.browser_shown": "ظاهر",
"prefs.built_in": "مدمج",
@@ -2288,6 +2333,7 @@
"prefs.carousel_previous": "السابق",
"prefs.choose_font": "اختيار ملف خط…",
"prefs.click": "نقر",
"prefs.command_dialog_values": "مربعات حوار الأوامر",
"prefs.contact_shading": "تظليل التلامس الناعم",
"prefs.contact_shading_note": "تظليل ناعم حيث تقترب القطع من بعضها.",
"prefs.context_menu": "القائمة السياقية",
@@ -2323,6 +2369,13 @@
"prefs.dialogs_edge": "عند حافة العرض",
"prefs.dialogs_left_column": "في العمود الأيسر",
"prefs.dialogs_note": "مكان فتح حوارات الأوامر: مثبتة عند حافة العرض، أو في العمود الأيسر مكان المستعرض والسجل، اللذين يعودان كما كانا عند إغلاق الحوار.",
"prefs.dimension_step_angle": "ما تضيفه أسهم النافذة والسهمان لأعلى ولأسفل والعجلة إلى الزاوية",
"prefs.dimension_step_length": "ما تضيفه أسهم النافذة والسهمان لأعلى ولأسفل والعجلة إلى الطول",
"prefs.dimension_steps": "خطوات النافذة",
"prefs.dimension_values": "قيم الأبعاد",
"prefs.dimension_values_note": "مكان كتابة قيمة البعد. للنافذة أسهم تغيّرها بخطوات؛ تضع أداة البعد النص أولًا.",
"prefs.dimension_values_view": "في العرض",
"prefs.dimension_values_window": "في نافذة",
"prefs.display": "العرض",
"prefs.edges": "الحواف والتظليل",
"prefs.extend": "توسيع",
@@ -2360,7 +2413,7 @@
"prefs.graphics_note": "وازن بين السرعة والنعومة. كل خيار يعرض أثره على عيّنة.",
"prefs.graphics_page.current_view": "العرض الحالي",
"prefs.graphics_page.fastest": "الأسرع",
"prefs.graphics_page.fold_items": "طي العناصر",
"prefs.graphics_page.fold_items": "تلخيص العناصر",
"prefs.graphics_page.in_view": "داخل العرض",
"prefs.graphics_page.live_details": "تفاصيل مباشرة",
"prefs.graphics_page.own_mix": "مزيجك الخاص",
@@ -2369,13 +2422,15 @@
"prefs.graphics_page.restart": "تلزم إعادة التشغيل",
"prefs.graphics_page.sharpest": "الأكثر وضوحًا",
"prefs.graphics_page.single_surface_analysis": "تحليل سطح واحد",
"prefs.graphics_page.status_chips": "شرائح الحالة",
"prefs.graphics_page.status_chips": "شارات الحالة",
"prefs.graphics_page.subtitle": "وازن بين السرعة والنعومة.",
"prefs.graphics_page.supersampling": "التنعيم الفائق",
"prefs.graphics_page.supersampling_adaptive": "فقط أثناء سكون العرض",
"prefs.graphics_page.surfaces_edges": "الأسطح والحواف",
"prefs.graphics_page.view_bar": "أيضًا في شريط العرض",
"prefs.graphics_presets_note": "الإعدادات المسبقة تضبط الخيارات أدناه معًا.",
"prefs.ground_shadow_follows_model": "ظل الأرض يتبع النموذج",
"prefs.ground_shadow_follows_model_help": "ضع الظل أسفل أدنى جسم مرئي. عند التعطيل، يبقى ظل النماذج المرفوعة على مستوى الأرض عند نقطة الأصل.",
"prefs.hide_constraint_glyphs_zoomed_out": "إخفاء رموز القيود عند التصغير",
"prefs.hide_constraint_glyphs_zoomed_out_note": "تختفي رموز القيود من العرض عندما يُصغَّر الرسم بقدر يجعلها تزحمه. معطّل: تبقى بحجم ثابت على الشاشة عند أي مستوى تكبير.",
"prefs.history": "السجل",
@@ -2402,6 +2457,10 @@
"prefs.invert_curved_roll_arrows_note": "سهما التدوير المنحنيان عند زاوية المكعب يدوران العرض داخل مستوى الشاشة. عند التشغيل: سهم التدوير مع عقارب الساعة يدور عكسها، وسهم العكس يدور معها. عند الإيقاف: يدور كل سهم بالعرض حسب انحنائه.",
"prefs.invert_navigator_arrows": "عكس أسهم تدوير مكعب العرض",
"prefs.invert_navigator_arrows_note": "تدوّر الأسهم المحيطة بالمكعب الكاميرا في الاتجاه الذي تشير إليه، كما في Fusion وInventor: السهم الموجود على اليسار يشير إلى اليمين ويدوّر الكاميرا إلى اليمين. عند الإيقاف: يدوّر السهم المكعب نحو الجهة التي يقع فيها، فيدوّر السهم الأيسر الكاميرا إلى اليسار. لا يتأثر سهما الدوران المنحنيان.",
"prefs.keep_unsaved_also_asks": "السؤال أيضًا عند إغلاق علامة التبويب",
"prefs.keep_unsaved_also_asks_tip": "عند إيقاف هذا الخيار، تُغلق علامة التبويب دون مربع المتصفح بعد تحديث نسخة كل تصميم غير محفوظ.",
"prefs.keep_unsaved_in_browser": "الاحتفاظ بالعمل غير المحفوظ في هذا المتصفح",
"prefs.keep_unsaved_in_browser_tip": "تُحدَّث التغييرات غير المحفوظة لكل تصميم مفتوح باستمرار في التخزين الخاص بهذا المتصفح. وتعرض الزيارة التالية استعادتها.",
"prefs.keep_work_on_tab_switch": "إبقاء مربعات الحوار والأدوات مفتوحة عند التبديل بين التصاميم",
"prefs.keep_work_on_tab_switch_note": "تحتفظ كل علامة تبويب تصميم بمربع الحوار أو الأداة المفتوحة، بما حددته وكتبته فيها، وبعلامة تبويب شريط الأدوات. وتجدها كما هي عند العودة إلى التصميم. عند الإيقاف: يؤدي التبديل بين التصاميم إلى إلغائها وعرض علامة التبويب «النموذج».",
"prefs.labels": "التسميات",
@@ -2413,10 +2472,12 @@
"prefs.layout_page.card": "بطاقة",
"prefs.layout_page.column": "عمود",
"prefs.layout_page.hover": "عند مرور المؤشر",
"prefs.layout_page.hover_panel_menus": "تبديل قوائم اللوحات عند مرور المؤشر",
"prefs.layout_page.hover_panel_menus_help": "انقر على اسم لوحة في الشريط لفتح قائمتها. مرّر المؤشر فوق اسم لوحة أخرى للتبديل. حرّك المؤشر خارج الشريط والقائمة المفتوحة لإغلاقها.",
"prefs.layout_page.icons_labels": "الأيقونات + التسميات",
"prefs.layout_page.ok_cancel": "موافق وإلغاء",
"prefs.layout_page.on_hover": "عند مرور المؤشر",
"prefs.layout_page.open": "فتح",
"prefs.layout_page.open": "تُفتح",
"prefs.layout_page.origin_bodies": "الأصل والأجسام في الأعلى",
"prefs.layout_page.panels": "اللوحات",
"prefs.layout_page.pinned_menus": "استبعاد الأوامر المثبتة من القوائم",
@@ -2456,8 +2517,8 @@
"prefs.navigation.cube_compact": "المكعب",
"prefs.navigation.device": "الجهاز",
"prefs.navigation.free": "حر",
"prefs.navigation.orbit_style": "نمط الدوران",
"prefs.navigation.radial_help": "اضغط مطولاً أو انقر سريعاً بالزر الأيمن لعرض حلقة من الأدوات.",
"prefs.navigation.orbit_style": "نمط التدوير",
"prefs.navigation.radial_help": "اضغط مطولًا أو انقر سريعًا بالزر الأيمن لعرض حلقة من الأدوات.",
"prefs.navigation.turntable": "طاولة دوارة",
"prefs.navigation_note": "كيف تحرك الفأرة ولوحة اللمس والفأرة ثلاثية الأبعاد العرض.",
"prefs.navigator_fit": "ملاءمة النموذج بعد تغيير العرض من متصفح العرض",
@@ -2475,7 +2536,7 @@
"prefs.origin_plane_names_note": "الأسماء المعروضة في المستعرض والحوارات والقوائم. {top} هو XY و{front} هو XZ و{right} هو YZ، والمحور Z للأعلى. تحتفظ الأوامر والسكربتات والتصاميم المحفوظة بـ XY وXZ وYZ.",
"prefs.overall_speed": "السرعة العامة",
"prefs.parameter_list_rows": "صفوف قائمة المعاملات",
"prefs.parameter_list_rows_tip": "الحد الأقصى للمعلمات التي يسردها السهم لأسفل في مربع تعبير فارغ.",
"prefs.parameter_list_rows_tip": "الحد الأقصى للمعاملات التي يسردها السهم لأسفل في مربع تعبير فارغ.",
"prefs.persistent_ribbon_pressed": "تمييز أوامر الشريط النشطة",
"prefs.persistent_ribbon_pressed_help": "تمييز أوامر الشريط أثناء تفعيل تأثيراتها. أوقف هذا الخيار للاحتفاظ بالمظهر المحايد السابق ومساعدة الأوامر.",
"prefs.perspective_fov": "مجال الرؤية",
@@ -2515,6 +2576,8 @@
"prefs.quality_smoother": "أنعم",
"prefs.quick_measure": "قياس سريع في زاوية العرض",
"prefs.quick_measure_note": "مقاس العناصر المحددة في الزاوية اليمنى السفلية من العرض: طول الحافة (أو Ø وR للحافة الدائرية)، ومساحة الوجه، وموضع النقطة، والمسافة والزاوية بين عنصرين، ومجموع عدة حواف أو أوجه. النقر ينسخه.",
"prefs.remember_dialog_values": "تذكّر قيم مربعات حوار الأوامر",
"prefs.remember_dialog_values.help": "معطّل افتراضيًا. عند تفعيله، بعد نجاح الأمر، يُعاد استخدام قيمه وخياراته الآمنة في المرة التالية التي تفتح فيها الأمر نفسه، حتى إغلاق SolidCraft.",
"prefs.render_previous_geometry_budget": "استخدام ميزانية الهندسة السابقة لمعاينة التصيير",
"prefs.render_previous_geometry_budget_help": "استعادة حد الهندسة الثابت السابق (512 ميبيبايت على سطح المكتب 64 بت، و128 ميبيبايت على المنصات الأخرى) بدلًا من الميزانية المعتمدة على الجهاز.",
"prefs.reports_saved": "{n} محفوظة",
@@ -2523,6 +2586,8 @@
"prefs.ribbon_click_ends_tool_note": "النقر على زر الشريط المضيء لأداة الرسم المستخدمة ينهي الأداة، فيعود النقر في العرض إلى التحديد. عند الإيقاف: يبدأ النقر الأداة من جديد.",
"prefs.ribbon_reveal": "إظهار مكان نتائج البحث في الشريط",
"prefs.ribbon_reveal_note": "عند تمرير المؤشر فوق نتيجة في «البحث في الأوامر» يظهر مكان الأمر في الشريط: تبويبه أو زره أو صفه في القائمة المنسدلة للوحة. ويومض هناك الأمر الذي بدأ من البحث، ويُعلِّم الأمر الجاري من قائمة منسدلة لوحتَه.",
"prefs.ribbon_tabs_only": "إظهار علامات تبويب الشريط فقط",
"prefs.ribbon_tabs_only_note": "يُطوى الشريط إلى صف علامات التبويب. يؤدي النقر على علامة تبويب إلى إظهار لوحاتها فوق منطقة العرض حتى يبدأ أمر أو يغادرها المؤشر. يبدّل الأمر «علامات تبويب الشريط فقط» في تبويب العرض، أو اختصاره، هذا الخيار.",
"prefs.right_click": "نقر أيمن",
"prefs.saving": "البدء والحفظ",
"prefs.scale_note": "أو Ctrl + / Ctrl − في النافذة. الأزرار 28 px عند 100%.",
@@ -2533,6 +2598,8 @@
"prefs.section_hatch_by_part": "حسب الجزء",
"prefs.section_hatch_one": "نمط واحد",
"prefs.select": "تحديد",
"prefs.select_hidden_edges": "تحديد الحواف المخفية في نمط «مظلل مع الحواف المخفية»",
"prefs.select_hidden_edges_help": "يتيح تحديد الحواف عبر الأوجه عند تفعيل نمط «مظلل مع الحواف المخفية»، بما في ذلك عند استخدام التقويس. عند الإيقاف يبقى سلوك التحديد كما في v0.30.1. تظل الأجسام المخفية غير قابلة للتحديد.",
"prefs.select_one_item": "تحديد عنصر واحد",
"prefs.select_other_hidden": "تحديد العناصر الهندسية المخفية باستخدام «تحديد عنصر آخر»",
"prefs.select_other_hidden_help": "عند التفعيل: تضمين الأجسام والمخططات المخفية والحواف المحجوبة قرب المؤشر. عند التعطيل: استخدام القائمة الأصلية للعناصر الهندسية المرئية أسفل المؤشر.",
@@ -2544,19 +2611,21 @@
"prefs.shortcuts_import": "استيراد",
"prefs.shortcuts_intro": "مفاتيح كل أمر.",
"prefs.shortcuts_not_assigned": "غير معيّن",
"prefs.shortcuts_press": "اضغط مفتاحاً...",
"prefs.shortcuts_press": "اضغط مفتاحًا...",
"prefs.single_surface_analysis": "استخدام تحليل سطح واحد",
"prefs.single_surface_analysis_help": "عند إيقاف هذا الخيار، يمكن إبقاء تظليل السطح وتحليل الاستمرارية مفعّلين معًا. عند تشغيله، يبقى آخر تحليل تم بدؤه أو تغييره فقط.",
"prefs.sketch.align_points": "المحاذاة مع نقاط الرسم الأخرى",
"prefs.sketch.align_points_note": "تتحاذى الخطوط أثناء الرسم مع النقاط على المحورين معًا.",
"prefs.sketch.auto_dimensions": "إضافة كل الأبعاد للأشكال الجديدة",
"prefs.sketch.auto_dimensions_note": "القيم المدخلة وكل بُعد يثبّت الشكل.",
"prefs.sketch.auto_locate": "التحديد انطلاقًا من أصل الرسم",
"prefs.sketch.auto_locate": "تحديد الموضع من أصل الرسم",
"prefs.sketch.auto_locate_note": "مع إضافة كل الأبعاد.",
"prefs.sketch.center_arc": "قوس المركز يتبع المؤشر",
"prefs.sketch.center_arc": "القوس بنقطة مركز يتبع المؤشر",
"prefs.sketch.click_selects": "النقر يحدد",
"prefs.sketch.conflict_colour": "لون التعارض",
"prefs.sketch.drawing": "الرسم",
"prefs.sketch.ellipse_axes": "رسم القطوع الناقصة مع محاورها",
"prefs.sketch.ellipse_axes_note": "المحوران الأكبر والأصغر كخطوط مساعدة، لتحديد أبعاد أنصاف المحاور والمحاور. عند الإيقاف: القطع الناقص وحده، ويعطي D عليه نصف قطره الأصغر أينما سُحبت القيمة، مرسومًا ومُعلَّمًا بـ R كما في السابق.",
"prefs.sketch.exit_keep": "إبقاء",
"prefs.sketch.exit_previous": "العودة إلى العرض السابق",
"prefs.sketch.exit_unless_rotated": "إلا إذا تم التدوير",
@@ -2568,7 +2637,7 @@
"prefs.sketch.prefer_free_points": "تفضيل النقاط القابلة للتحريك عند تداخلها",
"prefs.sketch.project_options": "السؤال عن خيارات الإسقاط أولًا",
"prefs.sketch.recent_snap": "الالتقاط عند تقاطع دليلين",
"prefs.sketch.select_other_hidden": "اختيار آخر يشمل الهندسة المخفية",
"prefs.sketch.select_other_hidden": "تحديد آخر يشمل الهندسة المخفية",
"prefs.sketch.shortcut_cycles_modes": "الاختصار مجددًا يبدّل الوضع",
"prefs.sketch.shortcut_cycles_modes_tip": "عند الإيقاف: تبدأ الأداة مجددًا.",
"prefs.sketch.snapping_guides": "الالتقاط والأدلة",
@@ -2650,6 +2719,10 @@
"prefs.theme_follow_off": "إيقاف",
"prefs.theme_follow_stop": "إيقاف الاتباع",
"prefs.theme_import_hint": "انقر لاختيار ملف سمة، أو أفلت ملفًا هنا، أو اضغط Ctrl+V (Cmd+V على Mac) بعد نسخ سمة",
"prefs.theme_notice_dismiss": "إخفاء",
"prefs.theme_option_blur": "تمويه خلف اللوحات",
"prefs.theme_option_blur_help": "تمويه خلف اللوحات شبه الشفافة، بالبكسل الفعلي. الصفر يوقف التمويه.",
"prefs.theme_option_invalid": "القيمة المحفوظة للخيار {option} غير صالحة؛ تُستخدم القيمة الافتراضية للسمة. تبقى القيمة المحفوظة كما هي.",
"prefs.theme_option_opacity": "العتامة",
"prefs.theme_option_opacity_help": "اضبط عتامة اللوحات العائمة. يُطبّق حد أدنى للعتامة للحفاظ على وضوح النص فوق ألوان منطقة العرض.",
"prefs.theme_option_tab_colours": "ألوان علامات التبويب",
@@ -2658,6 +2731,7 @@
"prefs.theme_option_tab_fade_ms_help": "تتغيّر ألوان اللوحات ومنطقة العرض تدريجيًا عند تبديل علامات التبويب. القيمة صفر تبدّلها فورًا.",
"prefs.theme_options_badge": "الخيارات",
"prefs.theme_options_more": "المزيد · {count}",
"prefs.theme_options_newer": "تتطلب هذه السمة إصدارًا أحدث من SolidCraft. بعد الترقية، أعد استيراد السمة الأصلية لاستعادة تعريفات خياراتها.",
"prefs.theme_options_reset": "إعادة تعيين",
"prefs.theme_options_unavailable": "غير متاح على هذا الجهاز",
"prefs.theme_paste": "لصق السمة",
@@ -2686,7 +2760,7 @@
"prefs.validation.each_step": "بعد كل خطوة",
"prefs.validation.label": "فحص النواة الكامل",
"prefs.validation.off": "متوقف (تجريبي)",
"prefs.validation_note": "بعد كل خطوة: يفحص كل عملية سحب ودمج وتقويس.\nمرة لكل ميزة: يفحص ناتج كل ميزة مرة واحدة.\nمتوقف (تجريبي): لا فحص.",
"prefs.validation_note": "بعد كل خطوة: يفحص كل عملية كنس وعملية منطقية وتقويس.\nمرة لكل ميزة: يفحص ناتج كل ميزة مرة واحدة.\nمتوقف (تجريبي): لا فحص.",
"prefs.value_boxes_in_view": "إظهار القيم بجانب مقابضها",
"prefs.value_boxes_in_view_tip": "مسافة البثق ونصف قطر التدوير... في مربع داخل العرض.",
"prefs.view": "سلوك العرض",
@@ -2721,6 +2795,12 @@
"recovery.just_now": "الآن",
"recovery.min_ago": "قبل {count} دقيقة",
"recovery.never_saved": "لم يُحفظ قط",
"recovery.open_saved": "فتح النسخة المحفوظة",
"recovery.restore": "استعادة",
"recovery.restore_intro": "التغييرات التي لم تُحفظ عند آخر إغلاق لهذه الصفحة محفوظة في هذا المتصفح.",
"recovery.restore_row": "{name}، تغيّر {when}",
"recovery.restore_title": "استعادة التصاميم غير المحفوظة؟",
"recovery.restore_title_one": "استعادة التصميم غير المحفوظ: {name}، تغيّر {when}؟",
"refused.change": "تعذّر تغيير {body}.",
"refused.cut": "تعذّر قطع {body}.",
"refused.face_curved": "أحد أوجهه المنحنية لا يمكن استخدامه في النمذجة.",
@@ -3242,9 +3322,11 @@
"render.frame_wide": "16:9 · عريض",
"render.gpu_depth": "أقصى عمق للمسار",
"render.gpu_options_help": "تُحفظ العيّنات والعمق والبذرة والتعريض مع هذا الإعداد. تُحفظ طريقة الصورة النشطة فقط.",
"render.gpu_path_crashed": "أُغلق SolidCraft أثناء إعداده على محوّل الرسوميات هذا في المرة السابقة، على الأرجح داخل برنامج تشغيل الرسوميات، لذا لن يُعاد إعداده تلقائيًا. استخدم تتبع الأشعة على المعالج المركزي بدلًا منه، أو حاول مرة أخرى (يعيد تحديث برنامج التشغيل المحاولة أيضًا).",
"render.gpu_path_failed": "متتبع المسارات على GPU غير متاح",
"render.gpu_path_first_setup": "قد يستغرق الإعداد الأول على وحدة GPU هذه عدة دقائق؛ ويبقى SolidCraft قابلًا للاستخدام أثناءها. تعيد المرات اللاحقة استخدام المظللات المترجمة حيث يحتفظ بها برنامج التشغيل.",
"render.gpu_path_preparing": "متتبع المسارات على GPU: ترجمة المظللات…",
"render.gpu_path_retry": "حاول مرة أخرى",
"render.gpu_seed": "البذرة العشوائية",
"render.gpu_unavailable": "تتبع المسارات على GPU غير جاهز على هذا الجهاز.",
"render.ground_apply": "ملاءمة الأرضية وحفظها",
@@ -3858,6 +3940,7 @@
"render.stage_panels.extent": "الامتداد",
"render.stage_panels.fit_save": "ملاءمة وحفظ",
"render.stage_panels.gpu_required": "يتطلب تتبّع المسار على GPU",
"render.stage_panels.grain_angle": "زاوية عروق الخشب",
"render.stage_panels.keep": "احتفاظ",
"render.stage_panels.kept": "ما يُحتفظ به",
"render.stage_panels.kept_fields": "الكاميرا، وأخذ العيّنات، والأبعاد، والروابط البصرية، وهندسة CAD، والخصائص الهندسية.",
@@ -3976,6 +4059,19 @@
"render.subsurface_enable": "مسار عشوائي للتشتت الداخلي",
"render.subsurface_help": "للعوازل المغلقة فقط. التشتت وامتصاص RGB معاملان منفصلان لكل mm. تتشتت المسارات داخل الهندسة الفعلية، ثم تنكسر أو تنعكس عند الحد. عطّل نفاذ الظل المستقيم وإزالة تشويش السطح والحجم الخارجي؛ واستخدم طبقة فرينل واحدة. ليس مظلل انتشار ولا شبكة كثافة ولا مادة مقيسة. الأوساط الكثيفة قد تحتاج مزيدًا من العيّنات وعمق المسار.",
"render.subsurface_rate": "معدل اصطدامات التشتت",
"render.surface_import.author": "المؤلف",
"render.surface_import.busy": "جارٍ استيراد الصورة…",
"render.surface_import.complete": "اختر صورة وأدخل مؤلفها ومصدرها وترخيصها.",
"render.surface_import.embedded": "صورة مستوردة",
"render.surface_import.failed": "لم يكتمل استيراد الصورة.",
"render.surface_import.license": "الترخيص / الملكية",
"render.surface_import.raytrace_required": "تتطلب الصور تتبع الأشعة على CPU أو GPU. اختره في الإخراج.",
"render.surface_import.repeat_u": "تكرار U",
"render.surface_import.repeat_v": "تكرار V",
"render.surface_import.save": "حفظ الصورة على الجسم",
"render.surface_import.saved_cpu": "احفظ الخرائط لاستخدامها في التصيير على CPU.",
"render.surface_import.source": "المصدر",
"render.surface_import.title": "استيراد صورة",
"render.tone_aces_fitted": "ACES مُلاءَم",
"render.tone_reinhard": "راينهارد",
"render.transmission_scope": "يدعم زجاج GPU الأوساط المنفصلة المعتمدة. لا تتوفر الأوساط المتداخلة غير المقيدة ولا مقدّر نفاذ خالٍ من التشويش.",
@@ -4052,6 +4148,8 @@
"sample.plate.name": "لوح نموذجي",
"sample.vents.about": "شقوق في نمط مستطيلي: الصفوف والأعمدة والخطوة معاملات",
"sample.vents.name": "لوح تهوية",
"scale.per_axis": "لكل محور",
"scale.uniform": "موحّد",
"search.command_keys": "Enter تشغيل Shift+Enter تشغيل مع الإبقاء مفتوحًا Esc إغلاق",
"search.commands": "الأوامر",
"search.design": "في هذا التصميم",
@@ -4091,7 +4189,7 @@
"settings.display_quality_note": "المستويات الأدق ترسم الأوجه والحواف المنحنية بنعومة أكبر وتستغرق وقتًا أطول لإنشاء الشبكة. يتغير العرض فقط، لا التصميم ولا الملفات المصدّرة.",
"settings.double_click_components": "النقر المزدوج على مكوّن لتنشيطه",
"settings.frame_rate_details_note": "يضيف رسمًا لأزمنة الإطارات الأخيرة مع المثلثات المرسومة فوقه، ومدرّجًا تكراريًا لأزمنة الإطارات، وما رسمته وحدة GPU. انقر معدل الإطارات في العرض لإظهارها أو إخفائها.",
"settings.frame_rate_note": "يعرض الإطارات في الثانية في الزاوية اليمنى السفلية من العرض. أثناء تفعيله يُعاد رسم العرض باستمرار.",
"settings.frame_rate_note": "يعرض الإطارات في الثانية في الزاوية اليمنى السفلية من العرض.",
"settings.insert_lists_open_designs": "إدراج تصميم يسرد التصاميم المفتوحة أولًا",
"settings.measure_axis_distances": "مسافات المحاور في القياس",
"settings.measure_axis_distances_help": "إظهار مركّبات الأبعاد على محاور العالم بجانب المسافة الكلية أثناء فتح أداة القياس.",
@@ -4142,6 +4240,32 @@
"sketch.dof_left": "تبقّت {count} درجات حرية",
"sketch.dof_left_one": "تبقّت {count} درجة حرية",
"sketch.draw_curves": "الرسم كـ {linetype}",
"sketch.error.closed_curve_two_crossings": "يحتاج المنحنى المغلق إلى نقطتي تقاطع لقصّه",
"sketch.error.nothing_to_break": "لا يوجد هنا ما يمكن كسره",
"sketch.error.offset_fillet_chamfer": "`{id}` منحنى إزاحة، وزواياه تتبع منحنياته المصدر: قوّس زوايا المنحنيات المصدر أو اشطفها قبل الإزاحة، أو فك ارتباط الإزاحة أولًا",
"sketch.error.offset_trim": "`{id}` منحنى إزاحة، وأطرافه تتبع منحنياته المصدر: عدّل المنحنيات المصدر بدلًا من ذلك، أو فك ارتباط الإزاحة أولًا",
"sketch.hint.arc.center_point": "قوس: انقر المركز ثم البداية، ودُر في أي اتجاه وانقر النهاية، أو اكتب زاوية القوس بإشارة موجبة أو سالبة",
"sketch.hint.arc.three_point": "قوس: انقر البداية ثم النهاية، ثم نقطة على القوس",
"sketch.hint.arc3d": "قوس ثلاثي الأبعاد: انقر البداية ثم النهاية، ثم نقطة على القوس (يمكن أن تقع النقاط خارج مستوى الرسم)",
"sketch.hint.break": "كسر: انقر على منحنى في الموضع الذي يُقسم عنده",
"sketch.hint.circle.center": "دائرة: انقر المركز، ثم نقطة على الدائرة",
"sketch.hint.constraint.generic": "قيد: اختر عناصر الرسم",
"sketch.hint.constraint.horizontal_vertical": "أفقي/رأسي: اختر خطًا، أو نقطتين، أو نقطة وخطًا؛ اضغط Shift مع الاستمرار لاختيار منتصف خط",
"sketch.hint.constraint.midpoint": "نقطة المنتصف: اختر نقطة وخطًا، أو خطين لتوسيط الثاني على الأول",
"sketch.hint.dimension": "البُعد: اختر عنصرًا أو عنصرين واكتب القيمة (أو انقر لوضعه أولًا)؛ انقر على بُعد لتعديله؛ اضغط Enter للتطبيق",
"sketch.hint.extend": "تمديد: انقر على منحنى قرب الطرف المراد تمديده (Shift: انقر منحنيين لتمديدهما معًا حتى نقطة التقائهما)",
"sketch.hint.fallback": "انقر في الرسم",
"sketch.hint.line": "خط: انقر النقاط؛ انقر بالزر الأيمن أو اضغط Esc للإنهاء",
"sketch.hint.line3d": "خط ثلاثي الأبعاد: انقر نقاطًا في الفضاء؛ انقر بالزر الأيمن أو اضغط Esc للإنهاء (يمكن أن تقع النقاط خارج مستوى الرسم)",
"sketch.hint.open_done": "تم الفتح",
"sketch.hint.point3d": "نقطة ثلاثية الأبعاد: انقر حيث توضع النقطة ثلاثية الأبعاد",
"sketch.hint.rectangle.center": "مستطيل من المركز: انقر المركز، ثم زاوية",
"sketch.hint.rectangle.rounded": "مستطيل مستدير الزوايا: انقر زاويتين متقابلتين (اضغط Tab للانتقال إلى خانة نصف القطر وكتابة نصف قطر الزوايا)",
"sketch.hint.rectangle.three_point": "مستطيل بثلاث نقاط: انقر نقطتين على أحد الأضلاع، ثم العرض",
"sketch.hint.rectangle.two_point": "مستطيل: انقر زاويتين متقابلتين",
"sketch.hint.slot": "شق: انقر الطرفين، ثم العرض",
"sketch.hint.spline3d": "منحنى شريحي ثلاثي الأبعاد: انقر نقاط الملاءمة في الفضاء ثلاثي الأبعاد؛ انقر بالزر الأيمن أو اضغط Esc للإنهاء",
"sketch.hint.trim": "قص: انقر على الجزء المراد إزالته من المنحنى، أو اسحب عبر الأجزاء المراد إزالتها",
"sketch.make_selection": "تحويل التحديد إلى {linetype}",
"sketch.open_ends": "مفتوحة {count}",
"sketch.status_conflict": "تعارض القيود",
@@ -4201,13 +4325,13 @@
"thread.automatic_size": "حجم تلقائي",
"thread.chamfer_depth_needed": "مطلوب لهذا الشكل",
"thread.designation": "التسمية",
"thread.drill_range": "مثقاب صانع اللقمة {drill} مم. القطر الأصغر القياسي للسن الداخلي {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
"thread.drill_range": "مثقاب التسنين الذي يوصي به صانعو ذكور التسنين {drill} مم. القطر الأصغر القياسي للسن الداخلي {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
"thread.drill_range_custom": "المثقاب هو القطر الأكبر ناقص الخطوة، {drill} مم. القطر الأصغر القياسي للسن الداخلي {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
"thread.drill_range_taper": "مثقاب صانع اللقمة {drill} مم. القطر الأصغر القياسي عند نهاية السن {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
"thread.drill_range_taper": "مثقاب التسنين الذي يوصي به صانعو ذكور التسنين {drill} مم. القطر الأصغر القياسي عند نهاية السن {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
"thread.end_square": "قائمة",
"thread.fade_short": "1.25 P (ISO 3508)",
"thread.fade_standard": "2.5 P (ISO 3508)",
"thread.fits_the_face": "يناسب الوجه، مثلاً {example}",
"thread.fits_the_face": "يناسب الوجه، مثلًا {example}",
"thread.form_bspp": "سن أنابيب G (ISO 228-1)",
"thread.form_bspt": "سن أنابيب مخروطي R، Rc (ISO 7-1)",
"thread.form_npt": "سن أنابيب مخروطي NPT (ASME B1.20.1)",
@@ -4363,6 +4487,7 @@
"ui.3d_sketch": "رسم ثلاثي الأبعاد",
"ui.activate": "تنشيط",
"ui.active_component": "المكوّن النشط",
"ui.addToQuickAccess": "إضافة إلى الوصول السريع",
"ui.add_configuration": "إضافة تكوين",
"ui.add_parameter": "إضافة معامل",
"ui.agent_chat": "محادثة الوكيل",
@@ -4464,7 +4589,7 @@
"ui.agent_chat_no_images": "هذا الوكيل لا يقبل الصور",
"ui.agent_chat_no_picture": "لا توجد صورة في الحافظة",
"ui.agent_chat_no_tool_call": "لم يستخدم النموذج أدوات SolidCraft، لذا لم يتغيّر التصميم. اختر نموذجًا يدعم استدعاء الأدوات؛ راجع «النماذج المحلية» في دليل محادثة الوكيل.",
"ui.agent_chat_node_too_old": "إصدار Node.js ‏{version} قديم جدًا لهذا الوكيل. ثبّت Node.js 20.10 أو أحدث، ثم اضغط على تحديث.",
"ui.agent_chat_node_too_old": "إصدار Node.js {version} قديم جدًا لهذا الوكيل. ثبّت Node.js 20.10 أو أحدث، ثم اضغط تحديث.",
"ui.agent_chat_none_found": "لم يُعثر على وكيل برمجي. ثبّت أحد هذه ثم اضغط تحديث:",
"ui.agent_chat_none_found_short": "لم يُعثر على وكيل",
"ui.agent_chat_not_installed": "غير مثبت",
@@ -4505,7 +4630,7 @@
"ui.agent_chat_send_now_tip": "إيقاف خطوة الوكيل الحالية وإرسال هذه بعدها (Ctrl+Enter)",
"ui.agent_chat_setup_body": "يتواصل SolidCraft مع وكيل برمجة مثبّت على هذا الحاسوب. اختر واحدًا من التفضيلات › الوكيل.",
"ui.agent_chat_setup_title": "إعداد مساعد",
"ui.agent_chat_side": "إرساء المحادثة",
"ui.agent_chat_side": "تثبيت المحادثة",
"ui.agent_chat_signed_in": "سجّل الدخول",
"ui.agent_chat_state_auth": "يلزم تسجيل الدخول",
"ui.agent_chat_state_busy": "جارٍ العمل…",
@@ -4570,10 +4695,10 @@
"ui.ai_key_note": "يُخزّن في سلسلة مفاتيح النظام، وليس في إعدادات SolidCraft أبدًا. اتركه فارغًا لخادم محلي.",
"ui.ai_keychain_error": "رفضت سلسلة مفاتيح النظام: {error}",
"ui.ai_kind": "النوع",
"ui.ai_mode_all": "فعل كل شيء",
"ui.ai_mode_all": "تنفيذ كل شيء",
"ui.ai_mode_all_note": "لا شيء يسأل أولًا. تبقى تقارير الأعطال محظورة وتظل حدود المجلدات سارية.",
"ui.ai_mode_all_warning": "يمكن للوكيل حفظ تصميمك والكتابة فوقه وتصديره واستبداله دون سؤال. استخدم هذا فقط مع وكيل تثق به، وعلى تصاميم حفظتها.",
"ui.ai_mode_ask": "السؤال أولاً",
"ui.ai_mode_ask": "السؤال أولًا",
"ui.ai_mode_ask_note": "كل تغيير في التصميم وكل إجراء على الملفات ينتظر سماحك.",
"ui.ai_mode_edits": "إجراء تعديلات",
"ui.ai_mode_edits_note": "أوامر النمذجة تعمل دون سؤال؛ أما الحفظ والفتح والتصدير وإجراءات الملفات أو الإجراءات الهدّامة الأخرى فتسأل.",
@@ -4697,6 +4822,7 @@
"ui.click_selects_whole_bodies": "النقر يحدد الأجسام كاملة",
"ui.click_then_press_key": "انقر ثم اضغط المفتاح الجديد",
"ui.close": "إغلاق",
"ui.close_tab": "إغلاق علامة التبويب",
"ui.coil": "ملف حلزوني",
"ui.coincident": "متطابق",
"ui.collapse": "طي",
@@ -4744,21 +4870,21 @@
"ui.construction.ribbon_label": "خطوط مساعدة",
"ui.construction.side_hint": "خط إنشائي: انقر الجهة التي يمتد نحوها الخط؛ انقر بزر الفأرة الأيمن للبدء من جديد",
"ui.context_menu": "القائمة السياقية",
"ui.continuity_crease": "تجعّد",
"ui.continuity_crease": "ثنية",
"ui.continuity_curvature": "الانحناء",
"ui.continuity_curvature_change": "تغيّر الانحناء",
"ui.continuity_curvature_change_tip": "تغيّر الانحناء (G3): يتغير الانحناء أيضًا بسلاسة عبر خط الالتقاء.",
"ui.continuity_curvature_tip": "متصل الانحناء (G2): تمر الانعكاسات دون انكسار.",
"ui.continuity_g0": "G0 تجعّد",
"ui.continuity_g0": "G0 ثنية",
"ui.continuity_g1": "G1 مماس",
"ui.continuity_g2": "G2 انحناء",
"ui.continuity_gap": "فجوة",
"ui.continuity_position": "الموضع",
"ui.continuity_position_tip": "الموضع (G0): تلتقي الأسطح، مع تجعّد عند التقائها.",
"ui.continuity_position_tip": "الموضع (G0): تلتقي الأسطح، مع ثنية عند التقائها.",
"ui.continuity_smooth_curvature_change": "تغيّر انحناء سلس",
"ui.continuity_smooth_curvature_change_tip": "تغيّر انحناء سلس (G4): حتى طريقة تغيّر الانحناء تستمر بسلاسة عبر خط الالتقاء.",
"ui.continuity_tangent": "مماس",
"ui.continuity_tangent_tip": "مماس (G1): لا تجعّد، لكن الانحناء يقفز عبر خط الالتقاء.",
"ui.continuity_tangent_tip": "مماس (G1): لا ثنية، لكن الانحناء يقفز عبر خط الالتقاء.",
"ui.copy": "نسخ",
"ui.copy_details": "نسخ التفاصيل",
"ui.copy_details_tip": "انسخ الرسالة كاملة للبحث عنها أو للإبلاغ عنها.",
@@ -4784,6 +4910,7 @@
"ui.default_modelling_orientation": "اتجاه النمذجة الافتراضي",
"ui.delete": "حذف",
"ui.delete_select_first": "حدّد أولًا ما تريد حذفه: الميزات أو الأجسام أو المكوّنات أو هندسة الرسم",
"ui.dependents_select_first": "حدّد أولًا ما تريد سرد معتمداته",
"ui.depth": "العمق",
"ui.design": "التصميم",
"ui.design_properties.author": "المؤلف",
@@ -4909,10 +5036,15 @@
"ui.extract_done": "أصبح المكوّن تصميمًا مستقلًا، مرتبطًا هنا.",
"ui.extrude": "بثق",
"ui.extrude_auto_to_object": "البثق إلى هدف مختار",
"ui.extrude_auto_to_object_tip": "وجه أو رأس أو مستوى موازٍ يغيّر المدى إلى «إلى كائن».",
"ui.extrude_auto_to_object_tip": "وجه أو رأس أو مستوى موازٍ يغيّر الامتداد إلى «إلى كائن».",
"ui.factor": "المعامل",
"ui.fade": "تلاشٍ",
"ui.fade_length": "طول التلاشي",
"ui.fair_iterations": "عدد التكرارات",
"ui.fair_keep_edges": "الحفاظ على الحواف",
"ui.fair_strength": "شدة التنعيم",
"ui.fair_surface_title": "تنعيم السطح",
"ui.fair_tolerance_default": "0.001 من حجم الجسم",
"ui.feature_history": "سجل الميزات",
"ui.feature_options": "خيارات الميزة",
"ui.features": "الميزات",
@@ -4939,9 +5071,13 @@
"ui.fillet_setback": "مسافة الإرجاع",
"ui.fillet_setback_hint": "{factor} × نصف القطر",
"ui.fillet_setback_least": "أكثر من {least}",
"ui.fillet_setback_tip": "حيث تبدأ رقعة الزاوية الناعمة: المسافة على طول كل حافة من الزاوية. الافتراضي {factor} ضعف نصف القطر. يجب أن تكون أكبر من أقل مسافة إرجاع، حيث تلتقي الاستدارات بدونها: نصف القطر حيث تكون الأوجه متعامدة.",
"ui.fillet_setback_tip": "حيث تبدأ رقعة الزاوية الناعمة: المسافة على طول كل حافة من الزاوية. الافتراضي {factor} ضعف نصف القطر. يجب أن تكون أكبر من أقل مسافة إرجاع، وهي التي تلتقي عندها التقويسات دون إرجاع: نصف القطر حين تكون الأوجه متعامدة.",
"ui.fillet_smooth_radius": "تغيّر ناعم لنصف القطر",
"ui.fillet_type_choices_tip": "نصف قطر ثابت: نصف قطر واحد على طول الحافة.\nطول الوتر: يبقى عرض التقويس نفسه مهما كانت الزاوية بين الوجوه.\nنصف قطر متغيّر: نصف قطر عند كل طرف، ومزيد على طول الحافة إن أضفته.\nمتصل الانحناء: مقطع بلا انكسار في الانعكاسات حيث يلتقي بالوجوه المستوية (G2).\nمنحنى: يتبع نصف القطر على طول الحافة أنصاف أقطار تحكّم تحدّدها.",
"ui.fillet_turn": "الالتفاف",
"ui.fillet_turn_mitre": "وصلة مائلة",
"ui.fillet_turn_swept": "ملتف",
"ui.fillet_turn_tip": "حيث يلتف تقويسان حول حافة ركن حادة بينهما، مثل التقويسات على قاعدة نتوء عند حوافه القائمة.\nوصلة مائلة: يمتد التقويسان حتى يلتقيا في وصلة مائلة.\nملتف: يتوقف التقويسان عند حافة الركن ويلتفان حولها، والركن طارة مماسة لكليهما. فقط لتقويسين بنصف قطر واحد حول حافة ركن مستقيمة ومحدبة.",
"ui.fillet_type_choices_tip": "نصف قطر ثابت: نصف قطر واحد على طول الحافة.\nطول الوتر: يبقى عرض التقويس نفسه مهما كانت الزاوية بين الوجوه.\nنصف قطر متغيّر: نصف قطر عند كل طرف، ومزيد على طول الحافة إن أضفته.\nمتصل الانحناء: مقطع بلا انكسار في الانعكاسات حيث يلتقي بالوجوه المستوية (G2).\nمنحنى شريحي: يتبع نصف القطر على طول الحافة أنصاف أقطار تحكّم تحدّدها.",
"ui.fillet_type_curvature": "متصل الانحناء",
"ui.fillet_type_tip": "المقطع عبر الحافة. متصل الانحناء: مقطع متصل الانحناء (G2) بين وجوه مستوية، فتمر الانعكاسات من الوجوه إلى التقويس دون انكسار.",
"ui.filter_commands_or_keys": "تصفية الأوامر أو المفاتيح",
@@ -4986,6 +5122,7 @@
"ui.heading_construct": "إنشاء مرجعي",
"ui.heading_create": "إنشاء",
"ui.heading_create_sketch": "إنشاء رسم",
"ui.heading_custom_view_navigator": "متصفح العرض المخصص",
"ui.heading_cylinder": "أسطوانة",
"ui.heading_design": "التصميم",
"ui.heading_draft": "ميل السحب",
@@ -5053,7 +5190,7 @@
"ui.hidePlanes": "إخفاء المستويات",
"ui.hideProfile": "إخفاء المقطع",
"ui.hide_component_history": "إخفاء سجله",
"ui.hide_origin_by_default": "إخفاء مجلد الأصل في التصاميم الجديدة",
"ui.hide_origin_by_default": "إخفاء مجلد نقطة الأصل في التصاميم الجديدة",
"ui.hide_origin_note": "تبدأ التصاميم الجديدة ومجلد نقطة الأصل مخفي. يحتفظ كل تصميم بإعداد نقطة الأصل الخاص به بمجرد إظهارها أو إخفائها.",
"ui.history_back": "الرجوع خطوة",
"ui.history_below_browser": "أسفل المستعرض",
@@ -5093,6 +5230,7 @@
"ui.inspect": "فحص",
"ui.instances": "النسخ",
"ui.intersect": "تقاطع",
"ui.intersection_curve_second_pick": "منحنى التقاطع: انقر على الوجه أو المنحنى الثاني (انقر بزر الماوس الأيمن للبدء من جديد)",
"ui.iso_4753": "ISO 4753",
"ui.isolate": "عزل",
"ui.join": "ضم",
@@ -5121,6 +5259,7 @@
"ui.manipulator_free_alt": "السحب الحر للمُعالج أيضًا مع Alt/Option",
"ui.manipulator_free_alt_tip": "عند التفعيل: Alt/Option أو Ctrl/Cmd على أسهم البثق / أداة التحويل يعطّل التقاط القيم. عند الإيقاف: Ctrl/Cmd فقط.",
"ui.manufacture": "التصنيع",
"ui.match_surface_title": "مطابقة السطح",
"ui.material_review.axes": "محاور محلية للمادة؛ لم يُحدد اتجاه للجسم.",
"ui.material_review.claims": "المصادر والتراخيص ادعاءات غير موثّقة. القيم المفقودة تبقى مجهولة.",
"ui.material_review.compressive_strength": "مقاومة الانضغاط",
@@ -5143,6 +5282,9 @@
"ui.material_review.title": "مراجعة بيانات المادة الفيزيائية",
"ui.material_review.unknown": "مجهول",
"ui.material_review.yield_strength": "مقاومة الخضوع",
"ui.mcp_agents": "الوكلاء عبر MCP",
"ui.mcp_compact": "مخرجات مضغوطة",
"ui.mcp_compact_note": "للوكلاء المتصلين عبر خادم MCP ‏(solidcraft-cli mcp): يلخّص inspect_design الرسومات، ولا تُنسَّق نتائج الأدوات، فتستهلك رموزًا أقل. يسري على جلسات الوكلاء التي تبدأ بعد التغيير؛ ويفعّله أيضًا ‎--compact أو SOLIDCRAFT_MCP_COMPACT=1.",
"ui.measure": "قياس",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "3MF مع وسوم الأوجه",
@@ -5184,6 +5326,7 @@
"ui.options": "الخيارات",
"ui.orbit": "تدوير العرض",
"ui.orbit_around": "التدوير حول",
"ui.organize": "تنظيم",
"ui.orientation": "الاتجاه",
"ui.origin": "نقطة الأصل",
"ui.palette_construction_mode": "وضع الخطوط المساعدة (X)",
@@ -5228,6 +5371,7 @@
"ui.point_cloud_add_filter": "إضافة مرشح…",
"ui.point_cloud_auto_range": "نطاق تلقائي",
"ui.point_cloud_color_by": "التلوين حسب",
"ui.point_cloud_dots": "نقاط منفردة",
"ui.point_cloud_file_colors": "ألوان الملف",
"ui.point_cloud_filter_by": "التصفية حسب",
"ui.point_cloud_height_high": "أعلى ارتفاع",
@@ -5272,6 +5416,7 @@
"ui.quality": "الجودة",
"ui.quantity": "الكمية",
"ui.quantity_2": "الكمية 2",
"ui.quick_access_full": "يتسع شريط الوصول السريع لـ {count} أمرًا؛ أزل أحدها لإضافة آخر",
"ui.quit": "خروج",
"ui.radial_context_menu": "قائمة سياقية دائرية",
"ui.radius": "نصف القطر",
@@ -5289,6 +5434,7 @@
"ui.relink_not_linked": "هذا المكوّن غير مرتبط بتصميم مصدر.",
"ui.relink_select_one": "حدد مكوّنًا مرتبطًا واحدًا أو إحدى نسخه أولًا.",
"ui.relink_source_all": "إعادة ربط المصدر (كل النسخ)…",
"ui.removeFromQuickAccess": "إزالة من الوصول السريع",
"ui.removeFromToolbar": "إزالة من شريط الأدوات",
"ui.removeSection": "إزالة المقطع",
"ui.render": "التصيير",
@@ -5297,6 +5443,8 @@
"ui.repeat_nothing": "لا يوجد ما يُكرَّر بعد: ابدأ أداة أو مربع حوار أولًا.",
"ui.replace_with_link_done": "أصبحت النسخ الآن مواضع للتصميم المرتبط.",
"ui.report_a_bug": "الإبلاغ عن خطأ",
"ui.report_characters_left_one": "الأحرف المتبقية: {count}",
"ui.report_characters_left_other": "الأحرف المتبقية: {count}",
"ui.reset": "إعادة تعيين",
"ui.reset_all": "إعادة تعيين الكل",
"ui.reset_the_settings_on_this_page": "إعادة تعيين إعدادات هذه الصفحة",
@@ -5581,6 +5729,14 @@
"values must be numbers or expressions": "يجب أن تكون القيم أعدادًا أو تعابير",
"view.auto_ortho_faces": "منظوري أثناء التدوير، ومتعامد على الأوجه",
"view.render_inspector": "مفتش التصيير",
"view_gizmo.arrows": "أسهم التدوير",
"view_gizmo.home": "زر العرض الرئيسي",
"view_gizmo.no_such_body": "لا يوجد في هذا التصميم جسم بهذا الاسم.",
"view_gizmo.note": "اختر جسمًا في العرض. يُحفظ مع تفضيلاتك وملفات تعريف الإعدادات، ويظهر في كل تصميم. انقر أحد أوجهه للنظر إليه مباشرة.",
"view_gizmo.texture": "الخامة",
"view_gizmo.themed": "حسب السمة",
"view_gizmo.too_detailed": "مفصّل أكثر مما يناسب متصفح العرض (أكثر من 10,000 مثلث)",
"view_gizmo.too_thin": "طويل ورفيع أكثر مما يناسب متصفح العرض: يجب ألا يقل أرفع جانب عن خُمس أطول جانب",
"web.files.insert_drawings_hint": "ملفات CAD أو شبكات أو DXF أو SVG إلى التصميم المفتوح",
"web.files.insert_stored_drawings_hint": "إلى التصميم المفتوح (ملفات CAD، شبكات، DXF، SVG)",
"web.open_full": "فتح في SolidCraft",
+1 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analiza dostupnosti",
"cmd.inspect_center_of_mass": "Centar mase",
"cmd.inspect_clearance": "Provjera zazora",
"cmd.inspect_colored_components": "Omogući boje komponenti",
"cmd.inspect_colored_components_disable": "Onemogući boje komponenti",
"cmd.inspect_compare": "Uporedi tijela",
"cmd.inspect_compare_picture": "Uporedi sliku",
"cmd.inspect_continuity": "Analiza kontinuiteta",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Učitava putanje SVG crteža u skicu, skalirane, zakrenute i postavljene gdje zadate. Koristite ga da logotip ili vektorski obris pretvorite u krive koje možete ekstrudirati.",
"help.sketch.inspect": "Otvara izvještaj o otvorenoj skici: koliko je stepeni slobode preostalo i je li potpuno ograničena. Koristite ga da saznate šta se još pomjera prije nego što završite skicu.",
"help.sketch.intersect": "Dodaje krive ili tačke gdje tijela, lica, ivice ili druge skice sijeku ravan skice. Ostaju povezane i prate model kada se promijeni.",
"help.sketch.intersection_curve": "Pravi 3D krive tamo gdje se dva tijela ili lica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
"help.sketch.intersection_curve": "Pravi 3D krive tamo gdje se dva tijela, lica ili krive drugih skica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
"help.sketch.isoparametric_curve": "Pravi 3D krivu u smjeru U ili V lica kroz tačku koju izaberete, povezanu s licem. Koristite je kao putanju ili liniju razdvajanja koja prati površ.",
"help.sketch.line": "Crta povezane linije tačku po tačku, kao otvoreni lanac ili zatvoren obris. To je osnovni alat za većinu profila.",
"help.sketch.line.midpoint": "Crta liniju od njene središnje tačke do jednog kraja, a drugi kraj se preslikava. Koristite je za linije centrirane na tački, npr. simetričnu ivicu.",
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analýza přístupnosti",
"cmd.inspect_center_of_mass": "Těžiště",
"cmd.inspect_clearance": "Vůle",
"cmd.inspect_colored_components": "Zapnout barvy komponent",
"cmd.inspect_colored_components_disable": "Vypnout barvy komponent",
"cmd.inspect_compare": "Porovnat tělesa",
"cmd.inspect_compare_picture": "Porovnat obrázek",
"cmd.inspect_continuity": "Analýza spojitosti",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Načte cesty výkresu SVG do náčrtu, ve zvoleném měřítku, natočení a poloze. Použijte to k přeměně loga nebo vektorového obrysu na křivky, které lze vysunout.",
"help.sketch.inspect": "Otevře zprávu o otevřeném náčrtu: kolik stupňů volnosti zbývá a zda je plně zavazbený. Použijte to ke zjištění, co se ještě hýbe, než náčrt dokončíte.",
"help.sketch.intersect": "Přidá křivky nebo body, kde tělesa, plochy, hrany nebo jiné náčrty protínají rovinu náčrtu. Zůstanou propojené a sledují model, když se změní.",
"help.sketch.intersection_curve": "Vytvoří 3D křivky tam, kde se setkávají dvě tělesa nebo plochy, propojené s nimi. Použijte je jako cesty nebo vodicí dráhy, které sledují model.",
"help.sketch.intersection_curve": "Vytvoří 3D křivky tam, kde se setkávají dvě tělesa, plochy nebo křivky jiných náčrtů, propojené s nimi. Použijte je jako cesty nebo vodicí dráhy, které sledují model.",
"help.sketch.isoparametric_curve": "Vytvoří 3D křivku ve směru U nebo V plochy bodem, který vyberete, propojenou s plochou. Použijte ji jako cestu nebo dělicí čáru, která sleduje povrch.",
"help.sketch.line": "Kreslí spojené úsečky bod po bodu, jako otevřený řetězec nebo uzavřený obrys. Je to základní nástroj pro většinu profilů.",
"help.sketch.line.midpoint": "Nakreslí úsečku od jejího středu k jednomu konci, přičemž druhý konec se zrcadlí. Použijte to pro úsečky vystředěné na bod, například souměrnou hranu.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Ukázková deska",
"sample.vents.about": "Drážky v obdélníkovém poli: řádky, sloupce a rozteč jsou parametry",
"sample.vents.name": "Větrací deska",
"scale.per_axis": "Podle os",
"scale.uniform": "Rovnoměrně",
"search.command_keys": "Enter spustit Shift+Enter spustit a ponechat otevřené Esc zavřít",
"search.commands": "Příkazy",
"search.design": "V tomto návrhu",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Tilgængelighedsanalyse",
"cmd.inspect_center_of_mass": "Massecentrum",
"cmd.inspect_clearance": "Frigangskontrol",
"cmd.inspect_colored_components": "Aktivér komponentfarver",
"cmd.inspect_colored_components_disable": "Deaktivér komponentfarver",
"cmd.inspect_compare": "Sammenlign emner",
"cmd.inspect_compare_picture": "Sammenlign billede",
"cmd.inspect_continuity": "Kontinuitetsanalyse",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Læser stierne i en SVG-tegning ind i en skitse, skaleret, drejet og placeret, som du angiver. Brug den til at gøre et logo eller en vektorkontur til kurver, du kan ekstrudere.",
"help.sketch.inspect": "Åbner en rapport om den åbne skitse: hvor mange frihedsgrader der er tilbage, og om den er fuldt bundet. Brug den til at finde ud af, hvad der stadig kan bevæge sig, før du afslutter skitsen.",
"help.sketch.intersect": "Tilføjer de kurver eller punkter, hvor emner, flader, kanter eller andre skitser krydser skitseplanet. De forbliver linkede og følger modellen, når den ændres.",
"help.sketch.intersection_curve": "Opretter 3D-kurver, hvor to emner eller flader mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
"help.sketch.intersection_curve": "Opretter 3D-kurver, hvor to emner, flader eller kurver fra andre skitser mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
"help.sketch.isoparametric_curve": "Opretter en 3D-kurve langs en flades U- eller V-retning gennem et punkt, du vælger, linket til fladen. Brug den som bane eller delelinje, der følger overfladen.",
"help.sketch.line": "Tegner forbundne linjer punkt for punkt som en åben kæde eller en lukket kontur. Det er det grundlæggende værktøj til de fleste profiler.",
"help.sketch.line.midpoint": "Tegner en linje fra dens midtpunkt ud til den ene ende, og den anden ende spejles. Brug den til linjer centreret om et punkt, f.eks. en symmetrisk kant.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Eksempelplade",
"sample.vents.about": "Slidser i et rektangulært mønster: Rækker, kolonner og deling er parametre",
"sample.vents.name": "Ventilationsplade",
"scale.per_axis": "Per akse",
"scale.uniform": "Ensartet",
"search.command_keys": "Enter kør Shift+Enter kør og hold åben Esc luk",
"search.commands": "Kommandoer",
"search.design": "I dette design",
+108 -3
View File
@@ -628,6 +628,8 @@
"Suppress Selected Folders": "Ausgewählte Ordner unterdrücken",
"Suppressed": "Unterdrückt",
"Surface": "Flächenkörper",
"Surface or face": "Flächenkörper oder Fläche",
"Surface or faces": "Flächenkörper oder Flächen",
"Surface, or faces to turn over": "Fläche oder umzudrehende Flächen",
"Surfaces": "Flächen",
"Swap Ends": "Enden vertauschen",
@@ -641,6 +643,7 @@
"Target": "Ziel",
"Target Body": "Zielkörper",
"Target Face (optional)": "Zielfläche (optional)",
"Target edges": "Zielkanten",
"Tear": "Reißen",
"Technical details": "Technische Details",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "Die gestrichelte Skizze ist eine gespeicherte Ebenenführung. Wählen Sie eine neue Fläche, um die Anbindung zu reparieren.",
@@ -648,6 +651,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Die Richtung muss eine Achse oder eine gerade Skizzenkurve in der Skizzenebene sein",
"The mirror line cannot also be mirrored": "Die Spiegellinie kann nicht gleichzeitig gespiegelt werden",
"The mirror line must be a straight curve of this sketch, not an origin axis": "Die Spiegellinie muss eine gerade Kurve dieser Skizze sein, keine Ursprungsachse",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Die Ursprungsachse steht senkrecht auf der Skizzenebene und hat daher keine Linie in ihr",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Das gespeicherte Autorvorkommen fehlt. Wählen Sie vor dem erneuten Anbinden eine gültige Autorplatzierung.",
"Then": "Then",
"These features will be deleted:": "Diese Merkmale werden gelöscht:",
@@ -739,6 +743,9 @@
"a picked edge's body is gone": "der Körper einer gewählten Kante existiert nicht mehr",
"add a key for a joint first": "zuerst einen Schlüssel für ein Gelenk hinzufügen",
"ai.connected_toast": "SolidCraft ist jetzt in {clients} verfügbar. Starten Sie diese neu, um es zu nutzen (Einstellungen > Assistent).",
"ai.http.allow": "Unsicheres HTTP erlauben",
"ai.http.confirm": "Für diesen Endpunkt erlauben",
"ai.http.warning": "Der API-Schlüssel und die Unterhaltung werden unverschlüsselt gesendet. Jeder mit Zugriff auf das Netzwerk kann sie lesen oder verändern. Verwenden Sie dies nur in einem vertrauenswürdigen Netzwerk.",
"ai.prefs.allow_control": "KI-Agenten verbinden lassen",
"ai.prefs.allow_control_note": "Ein lokaler Kanal (MCP). Nichts verlässt diesen Computer.\n\nÖffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
"ai.prefs.availability": "Verfügbarkeit",
@@ -960,8 +967,6 @@
"cmd.inspect_accessibility": "Zugänglichkeitsanalyse",
"cmd.inspect_center_of_mass": "Massenschwerpunkt",
"cmd.inspect_clearance": "Freiraum",
"cmd.inspect_colored_components": "Komponentenfarben einschalten",
"cmd.inspect_colored_components_disable": "Komponentenfarben ausschalten",
"cmd.inspect_compare": "Körper vergleichen",
"cmd.inspect_continuity": "Kontinuitätsprüfung",
"cmd.inspect_curvature_comb": "Krümmungskamm-Analyse",
@@ -1076,6 +1081,9 @@
"cmd.render.compare.divider": "Render-Vergleichstrenner verschieben",
"cmd.render.result.discard": "Renderergebnis verwerfen",
"cmd.render.result.select": "Renderergebnis auswählen",
"cmd.render.surface.import": "Oberflächentexturen importieren",
"cmd.render.texture.list": "Eingebettete Oberflächenbilder auflisten",
"cmd.render.texture.remove": "Eingebettetes Oberflächenbild entfernen",
"cmd.render_aging_describe": "Mehrschichtige Oberflächenalterung",
"cmd.render_backends": "Render-Backends",
"cmd.render_batch": "Alle Aufnahmen rendern",
@@ -1291,6 +1299,7 @@
"cmd.timeline_stop": "Verlauf anhalten",
"cmd.timeline_suppress": "Merkmal unterdrücken",
"cmd.ui.assistant": "Assistenten ein-/ausblenden",
"cmd.view.navigator": "Eigener Ansichtsnavigator",
"cmd.view.render_browser": "Render-Browser",
"cmd.view.render_dock": "Render-Panel verschieben",
"cmd.view.render_focus": "Render-Fokusansicht",
@@ -1389,11 +1398,17 @@
"dialog.computing": "Wird berechnet… {seconds} s",
"dialog.could_not_be_checked": "nicht prüfbar",
"dialog.custom_axis": "↖ Eigene Achse",
"dialog.cylinder_face_tip": "Die Fläche oder der Eckpunkt des Modells, auf dem der Zylinder steht: hier klicken, dann in der Ansicht darauf klicken. Leer gelassen platzieren ihn X, Y und Z.",
"dialog.dimension_insert_hint": "Doppelklicken Sie auf eine andere steuernde Bemaßung, um ihren Namen einzufügen",
"dialog.dimension_name": "Bemaßung: {name}",
"dialog.dimension_step_down": "Um {step} verringern",
"dialog.dimension_step_expression": "Nur eine Zahl lässt sich schrittweise ändern; ein Ausdruck bleibt, wie er eingegeben ist",
"dialog.dimension_step_up": "Um {step} erhöhen",
"dialog.edit_title": "{title} bearbeiten",
"dialog.no_shown_body": "Kein sichtbarer Körper zum Ändern vorhanden. Blenden Sie einen Körper ein oder wählen Sie „Neuer Körper“.",
"dialog.pattern_sketch_not_object": "Körper oder Funktionen wählen: Eine Skizze ist keines von beiden. Ihre Kurven mit Muster oder Spiegeln in der Skizze wiederholen oder sie zuerst extrudieren.",
"dialog.primitive_point_tip": "Ein Punkt, der X, Y und Z füllt: hier klicken, dann einen Eckpunkt, eine Kante oder Fläche des Modells, einen Punkt einer Skizze oder den Ursprung anklicken.",
"dialog.reference_edge_tip": "Eine gerade Kante der gewählten Fläche, von der aus der Abstand darunter gemessen wird: hier klicken, dann die Kante anklicken.",
"dialog.select": "↖ Auswählen",
"dialog.selected_count": "↖ {count} ausgewählt",
"dialog.selection_constraint": "Bedingung {id}",
@@ -1415,6 +1430,7 @@
"document.read_only_save": "Dieses Design wurde schreibgeschützt geöffnet: Speichern Sie es als neue Datei",
"document.save_changes": "Änderungen in {name} speichern?",
"document.save_copy": "Kopie speichern…",
"document.tab_unsaved_changes": "Ungespeicherte Änderungen",
"document.unsaved_changes": "{name} enthält ungespeicherte Änderungen. Wenn Sie nicht speichern, gehen sie verloren.",
"drive.rigid": "{joint} ist ein starres Gelenk. Es hält die verbundenen Teile fest zusammen und ermöglicht daher keine Bewegung. Um die Teile beweglich zu machen, ändern Sie den Gelenktyp unter „Gelenk bearbeiten“: Drehgelenk für Drehbewegungen, Schubgelenk für lineare Bewegungen oder Zylindergelenk für beides.",
"drive.stopped_by_contact": "Angehalten bei {value}: {a} würde {b} durchdringen",
@@ -1435,10 +1451,16 @@
"each rail's curves must be in one sketch": "die Kurven jeder Führungskurve müssen in derselben Skizze liegen",
"feature computation ended without a result": "die Berechnung des Merkmals wurde ohne Ergebnis beendet",
"file.open.drawing_filter": "Designs, CAD-Dateien, Netze und Zeichnungen",
"file.open.portal_fallback.no_portal_service": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Der Dateiauswahl-Dienst des Desktops ist nicht verfügbar.",
"file.open.portal_fallback.no_session_bus": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Es gibt keinen D-Bus-Sitzungsbus.",
"file.open.portal_fallback.xwayland": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Unter XWayland lehnt das Portal das Elternfenster ab.",
"file_load.built": "Bisher erstellt: {built} von {total} Merkmalen.",
"file_load.converting": "Volumenkörper {done} von {total} wird konvertiert",
"file_load.inserted": "{name} eingefügt",
"file_load.inserting": "Geometrie wird eingefügt",
"file_load.link_item": "{name} ({done} von {total})",
"file_load.opened": "{name} geöffnet",
"file_load.opened_in_tab": "{name} in neuem Tab geöffnet",
"file_load.opening": "Design wird geöffnet",
"file_load.preparing_thumbnail": "Vorschaubild wird vorbereitet...",
"file_load.preparing_view": "Ansicht wird vorbereitet...",
@@ -1674,7 +1696,7 @@
"help.sketch.insert_svg": "Liest die Pfade einer SVG-Zeichnung in eine Skizze ein, skaliert, gedreht und an der gewünschten Stelle. Damit machen Sie aus einem Logo oder einer Vektorkontur Kurven, die sich extrudieren lassen.",
"help.sketch.inspect": "Öffnet einen Bericht zur offenen Skizze: wie viele Freiheitsgrade übrig sind und ob sie vollständig bestimmt ist. Damit sehen Sie, was sich noch bewegt, bevor Sie die Skizze abschließen.",
"help.sketch.intersect": "Fügt die Kurven oder Punkte hinzu, an denen Körper, Flächen, Kanten oder andere Skizzen die Skizzenebene schneiden. Sie bleiben verknüpft und folgen Änderungen am Modell.",
"help.sketch.intersection_curve": "Erzeugt 3D-Kurven, wo sich zwei Körper oder Flächen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
"help.sketch.intersection_curve": "Erzeugt 3D-Kurven, wo sich zwei Körper, Flächen oder Kurven anderer Skizzen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
"help.sketch.isoparametric_curve": "Erzeugt eine 3D-Kurve in U- oder V-Richtung einer Fläche durch einen gewählten Punkt, mit der Fläche verknüpft. Als Pfad oder Trennlinie, die der Fläche folgt.",
"help.sketch.line": "Zeichnet verbundene Linien Punkt für Punkt, als offene Kette oder geschlossene Kontur. Das Grundwerkzeug für die meisten Profile.",
"help.sketch.line.midpoint": "Zeichnet eine Linie von ihrem Mittelpunkt zu einem Ende, das andere Ende gespiegelt. Für Linien, die auf einem Punkt mittig liegen, etwa eine symmetrische Kante.",
@@ -1982,6 +2004,7 @@
"material.selection.mixed": "Unterschiedliche Materialien",
"measure.at": "Bei",
"measure.item": "Element {number}",
"measure.line_to_centre": "Linie zum Mittelpunkt",
"measure.quick_copied": "Messwert kopiert",
"measure.quick_copy": "Klicken, um den Messwert zu kopieren",
"measure.quick_edges": "{count} Kanten",
@@ -2012,6 +2035,8 @@
"nav.blender.help": "Mittlere Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt; rechtes Ziehen lässt die Kamera unverändert",
"nav.creo.help": "Ziehen mit der mittleren Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt beim vertikalen Ziehen (nach unten zum Vergrößern); das Mausrad zoomt",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "Ein Ziehen mit der mittleren oder rechten Taste dreht, verschiebt oder zoomt weiter wie begonnen, bis die Taste losgelassen wird, egal welche Tasten dabei gedrückt oder losgelassen werden. Aus: Die gehaltenen Tasten entscheiden in jedem Moment des Ziehens.",
"nav.drag_keeps_mode.label": "Ziehen behält die Navigation bei, mit der es begann",
"nav.free_orbit.help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollneigung bei. Das Einrasten mit Umschalt ist deaktiviert.",
"nav.free_orbit.label": "Freie Drehung",
"nav.free_orbit.snap_help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollausrichtung bei. Halten Sie beim Ziehen zum Drehen die Umschalttaste gedrückt, um Blickrichtung und Rollausrichtung einrasten zu lassen.",
@@ -2248,6 +2273,14 @@
"prefs.auto_operation_note": "Vereinigung, Neuer Körper oder Schnitt, je nachdem, was das Material berührt.",
"prefs.axes": "Achsen",
"prefs.axis": "Achse",
"prefs.blur_cost": "Relativer Renderaufwand. Mehr Balken bedeuten meist mehr Arbeit.",
"prefs.blur_device": "Auf diesem Grafikgerät oder Renderpfad nicht verfügbar",
"prefs.blur_guard": "Die Unschärfequalität wurde für flüssigere Bilder verringert. Der Radius bleibt unverändert.",
"prefs.blur_off": "Aus",
"prefs.blur_reduce_transparency": "Durch die Systemeinstellung zur Transparenzreduzierung deaktiviert",
"prefs.blur_software": "Mit Softwaregrafik nicht verfügbar (gpu-software)",
"prefs.blur_source": "In dieser Arbeitsbereichsansicht nicht verfügbar",
"prefs.blur_web": "Auf diesem Web-Renderpfad nicht verfügbar",
"prefs.bodies_folders": "Körperordner",
"prefs.bodies_folders_always": "Immer",
"prefs.bodies_folders_never": "Nie",
@@ -2261,6 +2294,8 @@
"prefs.browser_eye_left": "Auge neben dem Aufklapppfeil",
"prefs.browser_eye_left_note": "Browserzeilen zeigen ihr Auge zum Ein- und Ausblenden wie bisher neben dem Aufklapppfeil statt in einer Spalte am rechten Zeilenende.",
"prefs.browser_reveal_on_hover": "Beim Überfahren einblenden",
"prefs.browser_rows_start_closed": "Browser-Zeilen beginnen geschlossen",
"prefs.browser_rows_start_closed_note": "Platzierungen und Ordner im Browser beginnen geschlossen, sodass ein Entwurf mit vielen Komponenten seine obersten Zeilen zeigt. Jede öffnet sich mit ihrem Pfeil, „Alle ausklappen“ oder „Im Browser suchen“, das nur die Zeilen bis zu seinem Körper öffnet und die von der letzten Suche geöffneten wieder schließt. Ein in der 3D-Ansicht gewählter Körper lässt den Browser, wie er ist. Ist es aus, beginnen die Ordner geöffnet, Platzierungen so, wie „Komponenten zu Beginn aufgeklappt“ es festlegt, und eine Auswahl in der 3D-Ansicht öffnet den Browser bis zu ihrem Körper, wie bisher.",
"prefs.browser_show_note": "Angezeigt lässt den Browser auf dem Bildschirm. Beim Überfahren einblenden blendet ihn aus, bis der Zeiger über seinen Platz oder die schmale Lasche an seinem linken Rand kommt; er bleibt offen, solange der Zeiger darin ist oder ein Menü daraus offen ist.",
"prefs.browser_shown": "Angezeigt",
"prefs.built_in": "Integriert",
@@ -2271,6 +2306,7 @@
"prefs.carousel_previous": "Zurück",
"prefs.choose_font": "Schriftdatei auswählen…",
"prefs.click": "Klicken",
"prefs.command_dialog_values": "Befehlsdialoge",
"prefs.contact_shading": "Weiche Kontaktschattierung",
"prefs.contact_shading_note": "Erzeugt weiche Schatten an Stellen, an denen sich Bauteile nahekommen.",
"prefs.context_menu": "Kontextmenü",
@@ -2306,6 +2342,13 @@
"prefs.dialogs_edge": "Am Rand der Ansicht",
"prefs.dialogs_left_column": "In der linken Spalte",
"prefs.dialogs_note": "Wo Befehlsdialoge öffnen: am Rand der Ansicht angedockt oder in der linken Spalte anstelle von Browser und Verlauf, die beim Schließen des Dialogs wie zuvor zurückkehren.",
"prefs.dimension_step_angle": "Was die Pfeile des Fensters, Auf/Ab und das Mausrad zu einem Winkel addieren",
"prefs.dimension_step_length": "Was die Pfeile des Fensters, Auf/Ab und das Mausrad zu einer Länge addieren",
"prefs.dimension_steps": "Schritte im Fenster",
"prefs.dimension_values": "Bemaßungswerte",
"prefs.dimension_values_note": "Wo der Wert einer Bemaßung eingegeben wird. Ein Fenster hat Pfeile, die ihn schrittweise ändern; das Bemaßungswerkzeug platziert zuerst den Text.",
"prefs.dimension_values_view": "In der Ansicht",
"prefs.dimension_values_window": "In einem Fenster",
"prefs.display": "Anzeige",
"prefs.edges": "Kanten und Schattierung",
"prefs.extend": "Erweitern",
@@ -2359,6 +2402,8 @@
"prefs.graphics_page.surfaces_edges": "Flächen und Kanten",
"prefs.graphics_page.view_bar": "Auch in der Ansichtsleiste",
"prefs.graphics_presets_note": "Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
"prefs.ground_shadow_follows_model": "Bodenschatten folgt dem Modell",
"prefs.ground_shadow_follows_model_help": "Den Schatten unter dem niedrigsten sichtbaren Körper platzieren. Wenn deaktiviert, bleibt der Schatten angehobener Modelle auf der Bodenebene durch den Ursprung.",
"prefs.hide_constraint_glyphs_zoomed_out": "Bindungssymbole bei starkem Herauszoomen ausblenden",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
"prefs.history": "Verlauf",
@@ -2394,6 +2439,8 @@
"prefs.layout_page.card": "Karte",
"prefs.layout_page.column": "Spalte",
"prefs.layout_page.hover": "Bei Mauszeiger",
"prefs.layout_page.hover_panel_menus": "Bereichsmenüs beim Darüberfahren wechseln",
"prefs.layout_page.hover_panel_menus_help": "Klicken Sie auf einen Bereichsnamen, um das Menü zu öffnen. Bewegen Sie den Zeiger auf einen anderen Bereichsnamen, um das Menü zu wechseln. Außerhalb des Menübands und des geöffneten Menüs schließt es sich.",
"prefs.layout_page.icons_labels": "Symbole + Beschriftungen",
"prefs.layout_page.ok_cancel": "OK und Abbrechen",
"prefs.layout_page.on_hover": "Bei Mauszeiger",
@@ -2496,6 +2543,8 @@
"prefs.quality_smoother": "Stärker geglättet",
"prefs.quick_measure": "Schnellmessung in der Ecke der Ansicht",
"prefs.quick_measure_note": "Zeigt unten rechts in der Ansicht die Größe der Auswahl an: bei einer Kante die Länge, bei einer kreisförmigen Kante zusätzlich Durchmesser (Ø) und Radius (R), bei einer Fläche den Flächeninhalt, bei einem Punkt die Position, bei zwei Elementen Abstand und Winkel sowie bei mehreren Kanten oder Flächen die Gesamtlänge bzw. Gesamtfläche. Ein Klick kopiert den Messwert.",
"prefs.remember_dialog_values": "Befehlsdialogwerte merken",
"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_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",
@@ -2511,6 +2560,8 @@
"prefs.search": "Einstellungen suchen",
"prefs.search_empty": "Keine passenden Einstellungen",
"prefs.select": "Auswählen",
"prefs.select_hidden_edges": "Verdeckte Kanten in der schattierten Ansicht mit verdeckten Kanten auswählen",
"prefs.select_hidden_edges_help": "Kanten durch Flächen auswählen, wenn die schattierte Ansicht mit verdeckten Kanten aktiv ist, auch zum Verrunden. Aus behält das Auswahlverhalten von v0.30.1 bei. Ausgeblendete Körper bleiben ausgeschlossen.",
"prefs.select_one_item": "Ein Element auswählen",
"prefs.select_other_hidden": "Verborgene Geometrie mit „Andere auswählen“ auswählen",
"prefs.select_other_hidden_help": "Ein: Verborgene Körper, Skizzen und verdeckte Kanten nahe dem Zeiger einbeziehen. Aus: Die ursprüngliche Liste sichtbarer Geometrie direkt unter dem Zeiger verwenden.",
@@ -2535,6 +2586,8 @@
"prefs.sketch.click_selects": "Klick wählt",
"prefs.sketch.conflict_colour": "Konfliktfarbe",
"prefs.sketch.drawing": "Zeichnen",
"prefs.sketch.ellipse_axes": "Ellipsen mit ihren Achsen zeichnen",
"prefs.sketch.ellipse_axes_note": "Haupt- und Nebenachse als Konstruktionslinien, damit Halbachsen und Achsen bemaßt werden können. Aus: nur die Ellipse, und D darauf ergibt ihren Nebenradius, wohin der Wert auch gezogen wird, gezeichnet und mit R beschriftet wie bisher.",
"prefs.sketch.exit_keep": "Beibehalten",
"prefs.sketch.exit_previous": "Zur Ansicht davor zurückkehren",
"prefs.sketch.exit_unless_rotated": "Außer bei Drehung",
@@ -2628,6 +2681,10 @@
"prefs.theme_follow_off": "Aus",
"prefs.theme_follow_stop": "Nicht mehr folgen",
"prefs.theme_import_hint": "Klicken, um eine Designdatei zu wählen, eine hierher ziehen oder mit kopiertem Design Strg+V (Cmd+V auf dem Mac) drücken",
"prefs.theme_notice_dismiss": "Ausblenden",
"prefs.theme_option_blur": "Unschärfe hinter Panels",
"prefs.theme_option_blur_help": "Unschärfe hinter durchscheinenden Panels in physischen Pixeln. Null bedeutet aus.",
"prefs.theme_option_invalid": "Der gespeicherte Wert für {option} ist ungültig; der Designstandard wird verwendet. Der gespeicherte Wert bleibt erhalten.",
"prefs.theme_option_opacity": "Deckkraft",
"prefs.theme_option_opacity_help": "Deckkraft schwebender Bedienfelder anpassen. Eine Mindestdeckkraft schützt die Lesbarkeit über den Ansichtsfarben.",
"prefs.theme_option_tab_colours": "Tabfarben",
@@ -2636,6 +2693,7 @@
"prefs.theme_option_tab_fade_ms_help": "Panel- und Ansichtsfarben beim Tabwechsel überblenden. Null wechselt sofort.",
"prefs.theme_options_badge": "Optionen",
"prefs.theme_options_more": "Mehr · {count}",
"prefs.theme_options_newer": "Dieses Design benötigt eine neuere Version von SolidCraft. Nach dem Upgrade das Originaldesign erneut importieren, um seine Optionsdefinitionen wiederherzustellen.",
"prefs.theme_options_reset": "Zurücksetzen",
"prefs.theme_options_unavailable": "Auf diesem Gerät nicht verfügbar",
"prefs.theme_paste": "Design einfügen",
@@ -3220,9 +3278,11 @@
"render.frame_wide": "16:9 · Breitbild",
"render.gpu_depth": "Maximale Pfadtiefe",
"render.gpu_options_help": "Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
"render.gpu_path_crashed": "SolidCraft wurde beim letzten Einrichten auf diesem Grafikadapter beendet, wahrscheinlich im Grafiktreiber, und richtet ihn deshalb nicht von selbst erneut ein. Verwenden Sie stattdessen Raytracing auf der CPU oder versuchen Sie es erneut (nach einem Treiberupdate wird es ebenfalls erneut versucht).",
"render.gpu_path_failed": "GPU-Pathtracer nicht verfügbar",
"render.gpu_path_first_setup": "Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
"render.gpu_path_preparing": "GPU-Pathtracer: Shader werden kompiliert…",
"render.gpu_path_retry": "Erneut versuchen",
"render.gpu_seed": "Zufallsstartwert",
"render.gpu_unavailable": "GPU-Pathtracing ist auf diesem Gerät nicht bereit.",
"render.ground_apply": "Boden anpassen und speichern",
@@ -3836,6 +3896,7 @@
"render.stage_panels.extent": "Ausdehnung",
"render.stage_panels.fit_save": "Einpassen und speichern",
"render.stage_panels.gpu_required": "Benötigt GPU-Pfadverfolgung",
"render.stage_panels.grain_angle": "Maserungswinkel",
"render.stage_panels.keep": "Behalten",
"render.stage_panels.kept": "Beibehalten",
"render.stage_panels.kept_fields": "Kamera, Abtastung, Abmessungen, optische Bindungen, CAD-Geometrie und technische Eigenschaften.",
@@ -3954,6 +4015,19 @@
"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.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.",
"render.surface_import.embedded": "Importiertes Bild",
"render.surface_import.failed": "Der Bildimport wurde nicht abgeschlossen.",
"render.surface_import.license": "Lizenz / Eigentum",
"render.surface_import.raytrace_required": "Bilder erfordern CPU- oder GPU-Raytracing. Wählen Sie es unter Ausgabe.",
"render.surface_import.repeat_u": "Wiederholung U",
"render.surface_import.repeat_v": "Wiederholung V",
"render.surface_import.save": "Bild am Körper speichern",
"render.surface_import.saved_cpu": "Speichern Sie Texturen für das CPU-Rendering.",
"render.surface_import.source": "Quelle",
"render.surface_import.title": "Bild importieren",
"render.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.",
@@ -4030,6 +4104,8 @@
"sample.plate.name": "Beispielplatte",
"sample.vents.about": "Langlöcher in einem Rechteckmuster: Zeilen, Spalten und Abstand sind Parameter",
"sample.vents.name": "Lüftungsplatte",
"scale.per_axis": "Je Achse",
"scale.uniform": "Gleichmäßig",
"search.command_keys": "Eingabe ausführen Umschalt+Eingabe ausführen und offen lassen Esc schließen",
"search.commands": "Befehle",
"search.design": "In diesem Design",
@@ -4672,6 +4748,7 @@
"ui.click_selects_whole_bodies": "Klick wählt ganze Körper aus",
"ui.click_then_press_key": "Klicken, dann die neue Taste drücken",
"ui.close": "Schließen",
"ui.close_tab": "Tab schließen",
"ui.coil": "Schraube",
"ui.coincident": "Koinzident",
"ui.collapse": "Einklappen",
@@ -4759,6 +4836,7 @@
"ui.default_modelling_orientation": "Standard-Modellierungsorientierung",
"ui.delete": "Löschen",
"ui.delete_select_first": "Wählen Sie zuerst aus, was gelöscht werden soll: Features, Körper, Komponenten oder Skizzengeometrie",
"ui.dependents_select_first": "Wählen Sie zuerst aus, wessen Abhängige aufgelistet werden sollen",
"ui.depth": "Tiefe",
"ui.design": "Design",
"ui.design_properties.author": "Autor",
@@ -4886,6 +4964,11 @@
"ui.factor": "Faktor",
"ui.fade": "Auslauf",
"ui.fade_length": "Auslauflänge",
"ui.fair_iterations": "Iterationen",
"ui.fair_keep_edges": "Kanten behalten",
"ui.fair_strength": "Stärke",
"ui.fair_surface_title": "FLÄCHENBEGRADIGUNG",
"ui.fair_tolerance_default": "0,001 der Körpergröße",
"ui.feature_history": "Feature-Verlauf",
"ui.feature_options": "Funktionsoptionen",
"ui.features": "Funktionen",
@@ -4914,6 +4997,10 @@
"ui.fillet_setback_least": "Mehr als {least}",
"ui.fillet_setback_tip": "Wo das glatte Eckflächenstück beginnt: wie weit von der Ecke entlang jeder Kante. Standard ist {factor} × der Radius. Er muss größer sein als der kleinste Rücksprung, wo sich die Rundungen ohne ihn treffen: der Radius, wo die Flächen rechtwinklig zueinander stehen.",
"ui.fillet_smooth_radius": "Radius glatt ändern",
"ui.fillet_turn": "Wendungen",
"ui.fillet_turn_mitre": "Gehrung",
"ui.fillet_turn_swept": "Geschwenkt",
"ui.fillet_turn_tip": "Wo zwei Rundungen um eine scharfe Eckkante zwischen ihnen herumlaufen, etwa die Rundungen am Fuß eines Zapfens an seinen senkrechten Kanten.\nGehrung: Die Rundungen laufen weiter, bis sie sich in einer Gehrung treffen.\nGeschwenkt: Die Rundungen enden an der Eckkante und schwenken um sie herum; die Ecke ist ein Torus, tangential zu beiden. Nur für Rundungen mit einem Radius um eine gerade, konvexe Eckkante.",
"ui.fillet_type_choices_tip": "Konstanter Radius: ein Radius entlang der ganzen Kante.\nSehnenlänge: die Breite der Rundung bleibt gleich, in welchem Winkel die Flächen auch aufeinandertreffen.\nVariabler Radius: ein Radius an jedem Ende und weitere entlang der Kante, wenn Sie sie hinzufügen.\nKrümmungsstetig: ein Querschnitt ohne Knick in den Spiegelungen, wo er auf ebene Flächen trifft (G2).\nSpline: der Radius entlang der Kante folgt Steuerradien, die Sie festlegen.",
"ui.fillet_type_curvature": "Krümmungsstetig",
"ui.fillet_type_tip": "Der Querschnitt über die Kante. Krümmungsstetig: ein krümmungsstetiger Querschnitt (G2) zwischen ebenen Flächen, sodass Spiegelungen ohne Knick von den Flächen in die Rundung laufen.",
@@ -4959,6 +5046,7 @@
"ui.heading_construct": "KONSTRUKTION",
"ui.heading_create": "ERSTELLEN",
"ui.heading_create_sketch": "SKIZZE ERSTELLEN",
"ui.heading_custom_view_navigator": "EIGENER ANSICHTSNAVIGATOR",
"ui.heading_cylinder": "ZYLINDER",
"ui.heading_design": "DESIGN",
"ui.heading_draft": "ABZIEHEN",
@@ -5063,6 +5151,7 @@
"ui.inspect": "Prüfen",
"ui.instances": "Instanzen",
"ui.intersect": "Schnitt",
"ui.intersection_curve_second_pick": "Schnittkurve: zweite Fläche oder Kurve anklicken (Rechtsklick zum Neubeginn)",
"ui.isolate": "Isolieren",
"ui.join": "Verbinden",
"ui.joint_play": "▶ Abspielen",
@@ -5090,6 +5179,7 @@
"ui.manipulator_free_alt": "Freies Ziehen des Manipulators auch mit Alt/Option",
"ui.manipulator_free_alt_tip": "An: Alt/Option oder Strg/Cmd auf Extrusions-/Gizmo-Pfeilen deaktiviert das Einrasten von Werten. Aus: nur Strg/Cmd.",
"ui.manufacture": "Fertigung",
"ui.match_surface_title": "FLÄCHENANPASSUNG",
"ui.material_review.axes": "Materiallokale Achsen; keine Körperausrichtung zugewiesen.",
"ui.material_review.claims": "Quellen und Lizenzen sind ungeprüfte Angaben. Fehlende Werte bleiben unbekannt.",
"ui.material_review.compressive_strength": "Druckfestigkeit",
@@ -5112,6 +5202,9 @@
"ui.material_review.title": "Physikalische Materialdaten prüfen",
"ui.material_review.unknown": "Unbekannt",
"ui.material_review.yield_strength": "Streckgrenze",
"ui.mcp_agents": "Agenten über MCP",
"ui.mcp_compact": "Kompakte Ausgabe",
"ui.mcp_compact_note": "Für Agenten, die über den MCP-Server (solidcraft-cli mcp) verbunden sind: inspect_design fasst Skizzen zusammen und Werkzeugergebnisse werden nicht formatiert, sodass sie weniger Tokens verbrauchen. Gilt für Agentensitzungen, die nach der Änderung beginnen; --compact oder SOLIDCRAFT_MCP_COMPACT=1 schalten es ebenfalls ein.",
"ui.measure": "Messen",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "3MF mit Flächenmarken",
@@ -5153,6 +5246,7 @@
"ui.options": "Optionen",
"ui.orbit": "Drehen",
"ui.orbit_around": "Umkreisen um",
"ui.organize": "Organisieren",
"ui.orientation": "Ausrichtung",
"ui.origin": "Ursprung",
"ui.palette_construction_mode": "Konstruktionsmodus (X)",
@@ -5197,6 +5291,7 @@
"ui.point_cloud_add_filter": "Filter hinzufügen…",
"ui.point_cloud_auto_range": "Bereich automatisch",
"ui.point_cloud_color_by": "Färben nach",
"ui.point_cloud_dots": "Einzelpunkte",
"ui.point_cloud_file_colors": "Farben der Datei",
"ui.point_cloud_filter_by": "Filtern nach",
"ui.point_cloud_height_high": "Oberste Höhe",
@@ -5266,6 +5361,8 @@
"ui.repeat_nothing": "Noch nichts zu wiederholen: Starten Sie zuerst ein Werkzeug oder einen Dialog.",
"ui.replace_with_link_done": "Die Kopien sind jetzt Platzierungen des verknüpften Designs.",
"ui.report_a_bug": "Fehler melden",
"ui.report_characters_left_one": "{count} Zeichen übrig",
"ui.report_characters_left_other": "{count} Zeichen übrig",
"ui.reset": "Zurücksetzen",
"ui.reset_all": "Alle zurücksetzen",
"ui.reset_the_settings_on_this_page": "Einstellungen auf dieser Seite zurücksetzen",
@@ -5547,6 +5644,14 @@
"values must be numbers or expressions": "Werte müssen Zahlen oder Ausdrücke sein",
"view.auto_ortho_faces": "Perspektivisch beim Drehen, orthografisch auf Flächen",
"view.render_inspector": "Render-Inspektor",
"view_gizmo.arrows": "Drehpfeile",
"view_gizmo.home": "Schaltfläche „Ausgangsansicht“",
"view_gizmo.no_such_body": "Diese Konstruktion hat keinen Körper mit diesem Namen.",
"view_gizmo.note": "Wählen Sie einen Körper in der Ansicht. Er wird mit Ihren Einstellungen und Einstellungsprofilen gespeichert und in jeder Konstruktion angezeigt. Klicken Sie auf eine seiner Flächen, um direkt darauf zu blicken.",
"view_gizmo.texture": "Textur",
"view_gizmo.themed": "Design",
"view_gizmo.too_detailed": "Zu detailliert für den Ansichtsnavigator (über 10.000 Dreiecke)",
"view_gizmo.too_thin": "Zu lang und dünn für den Ansichtsnavigator: Die dünnste Seite muss mindestens ein Fünftel der längsten sein",
"web.files.insert_drawings_hint": "CAD-Dateien, Netze, DXF oder SVG in das geöffnete Design",
"web.files.insert_stored_drawings_hint": "In das geöffnete Design (CAD-Dateien, Netze, DXF, SVG)",
"web.open_full": "In SolidCraft öffnen",
+10 -2
View File
@@ -22,8 +22,7 @@
"cmd.component_cycle_color": "Cycle Component Colour",
"cmd.construct_point_center": "Point At Centre Of Circle/Sphere/Torus",
"cmd.inspect_center_of_mass": "Centre of Mass",
"cmd.inspect_colored_components": "Enable Component Colours",
"cmd.inspect_colored_components_disable": "Disable Component Colours",
"cmd.inspect_colored_components": "Component Colours",
"cmd.parameters_favorite": "Favourite Parameter",
"cmd.render_aging_describe": "Layered Surface Aging",
"cmd.render_effects_describe": "Lens and Creative Effects",
@@ -69,6 +68,7 @@
"help.solid.split_face": "Divides the selected faces along a plane, the face of another body or sketch curves, without cutting the body. Use it to give part of a face its own draft, colour or offset.",
"help.third_party": "Third-party licences",
"home.new_mm_hint": "Millimetres · opens in Design",
"measure.line_to_centre": "Line to centre",
"navbar.custom.anchor.bottom_centre": "Bottom centre",
"navbar.custom.anchor.top_centre": "Top centre",
"navbar.custom.customise": "Customise navigation bar…",
@@ -86,6 +86,7 @@
"prefs.max_file_size_web": "The web app opens STEP and import files up to 512 MB and designs up to 256 MB: a browser tab can't hold larger files. The desktop app can raise these limits.",
"prefs.megabytes": "MB",
"prefs.network_locations_note": "For organisations that keep designs on file servers. Off: only servers you allow.",
"prefs.select_hidden_edges_help": "Select edges through faces when Shaded with hidden edges is active, including for Fillet. Off keeps v0.30.1 selection behaviour. Hidden bodies stay unselectable.",
"prefs.sketch.center_arc": "Centre-point arc follows the cursor",
"prefs.sketch.conflict_colour": "Conflict colour",
"prefs.sketch.tracking_guides": "Centre and midpoint guides",
@@ -234,6 +235,12 @@
"render.studio_area_help": "Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
"render.studio_center": "Centre X, Y, Z (mm)",
"render.studio_hdr_help": "Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
"render.surface_import.complete": "Choose an image and enter its author, source and licence.",
"render.surface_import.license": "Licence / ownership",
"sketch.hint.arc.center_point": "Arc: click centre and start, go round either way and click the end, or type a signed sweep",
"sketch.hint.circle.center": "Circle: click the centre, then a point on the circle",
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to centre the second on the first",
"sketch.hint.rectangle.center": "Centre rectangle: click the centre, then a corner",
"sketch_text.align_center": "Centre",
"ui.3d_mouse_kind_cap": "Return to Centre",
"ui.3d_mouse_looks_like_cap": "The reading stays put while you hold it, the way a Return to Centre device does.",
@@ -261,6 +268,7 @@
"ui.layout_compact": "Compact",
"ui.layout_floating": "Floating",
"ui.material_review.claims": "Sources and licences are unverified claims. Missing values remain unknown.",
"ui.organize": "Organise",
"ui.point_cloud_color_by": "Colour By",
"ui.point_cloud_file_colors": "File Colours",
"ui.point_cloud_no_file_colors": "The file has no colours",
+168 -12
View File
@@ -85,6 +85,7 @@
"Bottom Right": "Bottom Right",
"Boundary": "Boundary",
"Bounding box": "Bounding box",
"Bridge": "Bridge",
"CHECK": "CHECK",
"COMPONENTS": "COMPONENTS",
"CONFIGURATIONS": "CONFIGURATIONS",
@@ -628,6 +629,8 @@
"Suppress Selected Folders": "Suppress Selected Folders",
"Suppressed": "Suppressed",
"Surface": "Surface",
"Surface or face": "Surface or face",
"Surface or faces": "Surface or faces",
"Surface, or faces to turn over": "Surface, or faces to turn over",
"Surfaces": "Surfaces",
"Swap Ends": "Swap Ends",
@@ -641,6 +644,7 @@
"Target": "Target",
"Target Body": "Target Body",
"Target Face (optional)": "Target Face (optional)",
"Target edges": "Target edges",
"Tear": "Tear",
"Technical details": "Technical details",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "The dashed sketch is a saved plane guide. Select a new face to repair the attachment.",
@@ -648,6 +652,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "The direction must be an axis or a straight sketch curve that lies in the sketch plane",
"The mirror line cannot also be mirrored": "The mirror line cannot also be mirrored",
"The mirror line must be a straight curve of this sketch, not an origin axis": "The mirror line must be a straight curve of this sketch, not an origin axis",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "The origin axis is perpendicular to the sketch plane, so it has no line in the plane",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "The saved author occurrence is missing. Select a valid author placement before rebinding.",
"Then": "Then",
"These features will be deleted:": "These features will be deleted:",
@@ -739,6 +744,9 @@
"a picked edge's body is gone": "a picked edge's body is gone",
"add a key for a joint first": "add a key for a joint first",
"ai.connected_toast": "SolidCraft is now available in {clients}. Restart them to use it (Preferences > Assistant).",
"ai.http.allow": "Allow insecure HTTP",
"ai.http.confirm": "Allow for this endpoint",
"ai.http.warning": "The API key and conversation will be sent unencrypted. Anyone with access to the network may read or change them. Use this only on a network you trust.",
"ai.prefs.allow_control": "Let AI agents connect",
"ai.prefs.allow_control_note": "A local channel (MCP). Nothing leaves this computer.\n\nOpens a control channel on this computer only (127.0.0.1, port 27878 or the next free one). A program that connects must also show a secret token that only your user account can read, made fresh at every start. Takes effect the next time SolidCraft starts. Off: nothing listens unless you start SolidCraft with --control.",
"ai.prefs.availability": "Availability",
@@ -750,6 +758,8 @@
"ai.prefs.follow_camera": "Follow the camera",
"ai.prefs.follow_camera_off": "Automatic camera following is off.",
"ai.prefs.follow_camera_on": "The camera turns to face sketches the agent creates.",
"ai.prefs.instructions": "Custom instructions",
"ai.prefs.instructions_note": "Sent to the agent at the start of every chat, after SolidCraft's own instructions: how you like to work, rules to keep to. A change applies from the next chat (Connect or New chat).",
"ai.prefs.intro": "AI agents in SolidCraft.",
"ai.prefs.make_default": "Make default",
"ai.prefs.no_agents": "No agents yet. Add an agent or refresh to find installed agents.",
@@ -963,8 +973,7 @@
"cmd.inspect_analysis": "View Analysis",
"cmd.inspect_center_of_mass": "Center of Mass",
"cmd.inspect_clearance": "Clearance",
"cmd.inspect_colored_components": "Enable Component Colors",
"cmd.inspect_colored_components_disable": "Disable Component Colors",
"cmd.inspect_colored_components": "Component Colors",
"cmd.inspect_compare": "Compare Bodies",
"cmd.inspect_compare_picture": "Compare Picture",
"cmd.inspect_continuity": "Continuity Analysis",
@@ -1081,6 +1090,9 @@
"cmd.render.compare.divider": "Move Render Comparison Divider",
"cmd.render.result.discard": "Discard Render Result",
"cmd.render.result.select": "Select Render Result",
"cmd.render.surface.import": "Import Surface Maps",
"cmd.render.texture.list": "List Embedded Surface Images",
"cmd.render.texture.remove": "Remove Embedded Surface Image",
"cmd.render_aging_describe": "Layered Surface Aging",
"cmd.render_backends": "Render Backends",
"cmd.render_batch": "Render All Shots",
@@ -1172,7 +1184,7 @@
"cmd.sketch_arc_tangent": "Tangent Arc",
"cmd.sketch_arc_three_point": "3-Point Arc",
"cmd.sketch_auto_constrain": "AutoConstrain",
"cmd.sketch_auto_constrain_from_datum": "AutoConstrain from datum",
"cmd.sketch_auto_constrain_from_datum": "AutoConstrain from Datum",
"cmd.sketch_auto_project": "Auto-Project Edges on Reference",
"cmd.sketch_blend_curve": "Blend Curve",
"cmd.sketch_cancel": "Cancel Sketch",
@@ -1186,7 +1198,7 @@
"cmd.sketch_circle_two_point": "2-Point Circle",
"cmd.sketch_circle_two_tangent": "2-Tangent Circle",
"cmd.sketch_constrainer": "Sketch Constrainer",
"cmd.sketch_constraint_fix": "Fix/UnFix",
"cmd.sketch_constraint_fix": "Fix/Unfix",
"cmd.sketch_constraint_midpoint": "MidPoint",
"cmd.sketch_construction": "Normal/Construction",
"cmd.sketch_copy": "Copy Sketch Entities",
@@ -1296,6 +1308,7 @@
"cmd.timeline_stop": "Stop History",
"cmd.timeline_suppress": "Suppress Feature",
"cmd.ui.assistant": "Toggle Assistant",
"cmd.view.navigator": "Custom View Navigator",
"cmd.view.render_browser": "Render Browser",
"cmd.view.render_dock": "Move Render Dock",
"cmd.view.render_focus": "Render Focus View",
@@ -1311,25 +1324,26 @@
"cmd.view_front": "Front View",
"cmd.view_graphics_preset": "Graphics Preset",
"cmd.view_grid": "Grid",
"cmd.view_ground_shadow": "Ground shadow",
"cmd.view_home": "Home view",
"cmd.view_ground_shadow": "Ground Shadow",
"cmd.view_home": "Home View",
"cmd.view_isometric": "Isometric View",
"cmd.view_left": "Left View",
"cmd.view_list": "List Named Views",
"cmd.view_navigate": "Navigate View",
"cmd.view_navigation_bar": "Navigation bar",
"cmd.view_navigation_bar": "Navigation Bar",
"cmd.view_next_tab": "Next Tab",
"cmd.view_normal_to": "Normal to Selection",
"cmd.view_previous_tab": "Previous Tab",
"cmd.view_rename": "Rename Named View",
"cmd.view_render_frame": "Render Frame Guides",
"cmd.view_ribbon_tabs_only": "Ribbon Tabs Only",
"cmd.view_right": "Right View",
"cmd.view_save": "New Named View",
"cmd.view_shots_strip": "Shots Strip",
"cmd.view_spin": "Continuous orbit",
"cmd.view_tool": "Navigation tool",
"cmd.view_spin": "Continuous Orbit",
"cmd.view_tool": "Navigation Tool",
"cmd.view_top": "Top View",
"cmd.view_visual_style": "Visual style",
"cmd.view_visual_style": "Visual Style",
"color.blue": "Blue",
"color.copy": "Click to copy color values",
"color.edit": "Click to edit color",
@@ -1394,15 +1408,22 @@
"dialog.computing": "Computing… {seconds} s",
"dialog.could_not_be_checked": "could not be checked",
"dialog.custom_axis": "↖ Custom Axis",
"dialog.cylinder_face_tip": "The face or vertex of the model the cylinder stands on: click here, then click it in the view. Left empty, X, Y and Z place it.",
"dialog.dimension_insert_hint": "Click another driving dimension to insert its name",
"dialog.dimension_name": "Dimension: {name}",
"dialog.dimension_step_down": "Down by {step}",
"dialog.dimension_step_expression": "Only a number steps; an expression stays as typed",
"dialog.dimension_step_up": "Up by {step}",
"dialog.edit_title": "EDIT {title}",
"dialog.no_shown_body": "No shown body to change: show one, or choose New Body",
"dialog.pattern_sketch_not_object": "Pick bodies or features: a sketch is neither. Repeat its curves with the sketch's own Pattern or Mirror, or extrude it first.",
"dialog.primitive_point_tip": "A point that fills X, Y and Z: click here, then click a vertex, edge or face of the model, a point of a sketch, or the origin.",
"dialog.reference_edge_tip": "A straight edge of the picked face to measure the distance below from: click here, then click the edge.",
"dialog.select": "↖ Select",
"dialog.selected_count": "↖ {count} selected",
"dialog.selection_constraint": "Constraint {id}",
"dialog.selection_external_curve": "{sketch} · curve {id}",
"dialog.selection_external_point": "{point} · {sketch}",
"dialog.selection_guidance": "Select an input to change it. Click selected objects to remove them; Ctrl/Cmd-click also toggles them. Review the preview, then press OK.",
"dialog.selection_midpoint": "Midpoint · {id}",
"dialog.selection_profile": "{sketch} · profile {number}",
@@ -1420,6 +1441,7 @@
"document.read_only_save": "This design was opened read-only: save it as a new file",
"document.save_changes": "Save changes to {name}?",
"document.save_copy": "Save a Copy…",
"document.tab_unsaved_changes": "Unsaved changes",
"document.unsaved_changes": "{name} has unsaved changes. If you don't save, they are lost.",
"drive.rigid": "{joint} is a rigid joint: it holds its parts still, so it has nothing to drive. To let them move, change its type in Edit Joint: Revolute to rotate, Slider to slide, Cylindrical for both.",
"drive.stopped_by_contact": "Stopped at {value}: {a} would pass through {b}",
@@ -1440,10 +1462,16 @@
"each rail's curves must be in one sketch": "each rail's curves must be in one sketch",
"feature computation ended without a result": "feature computation ended without a result",
"file.open.drawing_filter": "Designs, CAD files, meshes and drawings",
"file.open.portal_fallback.no_portal_service": "The file chooser that opened is not the desktop's own: the desktop portal service is not available.",
"file.open.portal_fallback.no_session_bus": "The file chooser that opened is not the desktop's own: there is no D-Bus session bus.",
"file.open.portal_fallback.xwayland": "The file chooser that opened is not the desktop's own: under XWayland the portal refuses the parent window.",
"file_load.built": "Built so far: {built} of {total} features.",
"file_load.converting": "Converting solid {done} of {total}",
"file_load.inserted": "Inserted {name}",
"file_load.inserting": "Inserting geometry",
"file_load.link_item": "{name} ({done} of {total})",
"file_load.opened": "Opened {name}",
"file_load.opened_in_tab": "Opened {name} in a new tab",
"file_load.opening": "Opening design",
"file_load.preparing_thumbnail": "Preparing the thumbnail...",
"file_load.preparing_view": "Preparing the view...",
@@ -1600,7 +1628,7 @@
"help.parts.resize": "Changes the size or length of an inserted standard part and rebuilds it; its joints keep it seated. Use it when a screw turns out too long or a hole needs another size.",
"help.plastic.assign_rule": "Assigns a plastic rule to bodies, so their plastic features take its draft and clearance as defaults. Use it to set up a moulded part once for all its features.",
"help.plastic.boss": "Adds a screw boss on a face: a post with an optional hole, draft, root fillet and supporting ribs. Use it for screw points in moulded or printed housings.",
"help.plastic.lip": "Adds a lip or a matching groove along the rim of a shelled body. Use it so that two housing halves locate on each other and close tightly.",
"help.plastic.lip": "Adds a lip or a matching groove along the rim of a shelled body (Shell a face open first, then pick the rim around that opening). Use it so that two housing halves locate on each other and close tightly.",
"help.plastic.manage_rules": "Creates and edits plastic rules: material, wall thickness, radii, clearance and draft. Use them to keep the features of a moulded part consistent.",
"help.plastic.rest": "Adds a rest on a face: a round or rectangular pad, solid or hollow, with draft. Use it as a support or standoff in a moulded housing.",
"help.plastic.snap_fit": "Adds a snap-fit hook on a face, with its length, thickness and catch. Use it to clip parts together without screws.",
@@ -1679,7 +1707,7 @@
"help.sketch.insert_svg": "Reads the paths of an SVG drawing into a sketch, scaled, turned and placed where you set. Use it to turn a logo or vector outline into curves you can extrude.",
"help.sketch.inspect": "Opens a report on the open sketch: how many degrees of freedom are left and whether it is fully constrained. Use it to find out what still moves before you finish the sketch.",
"help.sketch.intersect": "Adds the curves or points where bodies, faces, edges or other sketches cross the sketch plane. They stay linked and follow the model when it changes.",
"help.sketch.intersection_curve": "Creates 3D curves where two bodies or faces meet, linked to them. Use them as paths or guide rails that follow the model.",
"help.sketch.intersection_curve": "Creates 3D curves where two bodies, faces or curves of other sketches meet, linked to them. Use them as paths or guide rails that follow the model.",
"help.sketch.isoparametric_curve": "Creates a 3D curve along a face's U or V direction through a point you pick, linked to the face. Use it as a path or split line that follows the surface.",
"help.sketch.line": "Draws connected lines point by point, as an open chain or a closed outline. It is the basic tool for most profiles.",
"help.sketch.line.midpoint": "Draws a line from its midpoint out to one end, with the other end mirrored. Use it for lines centred on a point, such as a symmetric edge.",
@@ -1785,6 +1813,7 @@
"help.view.navigate": "Turns, pans or zooms the view by steps, such as a quarter turn left. Use it to move the camera precisely instead of dragging.",
"help.view.navigation_bar": "Shows or hides the navigation bar at the bottom of the viewport. Hide it for more room, show it to reach the view tools by mouse.",
"help.view.rename": "Gives a saved named view a new name. The view itself stays the same.",
"help.view.ribbon_tabs_only": "Folds the ribbon to its tab row, so the view gets the room its panels took. Click a tab to show its panels over the view; choose this again to keep the whole ribbon shown.",
"help.view.save": "Saves the current camera position as a named view in the design. Use it to come back to the same angle later, for example for screenshots.",
"help.view.spin": "Orbits the view around the model continuously until the next click or key. Use it to present a model from all sides.",
"help.view.tool": "Latches a camera tool, orbit, pan or zoom, so that dragging with the left mouse button moves the view until you switch it off. Use it on a trackpad or without a middle mouse button.",
@@ -1999,6 +2028,7 @@
"material.selection.mixed": "Different materials",
"measure.at": "At",
"measure.item": "Item {number}",
"measure.line_to_centre": "Line to center",
"measure.quick_copied": "Measurement copied",
"measure.quick_copy": "Click to copy the measurement",
"measure.quick_edges": "{count} edges",
@@ -2029,6 +2059,8 @@
"nav.blender.help": "Middle drag orbits, Shift+middle pans, Ctrl+middle zooms; right drag leaves the camera alone",
"nav.creo.help": "Middle drag orbits, Shift+middle pans, Ctrl+middle drag vertically zooms (down to zoom in); scroll the wheel to zoom",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "A middle or right drag goes on orbiting, panning or zooming as it began until the button is released, whatever keys are pressed or let go meanwhile. Off: the keys held decide at every moment of the drag.",
"nav.drag_keeps_mode.label": "A drag keeps the navigation it started with",
"nav.free_orbit.help": "Mouse and 3D mouse rotate through both poles and preserve roll. Shift axis snapping is disabled.",
"nav.free_orbit.label": "Free orbit",
"nav.free_orbit.snap_help": "Mouse and 3D mouse rotate through both poles and preserve roll. Hold Shift during an orbit drag to snap the view direction and roll.",
@@ -2265,6 +2297,17 @@
"prefs.auto_operation_note": "Join, New Body or Cut, from what the material touches.",
"prefs.axes": "Axes",
"prefs.axis": "Axis",
"prefs.background_choose": "Choose picture…",
"prefs.background_image": "Picture",
"prefs.background_unreadable": "Could not read the background picture",
"prefs.blur_cost": "Relative rendering cost. More bars usually mean more work.",
"prefs.blur_device": "Unavailable on this graphics device or rendering path",
"prefs.blur_guard": "Blur quality was reduced to keep frames responsive. The radius is unchanged.",
"prefs.blur_off": "Off",
"prefs.blur_reduce_transparency": "Disabled by the system's Reduce transparency setting",
"prefs.blur_software": "Unavailable with software graphics (gpu-software)",
"prefs.blur_source": "Unavailable in this workspace view",
"prefs.blur_web": "Unavailable on this web rendering path",
"prefs.bodies_folders": "Bodies folders",
"prefs.bodies_folders_always": "Always",
"prefs.bodies_folders_never": "Never",
@@ -2278,6 +2321,8 @@
"prefs.browser_eye_left": "Eye beside the fold arrow",
"prefs.browser_eye_left_note": "Browser rows show their show/hide eye next to the fold arrow, as before, instead of in a column at the right end of the rows.",
"prefs.browser_reveal_on_hover": "Reveal on hover",
"prefs.browser_rows_start_closed": "Browser rows start closed",
"prefs.browser_rows_start_closed_note": "Placements and folders in the Browser start closed, so a design with many components shows its top-level rows. Each opens with its arrow, Expand All or Find in Browser, which opens just the rows down to its body and closes again the ones the last find opened. A body picked in the 3D view leaves the Browser as it is. When off, folders start open, placements start as Components start open says, and a pick in the 3D view opens the Browser down to its body, as before.",
"prefs.browser_show_note": "Shown keeps the browser on screen. Reveal on hover keeps it out of sight until the pointer comes over its place or the thin tab at its left edge, and it stays open while the pointer is inside it or a menu from it is open.",
"prefs.browser_shown": "Shown",
"prefs.built_in": "Built in",
@@ -2288,6 +2333,7 @@
"prefs.carousel_previous": "Previous",
"prefs.choose_font": "Choose font file…",
"prefs.click": "Click",
"prefs.command_dialog_values": "Command dialogs",
"prefs.contact_shading": "Soft contact shading",
"prefs.contact_shading_note": "Soft shading where parts come close.",
"prefs.context_menu": "Context menu",
@@ -2323,6 +2369,13 @@
"prefs.dialogs_edge": "At the view's edge",
"prefs.dialogs_left_column": "In the left column",
"prefs.dialogs_note": "Where command dialogs open: docked at the view's edge, or in the left column in place of the browser and the History, which come back as they were when the dialog closes.",
"prefs.dimension_step_angle": "What the window's arrows, Up/Down and the wheel add to an angle",
"prefs.dimension_step_length": "What the window's arrows, Up/Down and the wheel add to a length",
"prefs.dimension_steps": "Window steps",
"prefs.dimension_values": "Dimension values",
"prefs.dimension_values_note": "Where you type a dimension's value. A window has arrows that step it; the Dimension tool places the text first.",
"prefs.dimension_values_view": "In the view",
"prefs.dimension_values_window": "In a window",
"prefs.display": "Display",
"prefs.edges": "Edges and shading",
"prefs.extend": "Extend",
@@ -2376,6 +2429,8 @@
"prefs.graphics_page.surfaces_edges": "Surfaces and edges",
"prefs.graphics_page.view_bar": "Also in the view bar",
"prefs.graphics_presets_note": "Presets set the choices below together.",
"prefs.ground_shadow_follows_model": "Ground shadow follows the model",
"prefs.ground_shadow_follows_model_help": "Place the shadow below the lowest visible body. When off, raised models keep their shadow on the origin ground plane.",
"prefs.hide_constraint_glyphs_zoomed_out": "Hide constraint glyphs when zoomed out",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Constraint glyphs leave the view when the sketch is zoomed out far enough that they would clutter it. Off: they stay at a fixed screen size at every zoom.",
"prefs.history": "History",
@@ -2402,6 +2457,10 @@
"prefs.invert_curved_roll_arrows_note": "The two curved arrows at the cube's corner roll the view within the screen plane. On: the clockwise arrow rolls counterclockwise and the counterclockwise arrow rolls clockwise. Off: each arrow rolls the view the way it curls.",
"prefs.invert_navigator_arrows": "Invert the view cube rotation arrows",
"prefs.invert_navigator_arrows_note": "The arrows around the cube orbit the camera the way they point, as in Fusion and Inventor: the arrow on the left points right and swings the camera right. Off: an arrow rotates the cube toward the side it sits on, so the left arrow swings the camera left. The curved roll arrows are not affected.",
"prefs.keep_unsaved_also_asks": "Still ask when the tab closes",
"prefs.keep_unsaved_also_asks_tip": "With this off, the tab closes without the browser's box once every unsaved design's copy is current.",
"prefs.keep_unsaved_in_browser": "Keep unsaved work in this browser",
"prefs.keep_unsaved_in_browser_tip": "Each open design's unsaved changes are kept up to date in this browser's private storage. The next visit offers to restore them.",
"prefs.keep_work_on_tab_switch": "Keep dialogs and tools open when switching designs",
"prefs.keep_work_on_tab_switch_note": "Each design tab keeps its open dialog or tool, with what you picked and typed in it, and its toolbar tab. They are there again when you come back to the design. Off: switching designs cancels them and shows the Model tab.",
"prefs.labels": "Labels",
@@ -2413,6 +2472,8 @@
"prefs.layout_page.card": "Card",
"prefs.layout_page.column": "Column",
"prefs.layout_page.hover": "Hover",
"prefs.layout_page.hover_panel_menus": "Switch panel menus on hover",
"prefs.layout_page.hover_panel_menus_help": "Click a ribbon panel name to open its menu. Hover another panel name to switch. Move outside the ribbon and open menu to close it.",
"prefs.layout_page.icons_labels": "Icons + labels",
"prefs.layout_page.ok_cancel": "OK and Cancel",
"prefs.layout_page.on_hover": "On hover",
@@ -2515,6 +2576,8 @@
"prefs.quality_smoother": "smoother",
"prefs.quick_measure": "Quick measurement in the view's corner",
"prefs.quick_measure_note": "The selection's size in the view's bottom right corner: an edge's length, or Ø and R for a round one, a face's area, a point's position, two items' distance and angle, several edges' or faces' total. A click copies it.",
"prefs.remember_dialog_values": "Remember command dialog values",
"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_previous_geometry_budget": "Use previous Render preview geometry budget",
"prefs.render_previous_geometry_budget_help": "Restore the previous fixed geometry limit (512 MiB on 64-bit desktop, 128 MiB elsewhere) instead of the device-based budget.",
"prefs.reports_saved": "{n} saved",
@@ -2523,6 +2586,8 @@
"prefs.ribbon_click_ends_tool_note": "A click on the lit ribbon button of the sketch tool in use ends the tool, so clicks in the view select again. Off: the click starts the tool again.",
"prefs.ribbon_reveal": "Show search results in the ribbon",
"prefs.ribbon_reveal_note": "Hovering a result in Search commands shows where the command sits in the ribbon: its tab, its button or its row in a panel's drop-down. A command started from the search blinks there, and a running command from a drop-down marks its panel.",
"prefs.ribbon_tabs_only": "Show only the ribbon's tabs",
"prefs.ribbon_tabs_only_note": "The ribbon folds to its tab row. Clicking a tab shows its panels over the view until a command starts or the pointer leaves them. Ribbon Tabs Only in the View tab, or its shortcut, switches this.",
"prefs.right_click": "Right click",
"prefs.saving": "Start and save",
"prefs.scale_note": "Also Ctrl + / Ctrl − in the window. Buttons are 28 px at 100%.",
@@ -2533,6 +2598,8 @@
"prefs.section_hatch_by_part": "By part",
"prefs.section_hatch_one": "One style",
"prefs.select": "Select",
"prefs.select_hidden_edges": "Select hidden edges in Shaded with hidden edges",
"prefs.select_hidden_edges_help": "Select edges through faces when Shaded with hidden edges is active, including for Fillet. Off keeps v0.30.1 selection behavior. Hidden bodies stay unselectable.",
"prefs.select_one_item": "Select one item",
"prefs.select_other_hidden": "Select hidden geometry with Select Other",
"prefs.select_other_hidden_help": "On: include hidden bodies, sketches and occluded edges near the pointer. Off: use the original list of visible geometry under the pointer.",
@@ -2557,6 +2624,8 @@
"prefs.sketch.click_selects": "Click selects",
"prefs.sketch.conflict_colour": "Conflict color",
"prefs.sketch.drawing": "Drawing",
"prefs.sketch.ellipse_axes": "Draw ellipses with their axes",
"prefs.sketch.ellipse_axes_note": "The major and minor axes as construction lines, so the half-axes and axes can be dimensioned. Off: the ellipse alone, and D on it gives its minor radius wherever the value is pulled, drawn and labelled R as before.",
"prefs.sketch.exit_keep": "Keep",
"prefs.sketch.exit_previous": "Return to the view before",
"prefs.sketch.exit_unless_rotated": "Unless turned",
@@ -2650,6 +2719,10 @@
"prefs.theme_follow_off": "Off",
"prefs.theme_follow_stop": "Stop following",
"prefs.theme_import_hint": "Click to choose a theme file, drop one here, or press Ctrl+V (Cmd+V on a Mac) with a theme copied",
"prefs.theme_notice_dismiss": "Dismiss",
"prefs.theme_option_blur": "Blur behind panels",
"prefs.theme_option_blur_help": "Blur behind translucent panels, in physical pixels. Zero is off.",
"prefs.theme_option_invalid": "Saved value for {option} is invalid; using the theme default. The saved value is kept.",
"prefs.theme_option_opacity": "Opacity",
"prefs.theme_option_opacity_help": "Adjust floating panel opacity. A readability floor protects text over viewport colors.",
"prefs.theme_option_tab_colours": "Tab colors",
@@ -2658,6 +2731,7 @@
"prefs.theme_option_tab_fade_ms_help": "Fade panel and viewport colors when switching tabs. Zero switches instantly.",
"prefs.theme_options_badge": "Options",
"prefs.theme_options_more": "More · {count}",
"prefs.theme_options_newer": "This theme requires a newer SolidCraft. Upgrade, then reimport the original theme to recover its option definitions.",
"prefs.theme_options_reset": "Reset",
"prefs.theme_options_unavailable": "Unavailable on this device",
"prefs.theme_paste": "Paste theme",
@@ -2721,6 +2795,12 @@
"recovery.just_now": "just now",
"recovery.min_ago": "{count} min ago",
"recovery.never_saved": "never saved",
"recovery.open_saved": "Open saved version",
"recovery.restore": "Restore",
"recovery.restore_intro": "Changes that weren't saved when this page last closed are kept in this browser.",
"recovery.restore_row": "{name}, changed {when}",
"recovery.restore_title": "Restore unsaved designs?",
"recovery.restore_title_one": "Restore unsaved design: {name}, changed {when}?",
"refused.change": "Couldn't change {body}.",
"refused.cut": "Couldn't cut {body}.",
"refused.face_curved": "One of its curved faces can't be used for modelling.",
@@ -3242,9 +3322,11 @@
"render.frame_wide": "16:9 · Widescreen",
"render.gpu_depth": "Maximum path depth",
"render.gpu_options_help": "Samples, depth, seed and exposure are saved with this setup. Only the active image method is saved.",
"render.gpu_path_crashed": "SolidCraft closed while setting it up on this graphics adapter last time, most likely inside the graphics driver, so it is not set up again by itself. Choose CPU ray tracing instead, or try again (a driver update also tries again).",
"render.gpu_path_failed": "GPU path tracer unavailable",
"render.gpu_path_first_setup": "The first setup on this GPU can take several minutes; SolidCraft stays usable meanwhile. Later starts reuse the compiled shaders where the driver keeps them.",
"render.gpu_path_preparing": "GPU path tracer: compiling shaders…",
"render.gpu_path_retry": "Try again",
"render.gpu_seed": "Random seed",
"render.gpu_unavailable": "GPU path tracing is not ready on this device.",
"render.ground_apply": "Fit and save ground",
@@ -3858,6 +3940,7 @@
"render.stage_panels.extent": "Extent",
"render.stage_panels.fit_save": "Fit and save",
"render.stage_panels.gpu_required": "Needs GPU path tracing",
"render.stage_panels.grain_angle": "Grain angle",
"render.stage_panels.keep": "Keep",
"render.stage_panels.kept": "Kept",
"render.stage_panels.kept_fields": "Camera, sampling, dimensions, optical bindings, CAD geometry and engineering properties.",
@@ -3976,6 +4059,19 @@
"render.subsurface_enable": "Interior scattering random walk",
"render.subsurface_help": "Closed dielectric only. Scattering and RGB absorption are separate per-mm coefficients. Paths scatter inside actual geometry, then refract or reflect at the boundary. Disable straight shadow transmission, surface denoising and exterior volume; use one Fresnel stratum. Not a diffusion shader, density grid or measured material. Dense media may need more samples and path depth.",
"render.subsurface_rate": "Scattering collision rate",
"render.surface_import.author": "Author",
"render.surface_import.busy": "Importing image…",
"render.surface_import.complete": "Choose an image and enter its author, source and license.",
"render.surface_import.embedded": "Imported image",
"render.surface_import.failed": "The image import did not finish.",
"render.surface_import.license": "License / ownership",
"render.surface_import.raytrace_required": "Images require CPU or GPU ray tracing. Choose it in Output.",
"render.surface_import.repeat_u": "Repeat U",
"render.surface_import.repeat_v": "Repeat V",
"render.surface_import.save": "Save image on body",
"render.surface_import.saved_cpu": "Save maps to use them with CPU rendering.",
"render.surface_import.source": "Source",
"render.surface_import.title": "Import image",
"render.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.",
@@ -4052,6 +4148,8 @@
"sample.plate.name": "Sample Plate",
"sample.vents.about": "Slots in a rectangular pattern: rows, columns and pitch are parameters",
"sample.vents.name": "Vent Plate",
"scale.per_axis": "Per axis",
"scale.uniform": "Uniform",
"search.command_keys": "Enter run Shift+Enter run and keep open Esc close",
"search.commands": "Commands",
"search.design": "In this design",
@@ -4142,6 +4240,32 @@
"sketch.dof_left": "{count} degrees of freedom left",
"sketch.dof_left_one": "{count} degree of freedom left",
"sketch.draw_curves": "Draw as {linetype}",
"sketch.error.closed_curve_two_crossings": "a closed curve needs two crossings to trim",
"sketch.error.nothing_to_break": "nothing to break here",
"sketch.error.offset_fillet_chamfer": "`{id}` is an offset, and its corners follow its source curves: round or cut the source corners before offsetting, or break the offset association first",
"sketch.error.offset_trim": "`{id}` is an offset, and its ends follow its source curves: edit the source curves instead, or break the offset association first",
"sketch.hint.arc.center_point": "Arc: click center and start, go round either way and click the end, or type a signed sweep",
"sketch.hint.arc.three_point": "Arc: click start, end, then a point on the arc",
"sketch.hint.arc3d": "3D Arc: click start, end, then a point on the arc (points may leave the sketch plane)",
"sketch.hint.break": "Break: click a curve where it should split",
"sketch.hint.circle.center": "Circle: click the center, then a point on the circle",
"sketch.hint.constraint.generic": "Constraint: pick the sketch entities",
"sketch.hint.constraint.horizontal_vertical": "Horizontal/Vertical: pick a line, two points, or a point and a line; hold Shift to pick a line midpoint",
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to center the second on the first",
"sketch.hint.dimension": "Dimension: pick one or two entities and type the value (or click to place it first); click a dimension to edit it; Enter applies",
"sketch.hint.extend": "Extend: click a curve near the end to extend (Shift: click two curves to extend both to where they meet)",
"sketch.hint.fallback": "Click in the sketch",
"sketch.hint.line": "Line: click points; right-click or Esc to finish",
"sketch.hint.line3d": "3D Line: click world points; right-click or Esc to finish (points may leave the sketch plane)",
"sketch.hint.open_done": "Open done",
"sketch.hint.point3d": "3D Point: click where a 3D point goes",
"sketch.hint.rectangle.center": "Center rectangle: click the center, then a corner",
"sketch.hint.rectangle.rounded": "Rounded rectangle: click two opposite corners (Tab to the radius box to type the corner radius)",
"sketch.hint.rectangle.three_point": "3-point rectangle: click two points of one edge, then the width",
"sketch.hint.rectangle.two_point": "Rectangle: click two opposite corners",
"sketch.hint.slot": "Slot: click both ends, then the width",
"sketch.hint.spline3d": "3D Spline: click fit points in 3D; right-click or Esc to finish",
"sketch.hint.trim": "Trim: click the piece of a curve to remove, or drag across the pieces to remove",
"sketch.make_selection": "Convert the selection to {linetype}",
"sketch.open_ends": "Open {count}",
"sketch.status_conflict": "constraints conflict",
@@ -4363,6 +4487,7 @@
"ui.3d_sketch": "3D Sketch",
"ui.activate": "Activate",
"ui.active_component": "Active component",
"ui.addToQuickAccess": "Add to Quick Access",
"ui.add_configuration": "Add configuration",
"ui.add_parameter": "Add parameter",
"ui.agent_chat": "Agent chat",
@@ -4697,6 +4822,7 @@
"ui.click_selects_whole_bodies": "Click selects whole bodies",
"ui.click_then_press_key": "Click, then press the new key",
"ui.close": "Close",
"ui.close_tab": "Close tab",
"ui.coil": "Coil",
"ui.coincident": "Coincident",
"ui.collapse": "Collapse",
@@ -4784,6 +4910,7 @@
"ui.default_modelling_orientation": "Default modeling orientation",
"ui.delete": "Delete",
"ui.delete_select_first": "Select what to delete first: features, bodies, components or sketch geometry",
"ui.dependents_select_first": "Select what to list the dependents of first",
"ui.depth": "Depth",
"ui.design": "Design",
"ui.design_properties.author": "Author",
@@ -4913,6 +5040,11 @@
"ui.factor": "Factor",
"ui.fade": "Fade",
"ui.fade_length": "Fade Length",
"ui.fair_iterations": "Iterations",
"ui.fair_keep_edges": "Keep edges",
"ui.fair_strength": "Strength",
"ui.fair_surface_title": "FAIR SURFACE",
"ui.fair_tolerance_default": "0.001 of body size",
"ui.feature_history": "Feature history",
"ui.feature_options": "Feature Options",
"ui.features": "Features",
@@ -4941,6 +5073,10 @@
"ui.fillet_setback_least": "More than {least}",
"ui.fillet_setback_tip": "Where the smooth corner patch starts: how far along each edge from the corner. The default is {factor} × the radius. It must be more than the least setback, where the rounds meet without one: the radius where the faces are square to each other.",
"ui.fillet_smooth_radius": "Smooth radius change",
"ui.fillet_turn": "Turns",
"ui.fillet_turn_mitre": "Mitre",
"ui.fillet_turn_swept": "Swept",
"ui.fillet_turn_tip": "Where two rounds turn round a sharp corner edge between them, such as the rounds along a boss's base at its upright edges.\nMitre: the rounds run on until they meet in a mitre.\nSwept: the rounds stop at the corner edge and turn round it, the corner a torus tangent to both. Only for rounds of one radius round a straight, convex corner edge.",
"ui.fillet_type_choices_tip": "Constant radius: one radius all along the edge.\nChord length: the width across the round stays the same, whatever angle the faces meet at.\nVariable radius: a radius at each end, and more along the edge if you add them.\nCurvature continuous: a section without a kink in reflections where it meets flat faces (G2).\nSpline: the radius along the edge follows control radii you set.",
"ui.fillet_type_curvature": "Curvature continuous",
"ui.fillet_type_tip": "The section across the edge. Curvature continuous: a curvature-continuous (G2) section between planar faces, so reflections run from the faces into the round without a kink.",
@@ -4986,6 +5122,7 @@
"ui.heading_construct": "CONSTRUCT",
"ui.heading_create": "CREATE",
"ui.heading_create_sketch": "CREATE SKETCH",
"ui.heading_custom_view_navigator": "CUSTOM VIEW NAVIGATOR",
"ui.heading_cylinder": "CYLINDER",
"ui.heading_design": "DESIGN",
"ui.heading_draft": "DRAFT",
@@ -5093,6 +5230,7 @@
"ui.inspect": "Inspect",
"ui.instances": "Instances",
"ui.intersect": "Intersect",
"ui.intersection_curve_second_pick": "Intersection curve: click the second face or curve (right-click to start over)",
"ui.iso_4753": "ISO 4753",
"ui.isolate": "Isolate",
"ui.join": "Join",
@@ -5121,6 +5259,7 @@
"ui.manipulator_free_alt": "Manipulator free drag also on Alt/Option",
"ui.manipulator_free_alt_tip": "On: Alt/Option or Ctrl/Cmd on extrude / gizmo arrows disables value snapping. Off: Ctrl/Cmd only.",
"ui.manufacture": "Manufacture",
"ui.match_surface_title": "MATCH SURFACE",
"ui.material_review.axes": "Material-local axes; no body orientation is assigned.",
"ui.material_review.claims": "Sources and licenses are unverified claims. Missing values remain unknown.",
"ui.material_review.compressive_strength": "Compressive strength",
@@ -5143,6 +5282,9 @@
"ui.material_review.title": "Review Physical Material Data",
"ui.material_review.unknown": "Unknown",
"ui.material_review.yield_strength": "Yield strength",
"ui.mcp_agents": "Agents through MCP",
"ui.mcp_compact": "Compact output",
"ui.mcp_compact_note": "For agents connected through the MCP server (solidcraft-cli mcp): inspect_design summarises sketches and tool results are not pretty-printed, so they use fewer tokens. Applies to agent sessions started after the change; --compact or SOLIDCRAFT_MCP_COMPACT=1 also turn it on.",
"ui.measure": "Measure",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "3MF with face tags",
@@ -5184,6 +5326,7 @@
"ui.options": "Options",
"ui.orbit": "Orbit",
"ui.orbit_around": "Orbit around",
"ui.organize": "Organize",
"ui.orientation": "Orientation",
"ui.origin": "Origin",
"ui.palette_construction_mode": "Construction mode (X)",
@@ -5228,6 +5371,7 @@
"ui.point_cloud_add_filter": "Add filter…",
"ui.point_cloud_auto_range": "Automatic Range",
"ui.point_cloud_color_by": "Color By",
"ui.point_cloud_dots": "Dots",
"ui.point_cloud_file_colors": "File Colors",
"ui.point_cloud_filter_by": "Filter by",
"ui.point_cloud_height_high": "Highest",
@@ -5272,6 +5416,7 @@
"ui.quality": "Quality",
"ui.quantity": "Quantity",
"ui.quantity_2": "Quantity 2",
"ui.quick_access_full": "The quick-access bar holds {count} commands; remove one to add another",
"ui.quit": "Quit",
"ui.radial_context_menu": "Radial context menu",
"ui.radius": "Radius",
@@ -5289,6 +5434,7 @@
"ui.relink_not_linked": "This component is not linked to a source design.",
"ui.relink_select_one": "Select one linked component or one of its instances first.",
"ui.relink_source_all": "Relink Source (All Instances)…",
"ui.removeFromQuickAccess": "Remove from Quick Access",
"ui.removeFromToolbar": "Remove from Toolbar",
"ui.removeSection": "Remove Section",
"ui.render": "Render",
@@ -5297,6 +5443,8 @@
"ui.repeat_nothing": "Nothing to repeat yet: start a tool or dialog first.",
"ui.replace_with_link_done": "The copies are now placements of the linked design.",
"ui.report_a_bug": "Report a bug",
"ui.report_characters_left_one": "{count} character left",
"ui.report_characters_left_other": "{count} characters left",
"ui.reset": "Reset",
"ui.reset_all": "Reset All",
"ui.reset_the_settings_on_this_page": "Reset the settings on this page",
@@ -5581,6 +5729,14 @@
"values must be numbers or expressions": "values must be numbers or expressions",
"view.auto_ortho_faces": "Perspective while orbiting, ortho on faces",
"view.render_inspector": "Render Inspector",
"view_gizmo.arrows": "Rotation arrows",
"view_gizmo.home": "Home button",
"view_gizmo.no_such_body": "This design has no body of that name.",
"view_gizmo.note": "Pick a body in the view. It is kept with your preferences and settings profiles, and shown in every design. Click one of its faces to look straight at it.",
"view_gizmo.texture": "Texture",
"view_gizmo.themed": "Themed",
"view_gizmo.too_detailed": "Too detailed for the view navigator (over 10,000 triangles)",
"view_gizmo.too_thin": "Too long and thin for the view navigator: its thinnest side must be at least a fifth of its longest",
"web.files.insert_drawings_hint": "CAD files, meshes, DXF or SVG into the open design",
"web.files.insert_stored_drawings_hint": "Into the open design (CAD files, meshes, DXF, SVG)",
"web.open_full": "Open in SolidCraft",
+798 -3
View File
File diff suppressed because it is too large Load Diff
+824 -2
View File
File diff suppressed because it is too large Load Diff
+11 -3
View File
@@ -956,8 +956,6 @@
"cmd.inspect_accessibility": "Analyse d'accessibilité",
"cmd.inspect_center_of_mass": "Centre de gravité",
"cmd.inspect_clearance": "Jeu",
"cmd.inspect_colored_components": "Activer les couleurs des composants",
"cmd.inspect_colored_components_disable": "Désactiver les couleurs des composants",
"cmd.inspect_compare": "Comparer les corps",
"cmd.inspect_compare_picture": "Comparer les images",
"cmd.inspect_continuity": "Analyse de continuité",
@@ -1365,11 +1363,14 @@
"dialog.computing": "Calcul… {seconds} s",
"dialog.could_not_be_checked": "non vérifiable",
"dialog.custom_axis": "↖ Axe personnalisé",
"dialog.cylinder_face_tip": "La face ou le sommet du modèle sur lequel le cylindre est posé : cliquer ici, puis dessus dans la vue. Laissé vide, X, Y et Z le placent.",
"dialog.dimension_insert_hint": "Double-cliquez sur une autre cote pilotante pour insérer son nom",
"dialog.dimension_name": "Cote : {name}",
"dialog.edit_title": "MODIFIER {title}",
"dialog.no_shown_body": "Aucun corps affiché à modifier : affichez-en un ou choisissez Nouveau corps",
"dialog.pattern_sketch_not_object": "Choisissez des corps ou des fonctions : une esquisse n'est ni l'un ni l'autre. Répétez ses courbes avec la Répétition ou la Symétrie de l'esquisse, ou extrudez-la d'abord.",
"dialog.primitive_point_tip": "Un point qui remplit X, Y et Z : cliquer ici, puis sur un sommet, une arête ou une face du modèle, sur un point d'une esquisse ou sur l'origine.",
"dialog.reference_edge_tip": "Une arête droite de la face choisie, depuis laquelle la distance ci-dessous est mesurée : cliquer ici, puis sur l'arête.",
"dialog.select": "↖ Sélectionner",
"dialog.selected_count": "↖ {count} sélectionné(s)",
"dialog.selection_constraint": "Contrainte {id}",
@@ -1391,6 +1392,7 @@
"document.read_only_save": "Cette conception a été ouverte en lecture seule : enregistrez-la dans un nouveau fichier",
"document.save_changes": "Enregistrer les modifications de {name} ?",
"document.save_copy": "Enregistrer une copie…",
"document.tab_unsaved_changes": "Modifications non enregistrées",
"document.unsaved_changes": "{name} contient des modifications non enregistrées. Si vous n'enregistrez pas, elles seront perdues.",
"drive.rigid": "{joint} est une liaison rigide : elle maintient ses pièces immobiles, il n'y a donc rien à piloter. Pour les laisser bouger, changer son type dans Modifier la liaison : Revolute pour tourner, Slider pour coulisser, Cylindrical pour les deux.",
"drive.stopped_by_contact": "Arrêté à {value} : {a} traverserait {b}",
@@ -1622,7 +1624,7 @@
"help.sketch.insert_svg": "Lit les tracés d'un dessin SVG dans une esquisse, mis à l'échelle, tournés et placés comme vous le définissez. À utiliser pour transformer un logo ou un contour vectoriel en courbes que vous pouvez extruder.",
"help.sketch.inspect": "Ouvre un rapport sur l'esquisse ouverte : combien de degrés de liberté il reste et si elle est entièrement contrainte. À utiliser pour savoir ce qui bouge encore avant de terminer l'esquisse.",
"help.sketch.intersect": "Ajoute les courbes ou points où des corps, faces, arêtes ou d'autres esquisses traversent le plan d'esquisse. Ils restent liés et suivent le modèle lorsqu'il change.",
"help.sketch.intersection_curve": "Crée des courbes 3D là où deux corps ou faces se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
"help.sketch.intersection_curve": "Crée des courbes 3D là où deux corps, faces ou courbes d'autres esquisses se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
"help.sketch.isoparametric_curve": "Crée une courbe 3D le long de la direction U ou V d'une face, par un point que vous choisissez, liée à la face. À utiliser comme trajectoire ou ligne de partage qui suit la surface.",
"help.sketch.line": "Dessine des lignes connectées point par point, en chaîne ouverte ou en contour fermé. C'est l'outil de base de la plupart des profils.",
"help.sketch.line.midpoint": "Dessine une ligne de son milieu vers une extrémité, l'autre extrémité étant son symétrique. À utiliser pour des lignes centrées sur un point, comme une arête symétrique.",
@@ -3000,9 +3002,11 @@
"render.frame_wide": "16:9 · Écran large",
"render.gpu_depth": "Profondeur maximale des chemins",
"render.gpu_options_help": "Les échantillons, la profondeur, la graine et l'exposition sont enregistrés avec ce préréglage. Seule la méthode d'image active est enregistrée.",
"render.gpu_path_crashed": "SolidCraft s’est fermé pendant sa dernière préparation sur cet adaptateur graphique, très probablement dans le pilote graphique ; il n’est donc plus préparé de lui-même. Utilisez le lancer de rayons sur le CPU, ou réessayez (une mise à jour du pilote réessaie aussi).",
"render.gpu_path_failed": "Traceur de chemins GPU indisponible",
"render.gpu_path_first_setup": "La première initialisation sur ce GPU peut prendre plusieurs minutes ; SolidCraft reste utilisable pendant ce temps. Les démarrages suivants réutilisent les shaders compilés lorsque le pilote les conserve.",
"render.gpu_path_preparing": "Traceur de chemins GPU : compilation des shaders…",
"render.gpu_path_retry": "Réessayer",
"render.gpu_seed": "Graine aléatoire",
"render.gpu_unavailable": "Le tracé de chemins GPU n'est pas prêt sur cet appareil.",
"render.ground_apply": "Ajuster et enregistrer le sol",
@@ -3810,6 +3814,8 @@
"sample.plate.name": "Plaque d'exemple",
"sample.vents.about": "Rainures en répétition rectangulaire : lignes, colonnes et pas sont des paramètres",
"sample.vents.name": "Plaque d'aération",
"scale.per_axis": "Par axe",
"scale.uniform": "Uniforme",
"search.command_keys": "Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
"search.commands": "Commandes",
"search.design": "Dans cette conception",
@@ -4439,6 +4445,7 @@
"ui.click_selects_whole_bodies": "Un clic sélectionne des corps entiers",
"ui.click_then_press_key": "Cliquer, puis appuyer sur la nouvelle touche",
"ui.close": "Fermer",
"ui.close_tab": "Fermer l’onglet",
"ui.coil": "Hélice",
"ui.coincident": "Coïncidence",
"ui.collapse": "Réduire",
@@ -4922,6 +4929,7 @@
"ui.point_cloud_add_filter": "Ajouter un filtre…",
"ui.point_cloud_auto_range": "Plage automatique",
"ui.point_cloud_color_by": "Colorer par",
"ui.point_cloud_dots": "Points isolés",
"ui.point_cloud_file_colors": "Couleurs du fichier",
"ui.point_cloud_filter_by": "Filtrer par",
"ui.point_cloud_height_high": "Plus haut",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Análise de accesibilidade",
"cmd.inspect_center_of_mass": "Centro de masas",
"cmd.inspect_clearance": "Folga",
"cmd.inspect_colored_components": "Activar as cores dos compoñentes",
"cmd.inspect_colored_components_disable": "Desactivar as cores dos compoñentes",
"cmd.inspect_compare": "Comparar corpos",
"cmd.inspect_compare_picture": "Comparar a imaxe",
"cmd.inspect_continuity": "Análise de continuidade",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Le os trazados dun debuxo SVG nun bosquexo, escalados, xirados e colocados onde indiques. Úsao para converter un logotipo ou un contorno vectorial en curvas que podes extruír.",
"help.sketch.inspect": "Abre un informe do bosquexo aberto: cantos graos de liberdade quedan e se está totalmente restrinxido. Úsao para saber o que aínda se move antes de rematar o bosquexo.",
"help.sketch.intersect": "Engade as curvas ou os puntos onde corpos, caras, arestas ou outros bosquexos cruzan o plano do bosquexo. Quedan ligados e seguen o modelo cando cambia.",
"help.sketch.intersection_curve": "Crea curvas 3D onde se atopan dous corpos ou caras, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
"help.sketch.intersection_curve": "Crea curvas 3D onde se atopan dous corpos, caras ou curvas doutros bosquexos, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
"help.sketch.isoparametric_curve": "Crea unha curva 3D na dirección U ou V dunha cara por un punto que escollas, ligada á cara. Úsaa como traxectoria ou liña de división que segue a superficie.",
"help.sketch.line": "Debuxa liñas conectadas punto a punto, como cadea aberta ou como contorno pechado. É a ferramenta básica para a maioría dos perfís.",
"help.sketch.line.midpoint": "Debuxa unha liña desde o seu punto medio ata un extremo, co outro extremo reflectido. Úsao para liñas centradas nun punto, como unha aresta simétrica.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Placa de exemplo",
"sample.vents.about": "Ranuras nun patrón rectangular: filas, columnas e paso son parámetros",
"sample.vents.name": "Placa de ventilación",
"scale.per_axis": "Por eixo",
"scale.uniform": "Uniforme",
"search.command_keys": "Intro executar Maiús+Intro executar e manter aberto Esc pechar",
"search.commands": "Ordes",
"search.design": "Neste deseño",
+159 -3
View File
@@ -85,6 +85,7 @@
"Bottom Right": "למטה מימין",
"Boundary": "גבול",
"Bounding box": "תיבה תוחמת",
"Bridge": "גשר",
"CHECK": "בדיקה",
"COMPONENTS": "רכיבים",
"CONFIGURATIONS": "תצורות",
@@ -628,6 +629,8 @@
"Suppress Selected Folders": "השבתת התיקיות שנבחרו",
"Suppressed": "מושבת",
"Surface": "משטח",
"Surface or face": "משטח או פאה",
"Surface or faces": "משטח או פאות",
"Surface, or faces to turn over": "משטח, או פאות להיפוך",
"Surfaces": "משטחים",
"Swap Ends": "החלפת קצוות",
@@ -641,6 +644,7 @@
"Target": "יעד",
"Target Body": "גוף יעד",
"Target Face (optional)": "פאת יעד (אופציונלי)",
"Target edges": "מקצועות יעד",
"Tear": "קריעה",
"Technical details": "פרטים טכניים",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "הסקיצה המקווקוות מסמנת מישור שמור. יש לבחור פאה חדשה כדי לתקן את ההצמדה.",
@@ -648,6 +652,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "הכיוון חייב להיות ציר או עקומת סקיצה ישרה הנמצאת במישור הסקיצה",
"The mirror line cannot also be mirrored": "אי אפשר לשקף גם את קו השיקוף עצמו",
"The mirror line must be a straight curve of this sketch, not an origin axis": "קו השיקוף חייב להיות עקומה ישרה של הסקיצה הזו, ולא אחד מצירי הראשית",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "ציר הראשית ניצב למישור הסקיצה, ולכן אין לו קו במישור",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "מופע היצירה השמור חסר. יש לבחור מיקום יצירה תקין לפני הקישור מחדש.",
"Then": "אז",
"These features will be deleted:": "הפיצ'רים הבאים יימחקו:",
@@ -739,6 +744,9 @@
"a picked edge's body is gone": "הגוף של המקצוע שנבחר כבר לא קיים",
"add a key for a joint first": "יש להוסיף תחילה מפתח למפרק",
"ai.connected_toast": "SolidCraft זמין עכשיו ב־{clients}. יש להפעיל אותם מחדש כדי להשתמש בו (הגדרות > עוזר).",
"ai.http.allow": "אפשר HTTP לא מאובטח",
"ai.http.confirm": "אפשר עבור נקודת קצה זו",
"ai.http.warning": "מפתח ה־API והשיחה יישלחו ללא הצפנה. כל מי שיש לו גישה לרשת יכול לקרוא או לשנות אותם. יש להשתמש באפשרות זו רק ברשת מהימנה.",
"ai.prefs.allow_control": "לאפשר לסוכני AI להתחבר",
"ai.prefs.allow_control_note": "ערוץ מקומי (MCP). שום דבר לא יוצא מהמחשב הזה.\n\nפותח ערוץ בקרה במחשב זה בלבד (127.0.0.1, פורט 27878 או הפנוי הבא). תוכנה שמתחברת צריכה גם להציג אסימון סודי שרק חשבון המשתמש שלך יכול לקרוא, ונוצר מחדש בכל הפעלה. ייכנס לתוקף בהפעלה הבאה של SolidCraft. כבוי: שום דבר אינו מאזין, אלא אם SolidCraft מופעל עם --control.",
"ai.prefs.availability": "זמינות",
@@ -750,6 +758,8 @@
"ai.prefs.follow_camera": "לעקוב אחר המצלמה",
"ai.prefs.follow_camera_off": "מעקב המצלמה האוטומטי כבוי.",
"ai.prefs.follow_camera_on": "המצלמה פונה אל הסקיצות שהסוכן יוצר.",
"ai.prefs.instructions": "הוראות מותאמות אישית",
"ai.prefs.instructions_note": "נשלחות לסוכן בתחילת כל צ'אט, אחרי ההוראות של SolidCraft עצמה: דרך העבודה המועדפת עליך וכללים שיש להקפיד עליהם. שינוי חל מהצ'אט הבא (התחברות או צ'אט חדש).",
"ai.prefs.intro": "סוכני AI ב־SolidCraft.",
"ai.prefs.make_default": "לקבוע כברירת מחדל",
"ai.prefs.no_agents": "אין עדיין סוכנים. אפשר להוסיף סוכן או לרענן כדי למצוא סוכנים מותקנים.",
@@ -963,8 +973,7 @@
"cmd.inspect_analysis": "ניתוח תצוגה",
"cmd.inspect_center_of_mass": "מרכז מסה",
"cmd.inspect_clearance": "מרווח",
"cmd.inspect_colored_components": "הפעלת צבעי רכיבים",
"cmd.inspect_colored_components_disable": "ביטול צבעי רכיבים",
"cmd.inspect_colored_components": "צבעי רכיבים",
"cmd.inspect_compare": "השוואת גופים",
"cmd.inspect_compare_picture": "השוואה לתמונת ייחוס",
"cmd.inspect_continuity": "ניתוח רציפות",
@@ -1081,6 +1090,9 @@
"cmd.render.compare.divider": "הזז את מחיצת ההשוואה של הרינדור",
"cmd.render.result.discard": "השלך תוצאת רינדור",
"cmd.render.result.select": "בחר תוצאת רינדור",
"cmd.render.surface.import": "ייבוא מפות משטח",
"cmd.render.texture.list": "הצגת תמונות משטח מוטמעות",
"cmd.render.texture.remove": "הסרת תמונת משטח מוטמעת",
"cmd.render_aging_describe": "הזדקנות משטח בשכבות",
"cmd.render_backends": "מנועי הדמיה",
"cmd.render_batch": "רינדור כל הצילומים",
@@ -1296,6 +1308,7 @@
"cmd.timeline_stop": "עצירת ציר הזמן",
"cmd.timeline_suppress": "השבתת פיצ'ר",
"cmd.ui.assistant": "הצגה או הסתרה של העוזר",
"cmd.view.navigator": "ניווט תצוגה מותאם אישית",
"cmd.view.render_browser": "דפדפן רינדור",
"cmd.view.render_dock": "הזז את עגינת הרינדור",
"cmd.view.render_focus": "תצוגת מיקוד רינדור",
@@ -1323,6 +1336,7 @@
"cmd.view_previous_tab": "הכרטיסייה הקודמת",
"cmd.view_rename": "שינוי שם תצוגה שמורה",
"cmd.view_render_frame": "מדריכי מסגרת רינדור",
"cmd.view_ribbon_tabs_only": "רק לשוניות הסרט",
"cmd.view_right": "תצוגה ימנית",
"cmd.view_save": "תצוגה שמורה חדשה",
"cmd.view_shots_strip": "סרגל צילומים",
@@ -1394,15 +1408,22 @@
"dialog.computing": "מחשב… {seconds} שנ'",
"dialog.could_not_be_checked": "לא ניתן היה לבדוק",
"dialog.custom_axis": "↖ ציר מותאם",
"dialog.cylinder_face_tip": "הפאה או הקודקוד של המודל שעליהם הגליל ניצב: יש ללחוץ כאן ואז עליהם בתצוגה. אם נשאר ריק, X, Y ו-Z ממקמים אותו.",
"dialog.dimension_insert_hint": "יש ללחוץ על מידה מניעה אחרת כדי להוסיף את שמה",
"dialog.dimension_name": "מידה: {name}",
"dialog.dimension_step_down": "הפחתה ב־{step}",
"dialog.dimension_step_expression": "רק מספר משתנה בצעדים; ביטוי נשאר כפי שנכתב",
"dialog.dimension_step_up": "הוספה של {step}",
"dialog.edit_title": "עריכת {title}",
"dialog.no_shown_body": "אין גוף מוצג לשינוי: הציגו גוף, או בחרו „גוף חדש”",
"dialog.pattern_sketch_not_object": "בחרו גופים או פיצ'רים: סקיצה אינה אף אחד מהם. חזרו על העקומות שלה בעזרת המערך או השיקוף של הסקיצה עצמה, או בצעו לה שליפה קודם.",
"dialog.primitive_point_tip": "נקודה שממלאת את X, Y ו-Z: יש ללחוץ כאן ואז על קודקוד, מקצוע או פאה של המודל, על נקודה של סקיצה, או על הראשית.",
"dialog.reference_edge_tip": "מקצוע ישר של הפאה שנבחרה שממנו נמדד המרחק שלמטה: יש ללחוץ כאן ואז על המקצוע.",
"dialog.select": "↖ בחירה",
"dialog.selected_count": "↖ {count} נבחרו",
"dialog.selection_constraint": "אילוץ {id}",
"dialog.selection_external_curve": "{sketch} · עקומה {id}",
"dialog.selection_external_point": "{point} · {sketch}",
"dialog.selection_guidance": "יש לבחור קלט כדי לשנות אותו. לחיצה על אובייקט שנבחר מסירה אותו; גם Ctrl/Cmd+לחיצה מחליפה את מצבו. לאחר בדיקת התצוגה המקדימה יש ללחוץ על אישור.",
"dialog.selection_midpoint": "נקודת אמצע · {id}",
"dialog.selection_profile": "{sketch} · פרופיל {number}",
@@ -1420,6 +1441,7 @@
"document.read_only_save": "התכן הזה נפתח לקריאה בלבד: יש לשמור אותו כקובץ חדש",
"document.save_changes": "לשמור את השינויים ב־{name}?",
"document.save_copy": "שמירת עותק…",
"document.tab_unsaved_changes": "שינויים שלא נשמרו",
"document.unsaved_changes": "ב־{name} יש שינויים שלא נשמרו. ללא שמירה, הם יאבדו.",
"drive.rigid": "{joint} הוא מפרק קשיח: הוא מחזיק את החלקים שלו במקומם, ולכן אין מה להניע. כדי שהחלקים יזוזו, שנו את הסוג שלו ב״עריכת מפרק״: Revolute לסיבוב, Slider להחלקה, Cylindrical לשניהם.",
"drive.stopped_by_contact": "נעצר ב־{value}: {a} היה עובר דרך {b}",
@@ -1440,10 +1462,16 @@
"each rail's curves must be in one sketch": "עקומות כל מסילה חייבות להיות בסקיצה אחת",
"feature computation ended without a result": "חישוב הפיצ'ר הסתיים ללא תוצאה",
"file.open.drawing_filter": "תכנים, קובצי CAD, רשתות משולשים ושרטוטים",
"file.open.portal_fallback.no_portal_service": "בורר הקבצים שנפתח אינו של שולחן העבודה: שירות בחירת הקבצים של שולחן העבודה אינו זמין.",
"file.open.portal_fallback.no_session_bus": "בורר הקבצים שנפתח אינו של שולחן העבודה: אין אוטובוס הפעלה של D-Bus.",
"file.open.portal_fallback.xwayland": "בורר הקבצים שנפתח אינו של שולחן העבודה: תחת XWayland הפורטל דוחה את חלון האב.",
"file_load.built": "נבנו עד כה: {built} מתוך {total} פיצ'רים.",
"file_load.converting": "המרת גוף מוצק {done} מתוך {total}",
"file_load.inserted": "{name} הוכנס",
"file_load.inserting": "הוספת גאומטריה",
"file_load.link_item": "{name} ({done} מתוך {total})",
"file_load.opened": "{name} נפתח",
"file_load.opened_in_tab": "{name} נפתח בלשונית חדשה",
"file_load.opening": "פותח את התכן",
"file_load.preparing_thumbnail": "מכין את התמונה הממוזערת...",
"file_load.preparing_view": "מכין את התצוגה...",
@@ -1679,7 +1707,7 @@
"help.sketch.insert_svg": "קריאת הנתיבים של שרטוט SVG לתוך סקיצה, בקנה המידה, בסיבוב ובמיקום שנקבעו. כך הופכים לוגו או קו מתאר וקטורי לעקומות שניתן לשחל.",
"help.sketch.inspect": "פותח דוח על הסקיצה הפתוחה: כמה דרגות חופש נותרו והאם היא מאולצת במלואה. השתמשו בו כדי לגלות מה עדיין זז לפני שמסיימים את הסקיצה.",
"help.sketch.intersect": "מוסיף את העקומות או הנקודות שבהן גופים, פאות, קצוות או סקיצות אחרות חותכים את מישור הסקיצה. הן נשארות מקושרות ועוקבות אחר המודל כשהוא משתנה.",
"help.sketch.intersection_curve": "יוצר עקומות תלת־ממדיות במקום שבו שני גופים או פאות נפגשים, מקושרות אליהם. מתאים למסלולים או למסילות הנחיה שעוקבים אחר המודל.",
"help.sketch.intersection_curve": "יוצר עקומות תלת־ממדיות במקום שבו שני גופים, פאות או עקומות של סקיצות אחרות נפגשים, מקושרות אליהם. מתאים למסלולים או למסילות הנחיה שעוקבים אחר המודל.",
"help.sketch.isoparametric_curve": "יוצר עקומה תלת־ממדית לאורך כיוון U או V של פאה דרך נקודה שבוחרים, מקושרת לפאה. מתאים למסלול או לקו פיצול שעוקבים אחר המשטח.",
"help.sketch.line": "משרטט ישרים מחוברים נקודה אחר נקודה, כשרשרת פתוחה או כקו מתאר סגור. זה הכלי הבסיסי לרוב הפרופילים.",
"help.sketch.line.midpoint": "משרטט ישר מנקודת האמצע שלו אל אחד הקצוות, כשהקצה השני משוקף. מתאים לישרים הממורכזים סביב נקודה, כמו קצה סימטרי.",
@@ -1785,6 +1813,7 @@
"help.view.navigate": "סיבוב, הזזה או זום של התצוגה בצעדים, כמו רבע סיבוב שמאלה. מתאים להזזה מדויקת של המצלמה במקום גרירה.",
"help.view.navigation_bar": "הצגה או הסתרה של סרגל הניווט בתחתית חלון התצוגה. אפשר להסתיר אותו לשטח נוסף, או להציג אותו כדי להגיע אל כלי התצוגה בעכבר.",
"help.view.rename": "מתן שם חדש לתצוגה שמורה. התצוגה עצמה נשארת כפי שהיא.",
"help.view.ribbon_tabs_only": "מקפל את הסרט לשורת הלשוניות שלו, כך שחלון התצוגה מקבל את המקום שתפסו הלוחות. לחיצה על לשונית מציגה את הלוחות שלה מעל התצוגה; בחירה חוזרת בפקודה משאירה את הסרט כולו מוצג.",
"help.view.save": "שמירת מיקום המצלמה הנוכחי כתצוגה בשם בתכן. מתאים לחזרה לאותה זווית בהמשך, למשל לצילומי מסך.",
"help.view.spin": "הקפת המודל בתצוגה באופן רציף עד ללחיצה או למקש הבא. מתאים להצגת מודל מכל הצדדים.",
"help.view.tool": "נעילת כלי מצלמה, מסלול, הזזה או זום, כך שגרירה בכפתור השמאלי של העכבר מזיזה את התצוגה עד שמכבים אותו. מתאים למשטח מגע או כשאין כפתור עכבר אמצעי.",
@@ -1999,6 +2028,7 @@
"material.selection.mixed": "חומרים שונים",
"measure.at": "מיקום",
"measure.item": "פריט {number}",
"measure.line_to_centre": "מהקו למרכז",
"measure.quick_copied": "המדידה הועתקה",
"measure.quick_copy": "לחיצה מעתיקה את המדידה",
"measure.quick_edges": "{count} מקצועות",
@@ -2029,6 +2059,8 @@
"nav.blender.help": "גרירה אמצעית מסובבת, Shift וגרירה אמצעית מזיזים, Ctrl וגרירה אמצעית מבצעים זום; גרירה ימנית לא מזיזה את המצלמה",
"nav.creo.help": "גרירה בלחצן האמצעי מסובבת את התצוגה, Shift והלחצן האמצעי מזיזים אותה, Ctrl וגרירה אנכית בלחצן האמצעי משנים את הזום (גרירה מטה להגדלה); גלילת הגלגלת משנה גם היא את הזום",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "גרירה בכפתור האמצעי או הימני ממשיכה לסובב, להזיז או לשנות זום כפי שהתחילה עד שמשחררים את הכפתור, בלי קשר למקשים שנלחצים או משתחררים בינתיים. כבוי: המקשים הלחוצים קובעים בכל רגע של הגרירה.",
"nav.drag_keeps_mode.label": "גרירה שומרת על הניווט שבו התחילה",
"nav.free_orbit.help": "העכבר והעכבר התלת־ממדי מסובבים דרך שני הקטבים ושומרים על הגלגול. הצמדת הצירים עם Shift מושבתת.",
"nav.free_orbit.label": "סיבוב חופשי",
"nav.free_orbit.snap_help": "העכבר והעכבר התלת־ממדי מסובבים דרך שני הקטבים ושומרים על הגלגול. יש להחזיק את Shift בזמן גרירת סיבוב כדי להצמיד את כיוון התצוגה ואת הגלגול.",
@@ -2265,6 +2297,17 @@
"prefs.auto_operation_note": "איחוד, גוף חדש או חיתוך, לפי מה שהחומר נוגע בו.",
"prefs.axes": "צירים",
"prefs.axis": "ציר",
"prefs.background_choose": "בחירת תמונה…",
"prefs.background_image": "תמונה",
"prefs.background_unreadable": "לא ניתן לקרוא את תמונת הרקע",
"prefs.blur_cost": "עלות עיבוד יחסית. יותר פסים מציינים בדרך כלל יותר עבודה.",
"prefs.blur_device": "לא זמין בהתקן הגרפי או בנתיב העיבוד הזה",
"prefs.blur_guard": "איכות הטשטוש הופחתה כדי לשמור על תגובתיות הפריימים. הרדיוס לא השתנה.",
"prefs.blur_off": "כבוי",
"prefs.blur_reduce_transparency": "מושבת עקב הגדרת הפחתת השקיפות של המערכת",
"prefs.blur_software": "לא זמין עם עיבוד גרפי בתוכנה (gpu-software)",
"prefs.blur_source": "לא זמין בתצוגת סביבת העבודה הזו",
"prefs.blur_web": "לא זמין בנתיב העיבוד הזה בדפדפן",
"prefs.bodies_folders": "תיקיות גופים",
"prefs.bodies_folders_always": "תמיד",
"prefs.bodies_folders_never": "אף פעם",
@@ -2278,6 +2321,8 @@
"prefs.browser_eye_left": "סמל העין ליד חץ הקיפול",
"prefs.browser_eye_left_note": "שורות עץ התכן מציגות את סמל העין ליד חץ הקיפול, כמו קודם, במקום בעמודה בקצה הימני של השורות.",
"prefs.browser_reveal_on_hover": "הצגה בריחוף",
"prefs.browser_rows_start_closed": "שורות עץ התכן מוצגות סגורות",
"prefs.browser_rows_start_closed_note": "מיקומים ותיקיות בעץ התכן מוצגים סגורים, כך שתכן עם רכיבים רבים מציג את השורות העליונות שלו. כל שורה נפתחת בחץ שלה, ב„הרחבת הכול” או ב„איתור בעץ התכן”, שפותח רק את השורות שמובילות לגוף שלו וסוגר שוב את אלה שהאיתור הקודם פתח. גוף שנבחר בתצוגה התלת־ממדית משאיר את עץ התכן כפי שהוא. כשהאפשרות כבויה התיקיות מוצגות פתוחות, המיקומים מוצגים כפי שקובעת האפשרות „רכיבים פתוחים מההתחלה”, ובחירה בתצוגה התלת־ממדית פותחת את עץ התכן עד לגוף שלה, כמו קודם.",
"prefs.browser_show_note": "״מוצג״ משאיר את הדפדפן על המסך. ״הצגה בריחוף״ מסתיר אותו עד שהמצביע מגיע למקומו או ללשונית הדקה בקצהו השמאלי, והוא נשאר פתוח כל עוד המצביע בתוכו או שתפריט ממנו פתוח.",
"prefs.browser_shown": "מוצג",
"prefs.built_in": "מובנה",
@@ -2288,6 +2333,7 @@
"prefs.carousel_previous": "הקודם",
"prefs.choose_font": "בחירת קובץ גופן…",
"prefs.click": "לחיצה",
"prefs.command_dialog_values": "תיבות דו-שיח של פקודות",
"prefs.contact_shading": "הצללת מגע רכה",
"prefs.contact_shading_note": "הצללה רכה במקומות שבהם חלקים מתקרבים.",
"prefs.context_menu": "תפריט הקשר",
@@ -2323,6 +2369,13 @@
"prefs.dialogs_edge": "בקצה התצוגה",
"prefs.dialogs_left_column": "בעמודה השמאלית",
"prefs.dialogs_note": "היכן נפתחות תיבות הדו־שיח של הפקודות: מעוגנות בקצה התצוגה, או בעמודה השמאלית במקום הדפדפן וההיסטוריה, שחוזרים כפי שהיו כשהתיבה נסגרת.",
"prefs.dimension_step_angle": "מה שהחצים בחלון, למעלה/למטה והגלגלת מוסיפים לזווית",
"prefs.dimension_step_length": "מה שהחצים בחלון, למעלה/למטה והגלגלת מוסיפים לאורך",
"prefs.dimension_steps": "צעדי החלון",
"prefs.dimension_values": "ערכי מידות",
"prefs.dimension_values_note": "היכן מקלידים את ערך המידה. לחלון יש חצים שמשנים אותו בצעדים; כלי המידה מציב קודם את הטקסט.",
"prefs.dimension_values_view": "בתצוגה",
"prefs.dimension_values_window": "בחלון",
"prefs.display": "תצוגה",
"prefs.edges": "מקצועות והצללה",
"prefs.extend": "הרחבה",
@@ -2376,6 +2429,8 @@
"prefs.graphics_page.surfaces_edges": "משטחים וקצוות",
"prefs.graphics_page.view_bar": "גם בסרגל התצוגה",
"prefs.graphics_presets_note": "הגדרות קבועות מראש קובעות יחד את הבחירות שלמטה.",
"prefs.ground_shadow_follows_model": "צל הקרקע עוקב אחר המודל",
"prefs.ground_shadow_follows_model_help": "מקם את הצל מתחת לגוף הגלוי הנמוך ביותר. כשהאפשרות כבויה, הצל של מודלים מוגבהים נשאר במישור הקרקע העובר בראשית הצירים.",
"prefs.hide_constraint_glyphs_zoomed_out": "הסתרת סמלי אילוצים בהקטנה",
"prefs.hide_constraint_glyphs_zoomed_out_note": "סמלי האילוצים נעלמים כשהשרטוט מוקטן מספיק כדי שלא יעמיסו על התצוגה. כבוי: הם נשארים בגודל מסך קבוע בכל זום.",
"prefs.history": "ציר הזמן",
@@ -2402,6 +2457,10 @@
"prefs.invert_curved_roll_arrows_note": "שני החצים המעוגלים בפינת הקובייה מסובבים את התצוגה במישור המסך. פועל: החץ עם כיוון השעון מסובב נגד כיוון השעון, והחץ נגד כיוון השעון מסובב עם כיוון השעון. כבוי: כל חץ מסובב את התצוגה לפי העקום שלו.",
"prefs.invert_navigator_arrows": "היפוך חצי סיבוב קוביית התצוגה",
"prefs.invert_navigator_arrows_note": "החצים סביב הקובייה מחוויים את המצלמה לכיוון שהחץ מצביע, כמו ב-Fusion ו-Inventor: החץ שמשמאל מצביע ימימה ומניע את המצלמה ימימה. כבוי: החץ מסובב את הקובייה אל הצד שבו הוא יושב, כך שחץ שמאל מניע את המצלמה שמאלה. חצי הגלילה המעוגלים אינם מושפעים.",
"prefs.keep_unsaved_also_asks": "לשאול גם כשהכרטיסייה נסגרת",
"prefs.keep_unsaved_also_asks_tip": "כשאפשרות זו כבויה, הכרטיסייה נסגרת ללא חלון הדפדפן לאחר שהעותק של כל תכן שלא נשמר עודכן.",
"prefs.keep_unsaved_in_browser": "לשמור עבודה שלא נשמרה בדפדפן זה",
"prefs.keep_unsaved_in_browser_tip": "השינויים שלא נשמרו בכל תכן פתוח נשמרים מעודכנים באחסון הפרטי של דפדפן זה. בביקור הבא תוצע שחזורם.",
"prefs.keep_work_on_tab_switch": "השארת תיבות דו־שיח וכלים פתוחים במעבר בין עיצובים",
"prefs.keep_work_on_tab_switch_note": "כל לשונית עיצוב שומרת את תיבת הדו־שיח או הכלי הפתוחים בה, עם מה שבחרת והקלדת בהם, ואת לשונית סרגל הכלים שלה. הם מחכים לך כשתחזור לעיצוב. כבוי: מעבר בין עיצובים מבטל אותם ומציג את הלשונית מודל.",
"prefs.labels": "תוויות",
@@ -2413,6 +2472,8 @@
"prefs.layout_page.card": "כרטיס",
"prefs.layout_page.column": "עמודה",
"prefs.layout_page.hover": "מעבר עכבר",
"prefs.layout_page.hover_panel_menus": "מעבר בין תפריטי לוחות בריחוף",
"prefs.layout_page.hover_panel_menus_help": "לחצו על שם לוח ברצועת הכלים כדי לפתוח את התפריט שלו. העבירו את הסמן מעל שם של לוח אחר כדי לעבור לתפריט שלו. הזיזו את הסמן אל מחוץ לרצועת הכלים ולתפריט הפתוח כדי לסגור אותו.",
"prefs.layout_page.icons_labels": "סמלים + תוויות",
"prefs.layout_page.ok_cancel": "אישור וביטול",
"prefs.layout_page.on_hover": "בעת מעבר עכבר",
@@ -2515,6 +2576,8 @@
"prefs.quality_smoother": "חלק יותר",
"prefs.quick_measure": "מדידה מהירה בפינת התצוגה",
"prefs.quick_measure_note": "מציג את מידות הבחירה בפינה הימנית התחתונה של התצוגה, מעל ההיסטוריה: אורך מקצוע (או Ø ו-R למקצוע עגול), שטח פאה, מיקום נקודה, המרחק והזווית בין שני פריטים, והסכום של כמה מקצועות או פאות. לחיצה מעתיקה אותו.",
"prefs.remember_dialog_values": "זכור ערכים בתיבות דו-שיח של פקודות",
"prefs.remember_dialog_values.help": "כבוי כברירת מחדל. כשהוא פועל, לאחר הצלחת פקודה, הערכים והאפשרויות הבטוחים שלה ישמשו שוב בפעם הבאה שתפתח את אותה פקודה, עד לסגירת SolidCraft.",
"prefs.render_previous_geometry_budget": "שימוש בתקציב הגאומטריה הקודם לתצוגה מקדימה של רינדור",
"prefs.render_previous_geometry_budget_help": "שחזור מגבלת הגאומטריה הקבועה הקודמת (512 MiB במחשב שולחני של 64 סיביות, 128 MiB בפלטפורמות אחרות) במקום התקציב המבוסס על ההתקן.",
"prefs.reports_saved": "{n} שמורים",
@@ -2523,6 +2586,8 @@
"prefs.ribbon_click_ends_tool_note": "לחיצה על לחצן הסרט המודגש של כלי השרטוט שבשימוש מסיימת את הכלי, כך שלחיצות בתצוגה בוחרות שוב. כבוי: הלחיצה מפעילה את הכלי מחדש.",
"prefs.ribbon_reveal": "הצגת תוצאות חיפוש בסרט",
"prefs.ribbon_reveal_note": "מעבר עם הסמן מעל תוצאה בחיפוש פקודות מראה היכן הפקודה נמצאת בסרט: הלשונית, הכפתור או השורה שלה בתפריט הנפתח של לוח. פקודה שהופעלה מהחיפוש מהבהבת שם, ופקודה פעילה מתפריט נפתח מסמנת את הלוח שלה.",
"prefs.ribbon_tabs_only": "הצגת לשוניות הסרט בלבד",
"prefs.ribbon_tabs_only_note": "הסרט מתקפל לשורת הלשוניות שלו. לחיצה על לשונית מציגה את הלוחות שלה מעל התצוגה עד שפקודה מתחילה או שהמצביע עוזב אותם. הפקודה ״רק לשוניות הסרט״ בלשונית תצוגה, או קיצור המקשים שלה, מחליפה הגדרה זו.",
"prefs.right_click": "לחיצה ימנית",
"prefs.saving": "הפעלה ושמירה",
"prefs.scale_note": "גם Ctrl + / Ctrl − בחלון. הכפתורים בגודל 28 px ב־100%.",
@@ -2533,6 +2598,8 @@
"prefs.section_hatch_by_part": "לפי חלק",
"prefs.section_hatch_one": "סגנון אחד",
"prefs.select": "בחירה",
"prefs.select_hidden_edges": "בחירת קצוות מוסתרים בתצוגה מוצללת עם קצוות מוסתרים",
"prefs.select_hidden_edges_help": "מאפשר לבחור קצוות דרך פאות בתצוגה מוצללת עם קצוות מוסתרים, גם עבור Fillet. כיבוי שומר על אופן הבחירה של v0.30.1. גופים שהוסתרו עדיין אינם ניתנים לבחירה.",
"prefs.select_one_item": "בחירת פריט אחד",
"prefs.select_other_hidden": "בחירת גאומטריה מוסתרת באמצעות בחירה אחרת",
"prefs.select_other_hidden_help": "מופעל: כולל גופים, סקיצות וקצוות מוסתרים בקרבת הסמן. כבוי: שימוש ברשימה המקורית של גאומטריה גלויה מתחת לסמן.",
@@ -2557,6 +2624,8 @@
"prefs.sketch.click_selects": "לחיצה בוחרת",
"prefs.sketch.conflict_colour": "צבע התנגשות",
"prefs.sketch.drawing": "שרטוט",
"prefs.sketch.ellipse_axes": "שרטוט אליפסות עם הצירים שלהן",
"prefs.sketch.ellipse_axes_note": "הציר הראשי והציר המשני כקווי עזר, כדי שאפשר יהיה לתת מידות לחצאי הצירים ולצירים. כבוי: האליפסה בלבד, ו-D עליה נותן את הרדיוס המשני שלה בכל מקום שאליו נגרר הערך, משורטט ומסומן R כמו קודם.",
"prefs.sketch.exit_keep": "שמירה",
"prefs.sketch.exit_previous": "חזרה לתצוגה הקודמת",
"prefs.sketch.exit_unless_rotated": "אלא אם סובבה",
@@ -2650,6 +2719,10 @@
"prefs.theme_follow_off": "כבוי",
"prefs.theme_follow_stop": "הפסקת המעקב",
"prefs.theme_import_hint": "לחצו כדי לבחור קובץ ערכת נושא, שחררו קובץ כאן, או הקישו Ctrl+V ‏(Cmd+V ב-Mac) אחרי שהעתקתם ערכת נושא",
"prefs.theme_notice_dismiss": "הסתרה",
"prefs.theme_option_blur": "טשטוש מאחורי לוחות",
"prefs.theme_option_blur_help": "טשטוש מאחורי לוחות שקופים למחצה, בפיקסלים פיזיים. אפס מכבה את הטשטוש.",
"prefs.theme_option_invalid": "הערך השמור עבור {option} אינו תקין; נעשה שימוש בברירת המחדל של ערכת הנושא. הערך השמור נשמר ללא שינוי.",
"prefs.theme_option_opacity": "אטימות",
"prefs.theme_option_opacity_help": "כוונון אטימות הלוחות הצפים. רמת אטימות מינימלית שומרת על קריאות הטקסט על רקע צבעי חלון התצוגה.",
"prefs.theme_option_tab_colours": "צבעי כרטיסיות",
@@ -2658,6 +2731,7 @@
"prefs.theme_option_tab_fade_ms_help": "שנה בהדרגה את צבעי הלוח ואזור התצוגה בעת מעבר בין כרטיסיות. ערך אפס מבצע את המעבר מיד.",
"prefs.theme_options_badge": "אפשרויות",
"prefs.theme_options_more": "עוד · {count}",
"prefs.theme_options_newer": "ערכת נושא זו דורשת גרסה חדשה יותר של SolidCraft. לאחר השדרוג יש לייבא שוב את ערכת הנושא המקורית כדי לשחזר את הגדרות האפשרויות שלה.",
"prefs.theme_options_reset": "איפוס",
"prefs.theme_options_unavailable": "לא זמין במכשיר זה",
"prefs.theme_paste": "הדבקת ערכת נושא",
@@ -2721,6 +2795,12 @@
"recovery.just_now": "זה עתה",
"recovery.min_ago": "לפני {count} דק'",
"recovery.never_saved": "לא נשמר מעולם",
"recovery.open_saved": "פתיחת הגרסה השמורה",
"recovery.restore": "שחזור",
"recovery.restore_intro": "שינויים שלא נשמרו כשדף זה נסגר לאחרונה שמורים בדפדפן זה.",
"recovery.restore_row": "{name}, שונה {when}",
"recovery.restore_title": "לשחזר תכנים שלא נשמרו?",
"recovery.restore_title_one": "לשחזר תכן שלא נשמר: {name}, שונה {when}?",
"refused.change": "לא ניתן היה לשנות את {body}.",
"refused.cut": "לא ניתן היה לחתוך את {body}.",
"refused.face_curved": "אחת הפאות המעוקלות שלו אינה ניתנת לשימוש במידול.",
@@ -3242,9 +3322,11 @@
"render.frame_wide": "16:9 · רחב",
"render.gpu_depth": "עומק מסלול מרבי",
"render.gpu_options_help": "הדגימות, העומק, הזרע והחשיפה נשמרים עם ההגדרה הזאת. נשמרת רק שיטת התמונה הפעילה.",
"render.gpu_path_crashed": "SolidCraft נסגר בזמן ההכנה על מתאם גרפי זה בפעם הקודמת, ככל הנראה בתוך מנהל ההתקן הגרפי, ולכן ההכנה לא תתבצע שוב מעצמה. יש להשתמש במעקב קרניים על המעבד במקום זאת, או לנסות שוב (עדכון מנהל ההתקן גם מנסה שוב).",
"render.gpu_path_failed": "מעקב המסלולים ב־GPU אינו זמין",
"render.gpu_path_first_setup": "ההגדרה הראשונה ב־GPU הזה עשויה להימשך כמה דקות; SolidCraft נשאר זמין לשימוש בינתיים. הפעלות מאוחרות יותר משתמשות שוב בשיידרים שהודרו, כשמנהל ההתקן שומר אותן.",
"render.gpu_path_preparing": "מעקב מסלולים ב־GPU: הידור שיידרים…",
"render.gpu_path_retry": "לנסות שוב",
"render.gpu_seed": "זרע אקראי",
"render.gpu_unavailable": "מעקב מסלולים ב־GPU אינו מוכן במכשיר הזה.",
"render.ground_apply": "התאמת הקרקע ושמירתה",
@@ -3858,6 +3940,7 @@
"render.stage_panels.extent": "ממדי התחום",
"render.stage_panels.fit_save": "התאמה ושמירה",
"render.stage_panels.gpu_required": "נדרש מעקב נתיב ב-GPU",
"render.stage_panels.grain_angle": "זווית סיבי העץ",
"render.stage_panels.keep": "שמור",
"render.stage_panels.kept": "נשמר",
"render.stage_panels.kept_fields": "מצלמה, דגימה, ממדים, קישורים אופטיים, גאומטריית CAD ותכונות הנדסיות.",
@@ -3976,6 +4059,19 @@
"render.subsurface_enable": "הילוך אקראי לפיזור פנימי",
"render.subsurface_help": "לדיאלקטרי סגור בלבד. פיזור ובליעת RGB הם מקדמים נפרדים לכל mm. המסלולים מתפזרים בתוך הגאומטריה בפועל, ואז נשברים או מוחזרים בגבול. יש לבטל העברת צל ישרה, הסרת רעש משטחית ונפח חיצוני; ולהשתמש בשכבת פרנל אחת. אינו שיידר דיפוזיה, אינו רשת צפיפות ואינו חומר מדוד. תווך צפוף עשוי לדרוש יותר דגימות ועומק מסלול.",
"render.subsurface_rate": "קצב התנגשויות הפיזור",
"render.surface_import.author": "יוצר",
"render.surface_import.busy": "מתבצע ייבוא תמונה…",
"render.surface_import.complete": "בחר תמונה והזן את היוצר, המקור והרישיון שלה.",
"render.surface_import.embedded": "תמונה מיובאת",
"render.surface_import.failed": "ייבוא התמונה לא הושלם.",
"render.surface_import.license": "רישיון / בעלות",
"render.surface_import.raytrace_required": "תמונות מחייבות מעקב קרניים ב־CPU או ב־GPU. יש לבחור זאת תחת פלט.",
"render.surface_import.repeat_u": "חזרת U",
"render.surface_import.repeat_v": "חזרת V",
"render.surface_import.save": "שמירת תמונה על הגוף",
"render.surface_import.saved_cpu": "יש לשמור את המפות לשימוש בהדמיה ב־CPU.",
"render.surface_import.source": "מקור",
"render.surface_import.title": "ייבוא תמונה",
"render.tone_aces_fitted": "ACES מותאם",
"render.tone_reinhard": "ריינהרד",
"render.transmission_scope": "זכוכית GPU תומכת בתווכים נפרדים מאושרים. תווכים מקוננים ללא הגבלה ומעריך העברה ללא רעש אינם מסופקים.",
@@ -4052,6 +4148,8 @@
"sample.plate.name": "לוח לדוגמה",
"sample.vents.about": "חריצים במערך מלבני: מספר השורות, מספר העמודות והפסיעה הם פרמטרים",
"sample.vents.name": "לוח אוורור",
"scale.per_axis": "לפי ציר",
"scale.uniform": "אחיד",
"search.command_keys": "Enter הרצה Shift+Enter הרצה והשארה פתוח Esc סגירה",
"search.commands": "פקודות",
"search.design": "בתכן זה",
@@ -4142,6 +4240,32 @@
"sketch.dof_left": "נותרו {count} דרגות חופש",
"sketch.dof_left_one": "נותרה {count} דרגת חופש",
"sketch.draw_curves": "שרטוט כ־{linetype}",
"sketch.error.closed_curve_two_crossings": "a closed curve needs two crossings to trim",
"sketch.error.nothing_to_break": "nothing to break here",
"sketch.error.offset_fillet_chamfer": "`{id}` is an offset, and its corners follow its source curves: round or cut the source corners before offsetting, or break the offset association first",
"sketch.error.offset_trim": "`{id}` is an offset, and its ends follow its source curves: edit the source curves instead, or break the offset association first",
"sketch.hint.arc.center_point": "Arc: click center and start, go round either way and click the end, or type a signed sweep",
"sketch.hint.arc.three_point": "Arc: click start, end, then a point on the arc",
"sketch.hint.arc3d": "3D Arc: click start, end, then a point on the arc (points may leave the sketch plane)",
"sketch.hint.break": "Break: click a curve where it should split",
"sketch.hint.circle.center": "Circle: click the center, then a point on the circle",
"sketch.hint.constraint.generic": "Constraint: pick the sketch entities",
"sketch.hint.constraint.horizontal_vertical": "Horizontal/Vertical: pick a line, two points, or a point and a line; hold Shift to pick a line midpoint",
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to center the second on the first",
"sketch.hint.dimension": "Dimension: pick one or two entities and type the value (or click to place it first); click a dimension to edit it; Enter applies",
"sketch.hint.extend": "Extend: click a curve near the end to extend (Shift: click two curves to extend both to where they meet)",
"sketch.hint.fallback": "Click in the sketch",
"sketch.hint.line": "Line: click points; right-click or Esc to finish",
"sketch.hint.line3d": "3D Line: click world points; right-click or Esc to finish (points may leave the sketch plane)",
"sketch.hint.open_done": "Open done",
"sketch.hint.point3d": "3D Point: click where a 3D point goes",
"sketch.hint.rectangle.center": "Center rectangle: click the center, then a corner",
"sketch.hint.rectangle.rounded": "Rounded rectangle: click two opposite corners (Tab to the radius box to type the corner radius)",
"sketch.hint.rectangle.three_point": "3-point rectangle: click two points of one edge, then the width",
"sketch.hint.rectangle.two_point": "Rectangle: click two opposite corners",
"sketch.hint.slot": "Slot: click both ends, then the width",
"sketch.hint.spline3d": "3D Spline: click fit points in 3D; right-click or Esc to finish",
"sketch.hint.trim": "Trim: click the piece of a curve to remove, or drag across the pieces to remove",
"sketch.make_selection": "המרת הבחירה ל־{linetype}",
"sketch.open_ends": "קצוות פתוחים: {count}",
"sketch.status_conflict": "אילוצים סותרים",
@@ -4363,6 +4487,7 @@
"ui.3d_sketch": "סקיצה תלת־ממדית",
"ui.activate": "הפעלה",
"ui.active_component": "רכיב פעיל",
"ui.addToQuickAccess": "הוספה לגישה מהירה",
"ui.add_configuration": "הוספת תצורה",
"ui.add_parameter": "הוספת פרמטר",
"ui.agent_chat": "צ'אט עם סוכן",
@@ -4697,6 +4822,7 @@
"ui.click_selects_whole_bodies": "לחיצה בוחרת גופים שלמים",
"ui.click_then_press_key": "יש ללחוץ ואז להקיש על המקש החדש",
"ui.close": "סגירה",
"ui.close_tab": "סגירת הלשונית",
"ui.coil": "סליל",
"ui.coincident": "מתלכד",
"ui.collapse": "כיווץ",
@@ -4784,6 +4910,7 @@
"ui.default_modelling_orientation": "כיוון מידול ברירת מחדל",
"ui.delete": "מחיקה",
"ui.delete_select_first": "בחרו קודם מה למחוק: תכונות, גופים, רכיבים או גאומטריית סקיצה",
"ui.dependents_select_first": "בחרו קודם מה להציג את התלויים שלו",
"ui.depth": "עומק",
"ui.design": "תכן",
"ui.design_properties.author": "מחבר",
@@ -4913,6 +5040,11 @@
"ui.factor": "מקדם",
"ui.fade": "דעיכה",
"ui.fade_length": "אורך דעיכה",
"ui.fair_iterations": "מספר איטרציות",
"ui.fair_keep_edges": "שמירה על המקצועות",
"ui.fair_strength": "עוצמה",
"ui.fair_surface_title": "החלקת משטח",
"ui.fair_tolerance_default": "0.001 מגודל הגוף",
"ui.feature_history": "ציר הזמן",
"ui.feature_options": "אפשרויות פיצ'ר",
"ui.features": "פיצ'רים",
@@ -4941,6 +5073,10 @@
"ui.fillet_setback_least": "יותר מ-{least}",
"ui.fillet_setback_tip": "היכן מתחיל טלאי הפינה החלק: המרחק לאורך כל קצה מהפינה. ברירת המחדל היא פי {factor} מהרדיוס. עליה להיות גדולה מהנסיגה המזערית, שבה העיגולים נפגשים בלעדיה: הרדיוס כשהפאות ניצבות זו לזו.",
"ui.fillet_smooth_radius": "שינוי רדיוס חלק",
"ui.fillet_turn": "פניות",
"ui.fillet_turn_mitre": "מיטר",
"ui.fillet_turn_swept": "מסובב",
"ui.fillet_turn_tip": "היכן ששני עיגולים מסתובבים סביב קצה פינה חד ביניהם, כמו העיגולים בבסיס בליטה ליד הקצוות הזקופים שלה.\nמיטר: העיגולים נמשכים עד שהם נפגשים במיטר.\nמסובב: העיגולים נעצרים בקצה הפינה ומסתובבים סביבו, והפינה היא טורוס המשיק לשניהם. רק לעיגולים ברדיוס אחד סביב קצה פינה ישר וקמור.",
"ui.fillet_type_choices_tip": "רדיוס קבוע: רדיוס אחד לכל אורך הקצה.\nאורך מיתר: רוחב העיגול נשאר זהה, בכל זווית שבה הפאות נפגשות.\nרדיוס משתנה: רדיוס בכל קצה, ועוד לאורך הקצה אם מוסיפים.\nרציף בעקמומיות: חתך בלי שבר בהשתקפויות היכן שהוא פוגש פאות מישוריות (G2).\nספליין: הרדיוס לאורך הקצה עוקב אחרי רדיוסי בקרה שאתם קובעים.",
"ui.fillet_type_curvature": "רציף בעקמומיות",
"ui.fillet_type_tip": "החתך לרוחב הקצה. רציף בעקמומיות: חתך רציף בעקמומיות (G2) בין פאות מישוריות, כך שההשתקפויות עוברות מהפאות אל העיגול בלי שבר.",
@@ -4986,6 +5122,7 @@
"ui.heading_construct": "עזר",
"ui.heading_create": "יצירה",
"ui.heading_create_sketch": "יצירת סקיצה",
"ui.heading_custom_view_navigator": "ניווט תצוגה מותאם אישית",
"ui.heading_cylinder": "גליל",
"ui.heading_design": "תכן",
"ui.heading_draft": "שיפוע יציקה",
@@ -5093,6 +5230,7 @@
"ui.inspect": "בדיקה",
"ui.instances": "מופעים",
"ui.intersect": "חיתוך משותף",
"ui.intersection_curve_second_pick": "עקומת חיתוך: יש ללחוץ על הפאה או העקומה השנייה (לחיצה ימנית להתחלה מחדש)",
"ui.iso_4753": "ISO 4753",
"ui.isolate": "בידוד",
"ui.join": "איחוד",
@@ -5121,6 +5259,7 @@
"ui.manipulator_free_alt": "גרירה חופשית של הידית גם עם Alt/Option",
"ui.manipulator_free_alt_tip": "פעיל: Alt/Option או Ctrl/Cmd על חיצי השליפה / הידית מבטלים הצמדת ערכים. כבוי: Ctrl/Cmd בלבד.",
"ui.manufacture": "ייצור",
"ui.match_surface_title": "התאמת משטח",
"ui.material_review.axes": "צירים מקומיים של החומר; לא הוגדרה אוריינטציה לגוף.",
"ui.material_review.claims": "המקורות והרישיונות הם טענות שלא אומתו. ערכים חסרים נשארים לא ידועים.",
"ui.material_review.compressive_strength": "חוזק לחיצה",
@@ -5143,6 +5282,9 @@
"ui.material_review.title": "בדיקת נתוני חומר פיזיקליים",
"ui.material_review.unknown": "לא ידוע",
"ui.material_review.yield_strength": "חוזק כניעה",
"ui.mcp_agents": "סוכנים דרך MCP",
"ui.mcp_compact": "פלט מקוצר",
"ui.mcp_compact_note": "לסוכנים שמחוברים דרך שרת ה-MCP ‏(solidcraft-cli mcp): ‏inspect_design מסכם סקיצות ותוצאות הכלים אינן מעוצבות, כך שהן צורכות פחות טוקנים. חל על הפעלות סוכן שמתחילות אחרי השינוי; גם ‎--compact או SOLIDCRAFT_MCP_COMPACT=1 מפעילים אותו.",
"ui.measure": "מדידה",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "3MF עם תגי פאות",
@@ -5184,6 +5326,7 @@
"ui.options": "אפשרויות",
"ui.orbit": "סיבוב מבט",
"ui.orbit_around": "סיבוב מבט סביב",
"ui.organize": "ארגון",
"ui.orientation": "אוריינטציה",
"ui.origin": "ראשית",
"ui.palette_construction_mode": "מצב עזר (X)",
@@ -5228,6 +5371,7 @@
"ui.point_cloud_add_filter": "הוספת מסנן…",
"ui.point_cloud_auto_range": "טווח אוטומטי",
"ui.point_cloud_color_by": "צביעה לפי",
"ui.point_cloud_dots": "נקודות בודדות",
"ui.point_cloud_file_colors": "צבעי הקובץ",
"ui.point_cloud_filter_by": "סינון לפי",
"ui.point_cloud_height_high": "הגובה המרבי",
@@ -5272,6 +5416,7 @@
"ui.quality": "איכות",
"ui.quantity": "כמות",
"ui.quantity_2": "כמות 2",
"ui.quick_access_full": "סרגל הגישה המהירה מכיל {count} פקודות; יש להסיר אחת כדי להוסיף אחרת",
"ui.quit": "יציאה",
"ui.radial_context_menu": "תפריט הקשר מעגלי",
"ui.radius": "רדיוס",
@@ -5289,6 +5434,7 @@
"ui.relink_not_linked": "רכיב זה אינו מקושר לתכן מקור.",
"ui.relink_select_one": "יש לבחור תחילה רכיב מקושר אחד או אחד המופעים שלו.",
"ui.relink_source_all": "קישור מחדש למקור (כל המופעים)…",
"ui.removeFromQuickAccess": "הסרה מהגישה המהירה",
"ui.removeFromToolbar": "הסרה מסרגל הכלים",
"ui.removeSection": "הסרת מקטע",
"ui.render": "הדמיה",
@@ -5297,6 +5443,8 @@
"ui.repeat_nothing": "אין עדיין מה לחזור עליו: התחל קודם כלי או תיבת דו־שיח.",
"ui.replace_with_link_done": "העותקים הם עכשיו מיקומים של התכן המקושר.",
"ui.report_a_bug": "דיווח על באג",
"ui.report_characters_left_one": "תווים שנותרו: {count}",
"ui.report_characters_left_other": "תווים שנותרו: {count}",
"ui.reset": "איפוס",
"ui.reset_all": "איפוס הכול",
"ui.reset_the_settings_on_this_page": "איפוס ההגדרות בדף זה",
@@ -5581,6 +5729,14 @@
"values must be numbers or expressions": "הערכים חייבים להיות מספרים או ביטויים",
"view.auto_ortho_faces": "פרספקטיבה בזמן סיבוב, אורתוגרפי על פאות",
"view.render_inspector": "מפקח רינדור",
"view_gizmo.arrows": "חיצי סיבוב",
"view_gizmo.home": "לחצן תצוגת בית",
"view_gizmo.no_such_body": "אין בעיצוב הזה גוף בשם הזה.",
"view_gizmo.note": "בחרו גוף בתצוגה. הוא נשמר עם ההעדפות ופרופילי ההגדרות שלכם ומוצג בכל עיצוב. לחצו על אחת מפאותיו כדי להביט בה ישירות.",
"view_gizmo.texture": "מרקם",
"view_gizmo.themed": "לפי ערכת הנושא",
"view_gizmo.too_detailed": "מפורט מדי לניווט התצוגה (יותר מ-10,000 משולשים)",
"view_gizmo.too_thin": "ארוך ודק מדי לניווט התצוגה: הצד הדק ביותר חייב להיות לפחות חמישית מהצד הארוך ביותר",
"web.files.insert_drawings_hint": "קובצי CAD, רשתות משולשים, DXF או SVG אל התכן הפתוח",
"web.files.insert_stored_drawings_hint": "אל התכן הפתוח (קובצי CAD, רשתות משולשים, DXF, SVG)",
"web.open_full": "פתיחה ב־SolidCraft",
+1 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analiza dostupnosti",
"cmd.inspect_center_of_mass": "Središte mase",
"cmd.inspect_clearance": "Provjera zazora",
"cmd.inspect_colored_components": "Omogući boje komponenti",
"cmd.inspect_colored_components_disable": "Onemogući boje komponenti",
"cmd.inspect_compare": "Usporedi tijela",
"cmd.inspect_compare_picture": "Usporedi sliku",
"cmd.inspect_continuity": "Analiza kontinuiteta",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Čita putanje SVG crteža u skicu, skalirane, zakrenute i postavljene kako zadate. Upotrijebite ga za pretvaranje logotipa ili vektorskog obrisa u krivulje koje možete ekstrudirati.",
"help.sketch.inspect": "Otvara izvješće o otvorenoj skici: koliko je stupnjeva slobode preostalo i je li potpuno ograničena. Upotrijebite ga kako biste saznali što se još pomiče prije dovršetka skice.",
"help.sketch.intersect": "Dodaje krivulje ili točke u kojima tijela, lica, bridovi ili druge skice presijecaju ravninu skice. Ostaju povezane i prate model kad se promijeni.",
"help.sketch.intersection_curve": "Izrađuje 3D krivulje na mjestu gdje se dva tijela ili lica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
"help.sketch.intersection_curve": "Izrađuje 3D krivulje na mjestu gdje se dva tijela, lica ili krivulje drugih skica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
"help.sketch.isoparametric_curve": "Izrađuje 3D krivulju u smjeru U ili V lica kroz točku koju odaberete, povezanu s licem. Upotrijebite je kao putanju ili liniju razdvajanja koja prati plohu.",
"help.sketch.line": "Crta povezane linije točku po točku, kao otvoreni lanac ili zatvoreni obris. To je osnovni alat za većinu profila.",
"help.sketch.line.midpoint": "Crta liniju od njezine središnje točke do jednog kraja, a drugi kraj zrcali. Upotrijebite ga za linije centrirane na točku, primjerice simetričan brid.",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Hozzáférhetőség-elemzés",
"cmd.inspect_center_of_mass": "Tömegközéppont",
"cmd.inspect_clearance": "Hézag",
"cmd.inspect_colored_components": "Komponensszínek bekapcsolása",
"cmd.inspect_colored_components_disable": "Komponensszínek kikapcsolása",
"cmd.inspect_compare": "Testek összehasonlítása",
"cmd.inspect_compare_picture": "Kép összehasonlítása",
"cmd.inspect_continuity": "Folytonosságelemzés",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Beolvassa egy SVG-rajz útvonalait egy vázlatba, az Ön által megadott léptékkel, elforgatással és helyen. Használja egy logó vagy vektoros körvonal kihúzható görbékké alakítására.",
"help.sketch.inspect": "Jelentést nyit a megnyitott vázlatról: hány szabadságfok maradt, és teljesen kényszerezett-e. Használja annak kiderítésére, mi mozdul még, mielőtt befejezi a vázlatot.",
"help.sketch.intersect": "Hozzáadja azokat a görbéket vagy pontokat, ahol testek, lapok, élek vagy más vázlatok metszik a vázlatsíkot. Kapcsoltak maradnak, és követik a modellt, ha az megváltozik.",
"help.sketch.intersection_curve": "3D görbéket hoz létre ott, ahol két test vagy lap találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
"help.sketch.intersection_curve": "3D görbéket hoz létre ott, ahol két test, lap vagy más vázlatok görbéi találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
"help.sketch.isoparametric_curve": "3D görbét hoz létre egy lap U vagy V iránya mentén egy kiválasztott ponton át, a laphoz kapcsolva. Használja útvonalként vagy osztóvonalként, amely követi a felületet.",
"help.sketch.line": "Összefüggő vonalakat rajzol pontról pontra, nyitott láncként vagy zárt körvonalként. Ez a legtöbb profil alapeszköze.",
"help.sketch.line.midpoint": "Vonalat rajzol a felezőpontjától az egyik végéig, a másik véget tükrözve. Használja egy pontra középen álló vonalakhoz, például szimmetrikus élhez.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Mintalemez",
"sample.vents.about": "Hornyok téglalap mintában: a sorok, oszlopok és osztás paraméterek",
"sample.vents.name": "Szellőzőlemez",
"scale.per_axis": "Tengelyenként",
"scale.uniform": "Egyenletes",
"search.command_keys": "Enter futtatás Shift+Enter futtatás és nyitva tartás Esc bezárás",
"search.commands": "Parancsok",
"search.design": "Ebben a tervben",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analisis Aksesibilitas",
"cmd.inspect_center_of_mass": "Pusat Massa",
"cmd.inspect_clearance": "Kelonggaran",
"cmd.inspect_colored_components": "Aktifkan Warna Komponen",
"cmd.inspect_colored_components_disable": "Nonaktifkan Warna Komponen",
"cmd.inspect_compare": "Bandingkan Bodi",
"cmd.inspect_compare_picture": "Bandingkan dengan Gambar",
"cmd.inspect_continuity": "Analisis Kontinuitas",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Membaca path gambar SVG ke dalam sketsa, diskalakan, diputar, dan ditempatkan sesuai pengaturan Anda. Gunakan untuk mengubah logo atau kontur vektor menjadi kurva yang dapat diekstrusi.",
"help.sketch.inspect": "Membuka laporan tentang sketsa yang terbuka: berapa derajat kebebasan yang tersisa dan apakah sketsa terbatasi penuh. Gunakan untuk mengetahui apa yang masih bergerak sebelum Anda menyelesaikan sketsa.",
"help.sketch.intersect": "Menambahkan kurva atau titik tempat bodi, muka, tepi, atau sketsa lain memotong bidang sketsa. Semuanya tetap tertaut dan mengikuti model saat model berubah.",
"help.sketch.intersection_curve": "Membuat kurva 3D di tempat dua bodi atau muka bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
"help.sketch.intersection_curve": "Membuat kurva 3D di tempat dua bodi, muka, atau kurva sketsa lain bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
"help.sketch.isoparametric_curve": "Membuat kurva 3D sepanjang arah U atau V sebuah muka melalui titik yang Anda pilih, tertaut ke muka itu. Gunakan sebagai jalur atau garis pisah yang mengikuti permukaan.",
"help.sketch.line": "Menggambar garis-garis tersambung titik demi titik, sebagai rantai terbuka atau kontur tertutup. Ini alat dasar untuk sebagian besar profil.",
"help.sketch.line.midpoint": "Menggambar garis dari titik tengahnya ke satu ujung, dengan ujung lainnya dicerminkan. Gunakan untuk garis yang dipusatkan pada sebuah titik, seperti tepi simetris.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Pelat Contoh",
"sample.vents.about": "Slot dalam pola persegi panjang: baris, kolom, dan pitch adalah parameter",
"sample.vents.name": "Pelat Ventilasi",
"scale.per_axis": "Per sumbu",
"scale.uniform": "Seragam",
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan tetap buka Esc tutup",
"search.commands": "Perintah",
"search.design": "Di desain ini",
+7 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analisi di accessibilità",
"cmd.inspect_center_of_mass": "Centro di massa",
"cmd.inspect_clearance": "Distanza libera",
"cmd.inspect_colored_components": "Attiva colori dei componenti",
"cmd.inspect_colored_components_disable": "Disattiva colori dei componenti",
"cmd.inspect_compare": "Confronta corpi",
"cmd.inspect_compare_picture": "Confronta immagine",
"cmd.inspect_continuity": "Analisi di continuità",
@@ -1366,11 +1364,14 @@
"dialog.computing": "Calcolo in corso… {seconds} s",
"dialog.could_not_be_checked": "impossibile verificare",
"dialog.custom_axis": "↖ Asse personalizzato",
"dialog.cylinder_face_tip": "La faccia o il vertice del modello su cui poggia il cilindro: fai clic qui, poi su di esso nella vista. Se resta vuoto, lo posizionano X, Y e Z.",
"dialog.dimension_insert_hint": "Fai doppio clic su un’altra quota di controllo per inserirne il nome",
"dialog.dimension_name": "Quota: {name}",
"dialog.edit_title": "MODIFICA {title}",
"dialog.no_shown_body": "Nessun corpo visibile da modificare: mostrane uno o scegli Nuovo corpo",
"dialog.pattern_sketch_not_object": "Seleziona corpi o lavorazioni: uno schizzo non è nessuno dei due. Ripeti le sue curve con Ripetizione o Specchia dello schizzo stesso, oppure estrudilo prima.",
"dialog.primitive_point_tip": "Un punto che compila X, Y e Z: fai clic qui, poi su un vertice, uno spigolo o una faccia del modello, su un punto di uno schizzo oppure sull'origine.",
"dialog.reference_edge_tip": "Uno spigolo rettilineo della faccia scelta da cui si misura la distanza sotto: fai clic qui, poi sullo spigolo.",
"dialog.select": "↖ Seleziona",
"dialog.selected_count": "↖ Selezionati: {count}",
"dialog.selection_constraint": "Vincolo {id}",
@@ -1392,6 +1393,7 @@
"document.read_only_save": "Questa progettazione è stata aperta in sola lettura: salvala come nuovo file",
"document.save_changes": "Salvare le modifiche a {name}?",
"document.save_copy": "Salva una copia…",
"document.tab_unsaved_changes": "Modifiche non salvate",
"document.unsaved_changes": "{name} contiene modifiche non salvate. Se non salvi, andranno perse.",
"drive.rigid": "{joint} è un giunto rigido: tiene ferme le sue parti, quindi non c'è nulla da azionare. Per farle muovere, cambia il suo tipo in Modifica giunto: Rotante per ruotare, Scorrevole per scorrere, Cilindrico per entrambi.",
"drive.stopped_by_contact": "Arrestato a {value}: {a} attraverserebbe {b}",
@@ -1623,7 +1625,7 @@
"help.sketch.insert_svg": "Legge i tracciati di un disegno SVG in uno schizzo, scalati, ruotati e posizionati come imposti. Usalo per trasformare un logo o un contorno vettoriale in curve che puoi estrudere.",
"help.sketch.inspect": "Apre un rapporto sullo schizzo aperto: quanti gradi di libertà restano e se è completamente vincolato. Usalo per scoprire cosa si muove ancora prima di terminare lo schizzo.",
"help.sketch.intersect": "Aggiunge le curve o i punti in cui corpi, facce, spigoli o altri schizzi attraversano il piano dello schizzo. Restano collegati e seguono il modello quando cambia.",
"help.sketch.intersection_curve": "Crea curve 3D dove si incontrano due corpi o facce, collegate a essi. Usale come percorsi o guide che seguono il modello.",
"help.sketch.intersection_curve": "Crea curve 3D dove si incontrano due corpi, facce o curve di altri schizzi, collegate a essi. Usale come percorsi o guide che seguono il modello.",
"help.sketch.isoparametric_curve": "Crea una curva 3D lungo la direzione U o V di una faccia passante per un punto che scegli, collegata alla faccia. Usala come percorso o linea di divisione che segue la superficie.",
"help.sketch.line": "Disegna linee collegate punto per punto, come catena aperta o contorno chiuso. È lo strumento di base per la maggior parte dei profili.",
"help.sketch.line.midpoint": "Disegna una linea dal suo punto medio fino a un'estremità, con l'altra estremità speculare. Usalo per linee centrate su un punto, come un bordo simmetrico.",
@@ -3811,6 +3813,8 @@
"sample.plate.name": "Piastra di esempio",
"sample.vents.about": "Asole in ripetizione rettangolare: righe, colonne e passo sono parametri",
"sample.vents.name": "Piastra di ventilazione",
"scale.per_axis": "Per asse",
"scale.uniform": "Uniforme",
"search.command_keys": "Invio esegui Maiusc+Invio esegui e tieni aperto Esc chiudi",
"search.commands": "Comandi",
"search.design": "In questo progetto",
+159 -3
View File
@@ -85,6 +85,7 @@
"Bottom Right": "右下",
"Boundary": "境界",
"Bounding box": "境界ボックス",
"Bridge": "ブリッジ",
"CHECK": "チェック",
"COMPONENTS": "コンポーネント",
"CONFIGURATIONS": "構成",
@@ -628,6 +629,8 @@
"Suppress Selected Folders": "選択したフォルダーを抑制",
"Suppressed": "抑制",
"Surface": "サーフェス",
"Surface or face": "サーフェスまたは面",
"Surface or faces": "サーフェスまたは面",
"Surface, or faces to turn over": "サーフェス、または裏返す面",
"Surfaces": "サーフェス",
"Swap Ends": "端点を入れ替え",
@@ -641,6 +644,7 @@
"Target": "ターゲット",
"Target Body": "ターゲットボディ",
"Target Face (optional)": "対象面 (オプション)",
"Target edges": "ターゲットのエッジ",
"Tear": "引き裂き",
"Technical details": "技術情報",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "破線のスケッチは保存された平面ガイドです。新しい面を選択してアタッチメントを修復してください。",
@@ -648,6 +652,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "方向は、軸、またはスケッチ平面上にある直線のスケッチ曲線にしてください",
"The mirror line cannot also be mirrored": "ミラー線自体をミラーすることはできません",
"The mirror line must be a straight curve of this sketch, not an origin axis": "ミラー線は、原点の軸ではなく、このスケッチの直線にしてください",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "原点軸はスケッチ平面に垂直なため、平面内に線がありません",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "保存された作成元のオカレンスが見つかりません。再バインドする前に有効な作成元配置を選択してください。",
"Then": "次を実行 (Then)",
"These features will be deleted:": "以下のフィーチャーが削除されます:",
@@ -739,6 +744,9 @@
"a picked edge's body is gone": "選択したエッジのボディが存在しなくなりました",
"add a key for a joint first": "先にジョイントのキーを追加してください",
"ai.connected_toast": "SolidCraft が {clients} で使えるようになりました。使うには再起動してください(設定 > アシスタント)。",
"ai.http.allow": "安全でない HTTP を許可",
"ai.http.confirm": "このエンドポイントで許可",
"ai.http.warning": "API キーと会話は暗号化されずに送信されます。ネットワークにアクセスできる人は、内容を読み取ったり改ざんしたりする可能性があります。信頼できるネットワークでのみ使用してください。",
"ai.prefs.allow_control": "AIエージェントの接続を許可",
"ai.prefs.allow_control_note": "ローカルチャネル(MCP)です。このコンピューターから外には何も送信しません。\n\nこのコンピューター内だけのコントロールチャンネル(127.0.0.1、ポート 27878 または次に空いているポート)を開きます。接続するプログラムは、起動のたびに作られ、あなたのユーザーアカウントだけが読める秘密のトークンも提示する必要があります。次に SolidCraft を起動したときから有効です。オフ:--control を付けて起動しない限り、何も待ち受けません。",
"ai.prefs.availability": "利用設定",
@@ -750,6 +758,8 @@
"ai.prefs.follow_camera": "カメラを追従",
"ai.prefs.follow_camera_off": "カメラの自動追従はオフです。",
"ai.prefs.follow_camera_on": "エージェントが作成するスケッチにカメラを向けます。",
"ai.prefs.instructions": "カスタム指示",
"ai.prefs.instructions_note": "各チャットの開始時に、SolidCraft 自身の指示の後に続けてエージェントへ送られます。作業の進め方や守ってほしいルールを書いてください。変更は次のチャット(接続または新しいチャット)から適用されます。",
"ai.prefs.intro": "SolidCraftのAIエージェント。",
"ai.prefs.make_default": "既定にする",
"ai.prefs.no_agents": "エージェントはまだありません。追加するか、更新してインストール済みのエージェントを検索してください。",
@@ -963,8 +973,7 @@
"cmd.inspect_analysis": "表示解析",
"cmd.inspect_center_of_mass": "重心",
"cmd.inspect_clearance": "クリアランス",
"cmd.inspect_colored_components": "コンポーネントの色分けを有効化",
"cmd.inspect_colored_components_disable": "コンポーネントの色分けを無効化",
"cmd.inspect_colored_components": "コンポーネントの色分け",
"cmd.inspect_compare": "ボディを比較",
"cmd.inspect_compare_picture": "参考画像と比較",
"cmd.inspect_continuity": "連続性解析",
@@ -1081,6 +1090,9 @@
"cmd.render.compare.divider": "レンダー比較の境界を移動",
"cmd.render.result.discard": "レンダリング結果を破棄",
"cmd.render.result.select": "レンダリング結果を選択",
"cmd.render.surface.import": "サーフェスマップをインポート",
"cmd.render.texture.list": "埋め込みサーフェス画像の一覧",
"cmd.render.texture.remove": "埋め込みサーフェス画像を削除",
"cmd.render_aging_describe": "レイヤー式表面エイジング",
"cmd.render_backends": "レンダリング バックエンド",
"cmd.render_batch": "すべてのショットをレンダリング",
@@ -1296,6 +1308,7 @@
"cmd.timeline_stop": "履歴を停止",
"cmd.timeline_suppress": "フィーチャーを抑制",
"cmd.ui.assistant": "アシスタントの表示切り替え",
"cmd.view.navigator": "カスタム ビューナビゲーター",
"cmd.view.render_browser": "Renderブラウザー",
"cmd.view.render_dock": "レンダードックを移動",
"cmd.view.render_focus": "Renderフォーカス表示",
@@ -1323,6 +1336,7 @@
"cmd.view_previous_tab": "前のタブ",
"cmd.view_rename": "名前付きビューの名前を変更",
"cmd.view_render_frame": "レンダーフレームガイド",
"cmd.view_ribbon_tabs_only": "リボンのタブのみ",
"cmd.view_right": "右側面ビュー",
"cmd.view_save": "新規名前付きビュー",
"cmd.view_shots_strip": "ショットストリップ",
@@ -1394,15 +1408,22 @@
"dialog.computing": "計算中… {seconds} 秒",
"dialog.could_not_be_checked": "チェックできません",
"dialog.custom_axis": "↖ カスタム軸",
"dialog.cylinder_face_tip": "円柱が立つモデルの面または頂点: ここをクリックしてから、ビューでそれをクリック。空のままなら X、Y、Z で配置します。",
"dialog.dimension_insert_hint": "別の駆動寸法をクリックして名前を挿入",
"dialog.dimension_name": "寸法: {name}",
"dialog.dimension_step_down": "{step} 減らす",
"dialog.dimension_step_expression": "増減できるのは数値だけです。式はそのまま残ります",
"dialog.dimension_step_up": "{step} 増やす",
"dialog.edit_title": "{title}を編集",
"dialog.no_shown_body": "変更できる表示中のボディがありません。ボディを表示するか、新規ボディを選択してください",
"dialog.pattern_sketch_not_object": "ボディかフィーチャーを選んでください。スケッチはどちらでもありません。曲線はスケッチ内のパターンかミラーで繰り返すか、先に押し出してください。",
"dialog.primitive_point_tip": "X、Y、Z を埋める点: ここをクリックしてから、モデルの頂点、エッジ、面、スケッチの点、または原点をクリック。",
"dialog.reference_edge_tip": "下の距離を測る基準となる、選んだ面の直線エッジ: ここをクリックしてからエッジをクリック。",
"dialog.select": "↖ 選択",
"dialog.selected_count": "↖ {count} 個選択中",
"dialog.selection_constraint": "拘束 {id}",
"dialog.selection_external_curve": "{sketch} · 曲線 {id}",
"dialog.selection_external_point": "{point} · {sketch}",
"dialog.selection_guidance": "入力欄を選んで変更します。選択済みのオブジェクトをクリックすると解除できます。Ctrl/Cmd を押しながらクリックしても切り替えられます。プレビューを確認してから OK を押してください。",
"dialog.selection_midpoint": "中点 · {id}",
"dialog.selection_profile": "{sketch} · プロファイル {number}",
@@ -1420,6 +1441,7 @@
"document.read_only_save": "このデザインは読み取り専用で開かれています:新しいファイルとして保存してください",
"document.save_changes": "{name} への変更を保存しますか?",
"document.save_copy": "コピーを保存…",
"document.tab_unsaved_changes": "未保存の変更",
"document.unsaved_changes": "{name} には保存されていない変更があります。保存しない場合、変更内容は失われます。",
"drive.rigid": "{joint} は剛体ジョイントです。部品をその場に固定するため、駆動するものがありません。動かすには「ジョイントを編集」で種類を変更してください: 回転させるなら Revolute、スライドさせるなら Slider、両方なら Cylindrical。",
"drive.stopped_by_contact": "{value} で停止: {a} が {b} を通り抜けてしまいます",
@@ -1440,10 +1462,16 @@
"each rail's curves must be in one sketch": "各レールの曲線は1つのスケッチ内にある必要があります",
"feature computation ended without a result": "フィーチャーの計算が結果なしで終了しました",
"file.open.drawing_filter": "デザイン、CADファイル、メッシュ、図面",
"file.open.portal_fallback.no_portal_service": "開いたファイル選択はデスクトップのものではありません。デスクトップのファイル選択サービスが利用できません。",
"file.open.portal_fallback.no_session_bus": "開いたファイル選択はデスクトップのものではありません。D-Bus セッションがありません。",
"file.open.portal_fallback.xwayland": "開いたファイル選択はデスクトップのものではありません。XWayland ではポータルが親ウィンドウを拒否します。",
"file_load.built": "構築済み: 全{total}フィーチャー中{built}件。",
"file_load.converting": "ソリッドを変換中: {done}/{total}",
"file_load.inserted": "{name} を挿入しました",
"file_load.inserting": "ジオメトリを挿入中",
"file_load.link_item": "{name} ({done}/{total})",
"file_load.opened": "{name} を開きました",
"file_load.opened_in_tab": "{name} を新しいタブで開きました",
"file_load.opening": "デザインを開いています",
"file_load.preparing_thumbnail": "サムネイルを準備中...",
"file_load.preparing_view": "ビューを準備中...",
@@ -1679,7 +1707,7 @@
"help.sketch.insert_svg": "SVG 図面のパスをスケッチに読み込み、指定した位置に拡大縮小・回転して配置します。ロゴやベクター輪郭を、押し出しできる曲線にするときに使います。",
"help.sketch.inspect": "開いているスケッチのレポートを開きます。残りの自由度と、完全に拘束されているかどうかを表示します。スケッチを終える前に、まだ動く部分を確認するときに使います。",
"help.sketch.intersect": "ボディ、面、エッジ、ほかのスケッチがスケッチ平面と交差する曲線または点を追加します。リンクが保たれ、モデルが変わると追従します。",
"help.sketch.intersection_curve": "2つのボディまたは面が接する位置に、それらにリンクした 3D 曲線を作成します。モデルに追従するパスやガイドレールとして使います。",
"help.sketch.intersection_curve": "2つのボディ、面、またはほかのスケッチの曲線が接する位置に、それらにリンクした 3D 曲線を作成します。モデルに追従するパスやガイドレールとして使います。",
"help.sketch.isoparametric_curve": "選んだ点を通る面の U または V 方向に沿った 3D 曲線を、面にリンクして作成します。表面に追従するパスや分割線として使います。",
"help.sketch.line": "点を順に指定して、つながった直線を開いた連なりまたは閉じた外形として描きます。ほとんどの輪郭に使う基本のツールです。",
"help.sketch.line.midpoint": "中点から一方の端まで直線を描き、もう一方の端は反対側に対称に作られます。対称なエッジなど、点を中心にした直線に使います。",
@@ -1785,6 +1813,7 @@
"help.view.navigate": "左に90度回転するなど、ステップごとにビューを回転、移動、ズームします。ドラッグではなく、カメラを正確に動かすときに使います。",
"help.view.navigation_bar": "ビューポート下部のナビゲーションバーを表示または非表示にします。広く使いたいときは非表示に、マウスでビューツールを使うときは表示にします。",
"help.view.rename": "保存した名前付きビューの名前を変更します。ビュー自体は変わりません。",
"help.view.ribbon_tabs_only": "リボンをタブの行だけに折りたたみ、パネルが使っていた場所をビューに充てます。タブをクリックするとそのパネルがビューの上に表示されます。もう一度選ぶとリボン全体を表示したままにします。",
"help.view.save": "現在のカメラ位置を名前付きビューとしてデザインに保存します。スクリーンショット用など、後で同じ角度に戻るときに使います。",
"help.view.spin": "次のクリックまたはキー操作まで、モデルのまわりでビューを連続して周回させます。モデルをあらゆる方向から見せるときに使います。",
"help.view.tool": "オービット、パン、ズームのカメラツールを固定し、オフにするまで左ボタンのドラッグでビューを動かせるようにします。トラックパッドや、中ボタンのないマウスで使います。",
@@ -1999,6 +2028,7 @@
"material.selection.mixed": "異なる材料",
"measure.at": "位置",
"measure.item": "項目 {number}",
"measure.line_to_centre": "線から中心まで",
"measure.quick_copied": "測定値をコピーしました",
"measure.quick_copy": "クリックで測定値をコピー",
"measure.quick_edges": "エッジ {count} 本",
@@ -2029,6 +2059,8 @@
"nav.blender.help": "中ドラッグで回転、Shift+中ドラッグでパン、Ctrl+中ドラッグでズーム。右ドラッグではカメラは動きません。",
"nav.creo.help": "マウスの中ボタンドラッグで回転、Shift+中ボタンで平行移動、Ctrl+中ボタンの上下ドラッグでズーム(下方向で拡大)。ホイールの回転でもズーム",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "中ボタンまたは右ボタンのドラッグは、途中でキーを押したり離したりしても、ボタンを離すまで開始時の回転・パン・ズームを続けます。オフにすると、ドラッグ中の各時点で押しているキーによって決まります。",
"nav.drag_keeps_mode.label": "ドラッグは開始時のナビゲーションを維持",
"nav.free_orbit.help": "マウスと3Dマウスで、極点を越えて回転し、ロールを保ちます。\nShiftによる軸スナップは無効になります。",
"nav.free_orbit.label": "自由回転",
"nav.free_orbit.snap_help": "マウスと3Dマウスで両極を越えて回転でき、ロールも保持されます。オービットのドラッグ中にShiftを押すと、視線方向とロールがスナップします。",
@@ -2265,6 +2297,17 @@
"prefs.auto_operation_note": "材料が接触する対象に応じて結合、新規ボディ、切り取りを選びます。",
"prefs.axes": "軸",
"prefs.axis": "軸",
"prefs.background_choose": "画像を選択…",
"prefs.background_image": "画像",
"prefs.background_unreadable": "背景画像を読み込めませんでした",
"prefs.blur_cost": "相対的な描画負荷です。バーが多いほど、通常は処理量が増えます。",
"prefs.blur_device": "このグラフィックスデバイスまたは描画経路では利用できません",
"prefs.blur_guard": "画面の応答性を保つため、ぼかしの品質を下げました。半径は変更されていません。",
"prefs.blur_off": "オフ",
"prefs.blur_reduce_transparency": "システムの透明度を下げる設定により無効です",
"prefs.blur_software": "ソフトウェア描画では利用できません (gpu-software)",
"prefs.blur_source": "このワークスペース表示では利用できません",
"prefs.blur_web": "このウェブ描画経路では利用できません",
"prefs.bodies_folders": "ボディフォルダー",
"prefs.bodies_folders_always": "常に表示",
"prefs.bodies_folders_never": "表示しない",
@@ -2278,6 +2321,8 @@
"prefs.browser_eye_left": "折りたたみ矢印の隣の目のアイコン",
"prefs.browser_eye_left_note": "ブラウザーの各行の表示切り替えを、行の右端の列ではなく、以前のように折りたたみ矢印の隣に表示します。",
"prefs.browser_reveal_on_hover": "ホバーで表示",
"prefs.browser_rows_start_closed": "ブラウザーの行を閉じた状態で始める",
"prefs.browser_rows_start_closed_note": "ブラウザーの配置とフォルダーを閉じた状態で表示するため、多数のコンポーネントを含むデザインでは最上位の行だけが表示されます。各行は矢印、「すべて展開」または「ブラウザで検索」で開きます。「ブラウザで検索」はボディまでの行だけを開き、前回の検索で開いた行を再び閉じます。3Dビューでボディを選んでもブラウザーはそのままです。オフのときはフォルダーが開いた状態で始まり、配置は「コンポーネントを最初から展開」の設定に従い、3Dビューで選ぶとブラウザーがそのボディまで開きます(以前と同じ)。",
"prefs.browser_show_note": "「表示」はブラウザーを常に画面に表示します。「ホバーで表示」はポインターがその位置か左端の細いタブに来るまで隠し、ポインターが中にある間やそのメニューが開いている間は表示したままにします。",
"prefs.browser_shown": "表示",
"prefs.built_in": "組み込み",
@@ -2288,6 +2333,7 @@
"prefs.carousel_previous": "前へ",
"prefs.choose_font": "フォントファイルを選択…",
"prefs.click": "クリック",
"prefs.command_dialog_values": "コマンドダイアログ",
"prefs.contact_shading": "柔らかな接触陰影",
"prefs.contact_shading_note": "部品が近接する部分に柔らかな陰影を付けます。",
"prefs.context_menu": "コンテキストメニュー",
@@ -2323,6 +2369,13 @@
"prefs.dialogs_edge": "ビューの端",
"prefs.dialogs_left_column": "左の列",
"prefs.dialogs_note": "コマンドダイアログを開く場所:ビューの端にドッキングするか、左の列でブラウザーと履歴の代わりに表示します。ダイアログを閉じるとブラウザーと履歴は元どおりに戻ります。",
"prefs.dimension_step_angle": "ウィンドウの矢印、上下キー、ホイールで角度に加える量",
"prefs.dimension_step_length": "ウィンドウの矢印、上下キー、ホイールで長さに加える量",
"prefs.dimension_steps": "ウィンドウの増減量",
"prefs.dimension_values": "寸法値の入力",
"prefs.dimension_values_note": "寸法値を入力する場所です。ウィンドウでは矢印で値を増減でき、寸法ツールは先に文字を配置します。",
"prefs.dimension_values_view": "ビュー内",
"prefs.dimension_values_window": "ウィンドウ",
"prefs.display": "表示",
"prefs.edges": "エッジと陰影",
"prefs.extend": "拡張",
@@ -2376,6 +2429,8 @@
"prefs.graphics_page.surfaces_edges": "サーフェスとエッジ",
"prefs.graphics_page.view_bar": "ビューバーにも表示",
"prefs.graphics_presets_note": "プリセットは以下の設定をまとめて変更します。",
"prefs.ground_shadow_follows_model": "地面の影をモデルに追従させる",
"prefs.ground_shadow_follows_model_help": "表示中のボディの最下部に影を配置します。オフの場合、持ち上げたモデルの影は原点を通る地面上に残ります。",
"prefs.hide_constraint_glyphs_zoomed_out": "ズームアウト時に拘束グリフを隠す",
"prefs.hide_constraint_glyphs_zoomed_out_note": "スケッチを十分にズームアウトすると、拘束グリフは表示されず画面を散らかしません。オフ: ズームに関係なく固定の画面サイズのままです。",
"prefs.history": "履歴",
@@ -2402,6 +2457,10 @@
"prefs.invert_curved_roll_arrows_note": "キューブの隅にある2つのカーブした矢印は、画面内でビューをロールします。オン: 時計回りの矢印が反時計回りに、反時計回りの矢印が時計回りにロールします。オフ: 各矢印は、そのカーブの向きにビューをロールします。",
"prefs.invert_navigator_arrows": "ビューキューブの回転矢印を反転",
"prefs.invert_navigator_arrows_note": "キューブの周りの矢印が、矢印の指す方向にカメラを回します(Fusion や Inventor と同じ動き):左の矢印は右を指し、カメラを右に振ります。オフ: 矢印は、そのある面の方へキューブを回すため、左の矢印はカメラを左に振ります。カーブしたロール矢印は影響を受けません。",
"prefs.keep_unsaved_also_asks": "タブを閉じるときにも確認する",
"prefs.keep_unsaved_also_asks_tip": "オフにすると、未保存の各デザインのコピーが最新であれば、ブラウザーの確認なしでタブが閉じます。",
"prefs.keep_unsaved_in_browser": "未保存の作業をこのブラウザーに保持する",
"prefs.keep_unsaved_in_browser_tip": "開いている各デザインの未保存の変更を、このブラウザーのプライベートストレージに常に最新の状態で保持します。次回のアクセス時に復元を提案します。",
"prefs.keep_work_on_tab_switch": "デザインを切り替えてもダイアログとツールを開いたままにする",
"prefs.keep_work_on_tab_switch_note": "各デザインのタブが、開いているダイアログまたはツール(選択や入力した内容を含む)とツールバーのタブを保持します。デザインに戻ると元のまま表示されます。オフ: デザインを切り替えるとそれらはキャンセルされ、モデル タブが表示されます。",
"prefs.labels": "ラベル",
@@ -2413,6 +2472,8 @@
"prefs.layout_page.card": "カード",
"prefs.layout_page.column": "列",
"prefs.layout_page.hover": "ホバー時",
"prefs.layout_page.hover_panel_menus": "ポイントしてパネルメニューを切り替える",
"prefs.layout_page.hover_panel_menus_help": "リボンのパネル名をクリックしてメニューを開きます。別のパネル名にポインターを合わせると切り替わります。リボンと開いているメニューの外に移動すると閉じます。",
"prefs.layout_page.icons_labels": "アイコン+ラベル",
"prefs.layout_page.ok_cancel": "OK とキャンセル",
"prefs.layout_page.on_hover": "ホバー時",
@@ -2515,6 +2576,8 @@
"prefs.quality_smoother": "より滑らか",
"prefs.quick_measure": "ビューの隅にクイック測定",
"prefs.quick_measure_note": "選択したものの寸法をビューの右下、履歴の上に表示します: エッジの長さ(円形のものは Ø と R)、面の面積、点の位置、2 つの項目の距離と角度、複数のエッジや面の合計。クリックでコピーします。",
"prefs.remember_dialog_values": "コマンドダイアログの値を記憶",
"prefs.remember_dialog_values.help": "既定ではオフです。オンにすると、コマンドが成功した後、SolidCraft を閉じるまで、同じコマンドを次に開いたときに安全な値とオプションを再利用します。",
"prefs.render_previous_geometry_budget": "レンダープレビューで以前のジオメトリ予算を使用",
"prefs.render_previous_geometry_budget_help": "デバイスに基づく予算の代わりに、以前の固定ジオメトリ上限(64ビットデスクトップでは512 MiB、その他では128 MiB)に戻します。",
"prefs.reports_saved": "{n} 件保存済み",
@@ -2523,6 +2586,8 @@
"prefs.ribbon_click_ends_tool_note": "使用中のスケッチツールの点灯したリボンボタンをクリックするとツールが終了し、ビューのクリックで再び選択できます。オフ: クリックするとツールが最初から開始されます。",
"prefs.ribbon_reveal": "検索結果をリボンに表示",
"prefs.ribbon_reveal_note": "コマンド検索で結果にポインターを合わせると、そのコマンドがリボンのどこにあるか(タブ、ボタン、またはパネルのドロップダウン内の行)を表示します。検索から開始したコマンドはそこで点滅し、ドロップダウンから実行中のコマンドはそのパネルに印が付きます。",
"prefs.ribbon_tabs_only": "リボンのタブだけを表示",
"prefs.ribbon_tabs_only_note": "リボンをタブの行だけに折りたたみます。タブをクリックすると、コマンドが始まるかポインターが離れるまで、そのパネルがビューの上に表示されます。表示タブの「リボンのタブのみ」またはそのショートカットで切り替えます。",
"prefs.right_click": "右クリック",
"prefs.saving": "起動と保存",
"prefs.scale_note": "ウィンドウ内では Ctrl + / Ctrl − でも変更できます。100% のボタンは 28 px です。",
@@ -2533,6 +2598,8 @@
"prefs.section_hatch_by_part": "部品ごと",
"prefs.section_hatch_one": "単一スタイル",
"prefs.select": "選択",
"prefs.select_hidden_edges": "隠線表示で隠れた辺を選択する",
"prefs.select_hidden_edges_help": "「Shaded with hidden edges」で面の向こうの辺を選択し、Filletにも使用できます。オフではv0.30.1相当の選択動作になります。非表示のボディは選択対象になりません。",
"prefs.select_one_item": "項目を 1 つ選択",
"prefs.select_other_hidden": "「他を選択」で非表示のジオメトリを選択",
"prefs.select_other_hidden_help": "オン: ポインター付近の非表示のボディやスケッチ、遮られたエッジを含めます。オフ: ポインター直下の表示中のジオメトリだけを元のリストに表示します。",
@@ -2557,6 +2624,8 @@
"prefs.sketch.click_selects": "クリックで選択",
"prefs.sketch.conflict_colour": "競合色",
"prefs.sketch.drawing": "作図",
"prefs.sketch.ellipse_axes": "楕円を軸と一緒に描く",
"prefs.sketch.ellipse_axes_note": "長軸と短軸をコンストラクション線として描き、半軸と軸に寸法を付けられるようにします。オフ: 楕円のみを描き、D で短半径を寸法にします。値をどこへ引いても、これまでどおり R 付きで描かれます。",
"prefs.sketch.exit_keep": "維持",
"prefs.sketch.exit_previous": "開始前のビューに戻る",
"prefs.sketch.exit_unless_rotated": "回転した場合を除く",
@@ -2650,6 +2719,10 @@
"prefs.theme_follow_off": "オフ",
"prefs.theme_follow_stop": "追従を停止",
"prefs.theme_import_hint": "クリックしてテーマファイルを選ぶか、ここにドロップするか、テーマをコピーした状態で Ctrl+V(Mac では Cmd+V)を押します",
"prefs.theme_notice_dismiss": "非表示",
"prefs.theme_option_blur": "パネル背面のぼかし",
"prefs.theme_option_blur_help": "半透明パネルの背面のぼかしを物理ピクセル単位で指定します。0でオフになります。",
"prefs.theme_option_invalid": "{option} の保存値が無効なため、テーマの既定値を使用します。保存値は保持されます。",
"prefs.theme_option_opacity": "不透明度",
"prefs.theme_option_opacity_help": "フローティングパネルの不透明度を調整します。ビューポートの色に重なっても文字が読めるように下限が適用されます。",
"prefs.theme_option_tab_colours": "タブの色",
@@ -2658,6 +2731,7 @@
"prefs.theme_option_tab_fade_ms_help": "タブの切り替え時にパネルとビューポートの色を徐々に変えます。0で即座に切り替えます。",
"prefs.theme_options_badge": "オプション",
"prefs.theme_options_more": "その他 · {count}",
"prefs.theme_options_newer": "このテーマには新しいバージョンの SolidCraft が必要です。更新後、元のテーマを再インポートしてオプション定義を復元してください。",
"prefs.theme_options_reset": "リセット",
"prefs.theme_options_unavailable": "このデバイスでは利用できません",
"prefs.theme_paste": "テーマを貼り付け",
@@ -2721,6 +2795,12 @@
"recovery.just_now": "たった今",
"recovery.min_ago": "{count}分前",
"recovery.never_saved": "未保存",
"recovery.open_saved": "保存済みのバージョンを開く",
"recovery.restore": "復元",
"recovery.restore_intro": "このページを前回閉じたときに保存されていなかった変更は、このブラウザーに保持されています。",
"recovery.restore_row": "{name}、{when}に変更",
"recovery.restore_title": "未保存のデザインを復元しますか?",
"recovery.restore_title_one": "未保存のデザイン {name}({when}に変更)を復元しますか?",
"refused.change": "{body} を変更できませんでした。",
"refused.cut": "{body} をカットできませんでした。",
"refused.face_curved": "曲面の 1 つがモデリングに使えません。",
@@ -3242,9 +3322,11 @@
"render.frame_wide": "16:9 · ワイドスクリーン",
"render.gpu_depth": "最大パス深度",
"render.gpu_options_help": "サンプル数、深度、シード、露出をこの設定に保存します。保存するのは選択中の画像方式だけです。",
"render.gpu_path_crashed": "前回このグラフィックアダプターでの準備中に SolidCraft が終了しました。グラフィックドライバー内で終了した可能性が高いため、自動では再度準備しません。代わりに CPU レイトレーシングを使うか、もう一度試してください(ドライバーを更新した場合も再試行されます)。",
"render.gpu_path_failed": "GPUパストレーサーを利用できません",
"render.gpu_path_first_setup": "このGPUでの初回セットアップには数分かかることがあります。その間もSolidCraftは使用できます。以降の起動では、ドライバーが保持していればコンパイル済みのシェーダーを再利用します。",
"render.gpu_path_preparing": "GPUパストレーサー:シェーダーをコンパイル中…",
"render.gpu_path_retry": "もう一度試す",
"render.gpu_seed": "乱数シード",
"render.gpu_unavailable": "このデバイスでは GPU パストレーシングの準備ができていません。",
"render.ground_apply": "床をフィットして保存",
@@ -3858,6 +3940,7 @@
"render.stage_panels.extent": "範囲",
"render.stage_panels.fit_save": "フィットして保存",
"render.stage_panels.gpu_required": "GPUパストレーシングが必要です",
"render.stage_panels.grain_angle": "木目の角度",
"render.stage_panels.keep": "保持",
"render.stage_panels.kept": "保持",
"render.stage_panels.kept_fields": "カメラ、サンプリング、画像寸法、光学設定、CAD形状、工学的特性。",
@@ -3976,6 +4059,19 @@
"render.subsurface_enable": "内部散乱ランダムウォーク",
"render.subsurface_help": "閉じた誘電体専用。散乱とRGB吸収は別々の毎mm係数です。実際の形状内で散乱し、境界で屈折・反射します。直線透過影、表面ノイズ除去、外部ボリュームを無効にし、フレネル層数を1にします。拡散シェーダー、密度グリッド、実測物性ではありません。高密度ではサンプル数と深度が必要です。",
"render.subsurface_rate": "散乱衝突率",
"render.surface_import.author": "作者",
"render.surface_import.busy": "画像をインポート中…",
"render.surface_import.complete": "画像を選び、作者、出典、ライセンスを入力してください。",
"render.surface_import.embedded": "インポートした画像",
"render.surface_import.failed": "画像のインポートが完了しませんでした。",
"render.surface_import.license": "ライセンス / 所有権",
"render.surface_import.raytrace_required": "画像にはCPUまたはGPUのレイトレーシングが必要です。出力で選択してください。",
"render.surface_import.repeat_u": "U方向の繰り返し",
"render.surface_import.repeat_v": "V方向の繰り返し",
"render.surface_import.save": "ボディに画像を保存",
"render.surface_import.saved_cpu": "CPUレンダリングで使うにはマップを保存してください。",
"render.surface_import.source": "出典",
"render.surface_import.title": "画像をインポート",
"render.tone_aces_fitted": "ACES近似",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPUガラスは検証された非交差媒体に対応します。無制限の入れ子媒体やノイズのない透過推定器は提供しません。",
@@ -4052,6 +4148,8 @@
"sample.plate.name": "サンプル プレート",
"sample.vents.about": "矩形パターンに並んだスロット: 行数、列数、ピッチはパラメータ",
"sample.vents.name": "通気プレート",
"scale.per_axis": "軸ごと",
"scale.uniform": "均等",
"search.command_keys": "Enter 実行 Shift+Enter 実行して開いたままにする Esc 閉じる",
"search.commands": "コマンド",
"search.design": "このデザイン内",
@@ -4142,6 +4240,32 @@
"sketch.dof_left": "残り自由度 {count}",
"sketch.dof_left_one": "残り自由度 {count}",
"sketch.draw_curves": "{linetype}として描く",
"sketch.error.closed_curve_two_crossings": "a closed curve needs two crossings to trim",
"sketch.error.nothing_to_break": "nothing to break here",
"sketch.error.offset_fillet_chamfer": "`{id}` is an offset, and its corners follow its source curves: round or cut the source corners before offsetting, or break the offset association first",
"sketch.error.offset_trim": "`{id}` is an offset, and its ends follow its source curves: edit the source curves instead, or break the offset association first",
"sketch.hint.arc.center_point": "Arc: click center and start, go round either way and click the end, or type a signed sweep",
"sketch.hint.arc.three_point": "Arc: click start, end, then a point on the arc",
"sketch.hint.arc3d": "3D Arc: click start, end, then a point on the arc (points may leave the sketch plane)",
"sketch.hint.break": "Break: click a curve where it should split",
"sketch.hint.circle.center": "Circle: click the center, then a point on the circle",
"sketch.hint.constraint.generic": "Constraint: pick the sketch entities",
"sketch.hint.constraint.horizontal_vertical": "Horizontal/Vertical: pick a line, two points, or a point and a line; hold Shift to pick a line midpoint",
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to center the second on the first",
"sketch.hint.dimension": "Dimension: pick one or two entities and type the value (or click to place it first); click a dimension to edit it; Enter applies",
"sketch.hint.extend": "Extend: click a curve near the end to extend (Shift: click two curves to extend both to where they meet)",
"sketch.hint.fallback": "Click in the sketch",
"sketch.hint.line": "Line: click points; right-click or Esc to finish",
"sketch.hint.line3d": "3D Line: click world points; right-click or Esc to finish (points may leave the sketch plane)",
"sketch.hint.open_done": "Open done",
"sketch.hint.point3d": "3D Point: click where a 3D point goes",
"sketch.hint.rectangle.center": "Center rectangle: click the center, then a corner",
"sketch.hint.rectangle.rounded": "Rounded rectangle: click two opposite corners (Tab to the radius box to type the corner radius)",
"sketch.hint.rectangle.three_point": "3-point rectangle: click two points of one edge, then the width",
"sketch.hint.rectangle.two_point": "Rectangle: click two opposite corners",
"sketch.hint.slot": "Slot: click both ends, then the width",
"sketch.hint.spline3d": "3D Spline: click fit points in 3D; right-click or Esc to finish",
"sketch.hint.trim": "Trim: click the piece of a curve to remove, or drag across the pieces to remove",
"sketch.make_selection": "選択を{linetype}にする",
"sketch.open_ends": "開放 {count}",
"sketch.status_conflict": "拘束が競合しています",
@@ -4363,6 +4487,7 @@
"ui.3d_sketch": "3D スケッチ",
"ui.activate": "アクティブ化",
"ui.active_component": "アクティブなコンポーネント",
"ui.addToQuickAccess": "クイックアクセスに追加",
"ui.add_configuration": "構成を追加",
"ui.add_parameter": "パラメーターを追加",
"ui.agent_chat": "エージェントチャット",
@@ -4697,6 +4822,7 @@
"ui.click_selects_whole_bodies": "クリックでボディ全体を選択",
"ui.click_then_press_key": "クリックした後、割り当てるキーを押してください",
"ui.close": "閉じる",
"ui.close_tab": "タブを閉じる",
"ui.coil": "コイル",
"ui.coincident": "一致",
"ui.collapse": "折りたたむ",
@@ -4784,6 +4910,7 @@
"ui.default_modelling_orientation": "モデリングのデフォルト方向",
"ui.delete": "削除",
"ui.delete_select_first": "先に削除するものを選択してください: フィーチャ、ボディ、コンポーネント、スケッチ ジオメトリ",
"ui.dependents_select_first": "先に、依存先を表示する対象を選択してください",
"ui.depth": "深さ",
"ui.design": "デザイン",
"ui.design_properties.author": "作成者",
@@ -4913,6 +5040,11 @@
"ui.factor": "倍率",
"ui.fade": "切り上げ",
"ui.fade_length": "切り上げ長さ",
"ui.fair_iterations": "反復回数",
"ui.fair_keep_edges": "エッジを保持",
"ui.fair_strength": "強度",
"ui.fair_surface_title": "サーフェスフェアリング",
"ui.fair_tolerance_default": "ボディサイズの 0.001 倍",
"ui.feature_history": "フィーチャー履歴",
"ui.feature_options": "フィーチャーオプション",
"ui.features": "フィーチャー",
@@ -4941,6 +5073,10 @@
"ui.fillet_setback_least": "{least} より大きく",
"ui.fillet_setback_tip": "なめらかなコーナーパッチが始まる位置: コーナーから各エッジに沿った距離。既定値は半径の {factor} 倍。最小セットバック (セットバックなしでラウンド同士が交わる位置。面どうしが直角なら半径) より大きくする必要があります。",
"ui.fillet_smooth_radius": "半径をなめらかに変化",
"ui.fillet_turn": "曲がり角",
"ui.fillet_turn_mitre": "留め継ぎ",
"ui.fillet_turn_swept": "回転",
"ui.fillet_turn_tip": "2 つのフィレットが間にある鋭いコーナーエッジを回り込む所(ボスの根元のフィレットが立ち上がりエッジを回る所など)。\n留め継ぎ: フィレットは延びて留め継ぎで出会います。\n回転: フィレットはコーナーエッジで止まり、その周りを回ります。コーナーは両方に接するトーラスです。同じ半径のフィレットが、まっすぐな凸のコーナーエッジを回る場合のみ。",
"ui.fillet_type_choices_tip": "一定の半径: エッジ全体で一つの半径。\n弦長: 面がどんな角度で交わっても、フィレットの幅が同じ。\n可変半径: 両端の半径と、追加すればエッジ沿いの半径。\n曲率連続: 平面と接する所で映り込みが折れない断面 (G2)。\nスプライン: エッジ沿いの半径が、設定した制御半径に従います。",
"ui.fillet_type_curvature": "曲率連続",
"ui.fillet_type_tip": "エッジを横切る断面。曲率連続: 平面の間の曲率連続 (G2) 断面で、映り込みが面からフィレットへ折れずにつながります。",
@@ -4986,6 +5122,7 @@
"ui.heading_construct": "構築",
"ui.heading_create": "作成",
"ui.heading_create_sketch": "スケッチを作成",
"ui.heading_custom_view_navigator": "カスタム ビューナビゲーター",
"ui.heading_cylinder": "円柱",
"ui.heading_design": "デザイン",
"ui.heading_draft": "勾配",
@@ -5093,6 +5230,7 @@
"ui.inspect": "検査",
"ui.instances": "インスタンス",
"ui.intersect": "交差",
"ui.intersection_curve_second_pick": "交差曲線: 2 つ目の面または曲線をクリックします (右クリックでやり直し)",
"ui.iso_4753": "ISO 4753",
"ui.isolate": "分離表示",
"ui.join": "結合",
@@ -5121,6 +5259,7 @@
"ui.manipulator_free_alt": "マニピュレータのフリードラッグを Alt/Option でも有効",
"ui.manipulator_free_alt_tip": "オン: 押し出し/ギズモ矢印で Alt/Option または Ctrl/Cmd が値スナップを無効にします。オフ: Ctrl/Cmd のみ。",
"ui.manufacture": "製造",
"ui.match_surface_title": "サーフェス一致",
"ui.material_review.axes": "材料ローカル軸。ボディの方向は割り当てません。",
"ui.material_review.claims": "出典とライセンスの申告は未確認です。欠落値は不明のままです。",
"ui.material_review.compressive_strength": "圧縮強度",
@@ -5143,6 +5282,9 @@
"ui.material_review.title": "物理材料データを確認",
"ui.material_review.unknown": "不明",
"ui.material_review.yield_strength": "降伏強度",
"ui.mcp_agents": "MCP 経由のエージェント",
"ui.mcp_compact": "コンパクトな出力",
"ui.mcp_compact_note": "MCP サーバー (solidcraft-cli mcp) 経由で接続したエージェント向け: inspect_design はスケッチを要約し、ツールの結果は整形されないため、トークンが少なくて済みます。変更後に開始したエージェントのセッションから適用されます。--compact または SOLIDCRAFT_MCP_COMPACT=1 でも有効になります。",
"ui.measure": "計測",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "面タグ付き 3MF",
@@ -5184,6 +5326,7 @@
"ui.options": "オプション",
"ui.orbit": "回転",
"ui.orbit_around": "回転中心",
"ui.organize": "整理",
"ui.orientation": "向き",
"ui.origin": "原点",
"ui.palette_construction_mode": "補助線モード (X)",
@@ -5228,6 +5371,7 @@
"ui.point_cloud_add_filter": "フィルターを追加…",
"ui.point_cloud_auto_range": "範囲を自動設定",
"ui.point_cloud_color_by": "色の付け方",
"ui.point_cloud_dots": "単独の点",
"ui.point_cloud_file_colors": "ファイルの色",
"ui.point_cloud_filter_by": "フィルター",
"ui.point_cloud_height_high": "最高の高さ",
@@ -5272,6 +5416,7 @@
"ui.quality": "品質",
"ui.quantity": "数量",
"ui.quantity_2": "数量 2",
"ui.quick_access_full": "クイックアクセスバーに置けるコマンドは {count} 個までです。追加するには 1 つ削除してください",
"ui.quit": "終了",
"ui.radial_context_menu": "パイ メニュー",
"ui.radius": "半径",
@@ -5289,6 +5434,7 @@
"ui.relink_not_linked": "このコンポーネントは参照元の設計にリンクされていません。",
"ui.relink_select_one": "リンクされたコンポーネントまたはそのインスタンスを1つ選択してください。",
"ui.relink_source_all": "参照元を再リンク(すべてのインスタンス)…",
"ui.removeFromQuickAccess": "クイックアクセスから削除",
"ui.removeFromToolbar": "ツールバーから削除",
"ui.removeSection": "セクションを解除",
"ui.render": "レンダリング",
@@ -5297,6 +5443,8 @@
"ui.repeat_nothing": "まだ繰り返すものがありません。先にツールかダイアログを開始してください。",
"ui.replace_with_link_done": "コピーはリンクされたデザインの配置になりました。",
"ui.report_a_bug": "不具合を報告",
"ui.report_characters_left_one": "残り {count} 文字",
"ui.report_characters_left_other": "残り {count} 文字",
"ui.reset": "リセット",
"ui.reset_all": "すべてリセット",
"ui.reset_the_settings_on_this_page": "このページの設定をリセット",
@@ -5581,6 +5729,14 @@
"values must be numbers or expressions": "値は数値または式にしてください",
"view.auto_ortho_faces": "回転中は透視投影、面を正面に見ると平行投影",
"view.render_inspector": "レンダーインスペクター",
"view_gizmo.arrows": "回転矢印",
"view_gizmo.home": "ホーム ボタン",
"view_gizmo.no_such_body": "このデザインにその名前のボディはありません。",
"view_gizmo.note": "ビューでボディを選択してください。ボディは環境設定と設定プロファイルに保存され、すべてのデザインで表示されます。面をクリックすると、その面を正面から見ます。",
"view_gizmo.texture": "テクスチャ",
"view_gizmo.themed": "テーマ",
"view_gizmo.too_detailed": "ビューナビゲーターには細かすぎます (10,000 三角形を超えています)",
"view_gizmo.too_thin": "ビューナビゲーターには細長すぎます: 最も薄い辺が最も長い辺の 5 分の 1 以上である必要があります",
"web.files.insert_drawings_hint": "CADファイル、メッシュ、DXF、SVGを開いているデザインに挿入",
"web.files.insert_stored_drawings_hint": "開いているデザインに挿入(CADファイル、メッシュ、DXF、SVG)",
"web.open_full": "SolidCraft で開く",
File diff suppressed because it is too large Load Diff
+861 -46
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analisis Kebolehcapaian",
"cmd.inspect_center_of_mass": "Pusat Jisim",
"cmd.inspect_clearance": "Kelegaan",
"cmd.inspect_colored_components": "Aktifkan Warna Komponen",
"cmd.inspect_colored_components_disable": "Nyahaktifkan Warna Komponen",
"cmd.inspect_compare": "Bandingkan Jasad",
"cmd.inspect_compare_picture": "Banding dengan Imej",
"cmd.inspect_continuity": "Analisis Keselanjaran",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Membaca laluan lukisan SVG ke dalam lakaran, diskalakan, diputar dan diletakkan mengikut tetapan anda. Gunakan untuk menukar logo atau garis luar vektor kepada lengkung yang boleh disemperit.",
"help.sketch.inspect": "Membuka laporan tentang lakaran yang terbuka: berapa banyak darjah kebebasan yang tinggal dan sama ada ia terkekang sepenuhnya. Gunakan untuk mengetahui apa yang masih bergerak sebelum anda menamatkan lakaran.",
"help.sketch.intersect": "Menambah lengkung atau titik tempat jasad, muka, tepi atau lakaran lain melintasi satah lakaran. Ia kekal terpaut dan mengikut model apabila model berubah.",
"help.sketch.intersection_curve": "Mencipta lengkung 3D tempat dua jasad atau muka bertemu, terpaut kepadanya. Gunakan sebagai laluan atau rel pemandu yang mengikut model.",
"help.sketch.intersection_curve": "Mencipta lengkung 3D tempat dua jasad, muka atau lengkung lakaran lain bertemu, terpaut kepadanya. Gunakan sebagai laluan atau rel pemandu yang mengikut model.",
"help.sketch.isoparametric_curve": "Mencipta lengkung 3D di sepanjang arah U atau V muka melalui titik yang anda pilih, terpaut kepada muka itu. Gunakan sebagai laluan atau garis belahan yang mengikut permukaan.",
"help.sketch.line": "Melukis garis bersambung titik demi titik, sebagai rantaian terbuka atau garis luar tertutup. Ia ialah alat asas bagi kebanyakan profil.",
"help.sketch.line.midpoint": "Melukis garis daripada titik tengahnya ke satu hujung, dengan hujung satu lagi dicerminkan. Gunakan untuk garis yang berpusat pada satu titik, seperti tepi simetri.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Plat Contoh",
"sample.vents.about": "Corak slot segi empat tepat: baris, lajur dan pic sebagai parameter",
"sample.vents.name": "Plat Pengudaraan",
"scale.per_axis": "Mengikut paksi",
"scale.uniform": "Seragam",
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan kekalkan terbuka Esc tutup",
"search.commands": "Arahan",
"search.design": "Dalam reka bentuk ini",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Tilgjengelighetsanalyse",
"cmd.inspect_center_of_mass": "Massesenter",
"cmd.inspect_clearance": "Klaring",
"cmd.inspect_colored_components": "Aktiver komponentfarger",
"cmd.inspect_colored_components_disable": "Deaktiver komponentfarger",
"cmd.inspect_compare": "Sammenlign kropper",
"cmd.inspect_compare_picture": "Sammenlign bilde",
"cmd.inspect_continuity": "Kontinuitetsanalyse",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Leser banene fra en SVG-tegning inn i en skisse, skalert, dreid og plassert slik du angir. Bruk det til å gjøre en logo eller vektorkontur om til kurver du kan ekstrudere.",
"help.sketch.inspect": "Åpner en rapport om den åpne skissen: hvor mange frihetsgrader som gjenstår og om den er fullstendig bundet. Bruk det til å finne ut hva som fortsatt beveger seg før du fullfører skissen.",
"help.sketch.intersect": "Legger til kurvene eller punktene der kropper, flater, kanter eller andre skisser krysser skisseplanet. De forblir koblet og følger modellen når den endres.",
"help.sketch.intersection_curve": "Oppretter 3D-kurver der to kropper eller flater møtes, koblet til dem. Bruk dem som baner eller føringskurver som følger modellen.",
"help.sketch.intersection_curve": "Oppretter 3D-kurver der to kropper, flater eller kurver fra andre skisser møtes, koblet til dem. Bruk dem som baner eller føringskurver som følger modellen.",
"help.sketch.isoparametric_curve": "Oppretter en 3D-kurve langs U- eller V-retningen til en flate gjennom et punkt du velger, koblet til flaten. Bruk den som en bane eller delelinje som følger flaten.",
"help.sketch.line": "Tegner sammenhengende linjer punkt for punkt, som en åpen kjede eller en lukket kontur. Det er det grunnleggende verktøyet for de fleste profiler.",
"help.sketch.line.midpoint": "Tegner en linje fra midtpunktet og ut til den ene enden, med den andre enden speilet. Bruk det til linjer sentrert på et punkt, for eksempel en symmetrisk kant.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Eksempelplate",
"sample.vents.about": "Spor i et rektangulært mønster: rader, kolonner og stigning er parametere",
"sample.vents.name": "Ventilasjonsplate",
"scale.per_axis": "Per akse",
"scale.uniform": "Jevn",
"search.command_keys": "Enter kjør Shift+Enter kjør og behold åpen Esc lukk",
"search.commands": "Kommandoer",
"search.design": "I dette designet",
+14 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Toegankelijkheidsanalyse",
"cmd.inspect_center_of_mass": "Zwaartepunt",
"cmd.inspect_clearance": "Speling",
"cmd.inspect_colored_components": "Componentkleuren inschakelen",
"cmd.inspect_colored_components_disable": "Componentkleuren uitschakelen",
"cmd.inspect_compare": "Lichamen vergelijken",
"cmd.inspect_compare_picture": "Afbeelding vergelijken",
"cmd.inspect_continuity": "Continuïteitsanalyse",
@@ -1368,6 +1366,9 @@
"dialog.custom_axis": "↖ Aangepaste as",
"dialog.dimension_insert_hint": "Klik op een andere aandrijvende maat om de naam ervan in te voegen",
"dialog.dimension_name": "Maat: {name}",
"dialog.dimension_step_down": "{step} omlaag",
"dialog.dimension_step_expression": "Alleen een getal verspringt; een uitdrukking blijft zoals ingevoerd",
"dialog.dimension_step_up": "{step} omhoog",
"dialog.edit_title": "{title} BEWERKEN",
"dialog.no_shown_body": "Geen zichtbaar lichaam om te wijzigen: toon er een of kies Nieuw lichaam",
"dialog.pattern_sketch_not_object": "Kies lichamen of kenmerken: een schets is geen van beide. Herhaal de curven ervan met het eigen Patroon of Spiegelen van de schets, of extrudeer de schets eerst.",
@@ -1392,6 +1393,7 @@
"document.read_only_save": "Dit ontwerp is alleen-lezen geopend: sla het op als nieuw bestand",
"document.save_changes": "Wijzigingen in {name} opslaan?",
"document.save_copy": "Kopie opslaan…",
"document.tab_unsaved_changes": "Niet-opgeslagen wijzigingen",
"document.unsaved_changes": "{name} heeft niet-opgeslagen wijzigingen. Als u niet opslaat, gaan ze verloren.",
"drive.rigid": "{joint} is een starre verbinding: die houdt zijn onderdelen vast en heeft dus niets om aan te sturen. Wijzig het type in Verbinding bewerken om ze te laten bewegen: Draaiend om te draaien, Schuivend om te schuiven, Cilindrisch voor beide.",
"drive.stopped_by_contact": "Gestopt bij {value}: {a} zou door {b} heen gaan",
@@ -1623,7 +1625,7 @@
"help.sketch.insert_svg": "Leest de paden van een SVG-tekening in een schets, geschaald, gedraaid en geplaatst waar je instelt. Gebruik dit om een logo of vectorcontour om te zetten in curven die je kunt extruderen.",
"help.sketch.inspect": "Opent een rapport over de geopende schets: hoeveel vrijheidsgraden er nog over zijn en of hij volledig beperkt is. Gebruik dit om te zien wat nog beweegt voordat je de schets voltooit.",
"help.sketch.intersect": "Voegt de curven of punten toe waar lichamen, vlakken, randen of andere schetsen het schetsvlak kruisen. Ze blijven gekoppeld en volgen het model wanneer dat verandert.",
"help.sketch.intersection_curve": "Maakt 3D-curven waar twee lichamen of vlakken samenkomen, eraan gekoppeld. Gebruik ze als paden of geleiders die het model volgen.",
"help.sketch.intersection_curve": "Maakt 3D-curven waar twee lichamen, vlakken of curven van andere schetsen samenkomen, eraan gekoppeld. Gebruik ze als paden of geleiders die het model volgen.",
"help.sketch.isoparametric_curve": "Maakt een 3D-curve langs de U- of V-richting van een vlak door een punt dat je kiest, gekoppeld aan het vlak. Gebruik dit als pad of splitslijn die het oppervlak volgt.",
"help.sketch.line": "Tekent verbonden lijnen punt voor punt, als open keten of gesloten contour. Het is het basisgereedschap voor de meeste profielen.",
"help.sketch.line.midpoint": "Tekent een lijn vanuit zijn middelpunt naar één uiteinde, waarbij het andere uiteinde wordt gespiegeld. Gebruik dit voor lijnen die op een punt zijn gecentreerd, zoals een symmetrische rand.",
@@ -2194,6 +2196,13 @@
"prefs.dialogs_edge": "Aan de rand van de weergave",
"prefs.dialogs_left_column": "In de linkerkolom",
"prefs.dialogs_note": "Waar opdrachtdialogen openen: vastgezet aan de rand van de weergave, of in de linkerkolom in plaats van de browser en de geschiedenis, die terugkomen zoals ze waren wanneer de dialoog sluit.",
"prefs.dimension_step_angle": "Wat de pijlen van het venster, Omhoog/Omlaag en het wieltje bij een hoek optellen",
"prefs.dimension_step_length": "Wat de pijlen van het venster, Omhoog/Omlaag en het wieltje bij een lengte optellen",
"prefs.dimension_steps": "Stappen in het venster",
"prefs.dimension_values": "Maatwaarden",
"prefs.dimension_values_note": "Waar de waarde van een maat wordt ingevoerd. Een venster heeft pijlen die de waarde laten verspringen; het maatgereedschap plaatst eerst de tekst.",
"prefs.dimension_values_view": "In de weergave",
"prefs.dimension_values_window": "In een venster",
"prefs.display": "Weergave",
"prefs.edges": "Randen en schaduwing",
"prefs.extend": "Verlengen",
@@ -3809,6 +3818,8 @@
"sample.plate.name": "Voorbeeldplaat",
"sample.vents.about": "Sleuven in een rechthoekig patroon: rijen, kolommen en spoed zijn parameters",
"sample.vents.name": "Ventilatieplaat",
"scale.per_axis": "Per as",
"scale.uniform": "Uniform",
"search.command_keys": "Enter uitvoeren Shift+Enter uitvoeren en open houden Esc sluiten",
"search.commands": "Opdrachten",
"search.design": "In dit ontwerp",
+3 -2
View File
@@ -934,8 +934,6 @@
"cmd.inspect_accessibility": "Analiza dostępności",
"cmd.inspect_center_of_mass": "Środek masy",
"cmd.inspect_clearance": "Prześwit",
"cmd.inspect_colored_components": "Włącz kolory komponentów",
"cmd.inspect_colored_components_disable": "Wyłącz kolory komponentów",
"cmd.inspect_compare": "Porównaj bryły",
"cmd.inspect_continuity": "Analiza ciągłości",
"cmd.inspect_curvature_comb": "Analiza grzebienia krzywizny",
@@ -1347,6 +1345,7 @@
"document.read_only_save": "Ten projekt otwarto tylko do odczytu: zapisz go jako nowy plik",
"document.save_changes": "Zapisać zmiany w {name}?",
"document.save_copy": "Zapisz kopię…",
"document.tab_unsaved_changes": "Niezapisane zmiany",
"document.unsaved_changes": "{name} ma niezapisane zmiany. Jeśli ich nie zapiszesz, zostaną utracone.",
"drive.rigid": "{joint} to złącze sztywne: utrzymuje swoje części nieruchomo, więc nie ma czym sterować. Aby mogły się poruszać, zmień jego typ w Edytuj złącze: Obrotowe do obrotu, Przesuwne do przesuwu, Walcowe do obu.",
"drive.stopped_by_contact": "Zatrzymano przy {value}: {a} przeszedłby przez {b}",
@@ -1985,6 +1984,8 @@
"prefs.shortcuts": "Skróty",
"prefs.single_surface_analysis": "Używaj jednej analizy powierzchni",
"prefs.single_surface_analysis_help": "Gdy wyłączone, cieniowanie powierzchni i Ciągłość mogą być włączone jednocześnie. Włączenie zachowuje ostatnio uruchomioną lub zmienioną analizę.",
"prefs.sketch.ellipse_axes": "Rysuj elipsy z osiami",
"prefs.sketch.ellipse_axes_note": "Oś wielka i mała jako linie konstrukcyjne, aby można było wymiarować półosie i osie. Wyłączone: sama elipsa, a D na niej daje promień małej półosi, gdziekolwiek przeciągnąć wartość, rysowany i oznaczony R jak dotąd.",
"prefs.sketch_align_points": "Wyrównuj do innych punktów szkicu podczas rysowania",
"prefs.sketch_align_points_note": "Podczas rysowania punkt wyrównuje się jednocześnie poziomo z jednym punktem szkicu i pionowo z innym, z kropkowaną linią pomocniczą, a kliknięcie zachowuje oba wyrównania. Wyłączone: tylko z poprzednim punktem, po jednej osi.",
"prefs.sketch_conflict_colour": "Geometria w konflikcie w kolorze konfliktu",
+2
View File
@@ -1408,6 +1408,8 @@
"sample.plate.name": "Placa de exemplo",
"sample.vents.about": "Rasgos em padrão retangular: linhas, colunas e passo são parâmetros",
"sample.vents.name": "Placa de ventilação",
"scale.per_axis": "Por eixo",
"scale.uniform": "Uniforme",
"search.command_keys": "Enter executar Shift+Enter executar e manter aberto Esc fechar",
"search.commands": "Comandos",
"search.design": "Neste projeto",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Analiza accesibilității",
"cmd.inspect_center_of_mass": "Centru de masă",
"cmd.inspect_clearance": "Joc",
"cmd.inspect_colored_components": "Activează culorile componentelor",
"cmd.inspect_colored_components_disable": "Dezactivează culorile componentelor",
"cmd.inspect_compare": "Compară corpurile",
"cmd.inspect_compare_picture": "Compară imaginea",
"cmd.inspect_continuity": "Analiza continuității",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Citește traseele unui desen SVG într-o schiță, scalate, rotite și plasate așa cum stabiliți. Folosiți-o pentru a transforma un logo sau un contur vectorial în curbe pe care le puteți extruda.",
"help.sketch.inspect": "Deschide un raport despre schița deschisă: câte grade de libertate au rămas și dacă este complet constrânsă. Folosiți-l pentru a afla ce se mai mișcă înainte de a finaliza schița.",
"help.sketch.intersect": "Adaugă curbele sau punctele în care corpuri, fețe, muchii sau alte schițe taie planul schiței. Rămân legate și urmează modelul când acesta se schimbă.",
"help.sketch.intersection_curve": "Creează curbe 3D acolo unde se întâlnesc două corpuri sau fețe, legate de ele. Folosiți-le ca trasee sau ghidaje care urmează modelul.",
"help.sketch.intersection_curve": "Creează curbe 3D acolo unde se întâlnesc două corpuri, fețe sau curbe ale altor schițe, legate de ele. Folosiți-le ca trasee sau ghidaje care urmează modelul.",
"help.sketch.isoparametric_curve": "Creează o curbă 3D pe direcția U sau V a unei fețe, printr-un punct ales de dumneavoastră, legată de față. Folosiți-o ca traseu sau linie de separare care urmează suprafața.",
"help.sketch.line": "Desenează linii conectate punct cu punct, ca lanț deschis sau contur închis. Este instrumentul de bază pentru majoritatea profilurilor.",
"help.sketch.line.midpoint": "Desenează o linie de la mijlocul ei spre un capăt, celălalt capăt fiind în oglindă. Folosiți-o pentru linii centrate pe un punct, de exemplu o muchie simetrică.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Placă exemplu",
"sample.vents.about": "Canale într-o matrice dreptunghiulară: rândurile, coloanele și pasul sunt parametri",
"sample.vents.name": "Placă de ventilație",
"scale.per_axis": "Pe axe",
"scale.uniform": "Uniform",
"search.command_keys": "Enter execută Shift+Enter execută și păstrează deschis Esc închide",
"search.commands": "Comenzi",
"search.design": "În acest proiect",
+26 -4
View File
@@ -182,6 +182,8 @@
"Designs you open or save appear here.": "Здесь появляются открытые и сохранённые дизайны.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Определяется параметром «До объекта». Выберите «Угол», чтобы задать угловой предел.",
"Diameter": "Диаметр",
"Did this assembly open the wrong way up?": "Эта сборка открылась не той стороной вверх?",
"Did this part open the wrong way up?": "Эта деталь открылась не той стороной вверх?",
"Dimension new shapes": "Проставлять размеры новых фигур",
"Direction": "Направление",
"Discard": "Отменить",
@@ -606,6 +608,7 @@
"Square": "Квадрат",
"Stand it up": "Поставить вертикально",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Поставить вертикально: четверть оборота вокруг X одной операцией «Переместить», которую можно отменить.",
"Stand this assembly up?": "Поставить эту сборку вертикально?",
"Stand this part up?": "Поставить эту деталь вертикально?",
"Start angle": "Начальный угол",
"Start bulge": "Выпуклость в начале",
@@ -644,6 +647,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Направление должно быть осью или прямолинейной кривой эскиза, лежащей в плоскости эскиза",
"The mirror line cannot also be mirrored": "Линию зеркала нельзя одновременно отражать",
"The mirror line must be a straight curve of this sketch, not an origin axis": "Линия зеркала должна быть прямолинейной кривой этого эскиза, а не осью начала координат",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Ось начала координат перпендикулярна плоскости эскиза, поэтому линии в этой плоскости нет",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Сохранённое исходное вхождение отсутствует. Выберите корректное исходное положение перед перепривязкой.",
"Then": "Затем",
"These features will be deleted:": "Эти операции будут удалены:",
@@ -953,8 +957,6 @@
"cmd.inspect_accessibility": "Анализ доступности",
"cmd.inspect_center_of_mass": "Центр масс",
"cmd.inspect_clearance": "Зазор",
"cmd.inspect_colored_components": "Включить цвета компонентов",
"cmd.inspect_colored_components_disable": "Отключить цвета компонентов",
"cmd.inspect_compare": "Сравнить тела",
"cmd.inspect_compare_picture": "Сравнить с изображением",
"cmd.inspect_continuity": "Анализ непрерывности",
@@ -1356,6 +1358,7 @@
"dialog.dimension_insert_hint": "Дважды щёлкните другой управляющий размер, чтобы вставить его имя",
"dialog.dimension_name": "Размер: {name}",
"dialog.edit_title": "ИЗМЕНИТЬ: {title}",
"dialog.no_shown_body": "Нет отображаемого тела для изменения: покажите тело или выберите «Новое тело»",
"dialog.pattern_sketch_not_object": "Выберите тела или операции: эскиз — ни то, ни другое. Повторите его кривые собственным инструментом «Массив» или «Зеркало» в эскизе либо сначала выдавите его.",
"dialog.select": "↖ Выбрать",
"dialog.selected_count": "↖ Выбрано: {count}",
@@ -1608,7 +1611,7 @@
"help.sketch.insert_svg": "Читает контуры рисунка SVG в эскиз, масштабируя, поворачивая и размещая их так, как вы зададите. Используйте, чтобы превратить логотип или векторный контур в кривые, которые можно выдавить.",
"help.sketch.inspect": "Открывает отчёт об открытом эскизе: сколько осталось степеней свободы и определён ли он полностью. Используйте, чтобы выяснить, что ещё двигается, прежде чем завершить эскиз.",
"help.sketch.intersect": "Добавляет кривые или точки, где тела, грани, рёбра или другие эскизы пересекают плоскость эскиза. Они остаются связанными и следуют за моделью при её изменении.",
"help.sketch.intersection_curve": "Создаёт 3D-кривые там, где встречаются два тела или две грани, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
"help.sketch.intersection_curve": "Создаёт 3D-кривые там, где встречаются два тела, две грани или кривые других эскизов, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
"help.sketch.isoparametric_curve": "Создаёт 3D-кривую вдоль направления U или V грани через выбранную точку, связанную с гранью. Используйте как траекторию или линию разделения, следующую за поверхностью.",
"help.sketch.line": "Рисует соединённые линии точка за точкой, как открытую цепочку или замкнутый контур. Это основной инструмент для большинства профилей.",
"help.sketch.line.midpoint": "Рисует линию от её середины до одного конца, второй конец отражается зеркально. Используйте для линий с центром в точке, например симметричного ребра.",
@@ -1717,6 +1720,8 @@
"help.view.spin": "Непрерывно вращает вид вокруг модели до следующего щелчка или нажатия клавиши. Используйте, чтобы показать модель со всех сторон.",
"help.view.tool": "Закрепляет инструмент камеры (орбиту, панорамирование или масштаб), так что перетаскивание левой кнопкой мыши двигает вид, пока вы его не отключите. Используйте на трекпаде или без средней кнопки мыши.",
"help.view.visual_style": "Переключает способ отрисовки модели: тонированная с рёбрами или без них, каркас или со скрытыми рёбрами. Используйте каркас, чтобы заглянуть внутрь, а тонированный вид — для чистого вида.",
"history.sketch_under_constrained": "Недоопределён (степеней свободы: {count})",
"history.sketch_under_constrained_one": "Недоопределён ({count} степень свободы)",
"hole_presets.confirm_delete": "Подтвердить удаление",
"hole_presets.delete": "Удалить",
"hole_presets.delete_prompt": "Удалить этот личный пресет? Существующие отверстия не изменятся.",
@@ -2128,6 +2133,8 @@
"prefs.anti_aliasing_note": "Сглаживает неровные края.",
"prefs.applies_on_restart": "Применяется после перезапуска",
"prefs.assistant": "Ассистент",
"prefs.auto_operation": "Выбирать операцию автоматически",
"prefs.auto_operation_note": "Выбор «Объединить», «Новое тело» или «Вырезать» по тому, чего касается материал.",
"prefs.axes": "Оси",
"prefs.axis": "Ось",
"prefs.bodies_folders": "Папки тел",
@@ -2349,6 +2356,8 @@
"prefs.sketch_selection": "Эскиз и выделение",
"prefs.sketch_selection_heading": "Эскиз и выделение",
"prefs.sketch_selection_note": "Как работает привязка в эскизах и как щелчки выделяют объекты.",
"prefs.sketch_state_icons": "Значки эскизов в цвете их состояния",
"prefs.sketch_state_icons_note": "В истории и браузере значок эскиза принимает цвет его состояния (недоопределён, полностью определён или с конфликтом), а подсказка называет это состояние. Выкл.: у всех эскизов одинаковый значок.",
"prefs.sketch_tab_always": "Всегда показывать вкладку «Эскиз»",
"prefs.sketch_tab_always_note": "Вкладка «Эскиз» остаётся на ленте сразу после «Модель», пока не открыт ни один эскиз. Её инструменты рисования создают эскиз на выбранной плоскости или грани либо на плоскости, которую вы выберете следом, и сразу включают нужный инструмент.",
"prefs.sketch_tracking_guides": "Направляющие выравнивания по центру и середине",
@@ -2360,6 +2369,11 @@
"prefs.source": "Источник",
"prefs.speed": "Скорость",
"prefs.stand_up_note": "После открытия файла STEP или IGES с осью Z вверх предлагать повернуть его на четверть оборота вокруг X.",
"prefs.stand_up_unknown": "Для файлов из неизвестных программ",
"prefs.stand_up_unknown.ask": "Спрашивать в диалоговом окне",
"prefs.stand_up_unknown.note": "Показывать уведомление",
"prefs.stand_up_unknown.off": "Не предлагать",
"prefs.stand_up_unknown_note": "Как делается предложение, когда в файле не указана известная программа.",
"prefs.status_bar": "Строка состояния",
"prefs.status_bar_note": "Тонкая полоса вдоль нижнего края окна показывает, что изменяется, состояние открытого эскиза его цветом и единицы дизайна; щелчок по единицам изменяет их. Состояние эскиза при этом больше не показывается в углу вида.",
"prefs.status_done": "Готово",
@@ -3737,6 +3751,8 @@
"sample.plate.name": "Образец пластины",
"sample.vents.about": "Пазы в прямоугольном массиве: число рядов, столбцов и шаг заданы параметрами",
"sample.vents.name": "Вентиляционная пластина",
"scale.per_axis": "По осям",
"scale.uniform": "Равномерно",
"search.command_keys": "Enter выполнить Shift+Enter выполнить и оставить открытым Esc закрыть",
"search.commands": "Команды",
"search.design": "В этом дизайне",
@@ -3863,7 +3879,7 @@
"status.navigation": "Назначение кнопок мыши: {mapping}",
"status.navigation_tip": "Как мышь двигает вид. Щёлкните, чтобы выбрать другое назначение",
"status.under_constrained": "Недоопределён (степеней свободы: {count})",
"status.under_constrained_one": "Недоопределён (степеней свободы: {count})",
"status.under_constrained_one": "Недоопределён ({count} степень свободы)",
"status.units_tip": "Единицы дизайна. Щёлкните, чтобы изменить",
"the angle must be a number": "угол должен быть числом",
"the curves must be in one sketch": "кривые должны быть в одном эскизе",
@@ -3902,8 +3918,10 @@
"timeline.move_to_group": "Переместить операции в папку",
"timeline.rename_group": "Переименовать папку операций",
"timeline.reorder_many": "Изменить порядок операций",
"timeline.suppress_features": "Подавить операции",
"timeline.suppress_folders_tip": "Подавить или снять подавление со всех операций в папках и вложенных папках",
"timeline.ungroup": "Убрать папки операций",
"timeline.unsuppress_features": "Снять подавление с операций",
"to the face": "до грани",
"to the next face": "до следующей грани",
"toolbar.customize_note": "Вкладка {tab}. Отметьте команду, чтобы добавить её кнопку на панель инструментов. Можно также перетащить строку из выпадающего меню панели на панель инструментов, перетащить кнопку за пределы панели для удаления или использовать значок закрепления в строке выпадающего меню.",
@@ -4349,6 +4367,8 @@
"ui.cancel": "Отмена",
"ui.canvases": "Подложки",
"ui.center": "Центр",
"ui.center_arc_follows_cursor": "Дуга из центра следует направлению курсора",
"ui.center_arc_follows_cursor_tip": "Дуга из центра идёт по часовой стрелке или против неё — в ту сторону, в которую курсор обходит её центр. Выкл.: всегда против часовой стрелки от начальной точки.",
"ui.centerline": "Осевая линия",
"ui.chain": "Цепочка",
"ui.chamfer": "Фаска",
@@ -5160,11 +5180,13 @@
"update.installed": "SolidCraft {version} установлен. Перезапустите, чтобы использовать его.",
"update.installer_started": "Установщик запустится после закрытия SolidCraft.",
"update.later": "Напомнить позже",
"update.managed": "Доступно обновление: обновите SolidCraft в {manager}",
"update.no_notes": "У этого выпуска нет примечаний.",
"update.package_hint": "Эта копия установлена из {kind}. Скачайте новый пакет, затем установите его командой:",
"update.prefs_dev": "Это сборка для разработки ({version}): она не проверяет обновления.",
"update.prefs_flatpak": "Эту копию ({version}) обновляет Flatpak: выполните {command} или воспользуйтесь центром приложений. SolidCraft не проверяет обновления.",
"update.prefs_label": "Обновления",
"update.prefs_managed": "Это SolidCraft {version}, которым управляет {manager}. Проверка запрашивает у GitHub последний релиз и больше ничего не отправляет; обновляйте SolidCraft в {manager}.",
"update.prefs_note": "Это SolidCraft {version} ({kind}). При проверке у GitHub запрашивается последний выпуск, больше ничего не отправляется.",
"update.prefs_web": "Перезагрузите страницу, чтобы получить последнюю версию веб-приложения. Если сеть недоступна или обновление не успевает загрузиться, открывается сохранённая версия; незавершённая загрузка продолжится до следующей перезагрузки.",
"update.refresh_checking": "Проверка наличия новейшего выпуска…",
+1 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Анализа доступности",
"cmd.inspect_center_of_mass": "Центар масе",
"cmd.inspect_clearance": "Провера зазора",
"cmd.inspect_colored_components": "Омогући боје компоненти",
"cmd.inspect_colored_components_disable": "Онемогући боје компоненти",
"cmd.inspect_compare": "Упореди тела",
"cmd.inspect_compare_picture": "Упореди слику",
"cmd.inspect_continuity": "Анализа континуитета",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Учитава путање SVG цртежа у скицу, скалиране, окренуте и постављене како зададете. Користите га да логотип или векторски обрис претворите у криве које можете екструдирати.",
"help.sketch.inspect": "Отвара извештај о отвореној скици: колико је степени слободе остало и да ли је потпуно ограничена. Користите га да сазнате шта се још помера пре него што завршите скицу.",
"help.sketch.intersect": "Додаје криве или тачке у којима тела, лица, ивице или друге скице секу раван скице. Оне остају повезане и прате модел када се он промени.",
"help.sketch.intersection_curve": "Прави 3D криве тамо где се два тела или лица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
"help.sketch.intersection_curve": "Прави 3D криве тамо где се два тела, лица или криве других скица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
"help.sketch.isoparametric_curve": "Прави 3D криву дуж U или V смера лица кроз тачку коју изаберете, повезану са лицем. Користите је као путању или линију поделе која прати површ.",
"help.sketch.line": "Црта повезане линије тачку по тачку, као отворени ланац или затворени обрис. То је основни алат за већину профила.",
"help.sketch.line.midpoint": "Црта линију од њене средишње тачке до једног краја, а други крај се пресликава. Користите је за линије центриране на тачки, на пример за симетричну ивицу.",
+4 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Tillgänglighetsanalys",
"cmd.inspect_center_of_mass": "Masscentrum",
"cmd.inspect_clearance": "Frigång",
"cmd.inspect_colored_components": "Aktivera komponentfärger",
"cmd.inspect_colored_components_disable": "Inaktivera komponentfärger",
"cmd.inspect_compare": "Jämför kroppar",
"cmd.inspect_compare_picture": "Jämför bild",
"cmd.inspect_continuity": "Kontinuitetsanalys",
@@ -1392,6 +1390,7 @@
"document.read_only_save": "Den här konstruktionen öppnades skrivskyddad: spara den som en ny fil",
"document.save_changes": "Spara ändringar i {name}?",
"document.save_copy": "Spara en kopia…",
"document.tab_unsaved_changes": "Osparade ändringar",
"document.unsaved_changes": "{name} har osparade ändringar. Om du inte sparar går de förlorade.",
"drive.rigid": "{joint} är ett stelt led: det håller sina delar stilla och har därför inget att driva. Om delarna ska kunna röra sig, ändra ledets typ i Redigera led: Roterande för att rotera, Glidande för att glida, Cylindrisk för båda.",
"drive.stopped_by_contact": "Stoppade vid {value}: {a} skulle gå igenom {b}",
@@ -1623,7 +1622,7 @@
"help.sketch.insert_svg": "Läser in banorna från en SVG-ritning i en skiss, skalade, vridna och placerade där du anger. Använd det för att göra en logotyp eller en vektorkontur till kurvor som du kan extrudera.",
"help.sketch.inspect": "Öppnar en rapport om den öppna skissen: hur många frihetsgrader som återstår och om den är fullt definierad. Använd det för att ta reda på vad som fortfarande rör sig innan du avslutar skissen.",
"help.sketch.intersect": "Lägger till de kurvor eller punkter där kroppar, ytor, kanter eller andra skisser korsar skissplanet. De förblir länkade och följer modellen när den ändras.",
"help.sketch.intersection_curve": "Skapar 3D-kurvor där två kroppar eller ytor möts, länkade till dem. Använd dem som banor eller styrkurvor som följer modellen.",
"help.sketch.intersection_curve": "Skapar 3D-kurvor där två kroppar, ytor eller kurvor från andra skisser möts, länkade till dem. Använd dem som banor eller styrkurvor som följer modellen.",
"help.sketch.isoparametric_curve": "Skapar en 3D-kurva längs en ytas U- eller V-riktning genom en punkt som du väljer, länkad till ytan. Använd den som en bana eller delningslinje som följer ytan.",
"help.sketch.line": "Ritar sammanhängande linjer punkt för punkt, som en öppen kedja eller en sluten kontur. Det är det grundläggande verktyget för de flesta profiler.",
"help.sketch.line.midpoint": "Ritar en linje från dess mittpunkt ut till ena änden, med den andra änden speglad. Använd det för linjer centrerade på en punkt, till exempel en symmetrisk kant.",
@@ -3809,6 +3808,8 @@
"sample.plate.name": "Exempelplatta",
"sample.vents.about": "Spår i ett rektangulärt mönster: rader, kolumner och delning är parametrar",
"sample.vents.name": "Ventilationsplatta",
"scale.per_axis": "Per axel",
"scale.uniform": "Likformig",
"search.command_keys": "Enter kör Shift+Enter kör och behåll öppen Esc stäng",
"search.commands": "Kommandon",
"search.design": "I denna konstruktion",
+3 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "Erişilebilirlik analizi",
"cmd.inspect_center_of_mass": "Kütle merkezi",
"cmd.inspect_clearance": "Açıklık",
"cmd.inspect_colored_components": "Bileşen Renklerini Etkinleştir",
"cmd.inspect_colored_components_disable": "Bileşen Renklerini Devre Dışı Bırak",
"cmd.inspect_compare": "Gövdeleri karşılaştır",
"cmd.inspect_compare_picture": "Görüntüyle karşılaştır",
"cmd.inspect_continuity": "Süreklilik kontrolü",
@@ -1623,7 +1621,7 @@
"help.sketch.insert_svg": "Bir SVG çiziminin yollarını, belirlediğiniz ölçekte, açıda ve konumda bir eskize okur. Bir logoyu veya vektör dış hattını ekstrüzyon yapabileceğiniz eğrilere dönüştürmek için kullanın.",
"help.sketch.inspect": "Açık eskiz için bir rapor açar: kaç serbestlik derecesi kaldığını ve tam kısıtlı olup olmadığını. Eskizi bitirmeden önce nelerin hâlâ hareket ettiğini öğrenmek için kullanın.",
"help.sketch.intersect": "Gövdelerin, yüzlerin, kenarların veya diğer eskizlerin eskiz düzlemini kestiği eğrileri ya da noktaları ekler. Bağlı kalır ve model değiştiğinde onu izler.",
"help.sketch.intersection_curve": "İki gövdenin veya yüzün buluştuğu yerde, onlara bağlı 3B eğriler oluşturur. Bunları modeli izleyen yollar veya kılavuz raylar olarak kullanın.",
"help.sketch.intersection_curve": "İki gövdenin, yüzün veya diğer eskizlerin eğrilerinin buluştuğu yerde, onlara bağlı 3B eğriler oluşturur. Bunları modeli izleyen yollar veya kılavuz raylar olarak kullanın.",
"help.sketch.isoparametric_curve": "Bir yüzün U veya V yönü boyunca, seçtiğiniz bir noktadan geçen ve yüze bağlı bir 3B eğri oluşturur. Yüzeyi izleyen bir yol veya bölme çizgisi olarak kullanın.",
"help.sketch.line": "Bağlantılı çizgileri nokta nokta, açık bir zincir veya kapalı bir dış hat olarak çizer. Çoğu profil için temel araçtır.",
"help.sketch.line.midpoint": "Bir çizgiyi orta noktasından bir uca doğru çizer; diğer uç yansıtılır. Bir nokta etrafında ortalanmış çizgiler, örneğin simetrik bir kenar için kullanın.",
@@ -3809,6 +3807,8 @@
"sample.plate.name": "Örnek plaka",
"sample.vents.about": "Dikdörtgen desende yuvalar: satırlar, sütunlar ve aralık parametredir",
"sample.vents.name": "Havalandırma plakası",
"scale.per_axis": "Eksen başına",
"scale.uniform": "Tekdüze",
"search.command_keys": "Enter çalıştır Shift+Enter çalıştır ve açık tut Esc kapat",
"search.commands": "Komutlar",
"search.design": "Bu tasarımda",
+493 -81
View File
File diff suppressed because it is too large Load Diff
+159 -3
View File
@@ -85,6 +85,7 @@
"Bottom Right": "Dưới cùng bên phải",
"Boundary": "Biên dạng bao",
"Bounding box": "Hộp bao ngoài",
"Bridge": "Cầu nối",
"CHECK": "KIỂM TRA",
"COMPONENTS": "CHI TIẾT",
"CONFIGURATIONS": "CẤU HÌNH",
@@ -628,6 +629,8 @@
"Suppress Selected Folders": "Bỏ kích hoạt các thư mục đã chọn",
"Suppressed": "Bị tạm nén",
"Surface": "Bề mặt",
"Surface or face": "Bề mặt hoặc mặt",
"Surface or faces": "Bề mặt hoặc các mặt",
"Surface, or faces to turn over": "Mặt cong, hoặc các mặt cần lật hướng",
"Surfaces": "Các bề mặt",
"Swap Ends": "Hoán đổi hai đầu",
@@ -641,6 +644,7 @@
"Target": "Mục tiêu",
"Target Body": "Khối mục tiêu",
"Target Face (optional)": "Mặt đích (tùy chọn)",
"Target edges": "Các cạnh đích",
"Tear": "Xé rách mép chấn",
"Technical details": "Chi tiết kỹ thuật",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "Phác thảo nét đứt là đường dẫn mặt phẳng đã lưu. Hãy chọn một bề mặt mới để sửa lại liên kết.",
@@ -648,6 +652,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Hướng phải là một trục hoặc một đường cong phác thảo thẳng nằm trong mặt phẳng phác thảo",
"The mirror line cannot also be mirrored": "Đường đối xứng không thể đồng thời được lấy đối xứng",
"The mirror line must be a straight curve of this sketch, not an origin axis": "Đường đối xứng phải là một đường cong thẳng của phác thảo này, không phải trục gốc",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Trục gốc vuông góc với mặt phẳng phác thảo nên không có đường thẳng trong mặt phẳng này",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Bản thể tác giả đã lưu bị thiếu. Hãy chọn một vị trí tác giả hợp lệ trước khi gán lại.",
"Then": "Thì",
"These features will be deleted:": "Các tính năng sau sẽ bị xóa:",
@@ -739,6 +744,9 @@
"a picked edge's body is gone": "thân của một cạnh đã chọn không còn nữa",
"add a key for a joint first": "hãy thêm một khóa cho khớp trước",
"ai.connected_toast": "SolidCraft hiện đã sẵn sàng trong {clients}. Khởi động lại ứng dụng để sử dụng (Cài đặt > Trợ lý ảo).",
"ai.http.allow": "Cho phép HTTP không bảo mật",
"ai.http.confirm": "Cho phép với điểm cuối này",
"ai.http.warning": "Khóa API và cuộc trò chuyện sẽ được gửi mà không mã hóa. Bất kỳ ai có quyền truy cập mạng đều có thể đọc hoặc thay đổi chúng. Chỉ sử dụng trên mạng mà bạn tin cậy.",
"ai.prefs.allow_control": "Cho phép tác nhân AI kết nối",
"ai.prefs.allow_control_note": "Kênh cục bộ (MCP). Không có gì rời khỏi máy tính này.\n\nMở kênh điều khiển chỉ trên máy tính này (127.0.0.1, cổng 27878 hoặc cổng trống tiếp theo). Chương trình kết nối phải xuất trình mã token bảo mật chỉ tài khoản người dùng của bạn đọc được, được tạo mới mỗi lần khởi động. Có hiệu lực vào lần khởi động tiếp theo của SolidCraft. Khi tắt: không lắng nghe trừ khi bạn bật.",
"ai.prefs.availability": "Khả dụng",
@@ -750,6 +758,8 @@
"ai.prefs.follow_camera": "Theo camera",
"ai.prefs.follow_camera_off": "Tự động xoay camera theo phác thảo đang tắt.",
"ai.prefs.follow_camera_on": "Camera sẽ tự động xoay vuông góc với phác thảo do agent tạo.",
"ai.prefs.instructions": "Hướng dẫn tùy chỉnh",
"ai.prefs.instructions_note": "Được gửi cho agent khi bắt đầu mỗi cuộc trò chuyện, sau hướng dẫn riêng của SolidCraft: cách bạn muốn làm việc, các quy tắc cần tuân theo. Thay đổi có hiệu lực từ cuộc trò chuyện tiếp theo (Kết nối hoặc Cuộc trò chuyện mới).",
"ai.prefs.intro": "Tác nhân AI trong SolidCraft.",
"ai.prefs.make_default": "Đặt làm mặc định",
"ai.prefs.no_agents": "Chưa có agent nào. Thêm một agent hoặc làm mới để tìm các agent đã cài đặt.",
@@ -963,8 +973,7 @@
"cmd.inspect_analysis": "Phân tích khung nhìn",
"cmd.inspect_center_of_mass": "Trọng tâm",
"cmd.inspect_clearance": "Khoảng cách an toàn",
"cmd.inspect_colored_components": "Bật màu cụm chi tiết",
"cmd.inspect_colored_components_disable": "Tắt màu cụm chi tiết",
"cmd.inspect_colored_components": "Màu cụm chi tiết",
"cmd.inspect_compare": "So sánh các thân khối",
"cmd.inspect_compare_picture": "So sánh với ảnh tham chiếu",
"cmd.inspect_continuity": "Phân tích độ liên tục mặt",
@@ -1081,6 +1090,9 @@
"cmd.render.compare.divider": "Di chuyển vạch so sánh kết xuất",
"cmd.render.result.discard": "Bỏ kết quả kết xuất",
"cmd.render.result.select": "Chọn kết quả kết xuất",
"cmd.render.surface.import": "Nhập bản đồ bề mặt",
"cmd.render.texture.list": "Liệt kê ảnh bề mặt được nhúng",
"cmd.render.texture.remove": "Xóa ảnh bề mặt được nhúng",
"cmd.render_aging_describe": "Lão hóa bề mặt phân lớp",
"cmd.render_backends": "Kết xuất hình ảnh Sauends",
"cmd.render_batch": "Kết xuất mọi cảnh chụp",
@@ -1296,6 +1308,7 @@
"cmd.timeline_stop": "Dừng lịch sử",
"cmd.timeline_suppress": "Tạm nén tính năng",
"cmd.ui.assistant": "Bật/tắt trợ lý",
"cmd.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",
"cmd.view.render_focus": "Chế độ xem tập trung kết xuất",
@@ -1323,6 +1336,7 @@
"cmd.view_previous_tab": "Thẻ trước",
"cmd.view_rename": "Đổi tên góc nhìn đã lưu",
"cmd.view_render_frame": "Đường dẫn khung kết xuất",
"cmd.view_ribbon_tabs_only": "Chỉ các thẻ ribbon",
"cmd.view_right": "Góc nhìn bên phải",
"cmd.view_save": "Tạo góc nhìn đã lưu mới",
"cmd.view_shots_strip": "Dải cảnh chụp",
@@ -1394,15 +1408,22 @@
"dialog.computing": "Đang tính toán… {seconds} s",
"dialog.could_not_be_checked": "không thể kiểm tra",
"dialog.custom_axis": "↖ Trục tùy chỉnh",
"dialog.cylinder_face_tip": "Bề mặt hoặc đỉnh của mô hình mà khối trụ đứng trên đó: nhấp vào đây, sau đó nhấp vào nó trong khung nhìn. Để trống thì X, Y và Z đặt vị trí.",
"dialog.dimension_insert_hint": "Nhấp vào một kích thước điều khiển khác để chèn tên của nó",
"dialog.dimension_name": "Kích thước: {name}",
"dialog.dimension_step_down": "Giảm {step}",
"dialog.dimension_step_expression": "Chỉ số mới thay đổi theo bước; biểu thức giữ nguyên như đã nhập",
"dialog.dimension_step_up": "Tăng {step}",
"dialog.edit_title": "CHỈNH SỬA {title}",
"dialog.no_shown_body": "Không có thân khối đang hiển thị để thay đổi: hãy hiển thị một thân khối, hoặc chọn “Tạo mới Thân khối”",
"dialog.pattern_sketch_not_object": "Hãy chọn thân khối hoặc tính năng: bản phác thảo không phải là cả hai. Lặp lại các đường cong của nó bằng Mảng hoặc Lấy đối xứng trong chính bản phác thảo, hoặc đùn khối nó trước.",
"dialog.primitive_point_tip": "Một điểm điền vào X, Y và Z: nhấp vào đây, sau đó nhấp vào một đỉnh, cạnh hoặc bề mặt của mô hình, một điểm của bản phác thảo, hoặc gốc tọa độ.",
"dialog.reference_edge_tip": "Một cạnh thẳng của bề mặt đã chọn, từ đó đo khoảng cách bên dưới: nhấp vào đây, sau đó nhấp vào cạnh.",
"dialog.select": "↖ Chọn",
"dialog.selected_count": "↖ {count} đã chọn",
"dialog.selection_constraint": "Ràng buộc {id}",
"dialog.selection_external_curve": "{sketch} · đường cong {id}",
"dialog.selection_external_point": "{point} · {sketch}",
"dialog.selection_guidance": "Chọn một mục đầu vào để thay đổi. Nhấp vào đối tượng đã chọn để bỏ chọn; Ctrl/Cmd-nhấp để bật/tắt chọn. Xem trước kết quả, sau đó nhấn OK.",
"dialog.selection_midpoint": "Trung điểm · {id}",
"dialog.selection_profile": "{sketch} · biên dạng {number}",
@@ -1420,6 +1441,7 @@
"document.read_only_save": "Thiết kế này được mở ở chế độ chỉ đọc: hãy lưu thành tệp mới",
"document.save_changes": "Lưu các thay đổi đối với {name}?",
"document.save_copy": "Lưu bản sao…",
"document.tab_unsaved_changes": "Thay đổi chưa lưu",
"document.unsaved_changes": "{name} có các thay đổi chưa lưu. Nếu không lưu, các thay đổi sẽ bị mất.",
"drive.rigid": "{joint} là khớp nối cứng: nó giữ cố định các chi tiết, nên không có gì để dẫn động. Để các chi tiết chuyển động, hãy đổi loại khớp nối trong “Chỉnh sửa khớp nối”: Revolute để xoay, Slider để trượt, Cylindrical cho cả hai.",
"drive.stopped_by_contact": "Đã dừng tại {value}: {a} sẽ xuyên qua {b}",
@@ -1440,10 +1462,16 @@
"each rail's curves must be in one sketch": "các đường cong của mỗi đường dẫn hướng phải nằm trong cùng một phác thảo",
"feature computation ended without a result": "việc tính toán đặc trưng kết thúc mà không có kết quả",
"file.open.drawing_filter": "Thiết kế, tệp CAD, lưới đa giác và bản vẽ",
"file.open.portal_fallback.no_portal_service": "Bộ chọn tệp đã mở không phải của riêng desktop: dịch vụ chọn tệp của desktop không khả dụng.",
"file.open.portal_fallback.no_session_bus": "Bộ chọn tệp đã mở không phải của riêng desktop: không có bus phiên D-Bus.",
"file.open.portal_fallback.xwayland": "Bộ chọn tệp đã mở không phải của riêng desktop: dưới XWayland, portal từ chối cửa sổ cha.",
"file_load.built": "Đã dựng được: {built} trên {total} tính năng.",
"file_load.converting": "Đang chuyển đổi khối đặc {done} trên {total}",
"file_load.inserted": "Đã chèn {name}",
"file_load.inserting": "Đang chèn hình học",
"file_load.link_item": "{name} ({done} trên {total})",
"file_load.opened": "Đã mở {name}",
"file_load.opened_in_tab": "Đã mở {name} trong thẻ mới",
"file_load.opening": "Đang mở thiết kế",
"file_load.preparing_thumbnail": "Đang chuẩn bị ảnh thu nhỏ...",
"file_load.preparing_view": "Đang chuẩn bị góc nhìn...",
@@ -1679,7 +1707,7 @@
"help.sketch.insert_svg": "Đọc các đường dẫn của bản vẽ SVG vào phác thảo, theo tỉ lệ, góc xoay và vị trí bạn đặt. Dùng để biến logo hoặc đường bao véc tơ thành đường cong có thể đùn.",
"help.sketch.inspect": "Mở báo cáo về bản phác thảo đang mở: còn bao nhiêu bậc tự do và đã ràng buộc hoàn toàn chưa. Dùng nó để biết phần nào vẫn còn di chuyển được trước khi hoàn tất bản phác thảo.",
"help.sketch.intersect": "Thêm các đường cong hoặc điểm nơi khối, mặt, cạnh hoặc phác thảo khác cắt qua mặt phẳng phác thảo. Chúng vẫn được liên kết và thay đổi theo mô hình.",
"help.sketch.intersection_curve": "Tạo đường cong 3D tại nơi hai khối hoặc hai mặt gặp nhau, liên kết với chúng. Dùng làm đường dẫn hoặc ray dẫn hướng thay đổi theo mô hình.",
"help.sketch.intersection_curve": "Tạo đường cong 3D tại nơi hai khối, hai mặt hoặc đường cong của các phác thảo khác gặp nhau, liên kết với chúng. Dùng làm đường dẫn hoặc ray dẫn hướng thay đổi theo mô hình.",
"help.sketch.isoparametric_curve": "Tạo đường cong 3D theo hướng U hoặc V của một mặt qua điểm bạn chọn, liên kết với mặt đó. Dùng làm đường dẫn hoặc đường tách thay đổi theo bề mặt.",
"help.sketch.line": "Vẽ các đường thẳng nối liền từng điểm một, thành chuỗi hở hoặc đường bao khép kín. Đây là công cụ cơ bản cho hầu hết biên dạng.",
"help.sketch.line.midpoint": "Vẽ đường thẳng từ trung điểm ra một đầu, đầu kia đối xứng qua trung điểm. Dùng cho đường thẳng có tâm tại một điểm, như cạnh đối xứng.",
@@ -1785,6 +1813,7 @@
"help.view.navigate": "Xoay, dịch hoặc thu phóng khung nhìn theo từng bước, chẳng hạn xoay một phần tư vòng sang trái. Dùng để điều khiển camera chính xác thay vì kéo chuột.",
"help.view.navigation_bar": "Hiện hoặc ẩn thanh điều hướng ở cuối khung nhìn. Ẩn đi để có thêm chỗ, hiện ra để dùng các công cụ xem bằng chuột.",
"help.view.rename": "Đặt tên mới cho một góc nhìn đã lưu. Bản thân góc nhìn không đổi.",
"help.view.ribbon_tabs_only": "Thu gọn ribbon chỉ còn hàng thẻ, để khung nhìn có thêm chỗ mà các bảng từng chiếm. Bấm một thẻ để hiện các bảng của nó đè lên khung nhìn; chọn lại lệnh này để luôn hiện toàn bộ ribbon.",
"help.view.save": "Lưu vị trí camera hiện tại thành một góc nhìn có tên trong thiết kế. Dùng để quay lại đúng góc đó sau này, chẳng hạn khi chụp ảnh màn hình.",
"help.view.spin": "Quay khung nhìn quanh mô hình liên tục cho đến cú nhấp hoặc phím tiếp theo. Dùng để trình bày mô hình từ mọi phía.",
"help.view.tool": "Giữ cố định một công cụ camera (xoay quanh, dịch hoặc thu phóng) để kéo bằng nút chuột trái sẽ di chuyển khung nhìn cho đến khi bạn tắt nó. Dùng trên bàn di chuột hoặc khi không có nút chuột giữa.",
@@ -1999,6 +2028,7 @@
"material.selection.mixed": "Các vật liệu khác nhau",
"measure.at": "Tại",
"measure.item": "Mục {number}",
"measure.line_to_centre": "Từ đường đến tâm",
"measure.quick_copied": "Đã sao chép số đo",
"measure.quick_copy": "Nhấp để sao chép số đo",
"measure.quick_edges": "{count} cạnh",
@@ -2029,6 +2059,8 @@
"nav.blender.help": "Kéo chuột giữa để xoay, Shift+chuột giữa để trượt, Ctrl+chuột giữa để thu phóng; kéo chuột phải giữ nguyên camera",
"nav.creo.help": "Kéo nút giữa để xoay, Shift+nút giữa để di chuyển, Ctrl+kéo nút giữa theo chiều dọc để thu phóng (kéo xuống để phóng to); lăn con lăn để thu phóng",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "Kéo bằng nút giữa hoặc nút phải sẽ tiếp tục xoay, di chuyển hoặc thu phóng như lúc bắt đầu cho đến khi nhả nút, dù trong lúc đó có nhấn hay nhả phím nào. Tắt: các phím đang giữ quyết định ở mọi thời điểm của thao tác kéo.",
"nav.drag_keeps_mode.label": "Thao tác kéo giữ nguyên kiểu điều hướng lúc bắt đầu",
"nav.free_orbit.help": "Chuột và chuột 3D xoay tự do qua cả hai cực và giữ nguyên độ lăn nghiêng (Roll). Bắt dính trục bằng phím Shift sẽ bị tắt.",
"nav.free_orbit.label": "Xoay góc nhìn tự do",
"nav.free_orbit.snap_help": "Chuột và chuột 3D xoay qua cả hai cực và giữ nguyên góc nghiêng cuộn. Giữ Shift trong khi kéo xoay quỹ đạo để bắt dính hướng nhìn và góc nghiêng cuộn.",
@@ -2265,6 +2297,17 @@
"prefs.auto_operation_note": "Nối, Vật thể mới hoặc Cắt, tùy vào phần vật liệu tiếp xúc.",
"prefs.axes": "Các trục tọa độ",
"prefs.axis": "Trục tọa độ",
"prefs.background_choose": "Chọn ảnh…",
"prefs.background_image": "Ảnh",
"prefs.background_unreadable": "Không đọc được ảnh nền",
"prefs.blur_cost": "Chi phí kết xuất tương đối. Nhiều thanh hơn thường có nghĩa là cần xử lý nhiều hơn.",
"prefs.blur_device": "Không khả dụng trên thiết bị đồ họa hoặc đường kết xuất này",
"prefs.blur_guard": "Chất lượng làm mờ đã giảm để duy trì độ phản hồi của khung hình. Bán kính không thay đổi.",
"prefs.blur_off": "Tắt",
"prefs.blur_reduce_transparency": "Bị tắt bởi cài đặt Giảm độ trong suốt của hệ thống",
"prefs.blur_software": "Không khả dụng với đồ họa phần mềm (gpu-software)",
"prefs.blur_source": "Không khả dụng trong chế độ xem không gian làm việc này",
"prefs.blur_web": "Không khả dụng trên đường kết xuất web này",
"prefs.bodies_folders": "Thư mục thân khối",
"prefs.bodies_folders_always": "Luôn luôn",
"prefs.bodies_folders_never": "Không bao giờ",
@@ -2278,6 +2321,8 @@
"prefs.browser_eye_left": "Biểu tượng mắt cạnh mũi tên thu gọn",
"prefs.browser_eye_left_note": "Các hàng trong cây thư mục hiển thị biểu tượng mắt bật/tắt cạnh mũi tên thu gọn như trước đây, thay vì trong một cột ở đầu bên phải của các hàng.",
"prefs.browser_reveal_on_hover": "Hiện khi di chuột",
"prefs.browser_rows_start_closed": "Các dòng trong cây thư mục đóng sẵn",
"prefs.browser_rows_start_closed_note": "Vị trí đặt và thư mục trong cây thư mục bắt đầu ở trạng thái đóng, nên thiết kế có nhiều thành phần chỉ hiện các dòng cấp trên cùng. Mỗi dòng mở bằng mũi tên của nó, “Mở rộng tất cả” hoặc “Tìm trong cây đối tượng”, lệnh này chỉ mở các dòng dẫn tới thân khối và đóng lại các dòng mà lần tìm trước đã mở. Chọn một thân khối trong khung nhìn 3D không làm thay đổi cây thư mục. Khi tắt, các thư mục mở sẵn, vị trí đặt theo tùy chọn “Thành phần mở sẵn”, và việc chọn trong khung nhìn 3D mở cây thư mục tới thân khối đó, như trước đây.",
"prefs.browser_show_note": "Hiển thị giữ trình duyệt trên màn hình. Hiện khi di chuột ẩn nó cho đến khi con trỏ đến vị trí của nó hoặc thẻ mảnh ở cạnh trái, và nó vẫn mở khi con trỏ ở bên trong hoặc một menu của nó đang mở.",
"prefs.browser_shown": "Hiển thị",
"prefs.built_in": "Mặc định tích hợp",
@@ -2288,6 +2333,7 @@
"prefs.carousel_previous": "Trước",
"prefs.choose_font": "Chọn tệp phông chữ…",
"prefs.click": "Nhấp chuột",
"prefs.command_dialog_values": "Hộp thoại lệnh",
"prefs.contact_shading": "Đổ bóng tiếp xúc mềm",
"prefs.contact_shading_note": "Tạo bóng đổ mềm tại những vị trí các chi tiết tiếp giáp gần nhau.",
"prefs.context_menu": "Menu ngữ cảnh",
@@ -2323,6 +2369,13 @@
"prefs.dialogs_edge": "Ở cạnh khung nhìn",
"prefs.dialogs_left_column": "Trong cột bên trái",
"prefs.dialogs_note": "Nơi hộp thoại lệnh mở ra: gắn ở cạnh khung nhìn, hoặc trong cột bên trái thay cho trình duyệt và Lịch sử, hai phần này trở lại như cũ khi hộp thoại đóng.",
"prefs.dimension_step_angle": "Lượng mà mũi tên của cửa sổ, Lên/Xuống và con lăn cộng vào một góc",
"prefs.dimension_step_length": "Lượng mà mũi tên của cửa sổ, Lên/Xuống và con lăn cộng vào một độ dài",
"prefs.dimension_steps": "Bước của cửa sổ",
"prefs.dimension_values": "Giá trị kích thước",
"prefs.dimension_values_note": "Nơi bạn nhập giá trị kích thước. Cửa sổ có mũi tên để thay đổi theo bước; công cụ Kích thước đặt văn bản trước.",
"prefs.dimension_values_view": "Trong khung nhìn",
"prefs.dimension_values_window": "Trong một cửa sổ",
"prefs.display": "Hiển thị",
"prefs.edges": "Cạnh và đổ bóng",
"prefs.extend": "Kéo dài",
@@ -2376,6 +2429,8 @@
"prefs.graphics_page.surfaces_edges": "Bề mặt và cạnh",
"prefs.graphics_page.view_bar": "Cũng có trên thanh khung nhìn",
"prefs.graphics_presets_note": "Các cấu hình định sẵn sẽ thiết lập đồng thời các tùy chọn bên dưới.",
"prefs.ground_shadow_follows_model": "Bóng trên mặt đất di chuyển theo mô hình",
"prefs.ground_shadow_follows_model_help": "Đặt bóng bên dưới khối hiển thị thấp nhất. Khi tắt, bóng của mô hình được nâng lên vẫn nằm trên mặt phẳng đất đi qua gốc tọa độ.",
"prefs.hide_constraint_glyphs_zoomed_out": "Ẩn biểu tượng ràng buộc khi thu nhỏ",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Biểu tượng ràng buộc biến mất khi bản phác được thu nhỏ đủ xa để không làm rối khung nhìn. Tắt: chúng giữ kích thước màn hình cố định ở mọi mức thu phóng.",
"prefs.history": "Lịch sử",
@@ -2402,6 +2457,10 @@
"prefs.invert_curved_roll_arrows_note": "Hai mũi tên cong ở góc khối cuộn khung nhìn trong mặt phẳng màn hình. Khi bật: mũi tên theo chiều kim đồng hồ cuộn ngược chiều kim đồng hồ và ngược lại. Khi tắt: mỗi mũi tên cuộn khung nhìn theo chiều cong của nó.",
"prefs.invert_navigator_arrows": "Đảo ngược mũi tên xoay của khối điều hướng",
"prefs.invert_navigator_arrows_note": "Các mũi tên quanh khối xoay camera theo hướng mũi tên chỉ, như trong Fusion và Inventor: mũi tên bên trái chỉ sang phải và đưa camera sang phải. Khi tắt: mũi tên xoay khối về phía cạnh chứa nó, nên mũi tên bên trái đưa camera sang trái. Hai mũi tên cuộn cong không bị ảnh hưởng.",
"prefs.keep_unsaved_also_asks": "Vẫn hỏi khi đóng thẻ",
"prefs.keep_unsaved_also_asks_tip": "Khi tắt, thẻ sẽ đóng mà không có hộp thoại của trình duyệt khi bản sao của mọi thiết kế chưa lưu đã được cập nhật.",
"prefs.keep_unsaved_in_browser": "Giữ công việc chưa lưu trong trình duyệt này",
"prefs.keep_unsaved_in_browser_tip": "Các thay đổi chưa lưu của mỗi thiết kế đang mở được cập nhật liên tục trong bộ nhớ riêng của trình duyệt này. Lần truy cập sau sẽ đề nghị khôi phục chúng.",
"prefs.keep_work_on_tab_switch": "Giữ hộp thoại và công cụ mở khi chuyển giữa các thiết kế",
"prefs.keep_work_on_tab_switch_note": "Mỗi thẻ thiết kế giữ hộp thoại hoặc công cụ đang mở, cùng những gì bạn đã chọn và nhập trong đó, và thẻ thanh công cụ của nó. Chúng vẫn còn đó khi bạn quay lại thiết kế. Tắt: chuyển thiết kế sẽ hủy chúng và hiển thị thẻ Mô hình.",
"prefs.labels": "Nhãn chữ",
@@ -2413,6 +2472,8 @@
"prefs.layout_page.card": "Thẻ",
"prefs.layout_page.column": "Cột",
"prefs.layout_page.hover": "Rê chuột",
"prefs.layout_page.hover_panel_menus": "Chuyển menu nhóm khi di chuột",
"prefs.layout_page.hover_panel_menus_help": "Nhấp vào tên nhóm trên dải công cụ để mở menu. Di chuột lên tên nhóm khác để chuyển menu. Di chuyển con trỏ ra ngoài dải công cụ và menu đang mở để đóng menu.",
"prefs.layout_page.icons_labels": "Biểu tượng + nhãn",
"prefs.layout_page.ok_cancel": "Đồng ý và Hủy bỏ",
"prefs.layout_page.on_hover": "Khi rê chuột",
@@ -2515,6 +2576,8 @@
"prefs.quality_smoother": "mượt mà hơn",
"prefs.quick_measure": "Đo nhanh ở góc khung nhìn",
"prefs.quick_measure_note": "Hiển thị kích thước của đối tượng được chọn ở góc dưới bên phải khung nhìn, phía trên Lịch sử: chiều dài của cạnh (hoặc Ø và R với cạnh tròn), diện tích của mặt, vị trí của điểm, khoảng cách và góc giữa hai đối tượng, tổng của nhiều cạnh hoặc mặt. Nhấp để sao chép.",
"prefs.remember_dialog_values": "Ghi nhớ giá trị hộp thoại lệnh",
"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_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}",
@@ -2523,6 +2586,8 @@
"prefs.ribbon_click_ends_tool_note": "Nhấp vào nút ruy-băng đang sáng của công cụ phác thảo đang dùng sẽ kết thúc công cụ, để các cú nhấp trong khung nhìn lại chọn đối tượng. Tắt: cú nhấp khởi động lại công cụ.",
"prefs.ribbon_reveal": "Hiện kết quả tìm kiếm trên ribbon",
"prefs.ribbon_reveal_note": "Khi rê chuột lên một kết quả trong Tìm lệnh, ribbon cho biết lệnh nằm ở đâu: tab, nút hoặc dòng của nó trong menu thả xuống của một bảng. Lệnh bắt đầu từ tìm kiếm sẽ nhấp nháy ở đó, và lệnh đang chạy từ menu thả xuống sẽ đánh dấu bảng của nó.",
"prefs.ribbon_tabs_only": "Chỉ hiện các thẻ của ribbon",
"prefs.ribbon_tabs_only_note": "Ribbon thu gọn chỉ còn hàng thẻ. Bấm một thẻ sẽ hiện các bảng của nó đè lên khung nhìn cho đến khi một lệnh bắt đầu hoặc con trỏ rời khỏi chúng. Lệnh Chỉ các thẻ ribbon trong thẻ Xem, hoặc phím tắt của nó, bật tắt tùy chọn này.",
"prefs.right_click": "Nhấp chuột phải",
"prefs.saving": "Khởi động và lưu",
"prefs.scale_note": "Cũng có thể dùng Ctrl + / Ctrl − trong cửa sổ. Các nút có kích thước 28 px ở mức 100%.",
@@ -2533,6 +2598,8 @@
"prefs.section_hatch_by_part": "Theo chi tiết",
"prefs.section_hatch_one": "Một kiểu",
"prefs.select": "Chọn",
"prefs.select_hidden_edges": "Chọn cạnh bị che trong chế độ tô bóng có cạnh ẩn",
"prefs.select_hidden_edges_help": "Cho phép chọn cạnh xuyên qua bề mặt khi dùng chế độ tô bóng có cạnh ẩn, kể cả cho lệnh Fillet. Tắt để giữ cách chọn của v0.30.1. Các khối đã ẩn vẫn không thể chọn.",
"prefs.select_one_item": "Chọn một mục",
"prefs.select_other_hidden": "Chọn hình học ẩn bằng Chọn đối tượng khác",
"prefs.select_other_hidden_help": "Bật: bao gồm các khối, bản phác thảo ẩn và cạnh bị che gần con trỏ. Tắt: dùng danh sách ban đầu chỉ gồm hình học đang hiển thị ngay dưới con trỏ.",
@@ -2557,6 +2624,8 @@
"prefs.sketch.click_selects": "Nhấp để chọn",
"prefs.sketch.conflict_colour": "Màu xung đột",
"prefs.sketch.drawing": "Vẽ",
"prefs.sketch.ellipse_axes": "Vẽ hình elip kèm các trục",
"prefs.sketch.ellipse_axes_note": "Trục lớn và trục nhỏ dưới dạng đường gióng dựng hình, để có thể ghi kích thước bán trục và trục. Tắt: chỉ vẽ hình elip, và D trên nó cho bán kính nhỏ dù kéo giá trị đến đâu, được vẽ và ghi nhãn R như trước.",
"prefs.sketch.exit_keep": "Giữ nguyên",
"prefs.sketch.exit_previous": "Trở về góc nhìn trước đó",
"prefs.sketch.exit_unless_rotated": "Trừ khi đã xoay",
@@ -2650,6 +2719,10 @@
"prefs.theme_follow_off": "Tắt",
"prefs.theme_follow_stop": "Dừng theo dõi",
"prefs.theme_import_hint": "Nhấp để chọn tệp giao diện, thả tệp vào đây, hoặc nhấn Ctrl+V (Cmd+V trên Mac) khi đã sao chép một giao diện",
"prefs.theme_notice_dismiss": "Ẩn",
"prefs.theme_option_blur": "Làm mờ phía sau bảng",
"prefs.theme_option_blur_help": "Làm mờ phía sau các bảng bán trong suốt, tính bằng pixel vật lý. Giá trị 0 là tắt.",
"prefs.theme_option_invalid": "Giá trị đã lưu cho {option} không hợp lệ; đang dùng giá trị mặc định của giao diện. Giá trị đã lưu vẫn được giữ lại.",
"prefs.theme_option_opacity": "Độ đục",
"prefs.theme_option_opacity_help": "Điều chỉnh độ đục của các bảng nổi. Giới hạn độ đục tối thiểu giúp văn bản luôn dễ đọc trên nền màu của khung nhìn.",
"prefs.theme_option_tab_colours": "Màu thẻ",
@@ -2658,6 +2731,7 @@
"prefs.theme_option_tab_fade_ms_help": "Chuyển dần màu của bảng điều khiển và khung nhìn khi đổi thẻ. Đặt thành 0 để chuyển ngay lập tức.",
"prefs.theme_options_badge": "Tùy chọn",
"prefs.theme_options_more": "Thêm · {count}",
"prefs.theme_options_newer": "Giao diện này yêu cầu phiên bản SolidCraft mới hơn. Sau khi nâng cấp, hãy nhập lại giao diện gốc để khôi phục các định nghĩa tùy chọn.",
"prefs.theme_options_reset": "Đặt lại",
"prefs.theme_options_unavailable": "Không khả dụng trên thiết bị này",
"prefs.theme_paste": "Dán giao diện",
@@ -2721,6 +2795,12 @@
"recovery.just_now": "vừa xong",
"recovery.min_ago": "{count} phút trước",
"recovery.never_saved": "chưa từng lưu",
"recovery.open_saved": "Mở phiên bản đã lưu",
"recovery.restore": "Khôi phục",
"recovery.restore_intro": "Các thay đổi chưa được lưu khi trang này đóng lần trước được giữ trong trình duyệt này.",
"recovery.restore_row": "{name}, thay đổi {when}",
"recovery.restore_title": "Khôi phục các thiết kế chưa lưu?",
"recovery.restore_title_one": "Khôi phục thiết kế chưa lưu: {name}, thay đổi {when}?",
"refused.change": "Không thể thay đổi {body}.",
"refused.cut": "Không thể cắt {body}.",
"refused.face_curved": "Một trong các mặt cong của nó không dùng được để mô hình hóa.",
@@ -3242,9 +3322,11 @@
"render.frame_wide": "16:9 · Màn ảnh rộng",
"render.gpu_depth": "Độ sâu đường truyền tia tối đa",
"render.gpu_options_help": "Số mẫu, độ sâu, hạt giống và độ phơi sáng được lưu cùng thiết lập này. Chỉ lưu phương thức tạo ảnh đang hoạt động.",
"render.gpu_path_crashed": "Lần trước SolidCraft đã đóng khi đang thiết lập nó trên bộ điều hợp đồ họa này, nhiều khả năng là trong trình điều khiển đồ họa, nên nó sẽ không tự thiết lập lại. Hãy dùng dò tia trên CPU, hoặc thử lại (cập nhật trình điều khiển cũng sẽ thử lại).",
"render.gpu_path_failed": "Bộ dò đường truyền tia GPU không khả dụng",
"render.gpu_path_first_setup": "Lần thiết lập đầu tiên trên GPU này có thể mất vài phút; SolidCraft vẫn sử dụng được bình thường. Các lần khởi động sau sẽ tái sử dụng mã đổ bóng đã biên dịch nếu trình điều khiển lưu giữ chúng.",
"render.gpu_path_preparing": "Bộ dò đường truyền tia GPU: đang biên dịch mã đổ bóng…",
"render.gpu_path_retry": "Thử lại",
"render.gpu_seed": "Hạt giống ngẫu nhiên",
"render.gpu_unavailable": "Dò đường truyền tia GPU chưa sẵn sàng trên thiết bị này.",
"render.ground_apply": "Khớp và lưu mặt sàn",
@@ -3858,6 +3940,7 @@
"render.stage_panels.extent": "Phạm vi",
"render.stage_panels.fit_save": "Khớp và lưu",
"render.stage_panels.gpu_required": "Cần dò đường đi của tia bằng GPU",
"render.stage_panels.grain_angle": "Góc vân gỗ",
"render.stage_panels.keep": "Giữ lại",
"render.stage_panels.kept": "Được giữ lại",
"render.stage_panels.kept_fields": "Camera, lấy mẫu, kích thước, liên kết quang học, hình học CAD và đặc tính kỹ thuật.",
@@ -3976,6 +4059,19 @@
"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.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.",
"render.surface_import.embedded": "Ảnh đã nhập",
"render.surface_import.failed": "Quá trình nhập ảnh chưa hoàn tất.",
"render.surface_import.license": "Giấy phép / quyền sở hữu",
"render.surface_import.raytrace_required": "Ảnh cần dò tia trên CPU hoặc GPU. Hãy chọn trong phần Đầu ra.",
"render.surface_import.repeat_u": "Chu kỳ U",
"render.surface_import.repeat_v": "Chu kỳ V",
"render.surface_import.save": "Lưu ảnh trên khối",
"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.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.",
@@ -4052,6 +4148,8 @@
"sample.plate.name": "Tấm mẫu",
"sample.vents.about": "Các rãnh then trong mảng chữ nhật: số hàng, số cột và bước rãnh là các tham số",
"sample.vents.name": "Tấm khe thông gió",
"scale.per_axis": "Theo từng trục",
"scale.uniform": "Đồng đều",
"search.command_keys": "Enter chạy Shift+Enter chạy và giữ mở Esc đóng",
"search.commands": "Lệnh",
"search.design": "Trong thiết kế này",
@@ -4142,6 +4240,32 @@
"sketch.dof_left": "Còn {count} bậc tự do",
"sketch.dof_left_one": "Còn {count} bậc tự do",
"sketch.draw_curves": "Vẽ dưới dạng {linetype}",
"sketch.error.closed_curve_two_crossings": "a closed curve needs two crossings to trim",
"sketch.error.nothing_to_break": "nothing to break here",
"sketch.error.offset_fillet_chamfer": "`{id}` is an offset, and its corners follow its source curves: round or cut the source corners before offsetting, or break the offset association first",
"sketch.error.offset_trim": "`{id}` is an offset, and its ends follow its source curves: edit the source curves instead, or break the offset association first",
"sketch.hint.arc.center_point": "Arc: click center and start, go round either way and click the end, or type a signed sweep",
"sketch.hint.arc.three_point": "Arc: click start, end, then a point on the arc",
"sketch.hint.arc3d": "3D Arc: click start, end, then a point on the arc (points may leave the sketch plane)",
"sketch.hint.break": "Break: click a curve where it should split",
"sketch.hint.circle.center": "Circle: click the center, then a point on the circle",
"sketch.hint.constraint.generic": "Constraint: pick the sketch entities",
"sketch.hint.constraint.horizontal_vertical": "Horizontal/Vertical: pick a line, two points, or a point and a line; hold Shift to pick a line midpoint",
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to center the second on the first",
"sketch.hint.dimension": "Dimension: pick one or two entities and type the value (or click to place it first); click a dimension to edit it; Enter applies",
"sketch.hint.extend": "Extend: click a curve near the end to extend (Shift: click two curves to extend both to where they meet)",
"sketch.hint.fallback": "Click in the sketch",
"sketch.hint.line": "Line: click points; right-click or Esc to finish",
"sketch.hint.line3d": "3D Line: click world points; right-click or Esc to finish (points may leave the sketch plane)",
"sketch.hint.open_done": "Open done",
"sketch.hint.point3d": "3D Point: click where a 3D point goes",
"sketch.hint.rectangle.center": "Center rectangle: click the center, then a corner",
"sketch.hint.rectangle.rounded": "Rounded rectangle: click two opposite corners (Tab to the radius box to type the corner radius)",
"sketch.hint.rectangle.three_point": "3-point rectangle: click two points of one edge, then the width",
"sketch.hint.rectangle.two_point": "Rectangle: click two opposite corners",
"sketch.hint.slot": "Slot: click both ends, then the width",
"sketch.hint.spline3d": "3D Spline: click fit points in 3D; right-click or Esc to finish",
"sketch.hint.trim": "Trim: click the piece of a curve to remove, or drag across the pieces to remove",
"sketch.make_selection": "Chuyển đổi vùng chọn sang {linetype}",
"sketch.open_ends": "Đầu hở: {count}",
"sketch.status_conflict": "ràng buộc xung đột",
@@ -4363,6 +4487,7 @@
"ui.3d_sketch": "3D Phác thảo",
"ui.activate": "Kích hoạt",
"ui.active_component": "Cụm chi tiết đang kích hoạt",
"ui.addToQuickAccess": "Thêm vào Truy cập nhanh",
"ui.add_configuration": "Thêm cấu hình",
"ui.add_parameter": "Thêm tham số",
"ui.agent_chat": "Trò chuyện với AI Agent",
@@ -4697,6 +4822,7 @@
"ui.click_selects_whole_bodies": "Nhấp chuột chọn toàn bộ thân khối",
"ui.click_then_press_key": "Nhấp chuột, sau đó nhấn phím mới",
"ui.close": "Đóng",
"ui.close_tab": "Đóng thẻ",
"ui.coil": "Lò xo xoắn",
"ui.coincident": "Trùng điểm",
"ui.collapse": "Thu gọn",
@@ -4784,6 +4910,7 @@
"ui.default_modelling_orientation": "Hướng mô hình hóa mặc định",
"ui.delete": "Xóa",
"ui.delete_select_first": "Hãy chọn trước những gì cần xóa: tính năng, khối, thành phần hoặc hình học phác thảo",
"ui.dependents_select_first": "Hãy chọn trước đối tượng để liệt kê các phần phụ thuộc của nó",
"ui.depth": "Độ sâu",
"ui.design": "Thiết kế",
"ui.design_properties.author": "Tác giả",
@@ -4913,6 +5040,11 @@
"ui.factor": "Hệ số",
"ui.fade": "Thoát ren",
"ui.fade_length": "Chiều dài thoát ren",
"ui.fair_iterations": "Số lần lặp",
"ui.fair_keep_edges": "Giữ các cạnh",
"ui.fair_strength": "Cường độ",
"ui.fair_surface_title": "LÀM MƯỢT BỀ MẶT",
"ui.fair_tolerance_default": "0,001 lần kích thước thân khối",
"ui.feature_history": "Lịch sử tính năng",
"ui.feature_options": "Tùy chọn tính năng",
"ui.features": "Các tính năng",
@@ -4941,6 +5073,10 @@
"ui.fillet_setback_least": "Lớn hơn {least}",
"ui.fillet_setback_tip": "Nơi mảng góc trơn bắt đầu: khoảng cách dọc theo mỗi cạnh tính từ góc. Mặc định là {factor} × bán kính. Giá trị phải lớn hơn khoảng lùi nhỏ nhất, tại đó các đường bo gặp nhau mà không cần lùi: bằng bán kính khi các mặt vuông góc với nhau.",
"ui.fillet_smooth_radius": "Thay đổi bán kính trơn",
"ui.fillet_turn": "Chỗ rẽ",
"ui.fillet_turn_mitre": "Vát góc",
"ui.fillet_turn_swept": "Quét vòng",
"ui.fillet_turn_tip": "Nơi hai góc lượn rẽ quanh một cạnh góc sắc nằm giữa chúng, ví dụ các góc lượn ở chân một khối lồi tại các cạnh đứng của nó.\nVát góc: các góc lượn kéo dài cho đến khi gặp nhau theo đường vát.\nQuét vòng: các góc lượn dừng ở cạnh góc và quay quanh nó, góc là một mặt xuyến tiếp xúc với cả hai. Chỉ cho các góc lượn cùng bán kính quanh một cạnh góc thẳng và lồi.",
"ui.fillet_type_choices_tip": "Bán kính không đổi: một bán kính dọc suốt cạnh.\nĐộ dài dây cung: độ rộng ngang đường bo giữ nguyên, bất kể góc gặp nhau của các mặt.\nBán kính thay đổi: một bán kính ở mỗi đầu, và thêm dọc cạnh nếu bạn thêm vào.\nLiên tục độ cong: mặt cắt không làm gãy phản chiếu tại nơi gặp các mặt phẳng (G2).\nSpline: bán kính dọc cạnh theo các bán kính điều khiển bạn đặt.",
"ui.fillet_type_curvature": "Liên tục độ cong",
"ui.fillet_type_tip": "Mặt cắt ngang qua cạnh. Liên tục độ cong: mặt cắt liên tục độ cong (G2) giữa các mặt phẳng, nên phản chiếu đi từ các mặt vào đường bo mà không bị gãy.",
@@ -4986,6 +5122,7 @@
"ui.heading_construct": "DỰNG HÌNH",
"ui.heading_create": "TẠO MỚI",
"ui.heading_create_sketch": "TẠO PHÁC THẢO",
"ui.heading_custom_view_navigator": "KHỐI ĐIỀU HƯỚNG GÓC NHÌN TÙY CHỈNH",
"ui.heading_cylinder": "HÌNH TRỤ",
"ui.heading_design": "THIẾT KẾ",
"ui.heading_draft": "ĐỘ DỐC THOÁT KHUÔN",
@@ -5093,6 +5230,7 @@
"ui.inspect": "Kiểm tra",
"ui.instances": "Số bản",
"ui.intersect": "Giao nhau",
"ui.intersection_curve_second_pick": "Đường cong giao tuyến: nhấp vào bề mặt hoặc đường cong thứ hai (nhấp chuột phải để bắt đầu lại)",
"ui.iso_4753": "ISO 4753",
"ui.isolate": "Cô lập đối tượng",
"ui.join": "Nối liền",
@@ -5121,6 +5259,7 @@
"ui.manipulator_free_alt": "Kéo tự do bộ thao tác cũng bằng Alt/Option",
"ui.manipulator_free_alt_tip": "Bật: Alt/Option hoặc Ctrl/Cmd trên các mũi tên đùn / gizmo sẽ tắt bước nhảy giá trị. Tắt: chỉ Ctrl/Cmd.",
"ui.manufacture": "Gia công chế tạo",
"ui.match_surface_title": "KHỚP BỀ MẶT",
"ui.material_review.axes": "Các trục cục bộ của vật liệu; chưa gán hướng cho thân khối.",
"ui.material_review.claims": "Nguồn gốc và giấy phép là thông tin chưa được kiểm chứng. Các giá trị thiếu vẫn ở dạng chưa biết.",
"ui.material_review.compressive_strength": "Độ bền nén",
@@ -5143,6 +5282,9 @@
"ui.material_review.title": "Xem xét dữ liệu thuộc tính vật liệu",
"ui.material_review.unknown": "Chưa biết",
"ui.material_review.yield_strength": "Giới hạn chảy",
"ui.mcp_agents": "Tác tử qua MCP",
"ui.mcp_compact": "Đầu ra gọn",
"ui.mcp_compact_note": "Dành cho các tác tử kết nối qua máy chủ MCP (solidcraft-cli mcp): inspect_design tóm tắt các bản phác thảo và kết quả công cụ không được định dạng đẹp, nên tốn ít token hơn. Áp dụng cho các phiên tác tử bắt đầu sau khi thay đổi; --compact hoặc SOLIDCRAFT_MCP_COMPACT=1 cũng bật nó.",
"ui.measure": "Đo lường",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "3MF kèm thẻ bề mặt",
@@ -5184,6 +5326,7 @@
"ui.options": "Tùy chọn",
"ui.orbit": "Xoay quanh tâm (Orbit)",
"ui.orbit_around": "Xoay quanh",
"ui.organize": "Sắp xếp",
"ui.orientation": "Định hướng",
"ui.origin": "Gốc tọa độ",
"ui.palette_construction_mode": "Chế độ đường dựng (X)",
@@ -5228,6 +5371,7 @@
"ui.point_cloud_add_filter": "Thêm bộ lọc…",
"ui.point_cloud_auto_range": "Phạm vi tự động",
"ui.point_cloud_color_by": "Tô màu theo",
"ui.point_cloud_dots": "Điểm đơn lẻ",
"ui.point_cloud_file_colors": "Màu trong tệp",
"ui.point_cloud_filter_by": "Lọc theo",
"ui.point_cloud_height_high": "Cao nhất",
@@ -5272,6 +5416,7 @@
"ui.quality": "Chất lượng",
"ui.quantity": "Số lượng",
"ui.quantity_2": "Số lượng 2",
"ui.quick_access_full": "Thanh truy cập nhanh chứa tối đa {count} lệnh; hãy gỡ một lệnh để thêm lệnh khác",
"ui.quit": "Thoát ứng dụng",
"ui.radial_context_menu": "Menu ngữ cảnh dạng vòng (Radial)",
"ui.radius": "Bán kính",
@@ -5289,6 +5434,7 @@
"ui.relink_not_linked": "Chi tiết này chưa được liên kết với thiết kế nguồn nào.",
"ui.relink_select_one": "Hãy chọn trước một chi tiết được liên kết hoặc một thể hiện của nó.",
"ui.relink_source_all": "Liên kết lại nguồn (tất cả thể hiện)…",
"ui.removeFromQuickAccess": "Gỡ khỏi Truy cập nhanh",
"ui.removeFromToolbar": "Xóa khỏi thanh công cụ",
"ui.removeSection": "Xóa mặt cắt",
"ui.render": "Kết xuất hình ảnh",
@@ -5297,6 +5443,8 @@
"ui.repeat_nothing": "Chưa có gì để lặp lại: hãy bắt đầu một công cụ hoặc hộp thoại trước.",
"ui.replace_with_link_done": "Các bản sao hiện đã trở thành các vị trí đặt của thiết kế liên kết.",
"ui.report_a_bug": "Báo cáo lỗi",
"ui.report_characters_left_one": "Còn {count} ký tự",
"ui.report_characters_left_other": "Còn {count} ký tự",
"ui.reset": "Đặt lại",
"ui.reset_all": "Đặt lại tất cả",
"ui.reset_the_settings_on_this_page": "Đặt lại các cài đặt trên trang này",
@@ -5581,6 +5729,14 @@
"values must be numbers or expressions": "các giá trị phải là số hoặc biểu thức",
"view.auto_ortho_faces": "Phối cảnh khi xoay quỹ đạo, trực giao khi nhìn trực diện mặt",
"view.render_inspector": "Bảng thuộc tính kết xuất",
"view_gizmo.arrows": "Mũi tên xoay",
"view_gizmo.home": "Nút góc nhìn mặc định (Home)",
"view_gizmo.no_such_body": "Thiết kế này không có thân khối nào mang tên đó.",
"view_gizmo.note": "Chọn một thân khối trong khung nhìn. Thân khối được lưu cùng tùy chọn và hồ sơ cài đặt của bạn, và hiển thị trong mọi thiết kế. Nhấp vào một mặt của nó để nhìn thẳng vào mặt đó.",
"view_gizmo.texture": "Kết cấu",
"view_gizmo.themed": "Theo chủ đề",
"view_gizmo.too_detailed": "Quá chi tiết cho khối điều hướng góc nhìn (hơn 10.000 tam giác)",
"view_gizmo.too_thin": "Quá dài và mảnh cho khối điều hướng góc nhìn: cạnh mỏng nhất phải ít nhất bằng một phần năm cạnh dài nhất",
"web.files.insert_drawings_hint": "Tệp CAD, lưới đa giác, DXF hoặc SVG vào thiết kế đang mở",
"web.files.insert_stored_drawings_hint": "Vào thiết kế đang mở (tệp CAD, lưới đa giác, DXF, SVG)",
"web.open_full": "Mở trong SolidCraft",
+7 -3
View File
@@ -957,8 +957,6 @@
"cmd.inspect_accessibility": "可達性分析",
"cmd.inspect_center_of_mass": "質心",
"cmd.inspect_clearance": "間隙",
"cmd.inspect_colored_components": "啟用元件顏色",
"cmd.inspect_colored_components_disable": "停用元件顏色",
"cmd.inspect_compare": "比較實體",
"cmd.inspect_compare_picture": "與圖片比較",
"cmd.inspect_continuity": "連續性分析",
@@ -1366,11 +1364,14 @@
"dialog.computing": "正在計算… {seconds} 秒",
"dialog.could_not_be_checked": "無法檢查",
"dialog.custom_axis": "↖ 自訂軸",
"dialog.cylinder_face_tip": "圓柱體所立的模型面或頂點:先點擊此處,再在視圖中點擊它。留空時由 X、Y、Z 放置。",
"dialog.dimension_insert_hint": "點擊其他驅動尺寸以插入其名稱",
"dialog.dimension_name": "尺寸:{name}",
"dialog.edit_title": "編輯 {title}",
"dialog.no_shown_body": "沒有可變更的已顯示實體:請顯示一個,或選擇「新建實體」",
"dialog.pattern_sketch_not_object": "請選取實體或特徵:草圖兩者皆非。請用草圖自己的「陣列」或「鏡射」重複其曲線,或先將它擠出。",
"dialog.primitive_point_tip": "填入 X、Y、Z 的點:先點擊此處,再點擊模型的頂點、邊或面、草圖中的點,或原點。",
"dialog.reference_edge_tip": "所選面上的一條直邊,下方的距離從它量起:先點擊此處,再點擊該邊。",
"dialog.select": "↖ 選擇",
"dialog.selected_count": "↖ 已選擇 {count} 個",
"dialog.selection_constraint": "拘束 {id}",
@@ -1623,7 +1624,7 @@
"help.sketch.insert_svg": "將 SVG 圖形的路徑讀入草圖,並依您的設定縮放、旋轉和放置。用於把標誌或向量輪廓轉成可擠出的曲線。",
"help.sketch.inspect": "開啟目前草圖的報告:還剩多少個自由度,以及是否已完全拘束。用於在完成草圖之前找出哪些部分仍會移動。",
"help.sketch.intersect": "加入實體、面、邊或其他草圖與草圖平面相交的曲線或點。它們保持連結,並在模型變更時跟隨。",
"help.sketch.intersection_curve": "在兩個實體或面相接處建立 3D 曲線,並與它們連結。用作跟隨模型的路徑或導軌。",
"help.sketch.intersection_curve": "在兩個實體、面或其他草圖的曲線相接處建立 3D 曲線,並與它們連結。用作跟隨模型的路徑或導軌。",
"help.sketch.isoparametric_curve": "通過您選取的點,沿面的 U 或 V 方向建立 3D 曲線,並與該面連結。用作跟隨曲面的路徑或分割線。",
"help.sketch.line": "逐點畫出相連的直線,可為開放的鏈或封閉的輪廓。這是大多數輪廓的基本工具。",
"help.sketch.line.midpoint": "從中點向一端畫直線,另一端對稱映射。用於以一點為中心的直線,例如對稱的邊。",
@@ -3812,6 +3813,8 @@
"sample.plate.name": "範例板",
"sample.vents.about": "依矩形陣列排列的槽:列數、欄數和間距均為參數",
"sample.vents.name": "通風板",
"scale.per_axis": "依軸",
"scale.uniform": "等比",
"search.command_keys": "Enter 執行 Shift+Enter 執行並保持開啟 Esc 關閉",
"search.commands": "命令",
"search.design": "此設計中",
@@ -4442,6 +4445,7 @@
"ui.click_selects_whole_bodies": "點擊選擇整個實體",
"ui.click_then_press_key": "點擊,然後按新按鍵",
"ui.close": "關閉",
"ui.close_tab": "關閉分頁",
"ui.coil": "線圈",
"ui.coincident": "重合",
"ui.collapse": "摺疊",
+159 -3
View File
@@ -85,6 +85,7 @@
"Bottom Right": "右下",
"Boundary": "边界",
"Bounding box": "边界框",
"Bridge": "桥接",
"CHECK": "检查",
"COMPONENTS": "组件",
"CONFIGURATIONS": "配置",
@@ -628,6 +629,8 @@
"Suppress Selected Folders": "抑制所选文件夹",
"Suppressed": "已抑制",
"Surface": "曲面",
"Surface or face": "曲面或面",
"Surface or faces": "曲面或面",
"Surface, or faces to turn over": "曲面,或要翻转的面",
"Surfaces": "曲面",
"Swap Ends": "交换端点",
@@ -641,6 +644,7 @@
"Target": "目标",
"Target Body": "目标实体",
"Target Face (optional)": "目标面(可选)",
"Target edges": "目标边",
"Tear": "撕裂",
"Technical details": "技术详情",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "虚线草图是已保存的平面引导。选择一个新面以修复附着。",
@@ -648,6 +652,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "方向必须是轴,或位于草图平面内的直线草图曲线",
"The mirror line cannot also be mirrored": "镜像线本身不能再被镜像",
"The mirror line must be a straight curve of this sketch, not an origin axis": "镜像线必须是此草图中的直线曲线,不能是原点轴",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "原点轴垂直于草图平面,因此在该平面内没有直线",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "保存的作者实例缺失。重新绑定前请选择有效的作者放置。",
"Then": "操作",
"These features will be deleted:": "以下特征将被删除:",
@@ -739,6 +744,9 @@
"a picked edge's body is gone": "所选边所属的实体已不存在",
"add a key for a joint first": "请先为接头添加关键帧",
"ai.connected_toast": "SolidCraft 现已可在 {clients} 中使用。请重启它们(设置 > 助手)。",
"ai.http.allow": "允许不安全的 HTTP",
"ai.http.confirm": "允许此端点",
"ai.http.warning": "API 密钥和对话将以未加密的方式发送。任何能够访问该网络的人都可能读取或篡改它们。请仅在可信网络上使用。",
"ai.prefs.allow_control": "允许AI代理连接",
"ai.prefs.allow_control_note": "本地通道(MCP)。任何内容都不会离开此计算机。\n\n仅在本机开放控制通道(127.0.0.1,端口 27878 或下一个空闲端口)。连接的程序还必须出示一个每次启动时新生成、只有你的用户账户能读取的秘密令牌。下次启动 SolidCraft 时生效。关闭:除非用 --control 启动,否则不监听任何端口。",
"ai.prefs.availability": "可用性",
@@ -750,6 +758,8 @@
"ai.prefs.follow_camera": "跟随相机",
"ai.prefs.follow_camera_off": "相机自动跟随已关闭。",
"ai.prefs.follow_camera_on": "相机将转向代理创建的草图。",
"ai.prefs.instructions": "自定义指令",
"ai.prefs.instructions_note": "在每次对话开始时发送给代理,位于 SolidCraft 自身的指令之后:你喜欢的工作方式、需要遵守的规则。更改从下一次对话(连接或新对话)起生效。",
"ai.prefs.intro": "SolidCraft中的AI代理。",
"ai.prefs.make_default": "设为默认",
"ai.prefs.no_agents": "尚无代理。添加代理或刷新以查找已安装的代理。",
@@ -963,8 +973,7 @@
"cmd.inspect_analysis": "视图分析",
"cmd.inspect_center_of_mass": "质心",
"cmd.inspect_clearance": "间隙",
"cmd.inspect_colored_components": "启用组件颜色",
"cmd.inspect_colored_components_disable": "禁用组件颜色",
"cmd.inspect_colored_components": "组件颜色",
"cmd.inspect_compare": "比较实体",
"cmd.inspect_compare_picture": "比较参考图",
"cmd.inspect_continuity": "连续性分析",
@@ -1081,6 +1090,9 @@
"cmd.render.compare.divider": "移动渲染比较分隔线",
"cmd.render.result.discard": "丢弃渲染结果",
"cmd.render.result.select": "选择渲染结果",
"cmd.render.surface.import": "导入表面贴图",
"cmd.render.texture.list": "列出嵌入的表面图像",
"cmd.render.texture.remove": "移除嵌入的表面图像",
"cmd.render_aging_describe": "分层表面老化",
"cmd.render_backends": "渲染后端",
"cmd.render_batch": "渲染所有镜头",
@@ -1296,6 +1308,7 @@
"cmd.timeline_stop": "停止历史播放",
"cmd.timeline_suppress": "抑制特征",
"cmd.ui.assistant": "切换助手显示",
"cmd.view.navigator": "自定义视图导航器",
"cmd.view.render_browser": "渲染浏览器",
"cmd.view.render_dock": "移动渲染停靠面板",
"cmd.view.render_focus": "渲染专注视图",
@@ -1323,6 +1336,7 @@
"cmd.view_previous_tab": "上一个标签页",
"cmd.view_rename": "重命名命名视图",
"cmd.view_render_frame": "渲染构图辅助线",
"cmd.view_ribbon_tabs_only": "仅功能区选项卡",
"cmd.view_right": "右视图",
"cmd.view_save": "新建命名视图",
"cmd.view_shots_strip": "镜头条",
@@ -1394,15 +1408,22 @@
"dialog.computing": "正在计算… {seconds} 秒",
"dialog.could_not_be_checked": "无法检查",
"dialog.custom_axis": "↖ 自定义轴",
"dialog.cylinder_face_tip": "圆柱体所立的模型面或顶点:先单击此处,再在视图中单击它。留空时由 X、Y、Z 放置。",
"dialog.dimension_insert_hint": "单击其他驱动尺寸以插入其名称",
"dialog.dimension_name": "尺寸:{name}",
"dialog.dimension_step_down": "减少 {step}",
"dialog.dimension_step_expression": "只有数字可以增减;表达式保持不变",
"dialog.dimension_step_up": "增加 {step}",
"dialog.edit_title": "编辑 {title}",
"dialog.no_shown_body": "没有可更改的显示实体:请显示一个实体,或选择新建实体",
"dialog.pattern_sketch_not_object": "请选择实体或特征:草图两者都不是。请用草图自身的阵列或镜像重复其曲线,或先将其拉伸。",
"dialog.primitive_point_tip": "填入 X、Y、Z 的点:先单击此处,再单击模型的顶点、边或面、草图中的点,或原点。",
"dialog.reference_edge_tip": "所选面上的一条直边,下方的距离从它量起:先单击此处,再单击该边。",
"dialog.select": "↖ 选择",
"dialog.selected_count": "↖ 已选择 {count} 个",
"dialog.selection_constraint": "约束 {id}",
"dialog.selection_external_curve": "{sketch} · 曲线 {id}",
"dialog.selection_external_point": "{point} · {sketch}",
"dialog.selection_guidance": "选择输入项进行修改。点击已选对象可取消选择;按住 Ctrl/Cmd 点击也可切换选择。检查预览后,再按“确定”。",
"dialog.selection_midpoint": "中点 · {id}",
"dialog.selection_profile": "{sketch} · 轮廓 {number}",
@@ -1420,6 +1441,7 @@
"document.read_only_save": "此设计以只读方式打开:请另存为新文件",
"document.save_changes": "保存对 {name} 的更改?",
"document.save_copy": "保存副本…",
"document.tab_unsaved_changes": "未保存的更改",
"document.unsaved_changes": "{name} 有未保存的更改。如果不保存,将丢失这些更改。",
"drive.rigid": "{joint} 是刚性接头:它把零件固定在原位,因此没有可驱动的运动。要让零件运动,请在“编辑接头”中更改其类型:旋转用 Revolute,滑动用 Slider,两者兼有用 Cylindrical。",
"drive.stopped_by_contact": "停在 {value}:{a} 会穿过 {b}",
@@ -1440,10 +1462,16 @@
"each rail's curves must be in one sketch": "每条导轨的曲线必须位于同一个草图中",
"feature computation ended without a result": "特征计算结束时没有返回结果",
"file.open.drawing_filter": "设计、CAD 文件、网格和图纸",
"file.open.portal_fallback.no_portal_service": "打开的文件选择器并非桌面自带:桌面的文件选择服务不可用。",
"file.open.portal_fallback.no_session_bus": "打开的文件选择器并非桌面自带:没有 D-Bus 会话总线。",
"file.open.portal_fallback.xwayland": "打开的文件选择器并非桌面自带:在 XWayland 下,门户会拒绝父窗口。",
"file_load.built": "已构建:{built}/{total} 个特征。",
"file_load.converting": "正在转换实体 {done}/{total}",
"file_load.inserted": "已插入 {name}",
"file_load.inserting": "正在插入几何体",
"file_load.link_item": "{name} ({done}/{total})",
"file_load.opened": "已打开 {name}",
"file_load.opened_in_tab": "已在新标签页中打开 {name}",
"file_load.opening": "正在打开设计",
"file_load.preparing_thumbnail": "正在准备缩略图...",
"file_load.preparing_view": "正在准备视图...",
@@ -1679,7 +1707,7 @@
"help.sketch.insert_svg": "将 SVG 图形中的路径读入草图,并按设定进行缩放、旋转和放置。用于把标志或矢量轮廓变成可拉伸的曲线。",
"help.sketch.inspect": "打开当前草图的报告:显示剩余的自由度以及是否已完全约束。在完成草图之前,用它找出仍然可以移动的部分。",
"help.sketch.intersect": "添加实体、面、边或其他草图与草图平面相交处的曲线或点。它们保持关联,并随模型变化。",
"help.sketch.intersection_curve": "在两个实体或面相交处创建与它们关联的三维曲线。用作跟随模型的路径或导轨。",
"help.sketch.intersection_curve": "在两个实体、面或其他草图的曲线相交处创建与它们关联的三维曲线。用作跟随模型的路径或导轨。",
"help.sketch.isoparametric_curve": "通过你选取的点,沿面的 U 或 V 方向创建与该面关联的三维曲线。用作跟随曲面的路径或分割线。",
"help.sketch.line": "逐点绘制相连的直线,可以是开放链或封闭轮廓。这是绘制大多数轮廓的基本工具。",
"help.sketch.line.midpoint": "从中点向一端绘制直线,另一端随之对称生成。用于以某点为中心的直线,例如对称的边。",
@@ -1785,6 +1813,7 @@
"help.view.navigate": "按步骤旋转、平移或缩放视图,例如向左转四分之一圈。用于精确移动相机,而不是靠拖动。",
"help.view.navigation_bar": "显示或隐藏视口底部的导航栏。隐藏可腾出空间,显示便于用鼠标使用视图工具。",
"help.view.rename": "为已保存的命名视图改名。视图本身保持不变。",
"help.view.ribbon_tabs_only": "将功能区折叠为选项卡行,让视图得到面板原先占用的空间。单击选项卡可在视图上方显示其面板;再次选择此命令则始终显示完整的功能区。",
"help.view.save": "将当前相机位置作为命名视图保存到设计中。用于以后回到同一角度,例如截图。",
"help.view.spin": "让视图持续绕模型环绕,直到下一次点击或按键。用于从各个方向展示模型。",
"help.view.tool": "锁定一个相机工具(环绕、平移或缩放),之后用鼠标左键拖动即可移动视图,直到关闭。用于触控板或没有鼠标中键时。",
@@ -1999,6 +2028,7 @@
"material.selection.mixed": "不同材料",
"measure.at": "位置",
"measure.item": "项目 {number}",
"measure.line_to_centre": "线到圆心",
"measure.quick_copied": "已复制测量值",
"measure.quick_copy": "点击复制测量值",
"measure.quick_edges": "{count} 条边",
@@ -2029,6 +2059,8 @@
"nav.blender.help": "中键拖动旋转,Shift+中键拖动平移,Ctrl+中键拖动缩放;右键拖动不会移动相机",
"nav.creo.help": "中键拖动旋转,Shift+中键平移,Ctrl+中键上下拖动缩放(向下放大);滚动滚轮也可缩放",
"nav.creo.label": "Creo",
"nav.drag_keeps_mode.help": "中键或右键拖动会保持开始时的旋转、平移或缩放,直到松开按键,期间按下或松开任何键都不会改变。关闭后:拖动的每一刻都由当时按住的键决定。",
"nav.drag_keeps_mode.label": "拖动保持开始时的导航方式",
"nav.free_orbit.help": "鼠标和3D鼠标可保持滚转并越过两个极点旋转。Shift轴吸附将被禁用。",
"nav.free_orbit.label": "自由旋转",
"nav.free_orbit.snap_help": "鼠标和3D鼠标可越过两极旋转,并保留画面旋转角度。在拖动环绕时按住Shift,可吸附视图方向和画面旋转角度。",
@@ -2265,6 +2297,17 @@
"prefs.auto_operation_note": "根据材料接触的对象选择合并、新建实体或切割。",
"prefs.axes": "坐标轴",
"prefs.axis": "轴",
"prefs.background_choose": "选择图片…",
"prefs.background_image": "图片",
"prefs.background_unreadable": "无法读取背景图片",
"prefs.blur_cost": "相对渲染开销。条形越多,通常意味着处理工作越多。",
"prefs.blur_device": "此图形设备或渲染路径不支持此功能",
"prefs.blur_guard": "已降低模糊质量,以保持画面响应。模糊半径未改变。",
"prefs.blur_off": "关闭",
"prefs.blur_reduce_transparency": "已被系统的降低透明度设置禁用",
"prefs.blur_software": "软件图形渲染不支持此功能 (gpu-software)",
"prefs.blur_source": "此工作区视图不支持此功能",
"prefs.blur_web": "此网页渲染路径不支持此功能",
"prefs.bodies_folders": "实体文件夹",
"prefs.bodies_folders_always": "始终显示",
"prefs.bodies_folders_never": "不显示",
@@ -2278,6 +2321,8 @@
"prefs.browser_eye_left": "折叠箭头旁的眼睛图标",
"prefs.browser_eye_left_note": "浏览器各行的显示切换像以前一样位于折叠箭头旁,而不是在行右端的一列中。",
"prefs.browser_reveal_on_hover": "悬停时显示",
"prefs.browser_rows_start_closed": "浏览器中的行默认折叠",
"prefs.browser_rows_start_closed_note": "浏览器中的放置和文件夹默认折叠,因此包含大量组件的设计只显示顶层行。每一行可通过其箭头、“全部展开”或“在浏览器中查找”打开;“在浏览器中查找”只展开通往该实体的行,并重新折叠上次查找展开的行。在 3D 视图中选取实体不会改变浏览器。关闭时文件夹默认展开,放置按“组件初始展开”的设置显示,在 3D 视图中选取会使浏览器展开到该实体,与以前相同。",
"prefs.browser_show_note": "“显示”让浏览器始终留在屏幕上。“悬停时显示”会将其隐藏,直到指针移到它的位置或其左边缘的细标签上;指针在其中或其菜单打开时,它保持打开。",
"prefs.browser_shown": "显示",
"prefs.built_in": "内置",
@@ -2288,6 +2333,7 @@
"prefs.carousel_previous": "上一个",
"prefs.choose_font": "选择字体文件…",
"prefs.click": "单击",
"prefs.command_dialog_values": "命令对话框",
"prefs.contact_shading": "柔和接触阴影",
"prefs.contact_shading_note": "在零件相近处添加柔和阴影。",
"prefs.context_menu": "上下文菜单",
@@ -2323,6 +2369,13 @@
"prefs.dialogs_edge": "视图边缘",
"prefs.dialogs_left_column": "左侧栏",
"prefs.dialogs_note": "命令对话框的打开位置:停靠在视图边缘,或在左侧栏中替代浏览器和历史;关闭对话框后,浏览器和历史恢复原样。",
"prefs.dimension_step_angle": "窗口的箭头、上下方向键和滚轮给角度增减的量",
"prefs.dimension_step_length": "窗口的箭头、上下方向键和滚轮给长度增减的量",
"prefs.dimension_steps": "窗口步长",
"prefs.dimension_values": "尺寸值",
"prefs.dimension_values_note": "输入尺寸值的位置。窗口带有可增减数值的箭头;尺寸工具会先放置文字。",
"prefs.dimension_values_view": "在视图中",
"prefs.dimension_values_window": "在窗口中",
"prefs.display": "显示",
"prefs.edges": "边缘与着色",
"prefs.extend": "扩展",
@@ -2376,6 +2429,8 @@
"prefs.graphics_page.surfaces_edges": "曲面与边",
"prefs.graphics_page.view_bar": "也可在视图栏中设置",
"prefs.graphics_presets_note": "预设会同时设置以下选项。",
"prefs.ground_shadow_follows_model": "地面阴影跟随模型",
"prefs.ground_shadow_follows_model_help": "将阴影放在可见实体的最低点下方。关闭时,抬高模型的阴影仍位于经过原点的地面上。",
"prefs.hide_constraint_glyphs_zoomed_out": "缩小时隐藏约束符号",
"prefs.hide_constraint_glyphs_zoomed_out_note": "草图缩小到一定程度时隐藏约束符号,以免遮挡草图。关闭:任意缩放比例下符号仍保持固定屏幕大小。",
"prefs.history": "历史",
@@ -2402,6 +2457,10 @@
"prefs.invert_curved_roll_arrows_note": "立方体角落的两个弯曲箭头在屏幕平面内翻滚视图。开启:顺时针箭头逆时针翻滚,逆时针箭头顺时针翻滚。关闭:每个箭头按其弯曲方向翻滚视图。",
"prefs.invert_navigator_arrows": "反转视图立方体的旋转箭头",
"prefs.invert_navigator_arrows_note": "立方体周围的箭头按箭头所指的方向转动相机,与 Fusion 和 Inventor 相同:左侧箭头指向右,相机向右转动。关闭:箭头把立方体转向它所在的那一面,因此左侧箭头使相机向左转动。弯曲的翻滚箭头不受影响。",
"prefs.keep_unsaved_also_asks": "关闭标签页时仍然询问",
"prefs.keep_unsaved_also_asks_tip": "关闭此项后,只要每个未保存设计的副本都是最新的,标签页就会直接关闭,不显示浏览器的提示框。",
"prefs.keep_unsaved_in_browser": "在此浏览器中保留未保存的工作",
"prefs.keep_unsaved_in_browser_tip": "每个打开的设计的未保存更改都会在此浏览器的私有存储中保持最新。下次访问时会提示恢复。",
"prefs.keep_work_on_tab_switch": "切换设计时保持对话框和工具打开",
"prefs.keep_work_on_tab_switch_note": "每个设计标签页保留其打开的对话框或工具(包括其中已选择和输入的内容)以及工具栏选项卡。回到该设计时它们仍在原处。关闭时:切换设计会取消它们并显示“模型”选项卡。",
"prefs.labels": "标签",
@@ -2413,6 +2472,8 @@
"prefs.layout_page.card": "卡片",
"prefs.layout_page.column": "列",
"prefs.layout_page.hover": "悬停",
"prefs.layout_page.hover_panel_menus": "悬停切换面板菜单",
"prefs.layout_page.hover_panel_menus_help": "点击功能区的面板名称打开菜单。将指针悬停在另一面板名称上即可切换菜单。将指针移到功能区和已打开菜单之外即可关闭菜单。",
"prefs.layout_page.icons_labels": "图标+标签",
"prefs.layout_page.ok_cancel": "确定和取消",
"prefs.layout_page.on_hover": "悬停时",
@@ -2515,6 +2576,8 @@
"prefs.quality_smoother": "更平滑",
"prefs.quick_measure": "在视图角落显示快速测量",
"prefs.quick_measure_note": "在视图右下角、历史记录上方显示所选对象的尺寸:边的长度(圆形边显示 Ø 和 R)、面的面积、点的位置、两个对象之间的距离和角度、多条边或多个面的总和。点击即可复制。",
"prefs.remember_dialog_values": "记住命令对话框的值",
"prefs.remember_dialog_values.help": "默认关闭。开启后,命令成功执行后,在关闭 SolidCraft 前,下次打开同一命令时会重用其安全的值和选项。",
"prefs.render_previous_geometry_budget": "使用先前的渲染预览几何内存预算",
"prefs.render_previous_geometry_budget_help": "恢复先前的固定几何内存上限(64位桌面为512 MiB,其他平台为128 MiB),替代基于设备的预算。",
"prefs.reports_saved": "已保存 {n} 份",
@@ -2523,6 +2586,8 @@
"prefs.ribbon_click_ends_tool_note": "单击正在使用的草图工具的高亮功能区按钮会结束该工具,在视图中单击即可再次选择。关闭:单击会重新启动该工具。",
"prefs.ribbon_reveal": "在功能区中显示搜索结果",
"prefs.ribbon_reveal_note": "将指针悬停在“搜索命令”的结果上时,会显示该命令在功能区中的位置:其选项卡、按钮或面板下拉菜单中的行。从搜索启动的命令会在那里闪烁,从下拉菜单运行的命令会标记其面板。",
"prefs.ribbon_tabs_only": "仅显示功能区的选项卡",
"prefs.ribbon_tabs_only_note": "功能区折叠为选项卡行。单击选项卡会在视图上方显示其面板,直到命令开始或指针离开面板。“视图”选项卡中的“仅功能区选项卡”或其快捷键可切换此项。",
"prefs.right_click": "右键单击",
"prefs.saving": "启动和保存",
"prefs.scale_note": "也可在窗口中使用 Ctrl + / Ctrl −。100% 时按钮高度为 28 像素。",
@@ -2533,6 +2598,8 @@
"prefs.section_hatch_by_part": "按零件",
"prefs.section_hatch_one": "单一样式",
"prefs.select": "选择",
"prefs.select_hidden_edges": "在带隐藏边线的着色视图中选择隐藏边",
"prefs.select_hidden_edges_help": "在带隐藏边线的着色视图中,可穿过面选择边并用于圆角。关闭时保留v0.30.1的选择行为。隐藏的实体仍不可选择。",
"prefs.select_one_item": "选择一个项目",
"prefs.select_other_hidden": "使用“选择其他”选择隐藏几何体",
"prefs.select_other_hidden_help": "开启:包括指针附近的隐藏实体、草图和被遮挡的边。关闭:恢复原来的列表,仅包含指针正下方的可见几何体。",
@@ -2557,6 +2624,8 @@
"prefs.sketch.click_selects": "单击选择",
"prefs.sketch.conflict_colour": "冲突颜色",
"prefs.sketch.drawing": "绘制",
"prefs.sketch.ellipse_axes": "绘制椭圆时带上轴线",
"prefs.sketch.ellipse_axes_note": "将长轴和短轴绘制为构造线,以便标注半轴和轴。关闭:仅绘制椭圆,对其按 D 得到短半径,无论将数值拖到何处,都像以前一样绘制并标注 R。",
"prefs.sketch.exit_keep": "保持",
"prefs.sketch.exit_previous": "返回之前的视图",
"prefs.sketch.exit_unless_rotated": "除非已旋转",
@@ -2650,6 +2719,10 @@
"prefs.theme_follow_off": "关闭",
"prefs.theme_follow_stop": "停止跟随",
"prefs.theme_import_hint": "单击选择主题文件,拖放到此处,或复制主题后按 Ctrl+V(Mac 上为 Cmd+V)",
"prefs.theme_notice_dismiss": "忽略",
"prefs.theme_option_blur": "面板背后模糊",
"prefs.theme_option_blur_help": "半透明面板背后的模糊程度,以物理像素为单位。0 表示关闭。",
"prefs.theme_option_invalid": "{option} 的已保存值无效;正在使用主题默认值。已保存值将予以保留。",
"prefs.theme_option_opacity": "不透明度",
"prefs.theme_option_opacity_help": "调整浮动面板的不透明度。最低不透明度限制可确保文字在视口颜色上清晰可读。",
"prefs.theme_option_tab_colours": "选项卡颜色",
@@ -2658,6 +2731,7 @@
"prefs.theme_option_tab_fade_ms_help": "切换选项卡时渐变面板和视口颜色。设为零可立即切换。",
"prefs.theme_options_badge": "选项",
"prefs.theme_options_more": "更多 · {count}",
"prefs.theme_options_newer": "此主题需要更新版本的 SolidCraft。升级后,请重新导入原始主题以恢复其选项定义。",
"prefs.theme_options_reset": "重置",
"prefs.theme_options_unavailable": "此设备不支持",
"prefs.theme_paste": "粘贴主题",
@@ -2721,6 +2795,12 @@
"recovery.just_now": "刚刚",
"recovery.min_ago": "{count} 分钟前",
"recovery.never_saved": "从未保存",
"recovery.open_saved": "打开已保存的版本",
"recovery.restore": "恢复",
"recovery.restore_intro": "此页面上次关闭时未保存的更改保留在此浏览器中。",
"recovery.restore_row": "{name},更改于{when}",
"recovery.restore_title": "恢复未保存的设计?",
"recovery.restore_title_one": "恢复未保存的设计:{name},更改于{when}?",
"refused.change": "无法更改 {body}。",
"refused.cut": "无法切割 {body}。",
"refused.face_curved": "它的一个曲面无法用于建模。",
@@ -3242,9 +3322,11 @@
"render.frame_wide": "16:9 · 宽屏",
"render.gpu_depth": "最大路径深度",
"render.gpu_options_help": "采样数、深度、种子和曝光会随此配置保存。仅保存当前图像方法。",
"render.gpu_path_crashed": "上次在此图形适配器上设置时 SolidCraft 关闭了,很可能是在图形驱动程序内部,因此不会再自动设置。请改用 CPU 光线追踪,或重试(更新驱动程序后也会重试)。",
"render.gpu_path_failed": "GPU 路径追踪器不可用",
"render.gpu_path_first_setup": "首次在此 GPU 上设置可能需要几分钟,期间 SolidCraft 仍可正常使用。之后启动时,如果驱动程序保留了已编译的着色器,将直接复用。",
"render.gpu_path_preparing": "GPU 路径追踪器:正在编译着色器…",
"render.gpu_path_retry": "重试",
"render.gpu_seed": "随机种子",
"render.gpu_unavailable": "此设备上的 GPU 路径追踪尚未就绪。",
"render.ground_apply": "适配并保存地面",
@@ -3858,6 +3940,7 @@
"render.stage_panels.extent": "范围",
"render.stage_panels.fit_save": "适配并保存",
"render.stage_panels.gpu_required": "需要 GPU 路径追踪",
"render.stage_panels.grain_angle": "木纹角度",
"render.stage_panels.keep": "保留",
"render.stage_panels.kept": "保留",
"render.stage_panels.kept_fields": "相机、采样、图像尺寸、光学绑定、CAD 几何体和工程属性。",
@@ -3976,6 +4059,19 @@
"render.subsurface_enable": "内部散射随机游走",
"render.subsurface_help": "仅适用于封闭介质。散射和RGB吸收使用独立的每毫米系数。路径在实际几何内部散射,并在边界折射或反射。关闭直线透射阴影、表面降噪和外部体积;使用一个菲涅耳分层。这不是扩散着色器、密度网格或实测材料。高密度可能需要更多样本和路径深度。",
"render.subsurface_rate": "散射碰撞率",
"render.surface_import.author": "作者",
"render.surface_import.busy": "正在导入图像…",
"render.surface_import.complete": "请选择图像并填写作者、来源和许可。",
"render.surface_import.embedded": "导入的图像",
"render.surface_import.failed": "图像导入未完成。",
"render.surface_import.license": "许可 / 所有权",
"render.surface_import.raytrace_required": "图像需要 CPU 或 GPU 光线追踪。请在输出中选择。",
"render.surface_import.repeat_u": "U 方向重复",
"render.surface_import.repeat_v": "V 方向重复",
"render.surface_import.save": "将图像保存到实体",
"render.surface_import.saved_cpu": "保存贴图以用于 CPU 渲染。",
"render.surface_import.source": "来源",
"render.surface_import.title": "导入图像",
"render.tone_aces_fitted": "ACES 拟合",
"render.tone_reinhard": "Reinhard",
"render.transmission_scope": "GPU 玻璃支持经过验证且互不相交的介质;不支持任意嵌套介质,也不提供无噪声透射估计器。",
@@ -4052,6 +4148,8 @@
"sample.plate.name": "示例板",
"sample.vents.about": "按矩形阵列排列的槽:行数、列数和间距均为参数",
"sample.vents.name": "通风板",
"scale.per_axis": "按轴",
"scale.uniform": "等比",
"search.command_keys": "Enter 运行 Shift+Enter 运行并保持打开 Esc 关闭",
"search.commands": "命令",
"search.design": "此设计中",
@@ -4142,6 +4240,32 @@
"sketch.dof_left": "剩余 {count} 个自由度",
"sketch.dof_left_one": "剩余 {count} 个自由度",
"sketch.draw_curves": "绘制为 {linetype}",
"sketch.error.closed_curve_two_crossings": "a closed curve needs two crossings to trim",
"sketch.error.nothing_to_break": "nothing to break here",
"sketch.error.offset_fillet_chamfer": "`{id}` is an offset, and its corners follow its source curves: round or cut the source corners before offsetting, or break the offset association first",
"sketch.error.offset_trim": "`{id}` is an offset, and its ends follow its source curves: edit the source curves instead, or break the offset association first",
"sketch.hint.arc.center_point": "Arc: click center and start, go round either way and click the end, or type a signed sweep",
"sketch.hint.arc.three_point": "Arc: click start, end, then a point on the arc",
"sketch.hint.arc3d": "3D Arc: click start, end, then a point on the arc (points may leave the sketch plane)",
"sketch.hint.break": "Break: click a curve where it should split",
"sketch.hint.circle.center": "Circle: click the center, then a point on the circle",
"sketch.hint.constraint.generic": "Constraint: pick the sketch entities",
"sketch.hint.constraint.horizontal_vertical": "Horizontal/Vertical: pick a line, two points, or a point and a line; hold Shift to pick a line midpoint",
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to center the second on the first",
"sketch.hint.dimension": "Dimension: pick one or two entities and type the value (or click to place it first); click a dimension to edit it; Enter applies",
"sketch.hint.extend": "Extend: click a curve near the end to extend (Shift: click two curves to extend both to where they meet)",
"sketch.hint.fallback": "Click in the sketch",
"sketch.hint.line": "Line: click points; right-click or Esc to finish",
"sketch.hint.line3d": "3D Line: click world points; right-click or Esc to finish (points may leave the sketch plane)",
"sketch.hint.open_done": "Open done",
"sketch.hint.point3d": "3D Point: click where a 3D point goes",
"sketch.hint.rectangle.center": "Center rectangle: click the center, then a corner",
"sketch.hint.rectangle.rounded": "Rounded rectangle: click two opposite corners (Tab to the radius box to type the corner radius)",
"sketch.hint.rectangle.three_point": "3-point rectangle: click two points of one edge, then the width",
"sketch.hint.rectangle.two_point": "Rectangle: click two opposite corners",
"sketch.hint.slot": "Slot: click both ends, then the width",
"sketch.hint.spline3d": "3D Spline: click fit points in 3D; right-click or Esc to finish",
"sketch.hint.trim": "Trim: click the piece of a curve to remove, or drag across the pieces to remove",
"sketch.make_selection": "将选择转换为 {linetype}",
"sketch.open_ends": "开放 {count}",
"sketch.status_conflict": "约束冲突",
@@ -4363,6 +4487,7 @@
"ui.3d_sketch": "3D 草图",
"ui.activate": "激活",
"ui.active_component": "活动组件",
"ui.addToQuickAccess": "添加到快速访问",
"ui.add_configuration": "添加配置",
"ui.add_parameter": "添加参数",
"ui.agent_chat": "智能体聊天",
@@ -4697,6 +4822,7 @@
"ui.click_selects_whole_bodies": "单击选择整个实体",
"ui.click_then_press_key": "单击,然后按新按键",
"ui.close": "关闭",
"ui.close_tab": "关闭标签页",
"ui.coil": "螺旋",
"ui.coincident": "重合",
"ui.collapse": "折叠",
@@ -4784,6 +4910,7 @@
"ui.default_modelling_orientation": "默认建模方向",
"ui.delete": "删除",
"ui.delete_select_first": "请先选择要删除的内容:特征、实体、组件或草图几何",
"ui.dependents_select_first": "请先选择要列出其依赖项的对象",
"ui.depth": "深度",
"ui.design": "设计",
"ui.design_properties.author": "作者",
@@ -4913,6 +5040,11 @@
"ui.factor": "系数",
"ui.fade": "收尾",
"ui.fade_length": "收尾长度",
"ui.fair_iterations": "迭代次数",
"ui.fair_keep_edges": "保持边",
"ui.fair_strength": "强度",
"ui.fair_surface_title": "曲面光顺",
"ui.fair_tolerance_default": "实体尺寸的 0.001 倍",
"ui.feature_history": "特征历史",
"ui.feature_options": "特征选项",
"ui.features": "特征",
@@ -4941,6 +5073,10 @@
"ui.fillet_setback_least": "须大于 {least}",
"ui.fillet_setback_tip": "平滑角点曲面片开始的位置:从角点沿每条边的距离。默认值为半径的 {factor} 倍。必须大于最小后退距离(不后退时圆角相交之处;面相互垂直时即半径)。",
"ui.fillet_smooth_radius": "半径平滑变化",
"ui.fillet_turn": "转角",
"ui.fillet_turn_mitre": "斜接",
"ui.fillet_turn_swept": "旋转",
"ui.fillet_turn_tip": "两个圆角绕过它们之间尖锐的角边处,例如凸台根部的圆角绕过其竖直边。\n斜接:圆角继续延伸,直到以斜接相遇。\n旋转:圆角止于角边并绕它旋转,转角是与两者相切的圆环面。仅用于同一半径的圆角绕过笔直的凸角边时。",
"ui.fillet_type_choices_tip": "恒定半径:整条边一个半径。\n弦长:无论面以什么角度相交,圆角宽度都相同。\n可变半径:两端各一个半径,添加后沿边还有更多。\n曲率连续:与平面相接处反射没有折痕的截面 (G2)。\n样条:沿边的半径跟随您设置的控制半径。",
"ui.fillet_type_curvature": "曲率连续",
"ui.fillet_type_tip": "横跨边的截面。曲率连续:平面之间的曲率连续 (G2) 截面,反射从面平滑过渡到圆角,没有折痕。",
@@ -4986,6 +5122,7 @@
"ui.heading_construct": "构造",
"ui.heading_create": "创建",
"ui.heading_create_sketch": "创建草图",
"ui.heading_custom_view_navigator": "自定义视图导航器",
"ui.heading_cylinder": "圆柱体",
"ui.heading_design": "设计",
"ui.heading_draft": "拔模",
@@ -5093,6 +5230,7 @@
"ui.inspect": "检查",
"ui.instances": "实例数",
"ui.intersect": "相交",
"ui.intersection_curve_second_pick": "相交曲线:单击第二个面或曲线(右键单击重新开始)",
"ui.iso_4753": "ISO 4753",
"ui.isolate": "隔离",
"ui.join": "连接",
@@ -5121,6 +5259,7 @@
"ui.manipulator_free_alt": "操纵器自由拖动也适用于 Alt/Option",
"ui.manipulator_free_alt_tip": "开:在拉伸/小控件箭头上按 Alt/Option 或 Ctrl/Cmd 可关闭数值捕捉。关:仅 Ctrl/Cmd。",
"ui.manufacture": "制造",
"ui.match_surface_title": "曲面匹配",
"ui.material_review.axes": "材料局部坐标轴;不指定实体方向。",
"ui.material_review.claims": "来源和许可证声明未经核实。缺失值保持未知。",
"ui.material_review.compressive_strength": "抗压强度",
@@ -5143,6 +5282,9 @@
"ui.material_review.title": "检查物理材料数据",
"ui.material_review.unknown": "未知",
"ui.material_review.yield_strength": "屈服强度",
"ui.mcp_agents": "通过 MCP 连接的代理",
"ui.mcp_compact": "紧凑输出",
"ui.mcp_compact_note": "适用于通过 MCP 服务器 (solidcraft-cli mcp) 连接的代理:inspect_design 汇总草图,工具结果不做美化排版,从而占用更少的 token。更改后新开始的代理会话生效;--compact 或 SOLIDCRAFT_MCP_COMPACT=1 也会开启它。",
"ui.measure": "测量",
"ui.mesh_format_3mf": "3MF",
"ui.mesh_format_3mf_tagged": "带面标签的 3MF",
@@ -5184,6 +5326,7 @@
"ui.options": "选项",
"ui.orbit": "环绕",
"ui.orbit_around": "环绕",
"ui.organize": "整理",
"ui.orientation": "定向",
"ui.origin": "原点",
"ui.palette_construction_mode": "构造模式 (X)",
@@ -5228,6 +5371,7 @@
"ui.point_cloud_add_filter": "添加筛选…",
"ui.point_cloud_auto_range": "自动范围",
"ui.point_cloud_color_by": "着色方式",
"ui.point_cloud_dots": "独立点",
"ui.point_cloud_file_colors": "文件颜色",
"ui.point_cloud_filter_by": "筛选依据",
"ui.point_cloud_height_high": "最高高度",
@@ -5272,6 +5416,7 @@
"ui.quality": "质量",
"ui.quantity": "数量",
"ui.quantity_2": "数量 2",
"ui.quick_access_full": "快速访问栏最多容纳 {count} 个命令;请先移除一个再添加",
"ui.quit": "退出",
"ui.radial_context_menu": "环形上下文菜单",
"ui.radius": "半径",
@@ -5289,6 +5434,7 @@
"ui.relink_not_linked": "此组件未链接到源设计。",
"ui.relink_select_one": "请先选择一个链接组件或其实例。",
"ui.relink_source_all": "重新链接源文件(所有实例)…",
"ui.removeFromQuickAccess": "从快速访问中移除",
"ui.removeFromToolbar": "从工具栏移除",
"ui.removeSection": "移除剖面",
"ui.render": "渲染",
@@ -5297,6 +5443,8 @@
"ui.repeat_nothing": "还没有可重复的操作:请先启动一个工具或对话框。",
"ui.replace_with_link_done": "这些副本现在是链接设计的放置。",
"ui.report_a_bug": "报告缺陷",
"ui.report_characters_left_one": "剩余 {count} 个字符",
"ui.report_characters_left_other": "剩余 {count} 个字符",
"ui.reset": "重置",
"ui.reset_all": "全部重置",
"ui.reset_the_settings_on_this_page": "重置此页面上的设置",
@@ -5581,6 +5729,14 @@
"values must be numbers or expressions": "值必须是数字或表达式",
"view.auto_ortho_faces": "旋转时透视,正对表面时正交",
"view.render_inspector": "渲染检查器",
"view_gizmo.arrows": "旋转箭头",
"view_gizmo.home": "主页按钮",
"view_gizmo.no_such_body": "此设计中没有该名称的实体。",
"view_gizmo.note": "在视图中选择一个实体。它随首选项和设置配置文件保存,并在每个设计中显示。单击它的一个面即可正视该面。",
"view_gizmo.texture": "纹理",
"view_gizmo.themed": "主题",
"view_gizmo.too_detailed": "对视图导航器而言过于精细(超过 10,000 个三角形)",
"view_gizmo.too_thin": "对视图导航器而言过于细长:最薄的一边至少要有最长一边的五分之一",
"web.files.insert_drawings_hint": "将 CAD 文件、网格、DXF 或 SVG 插入当前设计",
"web.files.insert_stored_drawings_hint": "插入当前设计(CAD 文件、网格、DXF、SVG)",
"web.open_full": "在 SolidCraft 中打开",
+176 -24
View File
@@ -366,6 +366,13 @@ fn fs_ao_depth(i: TOut, @builtin(front_facing) front: bool) -> AoDepth {
return opaque_depth(i, front);
}
// The virtual ground preserves the viewport background; only contact occlusion is blended.
@fragment
fn fs_ground_ao(i: TOut) -> @location(0) vec4<f32> {
if (clipped(i.wp)) { discard; }
return vec4<f32>(0.0, 0.0, 0.0, 1.0 - occlusion(i));
}
/// The AO prepass's depth of an opaque surface's fragment.
fn opaque_depth(i: TOut, front: bool) -> AoDepth {
if (clipped(i.wp) || length(i.n) < 0.5) { discard; }
@@ -802,7 +809,9 @@ fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3<f32>, @location(
let sb = cb.xy / max(cb.w, 1e-6) * half;
var dir = sb - sa;
let len = length(dir);
if (len < 1e-6) { dir = vec2<f32>(1.0, 0.0); } else { dir = dir / len; }
// Intel OpenGL can miscompile vector/scalar division here (#8053). Scalar
// quotients keep the same direction and the fallback for a point.
if (len < 1e-6) { dir = vec2<f32>(1.0, 0.0); } else { dir = vec2<f32>(dir.x / len, dir.y / len); }
let nrm = vec2<f32>(-dir.y, dir.x);
// Two triangles: corners (t, side).
var ts = array<f32, 6>(0.0, 1.0, 1.0, 0.0, 1.0, 0.0);
@@ -874,10 +883,10 @@ fn fs_line_all(i: LOut) -> @location(0) vec4<f32> {
return out_color(i.c);
}
// Point clouds (`view/clouds.rs`): each point is a round dot a few pixels across, facing the
// screen, depth-tested and written like the model's faces, so the model hides the points behind
// it and they hide it. The cloud's style colours them by their own colour, a ramp or a palette,
// and shows only those whose filtered values lie in its ranges or sets.
// Point clouds and their cells (`view/clouds.rs`): dots, segments as beads (or lines when they
// have no size) and triangles, from one record per element. The cloud's style colours them by
// their own colour, a ramp or a palette, and shows only those whose filtered values lie in its
// ranges or sets; values vary across an element, so a range cuts it where its value crosses an end.
struct Cloud {
style: vec4<f32>, // x: colour (0 own, 1 ramp, 2 palette), y: dot size (px), z, w: ramp range
@@ -886,43 +895,88 @@ struct Cloud {
@group(1) @binding(3) var<uniform> cloud: Cloud;
struct CIn {
@location(0) p: vec3<f32>,
@location(1) v: f32,
@location(2) f: vec4<f32>,
@location(3) rgb: vec4<f32>,
@location(0) a: vec3<f32>,
@location(1) b: vec3<f32>,
@location(2) c: vec3<f32>,
@location(8) before: vec3<f32>,
@location(9) after: vec3<f32>,
@location(3) v: vec3<f32>,
@location(4) f0: vec3<f32>,
@location(5) f1: vec3<f32>,
@location(10) f2: vec3<f32>,
@location(11) f3: vec3<f32>,
@location(6) size: vec2<f32>,
@location(7) rgb: vec4<f32>,
};
struct COut {
@builtin(position) pos: vec4<f32>,
@location(0) wp: vec3<f32>,
@location(1) v: f32,
@location(2) f: vec4<f32>,
@location(3) rgb: vec3<f32>,
@location(4) at: vec2<f32>, // the place on its disc
@location(5) @interpolate(flat) has: u32,
@location(1) n: vec3<f32>, // zero: not lit
@location(2) v: f32,
@location(3) f: vec4<f32>,
@location(4) rgb: vec3<f32>,
@location(5) at: vec2<f32>, // a dot's place on its disc
@location(6) @interpolate(flat) has: u32,
};
fn cloud_out(i: CIn, w: vec3<f32>, wp: vec3<f32>, pos: vec4<f32>, n: vec3<f32>) -> COut {
var o: COut;
o.pos = pos;
o.wp = wp;
o.n = n;
o.v = dot(w, i.v);
o.f = vec4<f32>(dot(w, i.f0), dot(w, i.f1), dot(w, i.f2), dot(w, i.f3));
o.rgb = i.rgb.rgb;
o.at = vec2<f32>(0.0);
o.has = u32(round(i.rgb.a * 255.0));
return o;
}
fn behind(c: vec4<f32>) -> bool {
return near_z(c) < 0.0;
}
@vertex
fn vs_cloud_dot(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
var xs = array<f32, 6>(-1.0, 1.0, 1.0, -1.0, 1.0, -1.0);
var ys = array<f32, 6>(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0);
let at = vec2<f32>(xs[vi], ys[vi]);
let size = max(cloud.style.y, 1.0);
let centre = u.vp * vec4<f32>(i.p, 1.0);
var o: COut;
o.pos = vec4<f32>(centre.xy + at * size / u.screen.xy * centre.w, centre.z, centre.w);
// Behind the eye: nothing.
if (near_z(centre) < 0.0) { o.pos = vec4<f32>(0.0, 0.0, 0.0, -1.0); }
o.wp = i.p;
o.v = i.v;
o.f = i.f;
o.rgb = i.rgb.rgb;
o.has = u32(round(i.rgb.a * 255.0));
let centre = u.vp * vec4<f32>(i.a, 1.0);
var pos = vec4<f32>(centre.xy + at * size / u.screen.xy * centre.w, centre.z, centre.w);
if (behind(centre)) { pos = vec4<f32>(0.0, 0.0, 0.0, -1.0); }
var o = cloud_out(i, vec3<f32>(1.0, 0.0, 0.0), i.a, pos, vec3<f32>(0.0));
// Dots under two pixels are squares: a disc that small loses its centre pixel.
o.at = at * select(1.0, 0.0, size < 2.0);
return o;
}
/// The least half-width of a segment on screen, in pixels.
const MIN_HALF_PX: f32 = 0.75;
/// The frame of a bead along `d`: along it, across it level, and up, its height axis.
fn bead_frame(d: vec3<f32>) -> mat3x3<f32> {
var t = vec3<f32>(1.0, 0.0, 0.0);
if (length(d) > 1e-9) { t = d / length(d); }
var side = cross(vec3<f32>(0.0, 0.0, 1.0), t);
if (length(side) < 1e-6) { side = vec3<f32>(1.0, 0.0, 0.0); }
side = normalize(side);
return mat3x3<f32>(t, side, cross(t, side));
}
/// Corner `j` (0…9) of a bead's cross-section, a stadium: its place across and up, and its
/// normal. Corners 0…4 go up the right half-disc, 5…9 down the left one, so 4–5 is the flat top
/// and 9–0 the flat bottom.
fn stadium(j: u32, size: vec2<f32>) -> vec4<f32> {
let k = j % 10u;
let right = k < 5u;
let a = -1.5707963 + 0.7853982 * f32(k % 5u) + select(3.1415927, 0.0, right);
let r = 0.5 * size.y;
let flat_half = 0.5 * max(size.x - size.y, 0.0);
return vec4<f32>(select(-flat_half, flat_half, right) + r * cos(a), r * sin(a), cos(a), sin(a));
}
/// Whether filter `f` keeps the value `v`.
fn cloud_keeps(f: vec4<f32>, v: f32) -> bool {
if (f.x < 0.5) { return true; }
@@ -931,6 +985,101 @@ fn cloud_keeps(f: vec4<f32>, v: f32) -> bool {
return k >= 0 && k < 32 && ((bitcast<u32>(f.w) >> u32(k)) & 1u) != 0u;
}
/// Whether filter `f` leaves out a whole element with values `v` at its corners: all on one
/// side of a range, or one value at every corner, not kept. A set of values with corners that
/// differ may keep part of the element: its fragments decide.
fn cloud_drops(f: vec4<f32>, v: vec3<f32>) -> bool {
if (f.x > 1.5) { return all(v == vec3<f32>(v.x)) && !cloud_keeps(f, v.x); }
return f.x > 0.5 && (all(v < vec3<f32>(f.y)) || all(v > vec3<f32>(f.z)));
}
/// Whether a filter leaves out the whole element.
fn filtered_out(i: CIn) -> bool {
let has = u32(round(i.rgb.a * 255.0));
let values = array<vec3<f32>, 4>(i.f0, i.f1, i.f2, i.f3);
for (var k = 0u; k < 4u; k++) {
if (((has >> k) & 1u) != 0u && cloud_drops(cloud.filters[k], values[k])) { return true; }
}
return false;
}
/// The part in front of the near plane of the segment between clip-space `a` and `b`, as
/// fractions of it from `a`; empty (first above second) when all of it is behind. Clip-space
/// coordinates are affine along a line, so the crossing is exact.
fn in_front(a: vec4<f32>, b: vec4<f32>) -> vec2<f32> {
let za = near_z(a);
let zb = near_z(b);
if (za < 0.0 && zb < 0.0) { return vec2<f32>(1.0, 0.0); }
let t = za / (za - zb);
return vec2<f32>(select(0.0, t, za < 0.0), select(1.0, t, zb < 0.0));
}
@vertex
fn vs_cloud_segment(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
// Corner `vi % 10` of the cross-section at the start (`vi < 10`) or the end; the index
// buffer joins them into the bead's sides.
let pa = u.vp * vec4<f32>(i.a, 1.0);
let pb = u.vp * vec4<f32>(i.b, 1.0);
let part = in_front(pa, pb);
if (filtered_out(i) || part.x > part.y) {
return cloud_out(i, vec3<f32>(1.0, 0.0, 0.0), i.a, vec4<f32>(0.0, 0.0, 0.0, -1.0), vec3<f32>(0.0));
}
// Where the near plane cuts it, the bead ends there, square; its numbers are exact at `e`.
let at_end = vi >= 10u;
let e = select(part.x, part.y, at_end);
let cut = e != select(0.0, 1.0, at_end);
let f = bead_frame(i.b - i.a);
let c = stadium(vi % 10u, i.size);
let axis = mix(i.a, i.b, e);
// Where it meets the next or the previous bead, the cross-section lies in the plane halfway
// between their directions, stretched across to keep its width: both beads end on the same
// section. A turn sharper than 120° ends square instead.
let other = select(i.a - i.before, i.after - i.b, at_end);
var across_dir = f[1];
var stretch = 1.0;
if (!cut && length(other) > 1e-9) {
let g = bead_frame(other);
let mid = f[1] + g[1];
if (length(mid) > 1e-6 && dot(normalize(mid), f[1]) > 0.5) {
across_dir = normalize(mid);
stretch = 1.0 / dot(across_dir, f[1]);
}
}
let wp = axis + across_dir * (c.x * stretch) + f[2] * c.y;
let ca = u.vp * vec4<f32>(axis, 1.0);
let cw = u.vp * vec4<f32>(wp, 1.0);
let half = u.screen.xy * 0.5;
var pos = cw;
// At least a pixel and a half across: a bead seen from afar, or a line without size. A corner
// behind the eye has no place on the screen and needs no widening: the rasteriser clips it.
if (cw.w > 0.0) {
let c0 = mix(pa, pb, part.x);
let c1 = mix(pa, pb, part.y);
let along = (c1.xy / c1.w - c0.xy / c0.w) * half;
var across = vec2<f32>(-along.y, along.x);
if (length(across) < 1e-6) { across = vec2<f32>(1.0, 0.0); }
across = normalize(across);
var px = (cw.xy / cw.w - ca.xy / ca.w) * half;
if (length(px) < MIN_HALF_PX * abs(c.z)) {
px = across * c.z * MIN_HALF_PX;
pos = vec4<f32>((ca.xy / ca.w + px / half) * cw.w, cw.z, cw.w);
}
}
let n = select(f[1] * c.z + f[2] * c.w, vec3<f32>(0.0), i.size.y <= 0.0);
return cloud_out(i, vec3<f32>(1.0 - e, e, 0.0), wp, pos, n);
}
@vertex
fn vs_cloud_triangle(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
// Not `w[vi] = 1.0`: the Windows D3D12 shader compiler cannot write through a dynamic vector index.
let w = vec3<f32>(f32(vi == 0u), f32(vi == 1u), f32(vi == 2u));
let wp = i.a * w.x + i.b * w.y + i.c * w.z;
var n = cross(i.b - i.a, i.c - i.a);
if (length(n) > 0.0) { n = normalize(n); }
// The true clip-space corner, even behind the eye: the rasteriser clips a triangle exactly.
return cloud_out(i, w, wp, u.vp * vec4<f32>(wp, 1.0), n);
}
fn cloud_ramp(t: f32) -> vec3<f32> {
var stops = array<vec3<f32>, 5>(
vec3<f32>(40.0, 70.0, 200.0), vec3<f32>(30.0, 170.0, 230.0), vec3<f32>(60.0, 200.0, 90.0),
@@ -965,5 +1114,8 @@ fn fs_cloud(o: COut) -> @location(0) vec4<f32> {
} else if ((o.has & 16u) != 0u && mode > 0.5) {
c = cloud_ramp((o.v - cloud.style.z) / max(cloud.style.w - cloud.style.z, 1e-12));
}
if (length(o.n) > 0.5) {
c = c * (0.45 + 0.55 * abs(dot(normalize(o.n), normalize(u.back.xyz))));
}
return out_color(vec4<f32>(c, 1.0));
}
+667
View File
@@ -0,0 +1,667 @@
{
"order": 260,
"provenance": "Original SolidCraft stone-grey palette and semantic icon-role mapping",
"format": "solidcraft-theme/1",
"name": "Onyx",
"base": "dark",
"colors": {
"accent": "#9eafbd",
"accent_soft": "#3b4852",
"agent_busy": "#9eafbd",
"agent_ok": "#a5b9a5",
"app_bar": "#1c1e20",
"app_bar_hover": "#dededc16",
"app_bar_hover_button": "#dededc1c",
"app_bar_hover_strong": "#dededc28",
"app_bar_hover_subtle": "#dededc0c",
"app_bar_separator": "#383c3f",
"app_bar_tab_active": "#303235",
"app_bar_text": "#dededc",
"axis_x": "#d7938f",
"axis_y": "#a5b9a5",
"axis_z": "#98b2c8",
"bend_down": "#b8a7c3",
"bend_up": "#a5b9a5",
"body": "#93989c",
"body_edge": "#60666b",
"body_selected": "#3b4852",
"border": "#3d4145",
"box_window": "#9eafbd",
"chip": "#758b9b",
"construction_geom": "#b9a78e",
"construction_plane": "#b9a78e24",
"continuity": [
"#a5b9a5",
"#98b2c8",
"#c7b28b",
"#b8a7c3"
],
"control_border": "#4a4f54",
"control_fill": "#303235",
"cube_border": "#50565b",
"cube_face": "#34373a",
"cube_hover": "#53616c",
"cube_text": "#dededc",
"dialog_bg": "#2b2d2f",
"dialog_body": "#2b2d2f",
"dialog_border": "#3d4145",
"dialog_footer": "#27292b",
"dialog_label": "#dededc",
"dialog_rule": "#383c3f",
"dimension": "#c7b28b",
"dimension_driven": "#a59474",
"error": "#d7938f",
"field": "#1c1e20",
"form_accent": "#9d8faa",
"form_dim": "#c6c8c9",
"form_error": "#d7938f",
"form_error_fill": "#493334",
"form_fill": "#b9abc5",
"form_ink": "#dededc",
"form_primary": "#9eafbd",
"form_primary_fill": "#9eafbd",
"form_soft": "#343039",
"form_success": "#a5b9a5",
"form_success_fill": "#344139",
"form_tab_ink": "#b9abc5",
"glyph_bg": "#34373a",
"glyph_edge": "#d7938f",
"glyph_text": "#d7938f",
"grid": "#252729",
"grid_major": "#2e3235",
"hover": "#3b4852",
"hover_edge": "#b1c1ce",
"hover_edge_halo": "#181a1c",
"hover_profile": "#9eafbd30",
"hover_profile_edge": "#b1c1ce",
"icon": "#dededc",
"icons.agent": "#b8a7c3",
"icons.highlight": "#dededc",
"icons.marker_halo": "#1c1e20cc",
"interference": "#d7938f",
"joint_axis": "#98b2c8",
"joint_manipulator": "#9eafbd",
"logo": [
"#c9d4dc",
"#9eafbd",
"#647986"
],
"manipulator": "#9eafbd",
"mesh_accent": "#a29772",
"mesh_fill": "#beb48d",
"mesh_ink": "#beb48d",
"mesh_soft": "#34332b",
"nav_dim": "#b5b7b8",
"nav_field": "#1c1e20",
"nav_hover": "#43494f",
"nav_press": "#59636c",
"nav_tip": "#2b2d2f",
"nav_tip_ink": "#dededc",
"nav_tip_key_border": "#3b3e41",
"nav_viewport": "#27292b",
"on_accent": "#17191b",
"origin_plane": "#b9a78e2c",
"origin_plane_edge": "#b9a78e94",
"origin_plane_hover": "#9eafbd60",
"origin_point": "#b5b7b8",
"overlay": "#2b2d2f",
"panel": "#2b2d2f",
"panel_header": "#303235",
"preview_add": "#9eafbd",
"preview_cut": "#d7938f68",
"preview_new": "#c7b28b",
"preview_sketch": "#a5b9a5",
"profile_fill": "#9eafbd28",
"render_accent": "#a88e88",
"render_fill": "#c1a49e",
"render_ink": "#c1a49e",
"render_soft": "#38302f",
"rubber_band": "#9eafbd",
"section_cap": "#c7b28b",
"sel_edge": "#b1c1ce",
"sel_edge_rim": "#d0d9df",
"sel_face": "#3b4852",
"sel_profile": "#3b4852",
"sel_profile_edge": "#b1c1ce",
"sel_vertex": "#9eafbd",
"selected_bg": "#3b4852",
"selected_ink": "#dededc",
"sheet_fill": "#9eb9bd",
"sheet_ink": "#9eb9bd",
"sheet_soft": "#29363a",
"sketch": "#9eafbd",
"sketch_accent": "#b2b995",
"sketch_centerline": "#b5b7b8",
"sketch_conflict": "#d7938f",
"sketch_construction": "#b5b7b8",
"sketch_fixed": "#dededc",
"sketch_halo": "#181a1c",
"sketch_ink": "#b2b995",
"sketch_locked": "#a5b9a5",
"sketch_lost": "#d7938f",
"sketch_point": "#dededc",
"sketch_point_free": "#9eafbd",
"sketch_projected": "#b8a7c3",
"sketch_soft": "#32352c",
"surface_accent": "#a6947d",
"surface_fill": "#c0ae96",
"surface_ink": "#c0ae96",
"surface_soft": "#35322e",
"tab_on_fill_dark": "#17191b",
"text": "#dededc",
"text_dim": "#c6c8c9",
"timeline": "#2b2d2f",
"timeline_item": "#303235",
"timeline_item_error": "#493334",
"timeline_marker": "#b5b7b8",
"toolbar": "#2b2d2f",
"viewport_bottom": "#1b1d1f",
"viewport_top": "#222426",
"warning": "#c7b28b"
},
"radii": {
"radius_sm": 3,
"radius_md": 6,
"radius_lg": 8,
"radius_xl": 10,
"control_radius": 4,
"sketch_mode_radius": 6
},
"icon_palette": {
"ink": "#1b1d1f",
"line": "#d7d9d8",
"passive-1": "#b9bdc0",
"passive-2": "#8d9398",
"passive-3": "#646c72",
"guide": "#a2a8ac",
"solid": [
"#c9d4dc",
"#b2c1cc",
"#9eafbd"
],
"surface": [
"#d6cab8",
"#c0ae96",
"#a6947d"
],
"mesh": [
"#d4ccad",
"#beb48d",
"#a29772"
],
"form": [
"#d0c5d9",
"#b9abc5",
"#9d8faa"
],
"sketch": [
"#c9cfb3",
"#b2b995",
"#979f78"
],
"constraint": [
"#e1b7b2",
"#d7938f",
"#b77875"
],
"construction": [
"#d2c6b4",
"#b9a78e",
"#9f8c73"
]
},
"options": [
{
"id": "opacity",
"type": "range",
"default": 80,
"min": 60,
"max": 100
},
{
"id": "blur",
"type": "range",
"default": 60,
"max": 60
},
{
"id": "tab_colours",
"type": "bool",
"default": false
},
{
"id": "tab_fade_ms",
"type": "range",
"default": 240
},
{
"id": "accent",
"type": "choice",
"default": "slate",
"label": {
"en": "Accent",
"de": "Akzent",
"ja": "アクセント",
"ko": "강조색",
"zh": "强调色"
},
"help": {
"en": "Selection, highlights and Model controls; workspace and status colours keep their meaning.",
"de": "Auswahl, Hervorhebungen und Modell-Steuerung; Arbeitsbereichs- und Statusfarben behalten ihre Bedeutung.",
"ja": "選択・強調・モデルの操作色。ワークスペースや状態の色の意味は変えません。",
"ko": "선택, 강조 및 모델 조작의 색상입니다. 작업 공간과 상태 색상의 의미는 유지됩니다.",
"zh": "更改选择、高亮与模型控件的色调;工作空间和状态色保持原有含义。"
},
"values": [
{
"id": "slate",
"label": {
"en": "Slate blue",
"de": "Schieferblau",
"ja": "石板の青",
"ko": "슬레이트 블루",
"zh": "岩蓝"
}
},
{
"id": "silver",
"label": {
"en": "Silver",
"de": "Silber",
"ja": "シルバー",
"ko": "은색",
"zh": "银灰"
}
},
{
"id": "sage",
"label": {
"en": "Sage",
"de": "Salbei",
"ja": "セージ",
"ko": "세이지",
"zh": "鼠尾草绿"
}
}
]
},
{
"id": "contrast",
"type": "choice",
"default": "soft",
"label": {
"en": "Contrast",
"de": "Kontrast",
"ja": "コントラスト",
"ko": "대비",
"zh": "对比度"
},
"help": {
"en": "Soft stone layers or stronger separation between panels and fields.",
"de": "Weiche Steinschichten oder stärkere Trennung von Flächen und Feldern.",
"ja": "柔らかな石の階調か、パネルと入力欄を明確に分ける階調を選びます。",
"ko": "부드러운 석재 톤 또는 패널과 입력란을 더 뚜렷하게 구분합니다.",
"zh": "选择柔和的石色层次,或增强面板与输入框的区分。"
},
"values": [
{
"id": "soft",
"label": {
"en": "Soft",
"de": "Weich",
"ja": "ソフト",
"ko": "부드럽게",
"zh": "柔和"
}
},
{
"id": "defined",
"label": {
"en": "Defined",
"de": "Deutlich",
"ja": "くっきり",
"ko": "뚜렷하게",
"zh": "分明"
}
}
]
},
{
"id": "brightness",
"type": "range",
"effect": "surface_brightness",
"default": 100,
"min": 60,
"max": 120,
"step": 1,
"unit": "%",
"label": {
"en": "Brightness",
"de": "Helligkeit",
"ja": "明るさ",
"ko": "밝기",
"zh": "亮度"
},
"help": {
"en": "Adjust stone surfaces, viewport backgrounds and grid brightness. 100% keeps the authored tones; text and semantic colours stay unchanged.",
"de": "Helligkeit von Steinflächen, Ansichtsflächen und Raster. 100% erhält die ursprünglichen Töne; Text und Bedeutungsfarben bleiben unverändert.",
"ja": "石調の面・ビューポート背景・グリッドの明るさを調整します。100%は元の階調です。文字と意味のある色は変えません。",
"ko": "석재 표면·뷰포트 배경·격자의 밝기를 조절합니다. 100%는 원래 톤을 유지하며 글자와 의미 색상은 바꾸지 않습니다.",
"zh": "调整石色表面、视口背景和网格的亮度。100%保留原有色调;文字和语义颜色保持不变。"
}
},
{
"id": "foreground_colours",
"type": "choice",
"default": "vibrant",
"label": {
"en": "Foreground colours",
"de": "Vordergrundfarben",
"ja": "前景の色",
"ko": "전경 색상",
"zh": "前景颜色"
},
"help": {
"en": "Vibrant strengthens axes, icons and guides. Muted keeps the original quiet palette. Accent controls selection.",
"de": "Leuchtend verstärkt Achsen, Symbole und Hilfen. Gedämpft behält die ruhige ursprüngliche Palette. Akzent steuert die Auswahl.",
"ja": "鮮やかは軸・アイコン・作図ガイドを強調します。落ち着いた配色は元の控えめな色を保ちます。選択の色はアクセントで設定します。",
"ko": "선명함은 축·아이콘·가이드를 강조합니다. 차분함은 기존의 은은한 팔레트를 유지합니다. 선택 색상은 강조색 설정을 따릅니다.",
"zh": "鲜艳增强轴、图标和辅助线的色彩。柔和保留原有的沉静配色。选择颜色仍由强调色控制。"
},
"values": [
{
"id": "muted",
"label": {
"en": "Muted",
"de": "Gedämpft",
"ja": "落ち着いた",
"ko": "차분함",
"zh": "柔和"
}
},
{
"id": "vibrant",
"label": {
"en": "Vibrant",
"de": "Leuchtend",
"ja": "鮮やか",
"ko": "선명함",
"zh": "鲜艳"
}
}
]
}
],
"overlays": [
{
"when": {
"opt": "contrast",
"eq": "defined"
},
"colors": {
"app_bar": "#151719",
"panel": "#25282a",
"dialog_bg": "#25282a",
"dialog_body": "#25282a",
"dialog_footer": "#202325",
"panel_header": "#373b3e",
"field": "#171a1c",
"control_fill": "#383d41",
"border": "#4e555b",
"dialog_border": "#4e555b",
"dialog_rule": "#444b51",
"control_border": "#626c73",
"text": "#eeeeec",
"text_dim": "#ced1d2",
"form_ink": "#eeeeec",
"form_dim": "#ced1d2",
"dialog_label": "#eeeeec",
"toolbar": "#25282a",
"timeline": "#25282a"
}
},
{
"when": {
"opt": "accent",
"eq": "silver"
},
"colors": {
"accent": "#b7bdc2",
"manipulator": "#b7bdc2",
"joint_manipulator": "#b7bdc2",
"preview_add": "#b7bdc2",
"rubber_band": "#b7bdc2",
"box_window": "#b7bdc2",
"form_primary": "#b7bdc2",
"form_primary_fill": "#b7bdc2",
"sketch": "#b7bdc2",
"sketch_point_free": "#b7bdc2",
"sel_vertex": "#b7bdc2",
"accent_soft": "#454b50",
"hover": "#454b50",
"selected_bg": "#454b50",
"sel_face": "#454b50",
"sel_profile": "#454b50",
"body_selected": "#454b50",
"sel_edge": "#d5dadf",
"sel_profile_edge": "#d5dadf",
"hover_edge": "#d5dadf",
"hover_profile_edge": "#d5dadf",
"sel_edge_rim": "#dde0e3",
"hover_profile": "#b7bdc230",
"origin_plane_hover": "#b7bdc260",
"profile_fill": "#b7bdc228",
"cube_hover": "#454b50",
"chip": "#b7bdc2",
"logo": [
"#dde0e3",
"#c9cfd4",
"#b7bdc2"
]
},
"icon_palette": {
"solid": [
"#dde0e3",
"#c9cfd4",
"#b7bdc2"
]
}
},
{
"when": {
"opt": "accent",
"eq": "sage"
},
"colors": {
"accent": "#aabcae",
"manipulator": "#aabcae",
"joint_manipulator": "#aabcae",
"preview_add": "#aabcae",
"rubber_band": "#aabcae",
"box_window": "#aabcae",
"form_primary": "#aabcae",
"form_primary_fill": "#aabcae",
"sketch": "#aabcae",
"sketch_point_free": "#aabcae",
"sel_vertex": "#aabcae",
"accent_soft": "#414d45",
"hover": "#414d45",
"selected_bg": "#414d45",
"sel_face": "#414d45",
"sel_profile": "#414d45",
"body_selected": "#414d45",
"sel_edge": "#c2d2c5",
"sel_profile_edge": "#c2d2c5",
"hover_edge": "#c2d2c5",
"hover_profile_edge": "#c2d2c5",
"sel_edge_rim": "#d1ddd4",
"hover_profile": "#aabcae30",
"origin_plane_hover": "#aabcae60",
"profile_fill": "#aabcae28",
"cube_hover": "#414d45",
"chip": "#aabcae",
"logo": [
"#d1ddd4",
"#becec2",
"#aabcae"
]
},
"icon_palette": {
"solid": [
"#d1ddd4",
"#becec2",
"#aabcae"
]
}
},
{
"when": {
"opt": "foreground_colours",
"eq": "vibrant"
},
"colors": {
"axis_x": "#ff6d79",
"axis_y": "#4fd681",
"axis_z": "#4da7ff",
"agent_busy": "#4da7ff",
"agent_ok": "#4fd681",
"warning": "#ffd166",
"error": "#ff6d79",
"form_error": "#ff6d79",
"form_success": "#4fd681",
"glyph_edge": "#ff6d79",
"glyph_text": "#ff6d79",
"interference": "#ff6d79",
"sketch_conflict": "#ff6d79",
"sketch_lost": "#ff6d79",
"sketch_locked": "#4fd681",
"sketch_projected": "#c78bff",
"dimension": "#ffd166",
"dimension_driven": "#ffb166",
"construction_geom": "#ffb166",
"sketch_centerline": "#b5b7b8",
"sketch_construction": "#b5b7b8",
"joint_axis": "#42d9dd",
"bend_down": "#c78bff",
"bend_up": "#4fd681",
"preview_cut": "#ff6d7968",
"preview_new": "#ffd166",
"preview_sketch": "#4fd681",
"section_cap": "#ffd166",
"sketch_accent": "#4fd681",
"sketch_ink": "#4fd681",
"surface_accent": "#ffb166",
"surface_fill": "#ffb166",
"surface_ink": "#ffb166",
"mesh_accent": "#ffd166",
"mesh_fill": "#ffd166",
"mesh_ink": "#ffd166",
"form_accent": "#c78bff",
"form_fill": "#c78bff",
"form_tab_ink": "#c78bff",
"sheet_fill": "#63cbd5",
"sheet_ink": "#63cbd5",
"render_accent": "#ff6d79",
"render_fill": "#ff6d79",
"render_ink": "#ff6d79",
"icons.agent": "#c78bff",
"continuity": [
"#4fd681",
"#4da7ff",
"#ffd166",
"#c78bff"
],
"origin_plane_edge": "#ffb16694",
"sheet_soft": "#283a3e"
},
"icon_palette": {
"ink": "#1b1d1f",
"line": "#d7d9d8",
"passive-1": "#d3d8dc",
"passive-2": "#b2bbc2",
"passive-3": "#929fa9",
"guide": "#b4c8d6",
"solid": [
"#82c1ff",
"#62b2ff",
"#4da7ff"
],
"surface": [
"#ffc894",
"#ffba78",
"#ffb166"
],
"mesh": [
"#ffdf94",
"#ffd778",
"#ffd166"
],
"form": [
"#d8aeff",
"#ce99ff",
"#c78bff"
],
"sketch": [
"#84e2a7",
"#64db90",
"#4fd681"
],
"constraint": [
"#ff99a1",
"#ff7f89",
"#ff6d79"
],
"construction": [
"#ffc894",
"#ffba78",
"#ffb166"
]
}
}
],
"tabs": {
"MODEL": {
"viewport_top": "#24292d",
"viewport_bottom": "#1b2023",
"panel": "#303438"
},
"SURFACE": {
"viewport_top": "#292622",
"viewport_bottom": "#211e1b",
"panel": "#36332f"
},
"MESH": {
"viewport_top": "#282820",
"viewport_bottom": "#202019",
"panel": "#35352e"
},
"FORM": {
"viewport_top": "#26232b",
"viewport_bottom": "#1e1b22",
"panel": "#343137"
},
"SHEET METAL": {
"viewport_top": "#22282a",
"viewport_bottom": "#1b2022",
"panel": "#303638"
},
"RENDER": {
"viewport_top": "#2b2423",
"viewport_bottom": "#221c1b",
"panel": "#373130"
},
"SKETCH": {
"viewport_top": "#252921",
"viewport_bottom": "#1d211a",
"panel": "#33362f"
}
},
"borders": {
"primary_inset": {
"width": 0.5,
"gap": 1,
"color": "#dce2e70e"
}
},
"requires": 2
}
+40
View File
@@ -2,6 +2,46 @@
use crate::{DocError, Document, Mat, ModelBody, ModelState, Result};
use std::collections::BTreeSet;
/// A profile moved into the component receiving its own body is read through the recorded
/// transfer frame. Reading its live placement would move the source geometry when that
/// component moves, then move the transferred body a second time. All outputs of a constructor
/// must reach the sketch's component through the same frame; independent cross-component
/// references retain their ordinary live placement.
pub(crate) fn profile_frame(doc: &Document, feature: &crate::Feature, sketch: u64) -> Option<Mat> {
if !matches!(
feature.kind,
crate::FeatureKind::Extrude { operation: crate::Operation::NewBody, .. }
| crate::FeatureKind::Revolve { operation: crate::Operation::NewBody, .. }
) || feature.body_names.is_empty()
{
return None;
}
let owner = doc.feature(sketch)?.component;
if owner == feature.component {
return None;
}
let mut frames: std::collections::BTreeMap<&str, (u64, Mat)> =
feature.body_names.iter().map(|name| (name.as_str(), (feature.component, crate::IDENTITY))).collect();
let start = doc.feature_index(feature.id)?.saturating_add(1);
for step in doc.features.iter().take(doc.marker.unwrap_or(usize::MAX)).skip(start).filter(|step| !step.suppressed) {
let crate::FeatureKind::BodyTransfer { bodies, from, to, transform } = &step.kind else { continue };
for name in bodies {
if let Some((component, frame)) = frames.get_mut(name.as_ref()) {
if *component != *from || !valid_transfer_matrix(transform) {
return None;
}
*component = *to;
*frame = crate::mat_mul(transform, frame);
}
}
if frames.values().all(|(component, _)| *component == owner) {
let (_, first) = frames.values().next()?;
return frames.values().all(|(_, frame)| frame == first).then(|| crate::mat_inverse(first)).flatten();
}
}
None
}
pub fn valid_transfer_matrix(m: &Mat) -> bool {
crate::is_placement(m)
}
+25 -2
View File
@@ -393,6 +393,8 @@ pub(crate) struct ExactSketchCurves<'a> {
/// Each analytic curve's id, whether it is projected (linked) reference geometry, and its
/// segments.
segments: Vec<(&'a str, bool, Vec<Seg2>)>,
/// Each free-form curve's id (ellipses, conics, splines) and its exact segments.
free_form: Vec<(&'a str, Vec<Seg2>)>,
}
impl<'a> ExactSketchCurves<'a> {
@@ -400,10 +402,17 @@ impl<'a> ExactSketchCurves<'a> {
/// and segment allocation must not be repeated for every generated face.
pub(crate) fn new(sketch: &'a SolvedSketch) -> Self {
let mut segments = Vec::new();
let mut free_form = Vec::new();
if sketch.sketch.curves.len() > 2000 {
return Self { segments };
return Self { segments, free_form };
}
for (index, curve) in sketch.sketch.curves.iter().enumerate() {
if !curve.construction
&& let Some(exact) = sketch.sketch.exact_segs(index)
{
free_form.push((curve.id.as_str(), exact));
continue;
}
if curve.construction
|| !matches!(
curve.kind,
@@ -425,7 +434,21 @@ impl<'a> ExactSketchCurves<'a> {
};
segments.push((curve.id.as_str(), curve.link.is_some(), originals));
}
Self { segments }
Self { segments, free_form }
}
/// The one free-form sketch curve (an ellipse, conic or spline) a generated segment is a
/// whole piece of, either way round: one of the exact segments the curve is made of. A
/// segment two curves share stays ambiguous.
pub(crate) fn find_free_form(&self, generated: &Seg2) -> Option<String> {
let reversed = generated.reversed();
let mut found: Option<Option<&str>> = None;
for (id, originals) in &self.free_form {
if originals.iter().any(|original| *original == *generated || *original == reversed) {
found = Some(if found.is_some() { None } else { Some(*id) });
}
}
found.flatten().map(str::to_owned)
}
/// The one sketch curve a generated segment lies on. A curve drawn over projected
+322 -9
View File
@@ -89,20 +89,45 @@ pub fn format_value(value: f64, kind: expr::Kind, unit: &str, decimals: u8, f: &
if unit_kind != kind {
return Err(DocError::Invalid("number display unit has a different kind".into()));
}
format_display(value / scale, scale, decimals, f)
format_display(value / scale, scale, decimals, f, None)
}
/// Format a unit-less number with the same validation and decimal rounding policy.
pub fn format_number(x: f64, decimals: u8, f: &NumberFormat) -> Result<Shown> {
format_display(x, 1.0, decimals, f)
format_display(x, 1.0, decimals, f, None)
}
fn format_display(x: f64, scale: f64, decimals: u8, f: &NumberFormat) -> Result<Shown> {
/// Format an internal value with a rounding step in the selected display unit.
/// The step is a shortest round-trip decimal, finite, positive and at most 1e6.
/// Input decimal preparation precedes step rounding; requested decimal precision follows it.
/// Both rounding operations use `f.ties`. The original value is never changed.
/// The exact snapped display magnitude must be at most 1e12. An out-of-range snap
/// returns [`DocError::Invalid`] before final decimal-place rounding, even if that
/// final rounding would bring the displayed value back into range.
pub fn format_value_with_step(value: f64, kind: expr::Kind, unit: &str, decimals: u8, step: f64, f: &NumberFormat) -> Result<Shown> {
let (unit_kind, scale) = expr::unit_info(unit).ok_or_else(|| DocError::Invalid("unknown number display unit".into()))?;
if unit_kind != kind {
return Err(DocError::Invalid("number display unit has a different kind".into()));
}
format_display(value / scale, scale, decimals, f, Some(step))
}
/// Format a unit-less number with the step policy of [`format_value_with_step`].
/// Decimal precision can round a step multiple again; `Shown.value` always matches the text.
/// Out-of-range snapped magnitudes are rejected before final decimal-place rounding.
pub fn format_number_with_step(x: f64, decimals: u8, step: f64, f: &NumberFormat) -> Result<Shown> {
format_display(x, 1.0, decimals, f, Some(step))
}
fn format_display(x: f64, scale: f64, decimals: u8, f: &NumberFormat, step: Option<f64>) -> Result<Shown> {
// The unit table supplies a positive finite scale. Its decimal step must be at most
// 1e-9 internal units; the precision floor can make the effective tie window narrower.
let unit_resolution = (scale.log10() + 9.0).ceil().max(0.0) as usize;
let resolution = unit_resolution.max(usize::from(decimals) + 3);
let (rounded, value) = round_decimal(x, decimals, resolution, f.ties)?;
let (rounded, value) = match step {
Some(step) => round_decimal_step(x, step, decimals, resolution, f.ties)?,
None => round_decimal(x, decimals, resolution, f.ties)?,
};
let (integer, fraction) = rounded.split_once('.').unwrap_or((&rounded, ""));
let fraction = if f.trailing_zeros { fraction } else { fraction.trim_end_matches('0') };
let integer = if !f.leading_zero && integer == "0" && !fraction.is_empty() { "" } else { integer };
@@ -121,16 +146,25 @@ fn format_display(x: f64, scale: f64, decimals: u8, f: &NumberFormat) -> Result<
Ok(Shown { text, value })
}
/// Prepare a bounded magnitude, then round decimal digits, never a binary tie comparison.
fn round_decimal(x: f64, decimals: u8, resolution: usize, ties: Ties) -> Result<(String, f64)> {
/// Validate and prepare the magnitude once, identically for both rounding paths.
fn prepare_decimal(x: f64, decimals: u8, resolution: usize) -> Result<String> {
if decimals > 8 {
return Err(DocError::Invalid("number precision must be at most 8 decimal places".into()));
}
if !x.is_finite() || x.abs() > 1e12 {
return Err(DocError::Invalid("display value must be finite with magnitude at most 1e12".into()));
}
let prepared = format!("{:.*}", resolution, x.abs());
let (integer, fraction) = prepared.split_once('.').unwrap_or((&prepared, ""));
Ok(format!("{:.*}", resolution, x.abs()))
}
/// Prepare a bounded magnitude, then round decimal digits, never a binary tie comparison.
fn round_decimal(x: f64, decimals: u8, resolution: usize, ties: Ties) -> Result<(String, f64)> {
let prepared = prepare_decimal(x, decimals, resolution)?;
round_prepared(&prepared, x.is_sign_negative(), decimals, ties)
}
fn round_prepared(prepared: &str, negative: bool, decimals: u8, ties: Ties) -> Result<(String, f64)> {
let (integer, fraction) = prepared.split_once('.').unwrap_or((prepared, ""));
let places = usize::from(decimals);
let mut digits: Vec<u8> = integer.bytes().chain(fraction.bytes().take(places)).collect();
let discarded = fraction.as_bytes().get(places..).unwrap_or_default();
@@ -166,10 +200,81 @@ fn round_decimal(x: f64, decimals: u8, resolution: usize, ties: Ties) -> Result<
}
let rounded = String::from_utf8(digits).map_err(|_| DocError::Invalid("invalid prepared digits".into()))?;
let magnitude = rounded.parse::<f64>().map_err(|_| DocError::Invalid("invalid rounded number".into()))?;
let negative = x.is_sign_negative() && magnitude != 0.0;
let negative = negative && magnitude != 0.0;
Ok((rounded, if negative { -magnitude } else { magnitude }))
}
/// Divide decimal digits by a small decimal coefficient, retaining the full quotient.
/// A shortest f64 coefficient has at most 17 significant digits. With the unit table's
/// resolution <= 15, a scaled divisor is <= 1e21, so remainder * 10 fits u128.
/// At most 324 appended zeros are needed, even for the smallest positive subnormal.
fn round_decimal_step(x: f64, step: f64, decimals: u8, resolution: usize, ties: Ties) -> Result<(String, f64)> {
if !step.is_finite() || step <= 0.0 || step > 1e6 {
return Err(DocError::Invalid("number rounding step must be finite, positive and at most 1e6".into()));
}
let prepared = prepare_decimal(x, decimals, resolution)?;
let scientific = format!("{step:e}");
let (mantissa, exponent) = scientific.split_once('e').ok_or_else(|| DocError::Invalid("invalid decimal step".into()))?;
let fraction = mantissa.split_once('.').map_or(0, |(_, f)| f.len());
let coefficient = mantissa.replace('.', "").parse::<u128>().map_err(|_| DocError::Invalid("invalid step coefficient".into()))?;
let exponent = exponent.parse::<i32>().map_err(|_| DocError::Invalid("invalid step exponent".into()))? - fraction as i32;
let shift = exponent + resolution as i32;
let divisor = if shift > 0 { 10_u128.checked_pow(shift as u32).and_then(|n| coefficient.checked_mul(n)) } else { Some(coefficient) }
.ok_or_else(|| DocError::Invalid("rounding step scale overflow".into()))?;
let mut remainder = 0_u128;
let mut quotient = Vec::new();
for digit in prepared.bytes().filter(|d| *d != b'.').chain(std::iter::repeat_n(b'0', (-shift).max(0) as usize)) {
remainder = remainder * 10 + u128::from(digit - b'0');
quotient.push((remainder / divisor) as u8);
remainder %= divisor;
}
let up = remainder * 2 > divisor
|| (remainder * 2 == divisor && (ties == Ties::HalfAwayFromZero || quotient.last().is_some_and(|d| !d.is_multiple_of(2))));
// Exact quotient * coefficient, with the rounding increment folded into the carry.
let mut carry = if up { coefficient } else { 0 };
let mut product = Vec::new();
for digit in quotient.iter().rev() {
let n = u128::from(*digit) * coefficient + carry;
product.push(b'0' + (n % 10) as u8);
carry = n / 10;
}
while carry > 0 {
product.push(b'0' + (carry % 10) as u8);
carry /= 10;
}
product.reverse();
let product = String::from_utf8(product).map_err(|_| DocError::Invalid("invalid step digits".into()))?;
let mut magnitude = product.trim_start_matches('0').to_string();
if magnitude.is_empty() {
magnitude.push('0');
}
if exponent >= 0 {
if magnitude != "0" {
magnitude.extend(std::iter::repeat_n('0', exponent as usize));
}
} else {
let places = (-exponent) as usize;
if magnitude.len() <= places {
magnitude = format!("{}{}", "0".repeat(places + 1 - magnitude.len()), magnitude);
}
magnitude.insert(magnitude.len() - places, '.');
}
let (integer, fraction) = magnitude.split_once('.').unwrap_or((&magnitude, ""));
let whole = integer.parse::<u64>().map_err(|_| DocError::Invalid("rounded display magnitude overflow".into()))?;
if whole > 1_000_000_000_000 || (whole == 1_000_000_000_000 && fraction.bytes().any(|d| d != b'0')) {
return Err(DocError::Invalid("rounded display magnitude must be at most 1e12".into()));
}
// round_prepared needs at least the requested fraction digits, including for integer steps.
let missing = usize::from(decimals).saturating_sub(fraction.len());
if missing > 0 {
if !magnitude.contains('.') {
magnitude.push('.');
}
magnitude.extend(std::iter::repeat_n('0', missing));
}
round_prepared(&magnitude, x.is_sign_negative(), decimals, ties)
}
fn grouped_digits(text: &mut String, digits: &str, integer: bool, groups: bool) {
for (i, digit) in digits.chars().enumerate() {
if groups && digits.len() >= 5 && i > 0 && if integer { (digits.len() - i).is_multiple_of(3) } else { i.is_multiple_of(3) } {
@@ -471,6 +576,214 @@ mod number_tests {
}
}
#[test]
fn step_ties_and_decimal_fractions() {
for (x, step, dp, even, away) in [
(1.25, 0.5, 1, "1.0", "1.5"),
(1.75, 0.5, 1, "2.0", "2.0"),
(12.5, 5.0, 0, "10", "15"),
(25.0, 10.0, 0, "20", "30"),
(0.125, 0.05, 2, "0.10", "0.15"),
(0.375, 0.25, 2, "0.50", "0.50"),
(0.15, 0.1, 1, "0.2", "0.2"),
(0.25, 0.1, 1, "0.2", "0.3"),
(1.25, 0.5, 0, "1", "2"),
] {
for (ties, expected) in [(Ties::HalfEven, even), (Ties::HalfAwayFromZero, away)] {
let f = NumberFormat { ties, ..NumberFormat::default() };
for sign in [1.0, -1.0] {
let shown = format_number_with_step(sign * x, dp, step, &f).unwrap();
assert_eq!(shown.text, if sign < 0.0 { format!("−{expected}") } else { expected.into() });
assert_eq!(shown.value, sign * expected.parse::<f64>().unwrap());
}
}
}
}
#[test]
fn step_prepares_display_before_division_and_keeps_boundary_neighbors() {
for ties in [Ties::HalfEven, Ties::HalfAwayFromZero] {
let f = NumberFormat { ties, ..NumberFormat::default() };
for x in [1.75_f64.next_down(), 1.75, 1.75_f64.next_up(), 1.7499999996] {
assert_eq!(format_number_with_step(x, 1, 0.5, &f).unwrap().text, "2.0");
}
for x in [1.25_f64.next_down(), 1.25, 1.25_f64.next_up()] {
let expected = if ties == Ties::HalfEven { "1.0" } else { "1.5" };
assert_eq!(format_number_with_step(x, 1, 0.5, &f).unwrap().text, expected);
assert_eq!(format_number_with_step(-x, 1, 0.5, &f).unwrap().text, format!("−{expected}"));
}
assert_eq!(format_number_with_step(1.7499999994, 1, 0.5, &f).unwrap().text, "1.5");
assert_eq!(format_number_with_step(1.7500000006, 1, 0.5, &f).unwrap().text, "2.0");
}
let f = NumberFormat::default();
// The precision floor narrows the preparation window at eight decimals.
assert_eq!(format_number_with_step(1.7499999996, 8, 0.5, &f).unwrap().text, "1.50000000");
}
#[test]
fn step_tiny_values_large_magnitudes_and_exact_final_digits() {
let f = NumberFormat::default();
for step in [f64::from_bits(1), f64::MIN_POSITIVE, 1e-200, 1e-20, 1e-8] {
for x in [0.0, -0.0, f64::from_bits(1), -1e11, 1e11, 34947628153.461784] {
let shown = format_number_with_step(x, 5, step, &f).unwrap();
assert!(shown.value.is_finite());
assert_eq!(shown.value, shown.text.replace('−', "-").parse::<f64>().unwrap());
}
}
assert_eq!(format_number_with_step(1e12, 8, f64::from_bits(1), &f).unwrap().text, "1000000000000.00000000");
assert_eq!(format_number_with_step(34947628153.461784, 5, 1e-8, &f).unwrap().text, "34947628153.46178");
// A tiny step can move an exact decimal tie by less than any f64 ULP.
// Keep those digits through the second rounding rather than parsing the snap to f64.
assert_eq!(format_number_with_step(1.25, 1, 3e-323, &f).unwrap().text, "1.3");
assert_eq!(format_number_with_step(-1.25, 1, 3e-323, &f).unwrap().text, "−1.3");
for step in [0.5, 10.0, 1e6] {
for x in [-0.0, -0.1] {
let shown = format_number_with_step(x, 2, step, &f).unwrap();
assert_eq!(shown.text, "0.00");
assert_eq!(shown.value.to_bits(), 0.0_f64.to_bits());
}
}
}
#[test]
fn step_extreme_coefficients_have_independent_expected_digits() {
// Shortest decimals: largest subnormal = 2225073858507201 * 10^-323,
// smallest normal = 22250738585072014 * 10^-324. For x=1.25, integer
// division leaves remainders 2221948386734081 and 354296156594724.
// Twice the first is above its divisor, twice the second below: the
// snap is just above/below 1.25, so final rounding gives 1.3/1.2 in BOTH modes.
// Other expected digits were calculated with independent rational arithmetic.
for (step, quarter) in [(f64::MIN_POSITIVE.next_down(), "1.3"), (f64::MIN_POSITIVE, "1.2")] {
for ties in [Ties::HalfEven, Ties::HalfAwayFromZero] {
let f = NumberFormat { ties, ..NumberFormat::default() };
for (x, dp, expected) in [
(1.25, 1, quarter),
(99999999999.75, 0, "100000000000"),
(99999999999.75, 1, "99999999999.7"),
(99999999999.75, 2, "99999999999.75"),
(34947628153.461784, 2, "34947628153.46"),
(34947628153.461784, 5, "34947628153.46178"),
] {
for sign in [1.0, -1.0] {
let shown = format_number_with_step(sign * x, dp, step, &f).unwrap();
let text = if sign < 0.0 { format!("−{expected}") } else { expected.into() };
assert_eq!(shown.text, text, "x={x:?}, step={step:e}, dp={dp}, ties={ties:?}");
assert_eq!(shown.value, sign * expected.parse::<f64>().unwrap());
}
}
}
}
}
#[test]
fn step_and_final_decimal_rounding_carry_independently() {
// 9.975 / .05 = 199.5: both modes increment the step index to 200.
// 9.96 / .03 = 332 exactly: only the final rounding carries to 10.0.
// 1.26 / .1 = 12.6: the snap is 1.3, then zero decimals give 1 (not 1.26).
// 1.49 / .5 = 2.98: the snap is 1.5, then zero decimals give 2 (not 1).
for (x, step, dp, even, away) in [
(9.975, 0.05, 2, "10.00", "10.00"),
(9.96, 0.03, 1, "10.0", "10.0"),
(1.26, 0.1, 0, "1", "1"),
(1.49, 0.5, 0, "2", "2"),
(100000000000.25, 0.1, 0, "100000000000", "100000000000"),
(100000000000.25, 0.1, 1, "100000000000.2", "100000000000.3"),
(100000000000.25, 0.1, 2, "100000000000.20", "100000000000.30"),
(999999999999.75, 0.25, 0, "1000000000000", "1000000000000"),
] {
for (ties, expected) in [(Ties::HalfEven, even), (Ties::HalfAwayFromZero, away)] {
let f = NumberFormat { ties, ..NumberFormat::default() };
for sign in [1.0, -1.0] {
let shown = format_number_with_step(sign * x, dp, step, &f).unwrap();
assert_eq!(shown.text, if sign < 0.0 { format!("−{expected}") } else { expected.into() });
assert_eq!(shown.value, sign * expected.parse::<f64>().unwrap());
}
}
}
}
#[test]
fn step_rejects_invalid_inputs_and_out_of_range_snap() {
let f = NumberFormat::default();
for step in [0.0, -0.0, -0.5, f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1e6_f64.next_up()] {
assert!(matches!(format_number_with_step(1.0, 2, step, &f), Err(DocError::Invalid(_))));
assert!(matches!(format_value_with_step(1.0, Length, "mm", 2, step, &f), Err(DocError::Invalid(_))));
}
for x in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1e12_f64.next_up(), -1e12_f64.next_up()] {
assert!(matches!(format_number_with_step(x, 2, 0.5, &f), Err(DocError::Invalid(_))));
}
for dp in [9, 255] {
assert!(matches!(format_number_with_step(1.0, dp, 0.5, &f), Err(DocError::Invalid(_))));
}
for x in [1e12, -1e12] {
for ties in [Ties::HalfEven, Ties::HalfAwayFromZero] {
let f = NumberFormat { ties, ..f };
for step in [600_000.0, 0.6] {
// The .6 snap has magnitude 1e12 + .2: reject it even though
// zero decimal places would otherwise bring it back to 1e12.
assert!(matches!(format_number_with_step(x, 0, step, &f), Err(DocError::Invalid(_))));
}
}
assert_eq!(format_number_with_step(x, 0, 1e6, &f).unwrap().value, x);
}
for (kind, unit) in [(Length, "deg"), (Angle, "mm"), (Length, "unknown")] {
assert!(matches!(format_value_with_step(1.0, kind, unit, 2, 0.5, &f), Err(DocError::Invalid(_))));
}
}
#[test]
fn step_units_options_and_input_preservation() {
let f = NumberFormat::default();
let input: f64 = 25.4 * 1.25;
let bits = input.to_bits();
assert_eq!(format_value_with_step(input, Length, "in", 1, 0.5, &f).unwrap().text, "1.0");
assert_eq!(input.to_bits(), bits);
assert_eq!(format_value_with_step(std::f64::consts::PI / 6.0, Angle, "deg", 1, 5.0, &f).unwrap().text, "30.0");
assert_eq!(format_value_with_step(1e15, Length, "m", 1, 10.0, &f).unwrap().value, 1e12);
let styled = NumberFormat { marker: DecimalMarker::Comma, groups: true, ..f };
assert_eq!(format_number_with_step(-12345.25, 2, 0.5, &styled).unwrap().text, "−12\u{202f}345,00");
let trim = NumberFormat { leading_zero: false, trailing_zeros: false, ..styled };
assert_eq!(format_number_with_step(-0.6, 3, 0.5, &trim).unwrap().text, "−,5");
assert_eq!(format_number_with_step(1.23456789, 8, 1e-8, &styled).unwrap().text, "1,234\u{202f}567\u{202f}89");
for (distance, expected) in [(0.4, "1.0"), (0.6, "0.5")] {
assert_eq!(format_value_with_step((0.75 - distance * 1e-4) * 1e-6, Length, "nm", 1, 0.5, &f).unwrap().text, expected);
}
for step in [1e6_f64.next_down(), 0.12345678901234567, 2.2250738585072014e-308] {
let shown = format_number_with_step(12345.6789, 8, step, &f).unwrap();
assert_eq!(shown.value, shown.text.parse::<f64>().unwrap());
}
// Explicitly keep both historical policies, including their different decimal ties.
assert_eq!(format_number(2.665, 2, &f).unwrap().text, "2.66");
assert_eq!(length_number(2.665, "mm", 2), "2.67");
}
#[test]
fn step_seeded_integer_reference_and_hostile_domain() {
let mut seed = 0x4896_1138_0abc_def1;
for _ in 0..20_000 {
// Independent bounded integer oracle: x and step are thousandths; dp=3.
let n = random(&mut seed) % 1_000_000;
let s = random(&mut seed) % 100_000 + 1;
for ties in [Ties::HalfEven, Ties::HalfAwayFromZero] {
let f = NumberFormat { ties, ..NumberFormat::default() };
let q = n / s;
let r = n % s;
let up = 2 * r > s || (2 * r == s && (ties == Ties::HalfAwayFromZero || q % 2 == 1));
let expected = (q + u64::from(up)) * s;
let text = format!("{}.{:03}", expected / 1000, expected % 1000);
let shown = format_number_with_step(n as f64 / 1000.0, 3, s as f64 / 1000.0, &f).unwrap();
assert_eq!(shown.text, text);
// Exercise arbitrary positive subnormals and normal steps without quotient overflow.
let step = f64::from_bits(random(&mut seed));
if step.is_finite() && step > 0.0 && step <= 1e6 {
let shown = format_number_with_step(n as f64, 3, step, &f).unwrap();
assert!(shown.value.is_finite() && shown.value >= 0.0);
assert_eq!(shown.value, shown.text.parse::<f64>().unwrap());
}
}
}
}
fn random(state: &mut u64) -> u64 {
*state ^= *state << 13;
*state ^= *state >> 7;
+54 -3
View File
@@ -452,8 +452,14 @@ pub enum AxisRef {
SketchAxis { axis: String },
/// World X / Y / Z axis through the origin.
World { axis: String },
/// Any line in the sketch plane, in world coordinates.
Line { origin: Position, dir: Dir },
/// Any line in the sketch plane, in world coordinates. The origin's coordinates are length
/// expressions, so the axis follows parameter changes.
Line {
#[serde(with = "point_expr")]
#[walk(placement)]
origin: PointExpr,
dir: Dir,
},
/// An edge or round face of the model (a straight or circular edge, a cylinder, cone or
/// torus face), found again on each evaluation: [`crate::construct::model_axis`].
Model { geo: GeoRef },
@@ -734,6 +740,10 @@ pub enum FeatureKind {
/// from the vertex instead of where the rounds meet (a constant-radius fillet only).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
setbacks: Vec<CornerSetback>,
/// How two rounds turn where they meet at a sharp side edge: in a mitre (the default) or
/// swept round it (a constant-radius, exact, rolling-corner fillet without setbacks only).
#[serde(default, skip_serializing_if = "FilletTurn::is_mitre")]
turn: FilletTurn,
/// Round each picked edge's tangent chain with it (the default: edges continuing it
/// smoothly at its ends, as far as they go), or only the picked edges.
#[serde(default = "yes", skip_serializing_if = "is_yes")]
@@ -1040,6 +1050,8 @@ pub enum FeatureKind {
Split {
body: BodyName,
plane: PlaneRef,
// The tool face is picked (and named), so the split follows it (#11674).
#[walk(picks_faces)]
#[serde(default, skip_serializing_if = "Option::is_none")]
tool: Option<FaceAt>,
/// A straight line of this sketch as the tool (#1552): the plane through it along the
@@ -1475,7 +1487,7 @@ pub enum FeatureKind {
},
/// Plastic lip (raised band) or groove (recess) along the rim face picked at `face`.
Lip {
face: Position,
face: FacePick,
#[walk(length = "Width")]
width: Expr,
#[walk(length = "Height")]
@@ -1971,6 +1983,38 @@ impl FilletCorner {
}
}
/// How two constant rounds of one radius turn round a sharp, straight, convex side edge square to
/// both on a plane, such as the rounds along a boss's base at its upright edges.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilletTurn {
/// The rounds run on to where they cross and meet in a mitre.
#[default]
Mitre,
/// The rounds end at the side edge and the corner is their section revolved about it, a
/// torus tangent to both (`solidcraft_kernel::FilletTurn::Swept`).
Swept,
}
/// Why a swept turn is refused with a fillet's other options: the kernel sweeps the turn of two
/// constant rounds of one radius, at its own corners only.
pub const SWEPT_TAKES: &str = "a swept corner turns two constant rounds of one radius: `turn: swept` takes `type: constant`, \
`corner: rolling`, no `setbacks` and `fit: exact` or `reduce`";
impl FilletTurn {
pub fn is_mitre(&self) -> bool {
*self == FilletTurn::Mitre
}
/// The kernel's turn.
pub fn kernel(self) -> solidcraft_kernel::FilletTurn {
match self {
FilletTurn::Mitre => solidcraft_kernel::FilletTurn::Mitre,
FilletTurn::Swept => solidcraft_kernel::FilletTurn::Swept,
}
}
}
/// How Extend continues a surface past its edges.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -2374,6 +2418,12 @@ pub enum HoleKind {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[walk(angle = "Tip angle")]
tip_angle: Option<Expr>,
/// Printing aid: two bridge layers this thick below the counterbore's floor (a slot along
/// X, then a square), so a printer bridges the step instead of sagging into it. Absent:
/// a plain counterbore.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[walk(length = "Bridge layer")]
bridge: Option<Expr>,
},
Countersink {
#[walk(length = "Countersink diameter")]
@@ -3860,6 +3910,7 @@ impl Document {
/// Read a design file's JSON (any format this build knows; older ones are upgraded).
pub fn from_json(s: &str) -> Result<Document> {
crate::render_resources::check_json_budget(s)?;
crate::render_texture_resources::check_json_budget(s)?;
let mut d = crate::format::read(s)?;
if d.features.len() > MAX_FEATURES || d.params.len() > MAX_PARAMS {
return Err(DocError::Invalid("document too large".into()));
+579 -97
View File
File diff suppressed because it is too large Load Diff
+305 -3
View File
@@ -16,14 +16,17 @@ use solidcraft_sketch::Sketch;
use crate::eval::{PlacementReads, body_world, hash_placement, placement_digest};
use crate::*;
/// Work counted on this thread: bodies moved, STEP texts digested, joint faces and bodies
/// Work counted on this thread: bodies moved, STEP/mesh contents digested, joint faces and bodies
/// measured, bodies searched for by name, features searched for by id, bodies a cut (or
/// intersect) ran its booleans on, bodies searched for the cylinder a tap marking lies on, and
/// bodies whose faces a tapped hole read for its wall.
/// intersect) ran its booleans on, bodies searched for the cylinder a tap marking lies on,
/// bodies whose faces a tapped hole read for its wall, intersects run to find a pattern's copies
/// that cut nothing, and the tools a cut of many tools is made with (one when they are cut in one
/// go).
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(super) struct Counts {
pub moved: usize,
pub digested: usize,
pub mesh_digested: usize,
pub joint_faces: usize,
pub measures: usize,
pub body_searches: usize,
@@ -31,6 +34,8 @@ pub(super) struct Counts {
pub cut: usize,
pub cylinder_search: usize,
pub tapped_wall: usize,
pub miss_intersects: usize,
pub cut_tools: usize,
}
thread_local! {
@@ -38,6 +43,7 @@ thread_local! {
Cell::new(Counts {
moved: 0,
digested: 0,
mesh_digested: 0,
joint_faces: 0,
measures: 0,
body_searches: 0,
@@ -45,6 +51,8 @@ thread_local! {
cut: 0,
cylinder_search: 0,
tapped_wall: 0,
miss_intersects: 0,
cut_tools: 0,
})
};
}
@@ -1484,3 +1492,297 @@ fn a_hole_into_a_rim_still_bores_its_sheet_beside_far_ones() {
let bore = std::f64::consts::PI * (d / 2.0).powi(2) * 60.0;
assert!((removed - bore).abs() < 0.02 * bore, "removed {removed} of {bore}");
}
/// A 1.5 mm disc of 50 mm radius with a 3.5 mm through hole at `radius` on X, patterned `count`
/// times around Z (#9007). The pattern's id.
fn disc_with_hole_pattern(doc: &mut Document, radius: f64, count: usize) -> u64 {
let disc = FeatureKind::Cylinder {
base: point_expr(Vec3::ZERO),
axis: Dir(Vec3::Z),
radius: "50".into(),
height: "1.5".into(),
operation: Operation::NewBody,
references: Vec::new(),
};
doc.add_feature(disc, Some("Disc")).unwrap();
let hole: FeatureKind = serde_json::from_value(serde_json::json!({
"type": "hole", "position": [radius, 0, 1.5], "direction": [0, 0, -1], "diameter": "3.5",
}))
.unwrap();
let hole = doc.add_feature(hole, Some("Hole")).unwrap();
hole_pattern(doc, hole, count)
}
fn hole_pattern(doc: &mut Document, hole: u64, count: usize) -> u64 {
let pattern: FeatureKind = serde_json::from_value(serde_json::json!({
"type": "pattern", "features": [hole],
"pattern": {"type": "circular", "origin": [0, 0, 0], "axis": [0, 0, 1], "count": count.to_string(), "angle": "360 deg"},
}))
.unwrap();
doc.add_feature(pattern, None).unwrap()
}
fn disc_volume(model: &Model) -> f64 {
let st = model.state();
assert_eq!(st.bodies.len(), 1);
kernel::measure(&st.bodies[0].body).unwrap().volume
}
#[test]
fn a_circular_pattern_of_a_through_hole_cuts_its_copies_at_once() {
// The copies' tools run twice the disc's size along Z, so the kernel's box test, which
// grows each box by a share of the tool's size, finds them meeting (#9007); they are still
// 4.3 mm apart, centre to centre, and are cut in one go. No copy is intersected with the
// disc to see whether it takes material: each passes through the disc's material.
let mut doc = Document::new("sieve");
let pattern = disc_with_hole_pattern(&mut doc, 20.0, 29);
let mut model = Model::new();
let work = counted(|| model.evaluate(&doc));
assert!(model.results.iter().all(|r| r.error.is_none() && r.warning.is_none()), "{:?}", model.results);
assert_eq!((work.cut_tools, work.miss_intersects), (1, 0), "{work:?}");
let hole = std::f64::consts::PI * 1.75 * 1.75 * 1.5;
let disc = std::f64::consts::PI * 2500.0 * 1.5;
let v = disc_volume(&model);
assert!((v - (disc - 29.0 * hole)).abs() < 1e-3 * hole, "{v}");
// The same copies again, landing in the holes cut already: each is intersected, and each
// still counts as a copy that removes no material.
let hole_id = doc.features.iter().find(|f| f.name == "Hole").unwrap().id;
hole_pattern(&mut doc, hole_id, 29);
let work = counted(|| model.evaluate(&doc));
let again = model.results.last().unwrap();
assert!(again.error.is_none(), "{again:?}");
// (The kernel may add a note on a retry of a boolean on faces that lie on each other.)
assert!(again.warning.as_deref().is_some_and(|w| w.starts_with("28 of 28 cut copies remove no material")), "{again:?}");
assert_eq!(work.miss_intersects, 28, "{work:?}");
assert!((disc_volume(&model) - v).abs() < 1e-3 * hole);
assert!(model.results.iter().any(|r| r.id == pattern && r.warning.is_none()));
}
#[test]
fn copies_of_a_hole_that_come_close_are_cut_one_by_one() {
// At 8.5 mm radius, 16 copies of a 3.5 mm hole overlap their neighbours: they are not apart,
// and each is cut on its own, as before.
let mut doc = Document::new("crowded");
disc_with_hole_pattern(&mut doc, 8.5, 16);
let mut model = Model::new();
let work = counted(|| model.evaluate(&doc));
assert!(model.results.iter().all(|r| r.error.is_none() && r.warning.is_none()), "{:?}", model.results);
assert_eq!((work.cut_tools, work.miss_intersects), (15, 0), "{work:?}");
// At 9 mm they come within 0.012 mm of each other without touching: their exact shapes are
// apart, so they are cut in one go, and all 16 holes are cut.
let mut doc = Document::new("close");
disc_with_hole_pattern(&mut doc, 9.0, 16);
let mut model = Model::new();
let work = counted(|| model.evaluate(&doc));
assert!(model.results.iter().all(|r| r.error.is_none() && r.warning.is_none()), "{:?}", model.results);
assert_eq!((work.cut_tools, work.miss_intersects), (1, 0), "{work:?}");
let hole = std::f64::consts::PI * 1.75 * 1.75 * 1.5;
let disc = std::f64::consts::PI * 2500.0 * 1.5;
let v = disc_volume(&model);
assert!((v - (disc - 16.0 * hole)).abs() < 1e-3 * hole, "{v}");
}
/// A ring (25 mm outside, 16 mm bore, 55 mm high) with a blind groove, 8 mm wide and 30 mm deep,
/// cut into its bore from below, in a circular pattern of ten about Z: their boxes, grown as the
/// kernel grows them, meet, their exact shapes are 1.9 mm apart. The ring's faces counted as its
/// surfaces are, and the work.
fn grooved_ring_faces() -> (usize, Counts) {
let mut doc = Document::new("grooves");
let cylinder = |base: Vec3, radius: &str, height: &str, operation: Operation| FeatureKind::Cylinder {
base: point_expr(base),
axis: Dir(Vec3::Z),
radius: radius.into(),
height: height.into(),
operation,
references: Vec::new(),
};
doc.add_feature(cylinder(Vec3::ZERO, "25", "55", Operation::NewBody), Some("Ring")).unwrap();
doc.add_feature(cylinder(Vec3::new(0.0, 0.0, -1.0), "16", "57", Operation::Cut), Some("Bore")).unwrap();
let groove = doc.add_feature(cylinder(Vec3::new(16.0, 0.0, -1.0), "4", "31", Operation::Cut), Some("Groove")).unwrap();
hole_pattern(&mut doc, groove, 10);
let mut model = Model::new();
let work = counted(|| model.evaluate(&doc));
assert!(model.results.iter().all(|r| r.error.is_none() && r.warning.is_none()), "{:?}", model.results);
let st = model.state();
assert_eq!(st.bodies.len(), 1);
(kernel::measure(&st.bodies[0].body).unwrap().merged.faces, work)
}
#[test]
fn copies_that_notch_the_same_round_face_cut_at_once_leave_the_faces_of_one_by_one() {
// The ten grooves notch the same round bore. Cut all in one go, they leave it in as many
// faces as cutting them one after another does (#9007).
let (patterned, work) = grooved_ring_faces();
let cut = |a: kernel::Body, b: &kernel::Body| kernel::boolean(&a, b, kernel::BoolOp::Cut).unwrap().unwrap();
let ring =
cut(kernel::cylinder(Vec3::ZERO, Vec3::Z, 25.0, 55.0).unwrap(), &kernel::cylinder(Vec3::new(0.0, 0.0, -1.0), Vec3::Z, 16.0, 57.0).unwrap());
let groove = kernel::cylinder(Vec3::new(16.0, 0.0, -1.0), Vec3::Z, 4.0, 31.0).unwrap();
let one_by_one = (0..10).fold(ring, |ring, k| {
let copy = kernel::transform(&groove, Vec3::ZERO, Vec3::ZERO, Vec3::Z, (36.0 * k as f64).to_radians()).unwrap();
cut(ring, &copy)
});
assert_eq!(patterned, kernel::measure(&one_by_one).unwrap().merged.faces);
assert_eq!(work.cut_tools, 1, "{work:?}");
}
#[test]
fn tools_are_apart_only_when_they_neither_meet_nor_nest() {
let rod = |x: f64, y: f64, r: f64| kernel::cylinder(Vec3::new(x, y, -100.0), Vec3::Z, r, 200.0).unwrap();
// Long rods whose boxes, grown as the kernel grows them, meet: 0.5 mm apart.
assert!(super::tools_clearly_apart(&[rod(0.0, 0.0, 1.0), rod(2.5, 0.0, 1.0)]));
// Touching, crossing and nested rods are not apart.
assert!(!super::tools_clearly_apart(&[rod(0.0, 0.0, 1.0), rod(2.0, 0.0, 1.0)]));
assert!(!super::tools_clearly_apart(&[rod(0.0, 0.0, 1.0), rod(1.5, 0.0, 1.0)]));
assert!(!super::tools_clearly_apart(&[rod(0.0, 0.0, 3.0), rod(0.5, 0.0, 1.0)]));
let tube = kernel::boolean(&rod(0.0, 0.0, 5.0), &rod(0.0, 0.0, 4.0), kernel::BoolOp::Cut).unwrap().unwrap();
assert!(!super::tools_clearly_apart(&[tube.clone(), rod(0.0, 0.0, 4.5)]));
// A rod in the tube's bore, clear of its wall, is apart from it.
assert!(super::tools_clearly_apart(&[tube, rod(0.0, 0.0, 3.0)]));
}
fn imported_meshes() -> Document {
let mut doc = Document::new("reference meshes");
let comp = doc.add_component(Some("Part"), 0).unwrap();
let meshes = (0..2)
.map(|i| MeshData {
name: format!("Mesh{i}"),
positions: [0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 4.0].into(),
triangles: [0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3].into(),
color: Some([0.2, 0.4, 0.6]),
})
.collect();
let id = doc.add_feature(FeatureKind::MeshImport { file: "tetrahedra.stl".into(), meshes }, Some("Meshes")).unwrap();
doc.feature_mut(id).unwrap().component = comp;
doc
}
fn meshes_mut(doc: &mut Document) -> &mut Vec<MeshData> {
let FeatureKind::MeshImport { meshes, .. } = &mut doc.features[0].kind else { panic!("mesh fixture") };
meshes
}
fn same_as_fresh(doc: &Document, model: &Model) {
let mut fresh = Model::new();
fresh.no_cache = true;
fresh.evaluate(doc);
assert_eq!(model.results.iter().map(|r| r.fingerprint).collect::<Vec<_>>(), fresh.results.iter().map(|r| r.fingerprint).collect::<Vec<_>>());
let (actual, expected) = (model.state(), fresh.state());
assert_eq!(actual.bodies.len(), expected.bodies.len());
for (a, b) in actual.bodies.iter().zip(&expected.bodies) {
assert_eq!(a.name, b.name);
assert_eq!(a.mesh().positions, b.mesh().positions);
assert_eq!(a.mesh().triangles, b.mesh().triangles);
assert_eq!(a.body.color(), b.body.color());
}
assert!(model.results.iter().all(|r| r.error.is_none()));
}
#[test]
fn unchanged_mesh_payloads_are_not_read_again_for_rigid_motion() {
let mut doc = imported_meshes();
let mut model = Model::new();
assert_eq!(counted(|| model.evaluate(&doc)).mesh_digested, 2);
for k in 1..5 {
doc = doc.clone(); // Undo/preview copies share the arrays, not the document itself.
doc.occurrences[0].transform = rigid(Vec3::new(f64::from(k), 2.0, 3.0), Vec3::ZERO, Vec3::Z, 0.1 * f64::from(k));
assert_eq!(
counted(|| {
model.evaluate(&doc);
assert!(model.is_current(&doc));
})
.mesh_digested,
0
);
assert_eq!(model.last_recomputed, 0);
same_as_fresh(&doc, &model);
}
let saved = serde_json::to_string(&doc).unwrap();
let reopened: Document = serde_json::from_str(&saved).unwrap();
assert_eq!(counted(|| model.evaluate(&reopened)).mesh_digested, 2, "new array identities must be checked");
assert_eq!(model.last_recomputed, 0, "equal content has equal fingerprints");
same_as_fresh(&reopened, &model);
}
#[test]
fn mesh_payload_edits_invalidate_the_cache_and_undo_restores_the_geometry() {
let mut doc = imported_meshes();
let original = doc.clone();
let mut model = Model::new();
model.evaluate(&doc);
let fingerprint = model.results[0].fingerprint;
Arc::make_mut(&mut meshes_mut(&mut doc)[0].positions)[3] = 5.0;
assert!(!model.is_current(&doc));
assert_eq!(counted(|| model.evaluate(&doc)).mesh_digested, 1);
assert_ne!(model.results[0].fingerprint, fingerprint);
same_as_fresh(&doc, &model);
let edited = doc.clone();
Arc::make_mut(&mut meshes_mut(&mut doc)[0].triangles).swap(0, 1);
assert_eq!(counted(|| model.evaluate(&doc)).mesh_digested, 1);
same_as_fresh(&doc, &model);
for restored in [&original, &edited, &original] {
model.evaluate(restored);
same_as_fresh(restored, &model);
}
assert_eq!(model.results[0].fingerprint, fingerprint);
}
#[test]
fn mesh_metadata_and_order_remain_part_of_the_fingerprint() {
let mut doc = imported_meshes();
let mut model = Model::new();
model.evaluate(&doc);
for edit in 0..7 {
let before = model.results[0].fingerprint;
match edit {
0 => meshes_mut(&mut doc)[0].name = "Renamed".into(),
1 => meshes_mut(&mut doc)[0].color = Some([0.8, 0.1, 0.3]),
2 => doc.features[0].name = "Renamed feature".into(),
3 => meshes_mut(&mut doc).swap(0, 1),
4 => {
meshes_mut(&mut doc).pop();
}
5 => {
let mesh = meshes_mut(&mut doc)[0].clone();
meshes_mut(&mut doc).push(MeshData { name: "Added".into(), ..mesh });
}
_ => {
let FeatureKind::MeshImport { file, .. } = &mut doc.features[0].kind else { panic!("mesh fixture") };
*file = "renamed.stl".into();
}
}
assert!(!model.is_current(&doc));
let work = counted(|| model.evaluate(&doc));
if edit < 3 || edit == 6 {
assert_eq!(work.mesh_digested, 0, "metadata alone: {edit}");
}
assert_ne!(model.results[0].fingerprint, before, "edit {edit}");
same_as_fresh(&doc, &model);
}
}
#[test]
fn an_added_and_deleted_sketch_entity_keeps_its_fingerprint() {
let mut doc = Document::new("sketch fingerprint");
let id = doc.add_feature(line_sketch(PlaneRef::Origin { name: "XY".into() }, Vec2::new(10.0, 0.0)), None).unwrap();
let mut model = Model::new();
model.evaluate(&doc);
let key = model.state().sketch_keys.get(&id).copied();
let json = super::feature_json(doc.feature(id).unwrap(), &super::ImportDigests::default());
// Make and delete an id: the counters advance, but the geometry is the same.
{
let sk = doc.sketch_mut(id).unwrap();
let a = sk.add_point(Vec2::new(2.0, 2.0), None).unwrap();
let b = sk.add_point(Vec2::new(3.0, 2.0), None).unwrap();
let line = sk.add_line_pts(a, b, None).unwrap();
sk.remove_curves(&[line]);
sk.remove_points(&[b]);
sk.remove_points(&[a]);
}
let json_after = super::feature_json(doc.feature(id).unwrap(), &super::ImportDigests::default());
assert_eq!(json, json_after, "deleted ids are not geometry");
model.evaluate(&doc);
assert_eq!(model.state().sketch_keys.get(&id).copied(), key);
assert_eq!((model.last_recomputed, model.last_restored), (0, 0));
}
+23 -5
View File
@@ -8,7 +8,7 @@ use crate::sheet;
use solidcore_record::{Loop2, Seg2};
use solidcraft_geom::{Aabb3, Ring};
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState) -> Result<()> {
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, warning: &mut Option<String>) -> Result<()> {
match &f.kind {
FeatureKind::Emboss { sketch, profiles, depth, deboss, targets, face } => {
let ss = st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?;
@@ -59,7 +59,20 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
if st.bodies.is_empty() {
return Err(DocError::Invalid("there is no body to emboss".into()));
}
apply_op(doc, st, f, tools, if *deboss { Operation::Cut } else { Operation::Join }, &crate::plain(targets))
// #10940: an emboss or deboss whose tool lies clear of the body (a sketch on the far
// side of the material, a depth that stops short) used to report success and change
// nothing. Say so: the body is compared before and after, as the command's reply does.
let before: Vec<Body> = st.bodies.iter().map(|b| b.body.clone()).collect();
apply_op(doc, st, f, tools, if *deboss { Operation::Cut } else { Operation::Join }, &crate::plain(targets))?;
let unchanged = st.bodies.len() == before.len() && st.bodies.iter().zip(&before).all(|(b, old)| old.same(&b.body));
if unchanged {
let what = if *deboss { "deboss" } else { "emboss" };
append_warning(
warning,
format!("the {what} changed nothing; check where its profile lies, which side of the face the sketch is on, and the depth"),
);
}
Ok(())
}
FeatureKind::Rib { .. } => {
let tools = rib_tools(vals, st, f, None)?;
@@ -2318,11 +2331,16 @@ pub(super) fn plastic_eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &F
_ => false,
};
let (Some(_), Some(_), false) = (outer, openings.first(), alike) else {
// A solid box top has no opening: Shell that face first, then pick the rim
// around the cavity (#10437).
let what = match (loops.len(), openings.len()) {
(0 | 1, _) => "pick the rim face of a shelled body (a face with an inner and an outer edge)".to_string(),
(_, 0) => "pick the rim face of a shelled body: this face's inner edges are where other features stand on it, not an opening"
(0 | 1, _) => "Lip needs the rim of a shelled body: Shell a face open first, then pick the rim around that opening (a face with an outer edge and an inner hole)"
.to_string(),
(_, k) => format!("pick the rim face of a shelled body (round one opening): this face has {k} openings alike in size"),
(_, 0) => "Lip needs a rim around an opening: this face's inner edges are where other features stand on it, not a shell cavity. Shell a face open, then pick that rim"
.to_string(),
(_, k) => format!(
"Lip needs one clear opening as the rim: this face has {k} openings alike in size (for example a plate of holes). Shell one cavity, or pick a rim that rings a single opening"
),
};
return Err(DocError::Invalid(what));
};
+7 -4
View File
@@ -63,13 +63,16 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
let t = val(vals, thickness, Kind::Length)?;
let mut tools = Vec::new();
for n in bodies {
let b = st.body(n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
tools.push(kernel::thicken(&b.body, t, *symmetric)?);
let b = st.body(n).cloned().ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
// Kept face, offset and edge walls. Carry that history so a sketch can attach
// to the solid's faces and follow the thickness (#7641).
let thickened = kernel::thicken(&b.body, t, *symmetric)?;
tools.push(Traced::step(thickened, &[&b.traced()]));
}
if tools.is_empty() {
return Err(DocError::Invalid("pick the surfaces to thicken".into()));
}
apply_op(doc, st, f, tools, *operation, &crate::plain(targets))
apply_traced(doc, st, f, tools, *operation, &crate::plain(targets))
}
FeatureKind::SurfaceTrim { body, plane, tool, keep, keep_more, curves, with, keep_with, stitch, extend } => {
let i = st.bodies.iter().position(|b| &b.name == body).ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
@@ -308,7 +311,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?,
f.component,
)?;
let (o, d) = revolve_axis(&ss, axis, &*super::axis_state(doc, st, f.component, axis)?, &|name| {
let (o, d) = revolve_axis(vals, &ss, axis, &*super::axis_state(doc, st, f.component, axis)?, &|name| {
construction_axis_in(Some(doc), st, name, f.component)
})?;
let a = val(vals, angle, Kind::Angle)?;
+208 -22
View File
@@ -103,6 +103,11 @@ enum End {
/// At (or within half a pitch of) an end of the face where material goes on past it, a
/// shoulder, a head, a blind hole's bottom: the groove stops short, clear of it.
Closed,
/// At an end of an external thread's face where a shoulder starts and stays beyond the face's
/// end: the groove runs on through the plane of that end and is trimmed there, so the root
/// reaches the shoulder at every angle, a nut bottoms out on it, and nothing past the plane
/// is cut.
Shoulder,
/// Inside the face: the groove stops square there.
Inside,
}
@@ -121,6 +126,9 @@ pub(super) struct ThreadOn {
/// A modelled internal thread in a hole wider than it needs: the radius the bore is narrowed
/// to (the thread's minor radius) before the groove is cut, with the hole's own radius.
narrow: Option<f64>,
/// A shoulder keeps the square stop because the kernel couldn't check whether it stays beyond
/// the face's end.
shoulder_unchecked: bool,
}
/// The free-end test of a face's two ends (`side` 0 at its start, 1 at its end), each made
@@ -160,6 +168,22 @@ impl FaceEnds<'_> {
}
Ok(free)
}
/// Does the shoulder at this closed end stay beyond the plane of the face's end? Not where it
/// rises back along the face (a slanted plate, a boss on a round body), which a groove
/// running to that plane would cut into: there is material outside the face just inside it.
/// The caller keeps the square stop, and says so, when the kernel can't make the check.
fn clear_of_end_plane(&self, side: usize) -> Result<bool> {
#[cfg(test)]
if tests::FAIL_CLEARANCE.get() {
return Err(kernel::KernelError::Failed("clearance refused for the test".into()).into());
}
let (p, e) = (self.pitch, self.pitch * 1e-3);
let inside = if side == 0 { (self.cyl.start + e, self.cyl.start + p) } else { (self.cyl.end - p, self.cyl.end - e) };
// All the radii the groove cuts beyond the face; only touching their rims is not in its
// way, as in `free`.
kernel::material_in_ring(self.body, self.cyl, self.beyond.0 + e, self.beyond.1 - e, inside).map(|hit| !hit).map_err(DocError::from)
}
}
/// A thread on the cylindrical face at `p`: its size, and the part of the face it runs over.
@@ -333,11 +357,23 @@ pub(super) fn thread_on(st: &ModelState, feature: u64, p: Vec3, ask: &ThreadAsk)
// How each end of the thread meets the face: at a face end, or within half a pitch (and its
// runout) of one, where the groove would reach past it, that end's free-end test decides.
let near = |at: f64, side: usize| if side == 0 { at - lo } else { hi - at } < pitch / 2.0 + ease.get(side).copied().unwrap_or(0.0);
// A shoulder whose check the kernel couldn't make keeps the square stop, and the feature says so.
let mut unchecked = false;
let mut kind = |at: f64, side: usize| -> Result<End> {
Ok(if !ask.modeled || !near(at, side) {
End::Inside
} else if !ends.free(side)? {
End::Closed
let at_end = (if side == 0 { at - lo } else { hi - at }) < pitch * 1e-3;
let square = ease.get(side).copied().unwrap_or(0.0) == 0.0;
let clear = at_end && square && !cyl.internal && !form.is_taper() && {
let check = ends.clear_of_end_plane(side);
unchecked |= matches!(check, Err(DocError::Kernel(_)));
match check {
Err(DocError::Kernel(_)) => false,
other => other?,
}
};
if clear { End::Shoulder } else { End::Closed }
} else if (if side == 0 { at - lo } else { hi - at }) < pitch * 1e-3 {
End::Free
} else {
@@ -373,7 +409,7 @@ pub(super) fn thread_on(st: &ModelState, feature: u64, p: Vec3, ask: &ThreadAsk)
.then(|| form.groove(major, pitch, true))
.filter(|g| !(g.root_r - cyl.radius >= g.basic_depth / 4.0))
.map(|g| g.crest_r);
Ok(ThreadOn { info, cyl, ends, steep: ask.steep, ease, from: side, narrow })
Ok(ThreadOn { info, cyl, ends, steep: ask.steep, ease, from: side, narrow, shoulder_unchecked: unchecked })
}
/// The end of the face (0: its start, 1: its end) a thread of limited length starts at: the end
@@ -536,7 +572,9 @@ fn thread_grooves(
// Past a free end of the face the groove runs out into the air, a pitch beyond it. Before
// a closed one it stops with its section clear of the face's end (its centre as far inside
// as the section reaches that way), so it never cuts into the shoulder there; inside the
// face it stops square at the threaded length's end.
// face it stops square at the threaded length's end. At a shoulder it can be trimmed against
// ([`End::Shoulder`]) its whole section runs past the plane of the face's end, so every angle
// of the last turn is cut down to that plane ([`trim_at_shoulders`] takes off the rest).
//
// A runout `e` at an end (ISO 3508) fades the groove out over `e`: its section moves out of
// the material (an external thread's away from the axis, past the surface; an internal
@@ -548,12 +586,14 @@ fn thread_grooves(
(End::Free, false) => t.start - p,
(End::Free, true) => t.start,
(End::Closed, _) => (t.start - ease[0]).max(face.0 + w_lo),
(End::Shoulder, _) => face.0 - w_hi - 0.1 * p,
(End::Inside, _) => t.start - ease[0],
};
let s1 = match (ends[1], ease[1] > 0.0) {
(End::Free, false) => t.end + p,
(End::Free, true) => t.end,
(End::Closed, _) => (t.end + ease[1]).min(face.1 - w_hi),
(End::Shoulder, _) => face.1 + w_lo + 0.1 * p,
(End::Inside, _) => t.end + ease[1],
};
if !(s1 - s0 > p * 1e-3) {
@@ -698,6 +738,24 @@ fn taper_envelope(t: &ThreadInfo, radius: f64, ends: [End; 2], (d0, from): (f64,
Ok(ring.pop())
}
/// The groove with what runs past a shoulder's plane taken off ([`End::Shoulder`]), so cutting it
/// leaves the shoulder whole.
fn trim_at_shoulders(mut tool: Body, axis: Vec3, ends: [End; 2], face: (f64, f64), k: UnitVec3) -> Result<Body> {
for (end, at, keep_positive) in [(ends[0], face.0, true), (ends[1], face.1, false)] {
if end != End::Shoulder {
continue;
}
let plane = Plane::from_normal(axis + k * at, *k).ok_or_else(|| DocError::Invalid("thread axis".into()))?;
// An error of the cut, like the kernel's own: `cut` then falls back to the square stop.
let missed = || kernel::KernelError::Failed("the thread's groove misses its face".into());
let kept = kernel::half_space(&tool, &plane, keep_positive)?.0;
#[cfg(test)]
let kept = kept.filter(|_| !tests::EMPTY_TRIM.get());
tool = kept.ok_or_else(missed)?;
}
Ok(tool)
}
/// Evaluate a thread feature: a cosmetic thread is listed in the model's threads, a modelled
/// one cut into its body.
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, warning: &mut Option<String>) -> Result<()> {
@@ -768,7 +826,13 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
None => note,
});
}
cut(st, on, (*tip, angle, depth))
if let Some(note) = cut(st, on, (*tip, angle, depth))? {
*warning = Some(match warning.take() {
Some(w) => format!("{w}; {note}"),
None => note,
});
}
Ok(())
}
/// A runout as (mm, pitches): ISO 3508's standard 2.5 P and short 1.25 P, or a length.
@@ -783,9 +847,10 @@ pub(super) fn runout(vals: &BTreeMap<String, Value>, e: &Option<crate::ThreadRun
/// Cut a modelled thread resolved on its face into its body: the grooves (SMLib's exact screw
/// sweeps, a taper's crests by its envelope) by one B-rep boolean, then the chamfered tip
/// (`tip`, with its angle and depth when given).
pub(super) fn cut(st: &mut ModelState, on: ThreadOn, (tip, angle, depth): (ThreadTip, Option<f64>, Option<f64>)) -> Result<()> {
let ThreadOn { info: t, cyl, ends, steep, ease, from: from_side, narrow } = on;
/// (`tip`, with its angle and depth when given). What the user should be told about the result, if
/// anything, is returned.
pub(super) fn cut(st: &mut ModelState, on: ThreadOn, (tip, angle, depth): (ThreadTip, Option<f64>, Option<f64>)) -> Result<Option<String>> {
let ThreadOn { info: t, cyl, ends, steep, ease, from: from_side, narrow, shoulder_unchecked } = on;
let i = st.bodies.iter().position(|b| b.name == t.body).ok_or_else(|| DocError::Invalid("threaded body".into()))?;
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
let (mut input, radius) = (mb.traced(), cyl.radius);
@@ -822,21 +887,46 @@ pub(super) fn cut(st: &mut ModelState, on: ThreadOn, (tip, angle, depth): (Threa
}
false => None,
};
let mut grooves = Vec::new();
for g in thread_grooves(&t, groove_radius, (cyl.start, cyl.end), ends, steep, ease, taper)? {
let section = Region2 { outer: solidcore_record::Loop2::polygon(&g.section), holes: Vec::new() };
grooves.push(kernel::sweep_helix_radial(&g.plane, &section, g.base, g.axis, g.pitch, g.turns, &g.law)?);
}
if let Some(taper) = taper
&& let Some(envelope) = taper_envelope(&t, radius, ends, taper)?
{
grooves.push(envelope);
}
let tool = match grooves.as_slice() {
[one] => one.clone(),
many => kernel::union_all(&many.iter().collect::<Vec<_>>())?,
let cut_grooves = |ends: [End; 2]| -> Result<(Option<Body>, usize)> {
let mut grooves = Vec::new();
for g in thread_grooves(&t, groove_radius, (cyl.start, cyl.end), ends, steep, ease, taper)? {
let section = Region2 { outer: solidcore_record::Loop2::polygon(&g.section), holes: Vec::new() };
grooves.push(kernel::sweep_helix_radial(&g.plane, &section, g.base, g.axis, g.pitch, g.turns, &g.law)?);
}
if let Some(taper) = taper
&& let Some(envelope) = taper_envelope(&t, radius, ends, taper)?
{
grooves.push(envelope);
}
let tool = match grooves.as_slice() {
[one] => one.clone(),
many => kernel::union_all(&many.iter().collect::<Vec<_>>())?,
};
let tool = trim_at_shoulders(tool, t.axis_point, ends, (cyl.start, cyl.end), t.axis)?;
let cut = kernel::boolean(&input.body, &tool, BoolOp::Cut)?;
// Now rather than at the feature's end, where it could only fail the feature.
if ends.contains(&End::Shoulder)
&& let Some(body) = &cut
{
kernel::check_kept(body)?;
}
Ok((cut, tool.face_count()))
};
let (nb, groove_faces) = (kernel::boolean(&input.body, &tool, BoolOp::Cut)?, tool.face_count());
// A design that built with the square stop keeps building: where the kernel can't make the
// trimmed cut, the groove stops clear of the shoulder as it did, and the feature says so.
let mut made = cut_grooves(ends);
let mut square_stop = shoulder_unchecked;
if matches!(made, Err(DocError::Kernel(_))) && ends.contains(&End::Shoulder) {
made = cut_grooves(ends.map(|e| if e == End::Shoulder { End::Closed } else { e }));
square_stop = true;
}
let note = (square_stop && made.is_ok()).then(|| {
format!(
"the {} thread's groove could not be run down to the shoulder here, so it stops clear of it and its last turn is left uncut",
t.designation
)
});
let (nb, groove_faces) = made?;
let nb = nb.ok_or_else(|| DocError::Invalid("the thread removed everything".into()))?;
// The groove's faces have no role to name them by.
let groove = crate::lineage::Lineage::unnamed_faces(groove_faces);
@@ -854,7 +944,7 @@ pub(super) fn cut(st: &mut ModelState, on: ThreadOn, (tip, angle, depth): (Threa
if let Some(slot) = st.bodies.get_mut(i) {
*slot = ModelBody::traced_new(mb.name, nb, mb.feature);
}
Ok(())
Ok(note)
}
/// The chamfered tip of an external thread (ISO 4753, chamfered end CH) at the free ends `tip`
@@ -937,3 +1027,99 @@ fn tip_cones(
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
thread_local! {
/// Make [`trim_at_shoulders`] find nothing of the groove left.
pub(super) static EMPTY_TRIM: Cell<bool> = const { Cell::new(false) };
/// Make the rising-shoulder check fail as the kernel would.
pub(super) static FAIL_CLEARANCE: Cell<bool> = const { Cell::new(false) };
}
fn ask() -> ThreadAsk<'static> {
ThreadAsk {
form: None,
designation: Some("M10"),
hand: ThreadHand::Right,
starts: 1,
steep: SteepFlank::default(),
extent: Extent::Full,
from: None,
modeled: true,
ease_out: (0.0, 0.0),
tapped: false,
old_auto_size: false,
}
}
/// How a thread's ends meet the face at (5, 0, z) of `body`.
fn ends_of(body: Body, z: f64) -> [End; 2] {
let mut st = ModelState::default();
st.bodies.push(ModelBody::new("Part".into(), body, 1));
thread_on(&st, 1, Vec3::new(5.0, 0.0, z), &ask()).unwrap().ends
}
/// A shoulder that stays beyond the face's end takes the trim. One rising back along the face
/// (a slanted plate, a boss on a round body) keeps the square stop.
#[test]
fn only_a_shoulder_that_stays_beyond_the_faces_end_takes_the_trim() {
let z = Vec3::new(0.0, 0.0, 1.0);
let join = |a: &Body, b: &Body| kernel::boolean(a, b, BoolOp::Union).unwrap().unwrap();
let boss = kernel::cylinder(Vec3::new(0.0, 0.0, 2.0), z, 5.0, 26.0).unwrap();
let plate = kernel::box_solid(Vec3::new(-20.0, -20.0, -6.0), Vec3::new(20.0, 20.0, 2.0)).unwrap();
let flat = ends_of(join(&plate, &boss), 20.0);
assert!(flat.contains(&End::Shoulder), "{flat:?}");
let tilted = kernel::cylinder(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.2, 0.0, 1.0), 20.0, 6.0).unwrap();
let slanted = ends_of(join(&tilted, &boss), 20.0);
assert!(slanted.contains(&End::Closed) && !slanted.contains(&End::Shoulder), "{slanted:?}");
let round = kernel::cylinder(Vec3::new(0.0, -20.0, 0.0), Vec3::new(0.0, 1.0, 0.0), 10.0, 40.0).unwrap();
let on_round = ends_of(join(&round, &boss), 20.0);
assert!(on_round.contains(&End::Closed) && !on_round.contains(&End::Shoulder), "{on_round:?}");
}
/// Where the kernel can't check the shoulder, the thread still builds with the square stop, and
/// the feature says its last turn is left uncut.
#[test]
fn a_shoulder_the_kernel_cannot_check_keeps_the_square_stop_and_says_so() {
let plate = kernel::box_solid(Vec3::new(-20.0, -20.0, 0.0), Vec3::new(20.0, 20.0, 6.0)).unwrap();
let boss = kernel::cylinder(Vec3::new(0.0, 0.0, 6.0), Vec3::new(0.0, 0.0, 1.0), 5.0, 16.0).unwrap();
let body = kernel::boolean(&plate, &boss, BoolOp::Union).unwrap().unwrap();
let mut st = ModelState::default();
st.bodies.push(ModelBody::new("Part".into(), body, 1));
FAIL_CLEARANCE.set(true);
let on = thread_on(&st, 1, Vec3::new(5.0, 0.0, 14.0), &ask());
FAIL_CLEARANCE.set(false);
let on = on.unwrap();
assert!(on.ends.contains(&End::Closed) && !on.ends.contains(&End::Shoulder), "{:?}", on.ends);
let note = cut(&mut st, on, (ThreadTip::None, None, None)).unwrap();
assert!(note.is_some_and(|n| n.contains("M10") && n.contains("left uncut")));
}
/// Where the trim leaves nothing of the groove, a thread at a shoulder that built with the
/// square stop still builds with it, and the feature says its last turn is left uncut.
#[test]
fn a_shoulder_cut_the_kernel_refuses_falls_back_to_the_square_stop_and_says_so() {
let plate = kernel::box_solid(Vec3::new(-20.0, -20.0, 0.0), Vec3::new(20.0, 20.0, 6.0)).unwrap();
let boss = kernel::cylinder(Vec3::new(0.0, 0.0, 6.0), Vec3::new(0.0, 0.0, 1.0), 5.0, 16.0).unwrap();
let body = kernel::boolean(&plate, &boss, BoolOp::Union).unwrap().unwrap();
let build = |fail: bool| {
let mut st = ModelState::default();
st.bodies.push(ModelBody::new("Part".into(), body.clone(), 1));
let on = thread_on(&st, 1, Vec3::new(5.0, 0.0, 14.0), &ask()).unwrap();
EMPTY_TRIM.set(fail);
let note = cut(&mut st, on, (ThreadTip::None, None, None));
EMPTY_TRIM.set(false);
(note.unwrap(), kernel::measure(&st.bodies[0].body).unwrap().volume)
};
let (none, trimmed) = build(false);
assert_eq!(none, None);
let (note, square) = build(true);
assert!(note.is_some_and(|n| n.contains("M10") && n.contains("left uncut")));
// The square stop leaves about half a pitch's worth of groove uncut: well over a mm³.
assert!(square > trimmed + 1.0, "{square} vs {trimmed}");
}
}
+4 -2
View File
@@ -55,7 +55,8 @@ impl FeatureKind {
/// The body a feature that works on one body's faces names (shell, draft, split, offset,
/// delete and replace face, surface offset, the placed plastic features, sheet metal
/// conversion, ruled surface, untrim): its face points pick faces of that body.
/// conversion, ruled surface, untrim, Split Body's tool face): its face points pick faces of
/// that body.
pub fn face_body(&self) -> Option<&str> {
match self {
FeatureKind::Shell { body, .. }
@@ -66,10 +67,11 @@ impl FeatureKind {
| FeatureKind::ReplaceFace { body, .. }
| FeatureKind::SurfaceOffset { body, .. }
| FeatureKind::Boss { body, .. }
| FeatureKind::Lip { body, .. }
| FeatureKind::SnapFit { body, .. }
| FeatureKind::Rest { body, .. } => body.as_deref(),
FeatureKind::SheetConvert { body, .. } | FeatureKind::SurfaceRuled { body, .. } => Some(body.as_ref()),
FeatureKind::SurfaceUntrim { face, .. } => Some(face.body.as_ref()),
FeatureKind::SurfaceUntrim { face, .. } | FeatureKind::Split { tool: Some(face), .. } => Some(face.body.as_ref()),
_ => None,
}
}
@@ -12,6 +12,7 @@ revolve
moves: axis.dir[0], axis.dir[1], axis.dir[2], axis.origin[0], axis.origin[1], axis.origin[2], to[0], to[1], to[2]
face points: [3, 3.5, 3.25]
profiles: 1 all
placement: 9, 9.5, 9.25
fillet_constant
moves: edges[0][0], edges[0][1], edges[0][2]
fillet_chord
@@ -86,6 +87,7 @@ construction_plane
moves: plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2]
split
moves: plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2], tool.point[0], tool.point[1], tool.point[2]
face points: [0, 0, 0]
split_line
moves: plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2]
construction_axis
@@ -148,6 +150,7 @@ boss
face points: [0, 0, 0]
lip
moves: face[0], face[1], face[2]
face points: [0, 0, 0]
snap_fit
moves: direction[0], direction[2], hook[0], hook[1], position[0], position[1], position[2]
face points: [0, 0, 0]
@@ -166,6 +169,7 @@ surface_extrude
face points: [1, 1.5, 1.25], [2, 2.5, 2.25]
surface_revolve
moves: axis.dir[0], axis.dir[1], axis.dir[2], axis.origin[0], axis.origin[1], axis.origin[2]
placement: 9, 9.5, 9.25
surface_sweep
surface_sweep_options
surface_loft
@@ -0,0 +1,4 @@
//! A counterbore hole may save a `bridge` length: two printing layers that thick below the
//! counterbore's floor (a slot as wide as the hole along X, then a square) so a 3D printer bridges
//! the step. An older design has none and keeps a plain counterbore. The last release refuses a
//! design that has it (it asks for an update) instead of opening it without the bridge layers.
@@ -0,0 +1,3 @@
//! Saves exact encoded texture bytes, inert provenance and explicit saved surface roles.
//! Older designs have no embedded textures and retain their built-in surface bindings.
//! The last release asks for an update instead of silently dropping the new resources.
@@ -0,0 +1,5 @@
//! A fillet saves `turn: "swept"` when two of its rounds that turn round a sharp side edge (the
//! rounds along a boss's base, at its upright edges) are swept round that edge, their corner an
//! exact torus; absent, the default `mitre`, they meet in a mitre, as designs before this change were
//! built. An older design has none and keeps its mitres. The last release refuses a design with
//! this change (the newer format number).
@@ -0,0 +1,12 @@
//! Nothing new is saved for holes, but a hole placed on a face that names the bodies it drills
//! (`participants`: its `targets`, or the body of another component it was picked on) now finds
//! the face it starts on among those bodies' faces, where one of them passes through its position
//! (#12066). Where another body touched that face at the hole, older designs could save a
//! `direction` taken from the other body's face, pointing out of the drilled body; such a hole cut
//! only a shallow pocket. It is now mirrored into the drilled body's face when the design is
//! evaluated, as format 18 does for a direction taken from the far side of a thin plate, and
//! drills through. Holes whose direction enters one of their own bodies' faces there, holes whose
//! own bodies have no face at their position (placed on another body's face to drill through it),
//! holes that name no bodies, and holes at several sketch points (already limited to their bodies,
//! #2882) build as before. No rewrite is needed. The last release refuses a design with this
//! change (the newer format number) instead of building the pocket again.
@@ -0,0 +1,5 @@
//! A sketch's intersection link can save a curve of another sketch as either side (`a` or `b`,
//! `{"type": "sketch_curve", sketch, curve}`), standing in for that curve's extrusion along its
//! sketch's normal (#11048). An older design has only bodies and faces there, and opens as before.
//! The last release refuses a design that has one (it asks for an update) instead of opening the
//! link as lost.
@@ -0,0 +1,6 @@
//! A point cloud keeps the segments and triangles its file draws over its points (#8817):
//! `segments` and `triangles` in its `complex`, the indices of their points, and a column of named
//! numbers may lie on them (`on`: `segment` or `triangle`; a column that does not say is on the
//! points, as every design saved before this has it). Older designs need no transformation: their
//! clouds are points alone. The last release refuses a design saved with this (the newer format
//! number) instead of opening its clouds as loose points without their edges and faces.
@@ -0,0 +1,5 @@
//! A revolve's explicit axis `{origin, dir}` can save its origin's coordinates as length
//! expressions (`["W / 2", 0, "groove_floor"]`) so the axis follows the parameters; a plain number
//! stays a number. An older design has numbers only and reads as before. The last release refuses
//! a design with this change and asks for an update, instead of opening it and reading an
//! expression as the origin.
@@ -0,0 +1,6 @@
//! A new-body extrusion or revolve whose outputs transfer into its source sketch's component
//! reads the profile through the recorded transfer frame. Capturing that component's position
//! therefore moves the body once, as the pending preview does. Existing designs use their
//! saved transfer transforms without rewriting fields; independent cross-component references
//! keep their live placement. The last release refuses the newer design-format number rather
//! than reopening the design with the former double-placement behavior.
+3 -2
View File
@@ -77,6 +77,7 @@ pub mod render_optics;
pub mod render_resources;
pub mod render_settings;
pub mod render_surface;
pub mod render_texture_resources;
pub mod sheet;
pub mod sketch_equation;
pub mod text_params;
@@ -92,8 +93,8 @@ pub use eval::{
blend_target, curve_pick_error, draft_corner_options, edge_names_for, edge_refs, face_point_names, face_point_state, hole_direction_at,
hole_on_surface, import_body_names, join_pieces_added, map_wire, metric_thread_sizes, object_height, parse_metric_thread, pattern_matrices,
revolve_extent, revolve_extent_for_handle, revolve_extent_with_convention, select_profiles, sketch_hole_direction, sketch_line_plane,
state_in_frame, state_in_frame_at, suggested_thread, suggested_thread_in, tangent_face_chain, through_all_distance, world_state,
world_state_reusing,
state_in_frame, state_in_frame_at, suggested_thread, suggested_thread_in, tangent_face_chain, through_all_distance, with_placed_copies,
world_state, world_state_reusing,
};
pub use eval::{EvalCache, KeptResults};
pub use expr::Expr;

Some files were not shown because too many files have changed in this diff Show More