Compare commits

..

614 Commits

Author SHA1 Message Date
bherbruck 8e2b0cd60d Release 0.30.1 2026-11-02 11:11:46 -05:00
bherbruck 6cee7a91b9 Release 0.30.1 (#11176)
Bumps the version to 0.30.1 and folds the pending format changes into format 48.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:10:02 -05:00
bherbruck 15e7615ce3 PR rules: a PR has to explain itself and show its picture (#11188)
A pull request now starts by saying what it is in plain words, and shows
anything there is to look at in its description. A file in the diff
doesn't count. The new Picture check fails a PR that adds to the showcase
or docs/screenshots without showing a picture. It isn't a required check.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:43:22 +00:00
bherbruck 70b2466965 Corner window tests: wait for the size preview before clicking OK or reading the error (#11180)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:57:52 +00:00
Vvynot ec1f78c6fa Hole: editing a Single hole replaces the picked face (#10692) (#10797)
The editor reused the new-hole Face input, which is multi, so a second pick
appended and OK silently kept the first. Editing now makes that input single,
so a pick replaces the place.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-02 14:45:37 +00:00
github-actions[bot] 01b592e0c8 Regenerate generated files and fold pending files [skip ci] 2026-11-02 14:52:32 +00:00
Ray f6e932f61a Rounded Rectangle: corners take parameter expressions, as a 2-point rectangle's do (#10956)
`sketch.rectangle.rounded` read `p0` and `p1` with `req_vec2`, so a corner typed as an
expression (`["W / 2", "H / 2"]`) was refused with "`p0` must be a point [x, y]", although the
2-point rectangle it hands the corners to already places expression corners and keeps them
following their parameters. The corners are now read with `corner_arg`, as the 2-point and
center rectangles read theirs; the worked-out values still check the radius against the sides.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:10:17 +00:00
Vvynot 8ab33bb80c Dimension: the value box keeps a 30 px gap from the pointer (#11057) (#11073)
A value typed before its target opened the box 18 px right and 10 px below the
pointer, so its frame covered the geometry a click was about to pick. Near the
viewport's right or bottom edge the clamp then slid the box back over the
pointer, losing the gap where it mattered most. It now keeps a 30 px gap, and at
an edge it opens on the pointer's other side instead of sliding back.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-02 14:08:54 +00:00
NOWN-DEV e33b8cfb07 Sheet metal convert: trays made in SolidCraft convert back (#7529) (#7580)
Convert to Sheet Metal refused a tray made with SolidCraft's own sheet metal, exported and
imported again: its walls run on past each bend's end (notch, tear and round corner reliefs), so
the edges beside the bend cylinder's edge never reach the other face's plane and the face could
not be trimmed ("a face beside a chamfer picked as a bend does not reach the face across it").
Where that edge is round or runs on along the bend's line, the face now keeps its corner and
steps down square to the other plane, a step the bend's setback takes up again; the inset drops
an edge the setback leaves shorter than the tolerance.

Test: trays_of_its_own_sheet_metal_convert_back (notch, tear and round, the walls made in one
flange feature, and notch and round with the short walls in a second one): the volume, the flat's
size and outline area and every bend's length are the source's.

Fixes #7529.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:04:39 +00:00
Dahak Builds 768dc253dd Fix mirror/pattern feature copy dropping bottom slice on hollow parts (#6452) (#7994)
* claim: #6452

* Engine: a pattern or mirror copy of a join keeps its ends as drawn (#6452)

The copy's tool is rebuilt against the current state, which already holds
the source feature's own material; the embedded-join-end trim (#2059)
read that material as the target already filling the copy's bottom band
and trimmed it away before the copy matrix was applied. Mirroring or
patterning a join on a hollow part silently lost the floor-level slice.
Copies now skip the trim: their ends are where the source drew them.

* chore: re-trigger duplicate PR check

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 14:03:53 +00:00
dudedagger42 1d98b59883 Sketch Break: a length becomes the distance between the old ends; no piece of no length (#6826) (#6834)
Breaking a dimensioned line left the length on the first piece, which the solver stretched
back to the whole length, skewing the line. A cut on (or a hair from) an end, as where a tangent
arc meets a line, made a zero-length piece with its coincident and collinear ties every time
Break was clicked there.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:03:42 +00:00
Nathan Dane abea79d8ab Preferences redesign 4/11: General profiles, files and updates (#9876) (#10295)
* Redesign General preferences

* Update Preferences regression fixtures and gallery coverage

* Keep measured Preferences faces out of neutral exclusions

* Keep the profile name entry in Preferences search results

The redesigned Profiles section had no search-visible control, so a query
such as "profiles" dropped the whole section and the name field could not
be reached with a search active. Report the entry row as a hit so the
section stays on the results page, and point the typing-focus regression
test at the redesigned "Profile name" field and "Save as new" button. It
still checks that typed profile names stay out of the search box.

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-02 13:58:23 +00:00
Colin Faulkingham c4808e4715 MCP: clean up empty scratch folders on shutdown (#7836) (#7929)
* claim: #7836

* fix(mcp): remove empty scratch directories on command exit
2026-11-02 13:57:48 +00:00
Homero Thompson 6649cf4cfe Project: a refusal that names no body names the kind alone (#10626) (#10859)
The refusal of sketch.project ended in "face of " with nothing after the
"of" when the reference named no body: a picked face, edge or vertex is
found by its geometry, so the body it belongs to is not in the reference.

LinkSource::describe() now drops the "of <body>" when there is no body to
name and names the kind alone ("face", "edge", "vertex", "spun
profile", "isoparametric curve"). A source that names a body keeps its
name.

Only the text changes: the command refuses exactly what it refused before.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-11-02 13:49:26 +00:00
semmelrinde 9b431fea16 Hole: on a cone or torus face, drill along the face's normal, not a facet's (#10698) (#10885)
A hole placed on the side of a cone with no direction drilled along
the nearest display-mesh triangle's normal, up to 2.6° off the
surface's normal and sideways. Planes, cylinders and spheres already
gave their exact normal; cones and tori now do too, from the surface
the kernel reports for the face.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:48:20 +00:00
Steve Jason f484457c3b Expressions: sign(0) is 0, not 1 (#10996) 2026-11-02 13:47:34 +00:00
Colin Faulkingham 2339510e41 Sketch Trim: keep endpoint tangency on the surviving arc (#10851) (#10853)
* claim: #10851

* Sketch Trim: keep endpoint tangency on the surviving arc (#10851)
2026-11-02 13:47:26 +00:00
austin62jake a9fa61ce77 Viewport: the shader compiles in browsers again, so the web app's 3D view isn't black (#11080) (#11107)
* claim: #11080

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

* Viewport: the shader compiles in browsers again (#11080)

The ambient occlusion turn added in #9335 mixed `<<` and `|` without
parentheses. naga accepts that, so the desktop app ran, but browsers'
WGSL compiler refuses it: the sc_viewport module was invalid, every
pipeline built from it failed, and the web app drew nothing once a
design was open. The line gets its parentheses, and a test checks every
shader in the repository for the rule.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 13:46:45 +00:00
semmelrinde ac9d4e8a1d Move to Component: a sketch on a face inside the moved part goes along (#11063) (#11072)
* Move to Component: a sketch on a face inside the moved part goes along (#11063)

A face attachment stores the placement path it was found through. Moving
the placement into another component changes that path, the stored one
no longer starts at the design, and the move was refused as breaking
the sketch. Face attachments of sketches and construction planes, and
an emboss's face, now follow the move as the session's paths already do.

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

* Move to Component: attachments made through any placement of the old parent follow

A parent placed more than once is one definition: a face attachment made
through another placement of it than the one named also ran through the
moved placement and broke. Every stored path through the moved placement
now runs through its new place. A path that would grow past the 64
placements a design keeps is refused instead of saved unreadable.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:46:12 +00:00
Christian Glombek 39163148d9 Sheet metal: an edge flange on part of a stepped outline spans that part (#11034) (#11046)
A flange picked on one segment of a stepped base outline ran the base
face's whole length: trimming the picked edge back to the bend line
merged it with the outline's own edge on that line beyond the step, and
the flange took the merged edge. It now stays off the base's edges that
already lay on the bend line, so it spans the segment picked, with no
relief there: the base is already back at the bend line. The same
outline made by cutting notches into a rectangle gave the right flange
already.

Assisted-by: LLM
2026-11-02 13:45:56 +00:00
semmelrinde 3ca311f922 Sketch: a dog bone's outline outside it meets outside both notches (#10984) (#11000)
* Sketch: a dog bone's outline outside it meets outside both notches (#10984)

Where two tangent arcs touch at a cusp, the copies of the two arcs cross
twice, both crossings as far from the cusp, and rounding chose one: on
one side of a dog bone the outline ran both copies on and drew a loop
into the bone. Such a tie now takes the crossing that cuts both copies
short.

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

* Dog bone test: the outline goes outside, away from the picked bottom line

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:45:56 +00:00
Colin Faulkingham b978eeddcd Sketch: preserve short reference-line directions when dimensioning (#11001) (#11117)
* claim: #11001

* fix(sketch): keep short infinite references aligned
2026-11-02 13:45:36 +00:00
Vvynot 2ccca96929 Rename: a refused inline rename keeps the editor open (#10654) (#10952)
* Rename: a refused inline rename keeps the editor and says why (#10654)

commit_rename dropped the command's Result, so a refusal closed the editor
with no message and left the old name. It now returns the error, shows it in
the status line, and the inline editor stays open.

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

* Rename: keep the command's status line when the editor stays open (#10654)

The command completion already explains the refusal; do not overwrite it with
the raw engine error.

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-02 13:44:34 +00:00
Vvynot 8ac4c03602 Extrude: going back reaches the view it was clicked in from an open sketch (#10571) (#11035)
Extrude started with the sketch still open ends the sketch on its way. With
"If a sketch ends: Return to the previous view", that end starts a camera
animation, and turn_for_extrude read the camera through settled_cam() - the
target of that animation - so the view saved as "before Extrude" was where the
sketch end was already turning, not where the command was clicked. The view is
now taken before the sketch is finished.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-02 13:44:01 +00:00
bherbruck 063f481ea0 API keys: read the base URL's host the way the HTTP client does (#10953)
The check that lets a key go only over HTTPS or to this computer ended the host part at `/` or
`?`, while the HTTP client also ends it at `#`, so the two could disagree about the host. It now
ends where the client ends it, and over plain HTTP the host part must be exactly this computer's
name with at most a valid port: no user name, backslash or stray character.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:18:51 +00:00
Colin Faulkingham 3c806394b0 Edit Sketch: restore timeline and undo state when finishing (#10691) (#10791)
* claim: #10691

* Fix sketch edit completion history (#10691)
2026-11-02 09:37:38 +00:00
Nathan Dane 3ddd73728b Render workspace: one editor per Browser selection; delete the Render studio panel (#6281) (#8000)
* Dispatch the Render inspector by selection on current editors

* Exercise native CPU capture through the Sampling editor
2026-11-02 09:08:23 +00:00
Dahak Builds ef939b52eb Parameters: one meaning for value in every reply; round reported numbers (#10747) (#10812)
* claim: #10747

* Parameters: value is mm/degrees in every reply; round reported numbers (#10747)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 08:26:24 +00:00
momysnow c1612e9234 Move / Copy with Copy on a component copies the component (#9914) (#9930)
* claim: #9914

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

* test: Move/Copy copy of a picked component (#9914)

Tests for a component picked in Move/Copy with Create Copy: it is copied as an
independent component placed with the move, the original stays, one undo removes
the copy; a body picked with Create Copy stays a body copy; a component picked
without Create Copy moves its bodies as before.

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

* fix: Move/Copy copies a picked component as a component (#9914)

With Create Copy ticked and the component pick mode on (or a Browser component
row), a picked component is copied as an independent component (Paste New,
component.paste_new) and the copy moves by the move's transform (occurrence.move
in the world). Bodies picked still copy as bodies; without Create Copy a component
still moves its bodies. The copy and the bodies are one undo step.

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

* fix: solid.move copies of components reply like a move and roll back (#9914)

A copying move with components replies as a solid.move does: the bodies' feature reply,
with the copies listed in occurrences. The bodies are mapped to their frame before any
copy is made. The solid.move help documents occurrences. A copy that fails part way
changes nothing and leaves no undo step.

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

* fix: Move/Copy takes a picked component whether or not Create Copy is ticked (#9914)

A component picked in the component pick mode, or a Browser component row, goes into
the Move bodies input as a component, so Create Copy can be ticked after picking it.
Without Create Copy the component still moves its bodies. Not while editing an
existing move. The Browser model tests read the bodies the command would move.

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

* fix: Move/Copy preview draws a copied component where the copy lands (#9914)

The fast preview took the component's bodies from the listed occurrences as well as the
bodies, drawn moved by the move, so a component copy previews at the moved place
instead of the scratch design's unplaced states. Test: a component-only copy and a
mixed copy preview where the copy lands.

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

* fix: a copying move refuses bodies that are not a list (#9914)

With components, a bodies value that is not a list was ignored; without components it
is refused. It is refused now, before anything is copied.

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

* test: an edited move never takes a component pick, through the click (#9914)

Clicks the component in component pick mode while an existing move is edited: the pick
stays a body. Fails when the editing condition is dropped.

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

* fix: Move/Copy preview takes components only for a copy, and the mixed test can fail (#9914)

The engine refuses `occurrences` without `copy`, so the preview leaves
such params to the full preview too. The mixed-copy test now gives the
component a taller box, so its height shows the component was drawn.

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

* docs: component_pick no longer needs Create Copy ticked (#9914)

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

* test: a component picked with its parent is copied once (#9914)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 07:56:22 +00:00
Yogesh D 544de55baa Localization: Russian "Close draft" reads as a draft analysis (#9284) (#9387)
* claim: #9284

* Localization: Russian "Close draft" reads as a draft analysis (#9284)

The report form's "Close draft" button said «Закрыть анализ уклонов»
(close draft analysis). It closes the report draft: «Закрыть черновик»,
the word the Russian catalog already uses for this draft.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 07:31:59 +00:00
semmelrinde 29a6bbdb43 Hole: say when a hole removes no material, or all of a body (#10701, #10702) (#10836)
* Hole: a hole moved off the material says it removes no material (#10701)

A hole whose position, parameter or sketch point moves it off the part
cut nothing and said nothing: the feature had no warning, and a
parameter sweep listed the value as fine. A hole whose tool is clear of
every body it cuts now warns "the hole removes no material" ("1 of 2
holes remove no material" for several), as a pattern does of its
copies. The check compares boxes only, from the meshes the hole already
found its face on, so a large hole pattern costs no extra booleans.

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

* Hole: a hole wider than the part says it removes all of it (#10702)

A through hole wider than the part removed the whole body with no
message, also when a parameter widened it later. The hole now warns
"the hole removes all of Body1" when a body it cuts is gone after the
cut.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 07:13:00 +00:00
github-actions[bot] a169ad4194 Regenerate generated files and fold pending files [skip ci] 2026-11-02 07:21:59 +00:00
semmelrinde d907078434 Section details card: every row shows, and the numbers read like the pill (#10708) (#10799)
The card's area is as tall as it was the frame before, and the first
frame laid the body out shorter than it ends up, so the scroll area
stayed short and the Hatch row (and Flip, for a tilted plane) was cut
off. The card may now grow to the height it is allowed.

The offset is shown to the design's length decimals and a tilted normal
to three places, as the pill shows them.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 06:53:46 +00:00
kaltenbrunnermarkus 073bbc3f00 Assemblies: a construction axis follows its component when the component rotates (#10127) (#10212)
* claim: #10127

* Assemblies: a construction axis follows its component when the component rotates (#10127)

A construction axis or point built on a body now goes with that body when
Move/Copy moves or turns it. Each construction records the bodies its picks
lie on; the Move feature (not a copy) carries along every construction whose
bodies it all moved, with the same transform in the construction's component
frame. One that also reads something fixed stays where it is.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 06:40:58 +00:00
alfonsotames 9e5e143ac3 Sheet metal: a fold crosses slanted or curved edges of any panel (#10621) (#10922)
sheet.fold refused any fold whose bend zone wasn't a rectangle. It now
crosses the sheet's own outer edges, slanted or on any curve: the outline
is cut exactly across the zone, the panels keep their real outlines, and
flanges and other folds still go on them. The flat pattern's DXF keeps the
curve as drawn. A sharp corner in the zone stays refused.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 05:24:50 +00:00
Tarek 0dd8d9e5ec Add a dialog for Create Components from Bodies (#10919) 2026-11-02 05:23:00 +00:00
bherbruck 9fdf153054 Agent chat: only the chat's own tools skip the panel's question (#10955)
A permission question counted as one of SolidCraft's own tools when its title merely contained
"solidcraft". Now the whole title must be a tool of the chat's own MCP server, named as
`mcp__solidcraft-app__<tool>`, and its kind must be `other` or not given. Anything else goes through
the user's mode and remembered answers, or is asked in the panel.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 05:15:42 +00:00
Dahak Builds 5336007c2b Section Analysis: view.pick skips edges and vertices in the removed half (#10704) (#10712)
* claim: #10704

* Section Analysis: view.pick skips edges and vertices in the removed half

near_edges now applies the same section `kept` predicate the mouse picker
uses (viewport::pick, since #6722), so an agent picking through view.pick /
ui.pick no longer gets edges and corners the user cannot see.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 04:38:22 +00:00
bherbruck 21fc384e40 Control channel: a new connection has five seconds to show its token (#10961)
A client could connect and send nothing, or only blank lines, and hold one of the 16 connection
seats for as long as it liked. All reads before the token now share one five-second deadline; the
connection is then closed without a reply. An authenticated connection may idle as before.

`solidcraft-cli mcp --connect` used to open a connection at start and hold it until the first tool
call. It now only checks that the app is there, and the first call connects.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 04:23:23 +00:00
bherbruck 5d6a80ff35 Control token file: no file in a custom folder when icacls times out (#10958)
On Windows the token file is restricted with `icacls`, which may not finish in time (#6145). That
counted as success everywhere, and is safe in the default folder, the user's own local app data.
A folder picked with SOLIDCRAFT_CONTROL_DIR may be readable by others, so there a timeout now
removes the still empty file and returns an error. The default folder behaves as before. The
decision is a small function of its own, tested without running icacls.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 04:08:13 +00:00
bherbruck 9636188a0f Lock files: never written or read through a link (#10951)
A design's lock file went through the same writer as the design, which follows a symbolic link
so a linked design is saved where it lives. The lock now has a writer of its own that refuses
anything at its path but a regular file and replaces only that name. Reading a lock takes only a
regular file and no more than a lock's size. Saving a design through a link works as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:53:28 +00:00
github-actions[bot] ec39a8ebb4 Regenerate generated files and fold pending files [skip ci] 2026-11-02 06:12:24 +00:00
semmelrinde 450cc8b389 Russian: translate the 20 messages main gained after batch 7, construction lines included (batch 8) (#9445)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:32:48 +00:00
bherbruck 3c12c36bef Security policy: say what counts as a security issue and what's a normal bug (#10946)
Also points the links at tangency-labs/solidcraft. The old list asked for reports of file access outside the allowed folders, and for crashes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:29:43 +00:00
MarkusRe 6e67ae34b0 Agent chat: keep transcript shortcuts out of CAD (#10576)
Selectable transcript labels do not request TextEdit focus, so the global
clipboard handler also copied, cut and pasted selected CAD geometry while
the user worked with chat text.

Keep keyboard ownership with the visible chat after a pointer press until
the next press outside it. Release it when the panel is hidden, and do not
use selections from other native viewports as a global CAD shortcut guard.

Cover real label selection, clipboard events, fallback key events, quick
select-all, hidden panels and deliberate return to the CAD viewport.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:24:30 +00:00
bherbruck afd81380ab Release 0.30.0 2026-11-01 21:55:18 -05:00
bherbruck 3125a79827 Release 0.30.0 (#10894)
Bump the workspace to 0.30.0 and fold the pending design format changes into format 47.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:55:11 -05:00
SilentWolf75 1cbebe21bf Edit Hole: seed the face where the hole is now (#10689) (#10860)
* claim: #10689

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

* Edit Hole: seed the face where the hole is now (#10689)

Project the stored position onto the drilled face along the hole direction when opening the editor, matching evaluation, so OK works after a parameter moves the face.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 02:39:25 +00:00
github-actions[bot] 25a19cdefe Regenerate generated files and fold pending files [skip ci] 2026-11-02 05:40:52 +00:00
kaltenbrunnermarkus 12ebc80e15 Cosmetic tap markings: look for their cylinder only in nearby bodies (#10303)
* Holes and cuts: count the bodies a cut works on in the cost tests

A test helper, cut_work, adds a feature at the end of a design and reports
how many bodies its cut ran booleans on and which bodies it left exactly
as they were (same geometry, key and face names). A new test pins that a
cut still goes through the boolean where its tool touches a face, runs
along an edge, passes a hair beside a body or drills into a dome.

The sheet metal hole (#10226) and cosmetic thread (#10227) work reuse the
helper for their own shortcuts.

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

* Holes and cuts: leave bodies the tool cannot reach untouched without a full cut

A cut ran a whole boolean against every body of its component, and for a
body its tool does not reach that boolean only handed the body back: about
7 ms per body, so a hole in a design of 200 blocks took 1.3 s. Now a body
whose box lies clear of every tool's box is left as it is. Both boxes are
their meshes' grown past how far the exact faces can curve out from them,
the same test sheet metal cuts already use, so a tool that touches a body,
grazes it or drills into a dome still goes through the boolean. Bodies
shown in another frame, mesh bodies and Intersect (which removes the
bodies it misses) are unchanged.

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

* Cosmetic tap markings: look for their cylinder only in nearby bodies

A tap marking (a Thread feature, or the marking of a tapped hole) looked
for the cylinder at its point in every body of the design, and each
search meshes the body afresh: with many bodies and many markings that
grew quadratically (200 markings on 200 cylinders: 33 s of a 33 s run).
A body is now passed over when its box, from the mesh it keeps and
grown past that mesh's deflection, lies farther from the point than the
search itself would look (a twentieth of the body's size, plus its own
mesh's deflection). The search finds no face there, so the marking and
everything else stay the same.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:15:18 +00:00
dudedagger42 b470d46ffc Sketch: rebuilding a projection keeps the sketch's own curves on its points (#5388) (#5537)
* claim: #5388

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

* Sketch: rebuilding a projection keeps the sketch's own curves on its points (#5388)

When a projected face changed shape, update_link rebuilt the link with
drop_link_entities, whose remove_points also deleted every curve using a
projected point: a circle the user drew on a projected hole's centre vanished,
and the extrude on it lost its profile. Before the projected points go, the
sketch's own curves on them move to a free point at the same place.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:09:59 +00:00
Vvynot 6e98bd474e Grid: a selected plane previews before Create Sketch (#10804) (#10900)
Selecting a plane in the view now draws the grid on it, so the plane a
sketch would use shows before the button is clicked. Deselecting returns
the grid to the ground.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-02 02:09:30 +00:00
Colin Faulkingham ad23882080 Assistant: recover when an agent ignores Stop (#10800) (#10916)
* claim: #10800

* fix: recover when ACP agents ignore Stop
2026-11-02 02:09:27 +00:00
ABDULLAH 63fb818f33 Arabic: profile label in the dialog selection list matches the curve and constraint labels (#10927) 2026-11-02 02:09:25 +00:00
dudedagger42 96509412e9 Extrude: a Cut To the far side of a curved wall takes the wall (#8634)
A Cut To Object on a curved face ran the tool through the target and kept
the tool less the body, the piece at the start. From outside a curved wall
to its inside face that piece is the air before the wall: the cut took
nothing, or the tool lay in the curved face and the boolean failed on the
coincident faces.

Where the target's material ends at the picked face under the whole
profile (rays over the profile cross it once, material before, none
after), the tool now ends flat halfway across the gap beyond the face. It
takes what lies before the face and meets no face of the body. Otherwise
the trim is as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:01:42 +00:00
froglemonade2 f2ffd8d233 Save As: refuse a name that opens as another kind of file (#9745) (#9752)
* Save As: refuse a name that opens as another kind of file (#9745)

file.save and file.save_as wrote a design container under any name, so a
design saved as part.step was refused when opened again and part.stl opened
as a broken mesh. A name doc.open reads as a STEP/CAD exchange file, a mesh,
a 2D drawing, a point cloud or a SolidCraft drawing is now refused, pointing
to .sc and, for export formats, to file.export. Names that open again as the
design (.sc, legacy extensions, .json, anything else) save as before.

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

* Save As: check the folder scope before the design name (#9745)

A path outside an agent's folders is refused for that first, whatever its
extension, as before; the design-name check runs after it in `write`. Fixes
solidcraft-mcp tests::tool_calls_stay_inside_the_agents_folders, which saves
`x.step` outside the agent's folders and expects the scope refusal.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 01:51:19 +00:00
SilentWolf75 2b08e0c6a7 AppImage: refresh the icon cache so the taskbar shows the mark (#10673) (#10858)
* claim: #10673

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

* AppImage: point the taskbar at the installed icon PNG (#10673)

Install hicolor icons before writing the desktop entry, refresh the icon cache, and set Icon= to the absolute 256×256 path so Cinnamon and similar panels show the mark instead of a thin strip.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 01:50:44 +00:00
kaltenbrunnermarkus 1885fa739b Assemblies: a second planar joint can be added (#10331) (#10371)
* claim: 10331

* Assemblies: a second planar joint turns its part a half turn to meet (#10331)

A second planar joint between two boards that mates faces pointing the
same way needs the board turned a half turn on the first joint. The loop
solve starts exactly turned away, finds no way round, and left the new
joint as a conflict; Move then said the part has no movable joint axis.

A new loop-closing joint now tries its own angle, each angle on the
joint path between its two parts, or one of each, a half turn round, as
the first joint of a chain moves its part into place, and keeps the
first that closes the loop without a new conflict. Faces that can never
meet stay a conflict and nothing moves.

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

* Move test: give the blocked part faces that can never meet (#10331)

The blocked-Move test from #10509 used two boards whose second planar
joint mates two faces pointing the same way. That joint now closes its
loop with the board turned a half turn, so the scenario no longer
conflicts. Mate the yellow front against the red top instead, which no
turn on the first joint can bring together.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 01:29:29 +00:00
github-actions[bot] cacfbbc578 Regenerate generated files and fold pending files [skip ci] 2026-11-02 05:03:27 +00:00
Joberndo 2ae13ba9cf Feature editor: Undo, Redo and the History bar wait while an editor is open (#10688) (#10786)
* claim: #10688

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

* Feature editor: Undo, Redo and the History bar cancel an open editor first (#10688)

Undo, Redo and History steps changed the design under an open feature
editor, whose OK and Cancel then refused it. They now cancel the editor
first, putting its marker back, as starting another tool already does.
While an editor is still opening they wait for it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:36:34 +00:00
raykholo e91f5d73bd Joints: a typed joint that closes a loop starts where its parts are (#6061) (#10336)
A relation given no values already started from where its parts sit
(relation_values), so the part moves no further than it must. A typed joint
(planar, revolute, cylindrical ...) added between parts another joint
already relates started its values at 0 instead, so the loop solver began
far from the current placement and could report a conflict for a joint that
agrees with how the parts are placed.

Seed every loop-closing joint, typed or relation, from the current
placement when no values are given. Joints in an open chain and joints with
explicit values keep their initial values as before.
2026-11-02 00:24:33 +00:00
github-actions[bot] da501178aa Regenerate generated files and fold pending files [skip ci] 2026-11-02 04:35:57 +00:00
Daniel Katz 3231f049d0 Insert Part: ASME/ANSI inch hex cap screws, hex nuts and SAE/USS washers (#10316) (#10335)
* claim: #10316

* Insert Part: ASME/ANSI inch hex cap screws, hex nuts and SAE/USS washers (#10316)

Part of #10316. New standard-part families, tabled in inches as the
standards give them and built in millimetres: hex cap screw (ASME
B18.2.1) and hex nut (ASME B18.2.2), 1/4 to 1 in UNC and UNF from the
thread catalogue, and plain washers SAE and USS (ANSI B18.22.1 Type A).
Cap screws take the standard inch lengths, 1/2 to 8 in, defaulting to
the first at or above 2.5 d.

Lengths read as fractional inches in Insert Part, Change Part Size, part
names and part numbers. Insert Part gains a Unit system list (Metric,
Inch) above Type; it opens on Metric. parts.library reports each
family's system.

Inch parts get no cosmetic thread: solid.thread takes only metric
designations (#9571), and the old path would have made a wrong metric
mark. 9/16 waits for the thread catalogue; split lock washers and
carriage bolts follow in later PRs.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:21:21 +00:00
semmelrinde a22ea76ab6 Feature editor: a section, an analysis or the selection changed while it is open leaves OK and Cancel working (#10687) (#10822)
The editor accepts revision changes that only changed what is shown,
counted by the display revision, but only hiding and showing counted
there. Section Analysis, the surface analyses, continuity, the
selection, the status pill catching up and view.render_select bumped
the revision alone, so turning a section off with an editor open made
OK refuse ("the design changed while the feature editor was open") and
Cancel leave the timeline rolled back. They now go through
refresh_display, as visibility changes do.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:18:00 +00:00
austin62jake a735620aa8 Circular pattern: the axis origin takes length expressions and follows them (#10852) (#10855)
* claim: #10852

* Circular pattern: the axis origin takes length expressions and follows them (#10852)

solid.pattern.circular with axis {origin, dir} kept its origin as plain
numbers, so a pattern about a parameter-driven point stayed where the
point was when the pattern was made: the seed moved with the parameter
and the copies did not. Expressions were refused (#248).

The pattern's origin is now a point of expressions, as a Mirror plane's
origin is: numbers or length expressions, evaluated on each rebuild, and
renamed with their parameters. Plain numbers are saved as numbers, as
before. timeline.edit sets pattern.origin as expressions, and editing the
pattern in its dialog keeps them. One pending format change.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:17:15 +00:00
rugbymauri 059119b345 solid.move: an inserted part moves in world space beside another body (#9660) (#9683)
* claim: #9660

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

* Move bodies of differently turned components in the world together (#9660)

A Move feature moves each of its bodies in the body's own frame by one
vector and one turn. `solid.move` mapped the world vector and axis into
the frame of the first body listed only, so a body in a frame turned
otherwise (a part inserted with a non-default direction, moved together
with a block of the root) moved along its own axes.

When the bodies listed lie in frames that turn differently (or, for a
turn, also sit in different places), the move is now split into one
Move feature per frame, each with the vector and axis mapped into that
frame, in one undo step. Bodies sharing a frame still make one feature.

Fixes #9660

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-11-02 00:16:56 +00:00
jeromehzl e016c40f43 Sketch Intersect: picked faces meet as their whole surface, not the span under the pick (#9009) (#9107)
A surface extruded from a fit-point spline is one B-rep face per span,
joined by hidden seams; the viewport picks and highlights it as one face
(Mesh::surface_patch). sketch.intersection_curve turned a face pick into
the single nearest face, so the curve stopped at that span's ends.

project3d::mesh_of now returns the picked face's surface patch and
mesh_intersection keeps the triangles of every face in it. Body sources
and Project to Surface are unchanged.
2026-11-02 00:16:06 +00:00
austin62jake 1fb9dadcd9 Language: a Traditional Chinese system opens in Traditional Chinese, macOS's zh-Hant-TW included (#10306) (#10307)
* claim: #10306

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

* Language: a Chinese system tag goes by its script, then its region (#10306)

macOS lists its languages with a script (zh-Hant-TW), which no catalog
matches exactly, so the base zh (Simplified) was picked for a Traditional
Chinese system. Hong Kong and Macao tags got Simplified the same way.

A Chinese tag now picks zh-TW for the Hant script, zh for Hans, and
without a script zh-TW for the TW, HK and MO regions, zh otherwise.
Other languages keep the exact tag, then the base language.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:15:53 +00:00
github-actions[bot] 91a19548d3 Regenerate generated files and fold pending files [skip ci] 2026-11-02 03:47:23 +00:00
kaltenbrunnermarkus a277839236 Section Analysis: tilting the plane rebuilds the outline index once, not on every drag frame (#10373)
* claim: sectiontilt

* Section Analysis: tilting the plane no longer rebuilds the outline index every frame (#10373)

The section outline cut through an index of slabs along one normal, rebuilt
whenever the normal changed: tilting a Section Analysis rebuilt it every
drag frame (about 0.15 s on a 2.5 million triangle part). The index is now a
grid of cells holding each triangle by where its box sits, with the box
around each cell's triangles, so it serves every normal. It finds the same
segments, in the same order and to the bit, as cutting every triangle.

The viewport keeps one index per shown body mesh, no longer per normal.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:15:29 +00:00
cronos b5d931ff4c Preserve offset distances when trimming their source line (#10391) 2026-11-02 00:15:27 +00:00
Christian Glombek d7a17de3d9 Sheet metal: a perforation field bounded by a sketch's outline (#732) (#10754)
`sheet.perforate` takes an optional `sketch` (and `profiles`): the field's
outline. Holes go only inside its closed profiles, out of their inner loops,
`margin` clear of its edges, on a grid from the outline's lower-left corner.
On a folded sheet the outline maps through each panel the sketch lies on and
is clipped to it, so it never reaches a flange in the flat; a sketch on no
face of the sheet is refused. A mirrored copy mirrors the outline, and an
edit that names no sketch (the Perforate dialog has no outline) keeps it.
Without a sketch the field covers the whole sheet as before.

The feature saves `sketch` and `profiles`, a sheet's perforation its outline
in flat coordinates: one pending format note, no migration.

Assisted-by: LLM
2026-11-02 00:14:41 +00:00
leValdemar19 a33619fd2b Tangent Plane: line it up with a reference plane, at an angle (#8822) (#9002)
* claim: #8822

* Tangent Plane: line it up with a reference plane, at an angle (#8822)

`construct.plane.tangent` takes an optional `reference` (a plane or planar
face) and `angle`. On a cylinder, cone or torus the plane touches where the
face turns towards the reference, turned by the angle about the face's axis,
on the side of the pick; on a sphere it is parallel to the reference. The
plane follows the reference when it moves. The Tangent Plane dialog gets a
Reference plane input and, once one is picked, an Angle field; editing the
plane brings both back. Saved as a pending format change.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:13:22 +00:00
austin62jake e4e3c8609c OBJ export: welded as STL and 3MF are, so a curved body opens again closed (#9074) (#9213)
* claim: #9074

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

* OBJ export: welded as STL and 3MF are, so a curved body opens again closed (#9074)

The OBJ writer joined vertices only when their coordinates were equal bit
for bit, but each face of a curved body writes its edge points again a few
ulps apart, so neighbouring faces shared no vertices and the file reopened
as an open mesh with no volume.

It now welds each body as the STL and 3MF exports do (threemf::weld_tagged,
with the same tolerance), and every corner keeps its normal: the normal of
the original corner it came from, or for a point a side was split at, the
normal interpolated across the triangle.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:13:06 +00:00
CamWight 325917f2ae Ambient occlusion: no artifacts on the preview or on hollow bodies (#9327) (#9335)
* Ambient occlusion: the radius follows what the frame draws (#9327)

In an empty design the radius came from the model's box, which is empty, so a
preview got the smallest radius and the depth map's precision showed as specks.
It now comes from the box the depth range uses, and with nothing to draw there
is no occlusion.

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

* Ambient occlusion: the term drawn once and blurred by depth (#9327)

The lit pass worked the term out for every fragment it shaded, under a pattern
that changed from pixel to pixel, which read as grain. The term now has its own
texture, drawn once for each pixel of the prepass, with a fixed 4 x 4 pattern, and
a 4 x 4 blur that keeps to each pixel's plane takes the pattern out without
crossing an edge. The lit pass reads the result. The prepass and the term share
their pipelines and draw walk.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:12:57 +00:00
austin62jake 5ad2503503 Timeline folders: editing a feature in a folder leaves no stale id, and such designs open again (#9373) (#9393)
* claim: #9373

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

* Timeline folders: editing a feature in a folder leaves no stale id, and such designs open again (#9373)

timeline.redefine runs the edited feature's command on a scratch copy with the marker just after
the feature, and takes the new definition from the feature that run makes. Inside a timeline
folder that trial feature joined the folder too; it was removed from the feature list but not
from the folder, and its id, handed back, was the next feature's. Each edit left that id in the
folder (the same one, edit after edit), naming no feature or, once a later feature took it, a
feature twice, and the reader then refused the design: "damaged design: invalid feature in a
timeline folder". The trial feature now leaves the folders as it leaves the feature list.

The reader drops a folder entry naming a feature or joint the design doesn't have, or one a
folder already holds, and opens the design: folders only group the timeline, so nothing else is
lost. A folder damaged in itself is still refused.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:12:50 +00:00
PapaEcho f43198988d fix(ui): keep profile-name typing out of Preferences search (#9505) (#9568)
Report focused keyboard ownership from the shared text editor, matching the other Preferences fields. Regress real Name-field typing, focus retention, unchanged search text, and saving the resulting profile.
2026-11-02 00:12:36 +00:00
Sebastian Höpfl 6f73bd95c4 Assemblies: dragging a part with mirrored copies no longer meshes them on every frame (#10101)
A mirror of placed bodies follows its source, so each frame of a drag evaluated
it and made new copies with empty mesh cells. A reflection can't share its
source's triangles, so every copy was tessellated from nothing, and so was
anything an evaluated feature after the mirror made.

Keep the first copy of a body with its source: each later copy is a proper rigid
move of it, so a world state draws them from one set of triangles. The model
keeps each copy's own lazy mesh. A feature evaluated again that gives the same
solids (an import) keeps its meshes.

A mirror of placed bodies no longer searches its copies' revolved faces when no
later feature can build on bodies. That depends on the design alone and is part
of the mirror's placement digest.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:11:45 +00:00
CamWight 9d7544929e Tests: the same language on every PC (#10297) (#10298)
Since #10183 the app follows the system's language, and the tests followed it too, so a test
naming an English word failed on a PC set to another language. CI's runners read as English
and never saw it. In tests the system's language is now English: `cfg!(test)` for this crate's
own tests, as the engine's folders do, and the harness for the tests outside it. The preference
stays `system`, and the app is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:11:16 +00:00
kaltenbrunnermarkus eb0cecee31 Web app: the offline check with the real app finishes its download (#10368) (#10370)
* claim: 10368

* Web offline test: each copy of a real build gets a file of its own (#10368)

With --site, the six builds were copies of one real build that differed
only in index.html. The overlapping-downloads step holds back the files
the next build lacks, so nothing of v5 was held: its download finished
before v6 launched, v6's worker then retired and deleted it (correctly,
no page used it), and the step waited 240 s to see it complete.

Successive deployments differ in more file names than that (Trunk names
files after their contents). Each copy now gets own-<build>.txt, like
the stand-in's build-named script, so v5's own file is held as the step
intends.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:10:42 +00:00
semmelrinde 27951f40c3 Parameters: the Filter box also reads expressions (#9706) (#10385)
Typing a parameter's name now lists every parameter whose expression
uses it, and a value finds the parameter that holds it. The filter
still reads names, comments and groups, and a text parameter's text.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:10:09 +00:00
Homero Thompson 9e3cf05c4f fix(sketch): a line of another sketch projects from the Project dialog (#10390)
A straight line of another sketch, picked into the Project dialog, came
in as a named sketch curve, which the projection preselection did not
know: it refused with "select geometry or bodies that can be projected"
although the OK button was live. The same reference the external pick
stands for is now made for it, so the line projects (#10291).

Co-authored-by: vdirienzo <vdirienzo@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-11-02 00:10:01 +00:00
Homero Thompson 6459c9fcf0 A context error is not something to report (#10181) (#10594)
* wip: the diagnosis of #10181 (the bug reproduces on current main)

sketch.project has .enabled(in_sketch) (crates/engine/src/cmd/sketch_project.rs:24)
and in_sketch (crates/engine/src/cmd/mod.rs:285) answers 'no sketch is being edited'.
Pressing P outside a sketch goes keymap::press -> sketch_tool_for
(crates/ui-egui/src/keymap.rs:451-457) -> app.start("sketch.create") with
d.then_start = Some("sketch.project"): the sketch IS created, but the Project
dialog opens and the command is refused, so the dialog shows the red row and the
'Create Issue...' button, which opens the Report a bug form over the viewport.

The probe here reproduces the bug on the CURRENT main (two variants). It is a
diagnostic probe, not the regression test: it must be replaced before a PR.

* test: a context error offers no Create Issue button (#10181)

The Project panel showed the red row for a CONTEXT error ('sketch.project is not
available right now: no sketch is being edited') and offered the 'Create Issue...'
button, which opens the Report a bug form over the viewport. A context error is
not something to report, so the panel must not offer it.

RED: the test fails with the exact message of the issue.

* fix: a context error is not something to report (#10181)

The Project panel offered "Create Issue..." for the error the engine
raises when the command is not available in the current context
("`sketch.project` is not available right now: no sketch is being
edited (Create Sketch or Edit Sketch first)"). That refusal is guidance
about the context, not a failure of the design, so there is nothing to
report: ending the sketch under an open Project panel should not invite
the user to file a bug.

error_rows already spared the missing-pick errors ("select ...", "pick
...") the same way. It now spares the context refusals too, using the
engine's own kind rather than the message text: CommandSpec gains
disabled_refusal, which is the engine's own Disabled refusal for the
command in the current session, and the UI matches the dialog's error
against it. A real failure of the command (an invalid face, say) still
shows the red row with "Create Issue...".

* style: rustfmt the test of #10181

The formatter joins the two asserts of the test onto one line (the
repository sets max_width = 150 and use_small_heuristics = Max). The
tokens of the file are unchanged: the diff is the line breaks of the
asserts and nothing else.
2026-11-02 00:09:40 +00:00
austin62jake 5e7f3651fb Angle fields: a decimal point typed into an angle stays (#10582) (#10631)
* Angle fields: a decimal point typed into an angle stays (#10582)

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

* Angle fields: a decimal point typed into an angle stays (#10582)

Edit Canvas applies its Angle box at each keystroke and keeps the angle
in radians, so the box gets back a hair off the value it set; egui's
DragValue takes that for a change made elsewhere and rewrites the text
being typed, dropping a decimal point just typed (-3.7 became -37).
angle_field now hands the DragValue the value it set while the value it
is given is within 1e-9 of it.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:09:32 +00:00
Colin Faulkingham da586bc048 Fix viewport Unisolate after isolating a component (#9894) (#10635)
* claim: #9894

* Fix viewport recovery after component isolation
2026-11-02 00:09:23 +00:00
austin62jake 062a7b8288 Tangent Arc: a first click off a curve's end is refused at once, saying where to click (#10653) (#10655)
* Tangent Arc: a first click off a curve's end is refused at once, saying where to click (#10653)

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

* Tangent Arc: a first click off a curve's end is refused at once, saying where to click (#10653)

The Tangent Arc tool took any first click as its start; a click on a
line between its ends snaps to a place that is no point yet (on:l3:...,
mid:l1), and the second click then failed with "unknown sketch point"
naming that internal reference.

The tool now refuses such a first click at once, with its own hint as
the error, asking the command's rule (tangent_arc_starts_at, factored
out of arc_tangent: the picked point or one at the same place, and a
drawn curve leaving it). The command refuses a snapped start with its
existing message that start must be the end point of a line or arc.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:09:17 +00:00
Colin Faulkingham 5bbfa5ea6c Sketch: preserve source geometry when deleting a pattern (#10651) (#10661)
* claim: #10651

* fix(sketch): preserve source geometry when deleting patterns
2026-11-02 00:09:10 +00:00
austin62jake 75c46dad7b Tests: the autosave scenario crashes once its autosave has landed (#10690) (#10694)
* Tests: the autosave scenario crashes once its autosave has landed (#10690)

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

* Tests: the autosave scenario crashes once its autosave has landed (#10690)

s25_autosave_recovery 'crashed' right after the extrude; under load the
autosave of it, written on a worker thread, had not landed, so the next
launch recovered the earlier autosave without the body (step 16).

The harness's crash restart now runs frames, bounded, until the active
autosave has caught up (Autosaver::caught_up: no write running and the
last autosave holds the design as it is). The app's own ticks still do
the writing, so an autosave that stopped writing still fails the test.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:09:03 +00:00
github-actions[bot] af54b09bb1 Regenerate generated files and fold pending files [skip ci] 2026-11-02 01:51:55 +00:00
github-actions[bot] dcbca523a0 Regenerate generated files and fold pending files [skip ci] 2026-11-02 01:42:49 +00:00
froglemonade2 e9b08f4af4 Control channel: report the dialog as data, the open tool windows, and what ui.start opened (#9087) (#10740)
* claim: #9087

* Control channel: report the dialog as data, the open windows, and what ui.start opened (#9087)

`ui.inspect.dialog` (and `ui.editFeature`'s reply) was the dialog's Rust Debug text, which
changed with every refactor and couldn't be parsed. It is now an object: the dialog's command,
title, the feature an edit rebuilds, its selection inputs, its error, and the commands OK runs
(or what it waits for). `hover` in `ui.inspect` and `ui.selection` is a selection-like item
instead of Debug text.

`ui.inspect` gains `windows`: the tool windows open beside the model (Parameters, Bill of
Materials, Design Properties, Sketch Report, material card review) by the command that opens
each, and the Help menu's (About, Updates, Shortcuts, Crash Reports, Report, Messages) by their
`ui.help` item. Before, an agent couldn't tell these windows were open.

`ui.start` and `ui.editFeature` reply with what they opened (tool, dialog, windows), the status
line, and `pending` when the start waits for the history and opens on a later frame.

Scenario `dialog` checks match words of the dialog's state from the app directly, so the
scenario files are unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:08:48 +00:00
Dahak Builds 6a7275a5af Agent chat: copy an entire sent prompt or assistant message (#10675) (#10741)
* claim: #10675

* agent chat: Copy message on each sent prompt and assistant text block (#10675)

A visible Copy message button on each copyable transcript message and a
right-click menu with the same action. Assistant replies copy their
original Markdown, prompts copy the text as entered; picture-only prompts
and thoughts get no action. Hover-only message areas keep partial text
selection working.

* agent chat: complete Copy message locales and tests (#10675)

* agent chat: route the message copy menu through ui_kit::Container (ui-containers gate)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 00:08:41 +00:00
PapaEcho 666356dba0 Assistant: configurable show/hide shortcut (#10764) (#10767)
* claim: #10764

* Assistant: make show/hide shortcut configurable (#10764)
2026-11-02 00:08:23 +00:00
austin62jake 3fe2b3386f Sketch: Remove Spline Point takes one point out of a spline (#10783) (#10793)
* Sketch: Remove Spline Point takes one fit point out of a spline (#10783)

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

* Sketch: Remove Spline Point takes one point out of a spline (#10783)

sketch.spline.remove_point takes one fit or control point out of a
spline. The spline keeps its id and runs through its other points, which
stay where they are. The point goes with the constraints on it, unless
another curve uses it. A closed spline's closing point, an end joined to
other geometry, the last 2 points (3 when closed), and projected, offset
or patterned splines are refused with a message.

The right-click menu of a spline point, picked alone, offers it beside
Reverse Spline Direction.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-02 00:08:11 +00:00
Colin Faulkingham afe06a1a3d Edit Joint: show components for point and frame origins (#7749) (#10838)
* claim: #7749

* fix(ui): show components for point and frame joint origins
2026-11-02 00:07:29 +00:00
Colin Faulkingham eaf34d2528 Construction planes: refuse duplicate feature names (#10707) (#10743)
* claim: #10707

* Fix construction plane name collisions (#10707)
2026-11-02 00:06:59 +00:00
Dahak Builds 2b9261050f Sketch: stop tying expression-placed points to the origin with an automatic Coincident (#10703) (#10722)
* claim: #10703

* Sketch: do not tie expression-placed points to the origin with an automatic Coincident

A point whose coordinates are parameter expressions follows those
expressions. When they came to 0 at draw time the point landed on the
origin and connect_points tied it there with a Coincident, exactly like
a snapped click; the next parameter change then conflicted with the tie.
connect_points now skips points that carry exprs; plain-number points
keep the snap.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 00:06:51 +00:00
semmelrinde eac1425661 Hole: a face name shared by two bodies is no split face (#10699) (#10872)
* Face names: one name on two bodies is no split face (#10699)

A hole placed on the top of one of two bodies an extrude made, or on a
piece of a split body, warned "face `F2:top` was split; using the piece
nearest the picked point": the face name is on both bodies, and any
name found more than once counted as split. A name on several bodies,
one face on each, now finds the face the point lies on without the
note; pieces on one body, or a point on none of them, still say split.

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

* Hole: look for a face name shared by two bodies on the body the point is on (#10699)

The lookup over every body counts a name on several bodies as split on
purpose (a copy's face is not a piece of the source's, see
`the_same_face_on_a_copy_is_not_a_piece_of_the_named_bodys_face`), so
the first change, which made it unique there, is undone. A hole whose
face name is found split while its point lies on one of those faces now
looks again on that body alone: one face of the name there is the face,
whole; pieces on that body still say split.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:06:43 +00:00
github-actions[bot] 88dbda42a7 Regenerate generated files and fold pending files [skip ci] 2026-11-02 00:31:13 +00:00
Homero Thompson 9e30ff5878 fix(#5505): a zero length dimension keeps constraining the sketch (#10790)
The residual of a length dimension is the magnitude of the distance between
the line's two ends. At zero the magnitude has no derivative, so the rank
analysis read the constraint as if it were not there and the sketch reported
a degree of freedom that was not there.

At zero the two ends of the line meet, so the residual says it with the
vector between them, the way a coincidence does. That is the treatment a
distance of zero already gets (upstream #4397); the length family was left
out of it.

Regression test: crates/sketch/src/tests.rs, zero_length_keeps_the_line_constrained.

Co-authored-by: Homero Thompson <homero@local>
2026-11-02 00:05:09 +00:00
thocsmith 4c4c768d57 Cancel button on the Computing toast (#8266) (#10366)
* claim: #8266

* Computing toast: a Cancel that gives up on the move (#8266)

The Computing toast for a history command (marker move, undo, redo,
suppress, reorder) or a delete's check computing off the window thread now
has a Cancel button. It drops the job and the commands queued behind it;
the design stays as it was, since a deferred command only runs for real
once its evaluation is in. A feature edit applying keeps its dialog's own
Cancel, so its toast shows none.

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-02 00:04:53 +00:00
Dahak Builds 83fe1f874f Sketch Project: keep hidden origin planes hidden in Project (#10778) (#10834)
* claim: #10778

* Sketch Project: keep hidden origin planes hidden (#10778)

WIP from dead worker: fix + test written, gates not yet run.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 00:04:45 +00:00
Colin Faulkingham 0734616104 View cube: follow the selected projection (#10634) (#10640)
* claim: #10634

* Make navigation cube follow viewport projection (#10634)
2026-11-02 00:04:43 +00:00
Dahak Builds d68d86dae9 Dialog form cards widen their label column for the current language (#10620) (#10646)
* claim: #10620

* widen dialog form-card label columns to the measured label width (#10620)

* fix compile of the grow tests

* grow the card only past the label cap and tag the form scope in place (#10620)

* keep scope_grow crate-internal so the gallery scan stays element-only (#10620)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-02 00:04:41 +00:00
rugbymauri 7d86a8cfd3 Sketch: dragging a constrained centre point of a centre rectangle moves it, not resizes it (#10510) (#10535)
* claim: #10510

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

* Sketch: dragging a centre rectangle's constrained centre point keeps the rectangle's size

A centre rectangle's centre sits on the middle of its diagonal, and a
press there picked the diagonal's midpoint before the centre point. The
drag then took the diagonal by its middle: with the centre free the
rectangle moved along, but with the centre held (on the origin, on a
line) the diagonal turned and stretched about it, and the rectangle
grew or shrank with the pointer, many times its size.

A drag that starts on a line's middle where a point is held by a
Midpoint constraint now drags that point, as a drag that starts on the
point does. A centre held on the origin stays there and the rectangle
keeps its size; a centre on a line slides the whole rectangle along it;
a free centre still moves the rectangle. Clicking the middle still
selects the line's midpoint for constraints.

Fixes #10510

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-11-02 00:04:38 +00:00
semmelrinde cea07cbc66 Sketch Slice: cut at the sketch plane while a Section Analysis is on (#10705) (#10768)
With a section on, Slice in the Sketch Palette lit up but the model was
still cut only by the section: the viewport took the sketch's plane only
when there was no section. Slice now takes it whenever it is on in the
sketch being edited; the section comes back when Slice is turned off or
the sketch closes. A slice is hatched as it is without a section.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:02:59 +00:00
Colin Faulkingham 0b90731096 Dialogs: readable sketch selection labels (#10752) (#10770)
* claim: #10752

* Fix readable sketch selection labels in dialogs (#10752)
2026-11-02 00:02:37 +00:00
SilentWolf75 afa4f72f7d Insert SVG: say why a sketch is too large (#10874) (#10880)
* claim: #10874

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

* Insert drawing: name the sketch size cap when a file is too dense (#10874)

A large SVG/DXF failed with opaque \"sketch is too large\". Say the 20000 point/curve limit and that scale alone does not reduce entity count, and refuse dense drawings before adding them.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-01 23:37:55 +00:00
Sebastian Höpfl cfc46448a8 Joints: an edge origin shares the joint faces' measurement (#10866)
`face_of` called `Body::faces`, which tessellates the whole body, on every
solve. Face origins went through the cached `ModelBody::joint_faces`, at
the same tolerance, but edge origins never did, so one edge origin on a
large body cost a drag or a Drive Joints step far more than the solve.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:51:42 +00:00
github-actions[bot] 58c64ca4d4 Regenerate generated files and fold pending files [skip ci] 2026-11-01 22:29:27 +00:00
Nathan Dane 5c7e50cb67 Preferences redesign 2/11: shared widgets and painters in ui_kit (#9874) (#9890)
* claim: #9874

* Add shared Preferences cards, figure painters and gallery entries to ui_kit

* Preferences: Arabic, Hebrew and Vietnamese carousel labels

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

* Use the app's selection colours in the Preferences cards

* Measure Preferences figure inks and use shared palette metrics

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:58:35 +00:00
Colin Faulkingham b6aabb88ff Insert Design: place reused links from their parent origin (#10813) (#10825)
* claim: #10813

* fix: place reused linked designs from parent origin (#10813)
2026-11-01 21:47:53 +00:00
DuBearte a1dc849e8d Sketch Fillet and Chamfer: a refused size's error goes once the size fits (#9316) (#9469)
A corner dialog copied the preview's error into its own, where nothing
cleared it when the preview recovered. The dialog already shows the
preview's error, so the copy is gone.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:25:21 +00:00
Kushagra Kumar 446590dca1 fix(engine): report design variant in timeline.inspect and accept in timeline.add (#10748) (#10792)
* fix(engine): report design variant in timeline.inspect and accept in timeline.add (#10748)

* ci: refresh checks to clear cancelled workflow run
2026-11-01 21:00:44 +00:00
CamWight 303236e524 Tests: io's byte mutator moves to a testkit crate, a dev-dependency only (#5184) (#7682)
* Tests: io's byte mutator moves to a testkit crate, a dev-dependency only (no behaviour change)

`solidcraft-testkit` (the Testkit class xtask/src/layers.rs reserves) holds the mutator and the
no-panic check unchanged: the same seeds, mutations, budget and doc comment. io's reader fuzz
tests use it as a dev-dependency and give the same case counts; the mutator's own test moves
with it. The co-editing wire's decoders will fuzz with it too (#5184, CE6).

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

* Cargo.lock: testkit at the workspace version

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-01 20:28:58 +00:00
bherbruck 97336ccfe7 Kernel pause: pull requests are judged by the kernel directories they change (#10807)
A pull request outside crates/kernel, crates/geom and vendor/truck* is no longer labelled for a
kernel word in its title. Issues and discussions keep the title check.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:19:41 +00:00
cronos e1d4cb433c Cover sketch line-arc fillets in both selection orders (#10242) 2026-11-01 20:19:16 +00:00
Thodoris Tsagkogeorgas 0ad74f2577 Revolve: keep the angle grab point when the sweep is zero (#9601) (#9602)
A revolve of 0° still builds no solid. The handle stays on the profile so the angle can be dragged back into range.
2026-11-01 20:18:27 +00:00
semmelrinde 835e261593 Coil: up and down arrows on the Revolutions field (#7481) (#7766)
* claim: #7481

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

* Coil: step arrows on the Revolutions field (#7481)

The Coil dialog drew Revolutions as a plain expression row, the one
count without the step arrows the pattern quantities have. It is now a
count row from 1 to 200 turns (the helix's limit); a fraction still
types in, and an arrow steps it to the next whole turn.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:12:56 +00:00
github-actions[bot] 84c3b6356c Regenerate generated files and fold pending files [skip ci] 2026-11-01 20:42:12 +00:00
github-actions[bot] d730a273dc Regenerate generated files and fold pending files [skip ci] 2026-11-01 20:33:03 +00:00
semmelrinde 89cb651084 Warnings: quote a long generated name by its start and end (#8673)
A chamfer's warning quoted the full generated names of fourteen split edges,
122,701 characters (#8541). Names over about 80 characters are now quoted by
their first 48 and last 24 characters.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:12:42 +00:00
Kai Evans 9efcb461f8 Feature edits: OK computes off the window thread in a dialog without a live preview too (#7388)
Offset Plane and a cosmetic Thread have no live preview, so their edits' OK ran on the window
thread and recomputed every later feature there. Any edit now goes through the off-thread
transaction. The editor's pinned original is used only while the dialog previews, as
open_feature_edit keeps it: a Thread switched from Modeled to cosmetic edits the design as it is,
so a body hidden meanwhile doesn't refuse it as a changed design.
2026-11-01 20:11:05 +00:00
Daniel 958ab867cd Press Pull on a chamfer or fillet face edits it where you clicked (#4986) (#9771)
* claim: #4986

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

* Press Pull on a chamfer's face changes the chamfer's distance (#4986)

A chamfer's face is flat, so solid.press_pull and the Press Pull start
path in the UI took it for a planar face and extruded it. Both now look
for a fillet's or chamfer's face before asking whether the face is
planar, so a chamfer's face reopens the chamfer and its distance grows
by the distance pulled, as a fillet's radius already does. A first pick
with Press Pull already open is routed the same way.

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

* Press Pull on a blend face puts the arrow on the edge clicked

A fillet or chamfer reopened by Press Pull from one of its faces drew
its radius or distance arrow on the feature's first stored edge, which
can be a corner the user did not click and cannot see (the Sample
Plate's fillet, from the far corner). The click's point now goes with
the reopened dialog as a hint that is never saved, and the arrow sits
on the feature's edge nearest it, at the point on that edge nearest the
click. Opened any other way (the history, Edit Feature) the arrow stays
on the first edge as before.

A click point that is not finite is not kept, and a point on a flat face next to a chamfer still extrudes that face (tested).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:11:00 +00:00
Jesper We cffa6e1580 Loft and Surface Loft: two sections from one sketch (#8221) (#8222)
* claim: #8221

* Loft and Surface Loft: two sections from one sketch (#8221)
2026-11-01 20:10:13 +00:00
semmelrinde ce296a1ad5 Extrude preview: a round face sweeps round walls, with lines up its corners only (#8838)
The live preview of extruding a picked face built its walls from the
face's display triangles and drew a line up the wall at every boundary
point, each wall piece shaded flat. A round face (the Bolted Flange's
flange) looked like a polygon while extruding. Lines now go up only
where an edge of the face ends and the outline turns there more than
its curves do, and a wall's normal turns with it between smooth pieces.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:09:21 +00:00
semmelrinde 0515dee9ce Colour popover: a held step button keeps stepping, and R, G and B have tooltips (#10201) (#10203)
* Colour popover: a held step button keeps stepping, and R, G and B have tooltips (#10201)

The step buttons beside each colour number sensed clicks only. egui
stops counting a press as a possible click once it outlasts its longest
click (0.8 s), so the held button stopped repeating after 8 steps. They
now sense drags too, which keeps a long press down on the button until
it is let go.

R, G and B now carry tooltips (Red, Green, Blue) as H, S and V already
did: a single letter doesn't name the colour in every language.

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

* Colour popover: the R, G and B tooltips in Vietnamese too, a complete catalog (#10201)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:08:43 +00:00
Vvynot 0478f7e401 Hole: reference Distance shows the document precision (#10693) (#10777)
* Hole: reference Distance shows the document precision (#10693)

A measured reference distance showed the raw float (10.000000868569572 mm).
The field now reads at the document's length precision while Refs keeps the
measured value for the model, so OK still sends what was measured.

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

* Hole: restored literals use the document precision (#10693)

Round every literal reference distance on Edit Feature, including comma
decimals and exact whole values; expressions stay as written.

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-01 20:05:49 +00:00
Rec0iL 52f0b52b45 Agent chat: a searchable model picker with a custom entry, listed from every provider, and OpenClaw and OpenRouter presets (#2582) (#10255)
* ui_kit: search_picker, a filtered dropdown with a custom entry

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

* Agent chat: endpoints list their models with names, say when listing fails, and OpenClaw and OpenRouter presets (#2582)

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

* Agent chat: a typed model goes to endpoints and to agents that offer a model option

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

* Agent chat: the header's model pickers search and take a custom model

The popup can't grow once it is open, so search_picker now fixes the height of what changes below
its box when it opens: the custom row or a late list could otherwise end up out of reach.

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

* AI preferences: pick an endpoint's model when adding it and on its card (#2582)

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

* Agent chat: model picker rows read from the left, and a refused typed model says which

From running it: list_picker centres its text, which reads badly for a long list of ids, and an
agent's own refusal can say no more than "Internal error". The search box also asks for a repaint
while it waits for the focus, so an idle window does not stop short of it.

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

* Agent chat: docs for the model picker

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

* Agent chat: an endpoint's answer that is no model list says so, an empty list too, and editing lists with the kept key

A 200 without a `data` array (a web page where the endpoint should be, as OpenClaw's gateway
serves until its chat endpoint is enabled) was cached as a successful, empty listing, so the
picker stayed blank with nothing to say and never asked again. Now it is an error with its own
note, an empty list says so, and both are asked for again the next time.

The Edit dialog lists its endpoint's models when it opens, with the kept key read from the
keychain on the listing's thread, as the card does; the key stays out of the field.

The method-not-found test expects the refusal notice that names the model, as every refused
model gets since this branch.

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

* Agent chat: models from several providers fold under their providers inside the picker

As Chemiseblanc does in #10187: with two or more groups, `search_picker` draws each group as a
header with a chevron, the name and how many of its choices are listed, and a click folds or opens
it. Before anything is typed only the current value's group (and the one the caller asks for) is
open; a group with a match opens once something is typed; a fold made by hand stays until the popup
closes. Enter takes the first row shown, so never one folded away. A lone group keeps its plain
heading.

The header's model picker feeds its providers in as groups, so the separate provider drop-down
goes; the control channel's `provider` now says which group opens first. A header click gives the
focus back to the search box, so typing goes on filtering.

The dialog's listing test checks what kind of listing it got rather than translated words, which
the listing thread may render in another language than the test's.

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

* Agent chat: rustfmt the provider groups

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

* Agent chat: an endpoint's models named `Vendor: Model` fold under their vendors

OpenRouter lists `openai/gpt-6.1-terra` as "OpenAI: GPT-6.1 Terra". A listing whose ids read
`vendor/model` and whose names read `Vendor: Model` now names its providers, so the picker folds
it under its vendors like a multi-provider agent; the rows read the model's name, and the id stays
in the tooltip and in the search. An agent's own `Provider: …` word still comes first. One vendor
is one flat list, as before.

The Add dialog's picker has no current model to prefer, so with nothing to prefer every group
starts open; folding is a click away.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:01:26 +00:00
ABDULLAH 1ae93218a7 Arabic: outline term in the cut preview tint help (#10801) 2026-11-01 19:50:21 +00:00
P'tite Tête 3017641b41 fix: Projection d'une silhouette à congés coniques fermée en un profil (#10773)
- snap_tangent_arcs : chaque extrémité d'arc prend la ligne la plus proche
- une ligne ne prend qu'une extrémité d'un arc ; l'autre passe à sa ligne suivante
- une ligne plus loin sur le contour est ignorée quand l'extrémité touche déjà une autre pièce
- tests : 4 unitaires (project_order.rs) et the_silhouette_of_conic_rounds_closes

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-11-01 19:41:32 +00:00
github-actions[bot] 17b9a8e67f Regenerate generated files and fold pending files [skip ci] 2026-11-01 19:55:56 +00:00
github-actions[bot] 90c09eaaf9 Regenerate generated files and fold pending files [skip ci] 2026-11-01 19:48:16 +00:00
Dahak Builds 251573a5a4 MCP: compare the model with a reference picture (#1486) (#6416)
* claim: #1486

* Compare picture: overlap and edge offsets of the model against a reference picture (#1486)

inspect.compare_picture lays the model's silhouette over the part found in a
PNG or JPEG and slides it to the best overlap: overlap (0..1), the model's
edges against the picture's in mm at nine stations, and an overlay PNG. The
MCP tool compare_picture runs it and hands the overlay back as an image block.

inspect.silhouette grows a reusable silhouette::outline for the model side.

* chore: re-trigger checks (defaults line already present in PR body)

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

* i18n: carry the Compare Picture label in Arabic, Hebrew and Vietnamese too

The complete-catalog guard (every_complete_catalog_carries_every_english_message)
failed because the pending file covered only en/ja/zh; ar, he and vi are
complete catalogs and need the key as well.

* Compare picture: one uniform scale, capped fit, sturdier part finding

Review on #6416: fit no longer stretches the axes apart (width is the default,
both scales uniformly by width and reports aspect_mismatch); the fitted model
takes the same MAX_GRID cap as the picture; the border flood seeds only from
border pixels near the border's main colour, reads alpha, retries at a
stricter step, and says which case failed; the slide searches the whole
placement range instead of a tenth around the centre; the model's holes get
the picture's fill rule; crop becomes crop_px / crop_fraction with explicit
units. Pins #1486's bracket (overlap 0.865, bottom -10 mm at the upright) and
adds a regression test per problem.

* Compare picture: alpha only for a see-through border, part_fills_crop, outline-only wording

Review re-test on #6416: one translucent backdrop pixel no longer switches the read to
alpha — alpha decides only when most of the border is see-through, and any ink is then
part, so a part drawn at alpha 100 survives; part_fills_crop: true takes the crop box as
the part itself, for one view cropped tight off a board where the part takes most of the
border and the flood would keep the backdrop strip instead; a third backdrop pass compares
each pixel to the border's main colour, so a white part on (245, 245, 245) JPEG at quality
75 separates; the command, tool and docs descriptions now say the compare is outline only:
holes are not compared. A banded light backdrop stays a follow-up. A test per case.

* MCP compare: format the picture tests

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

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-01 18:30:18 +00:00
Nathan Dane bdce595fb3 Render results: retained renders, Live and render compare, render all shots (#6281) (#7375)
* Render results: keep up to eight completed renders and select or discard them

* Render results: Live and render compare with a divider, and render all shots

* fix: use comparison theme tint and reveal reset test targets

* fix: restore Unicode expectations in render workspace tests
2026-11-01 18:30:11 +00:00
semmelrinde 862807ffc9 Lost GPU: the per-frame probe meets a D3D12 device reset before the first upload does (#6528) (#7767)
* claim: #6528

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

* Lost GPU: the per-frame probe opens a command list, as the upload would (#6528)

On a reset D3D12 device the empty submission before a frame's uploads
went through (it only signals a fence), and the first queue write then
panicked inside wgpu-core (PendingWrites::activate unwrapping Lost).
The probe now submits a command buffer with a debug marker: finishing it
makes the same begin_encoding call, through a path where wgpu loses the
device and runs its lost callback instead of panicking. The picture
capturer's probe does the same.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:20:40 +00:00
github-actions[bot] 6f368a5b77 Regenerate generated files and fold pending files [skip ci] 2026-11-01 19:01:13 +00:00
rbousigues88 a0ed5a9923 Measure: a fillet's torus or a sphere reports its radius (#7159) (#7160)
* claim: #7159

* Measure: a fillet's torus or a sphere reports its radius (#7159)

The face of a fillet round a circular edge is a torus and a ball's face a sphere, which
Measure listed by area only. Their face item now carries radius, diameter and surface
(and the ring radius of a torus); they stay out of Shape, so distances are unchanged.

Fixes #7159.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 18:03:17 +00:00
N3MI-DG 38ae728538 Preview: new surface is tinted whatever its size, and the tints can be turned off (#9111) (#9112)
* claim: #9111

* Preview: new surface is tinted whatever its size, and the tints can be turned off (#9111)

A preview decided which faces of a changed body were new surface by asking
whether their triangle centroids lay on the old body, within a tolerance that
follows the part's size. A round or chamfer small beside the part never leaves
that tolerance, so it was drawn as if it had always been there, and sampling at
a fixed step told alike faces apart by nothing but how many triangles they had.

The kernel already records which faces an operation generated, so `build` takes
that record alongside the geometric test. The test stays because a body carries
the history of whatever last made it, which is not the previewed operation when
a parameter change re-evaluates the design: the record only ever adds faces to
the answer. The vote itself now asks about every centroid of a small face,
spreads a large face's samples without a resonant step, and calls a face new
above an eighth of its samples, which a blend tangent on both of its sides
reaches with the one row of facets in five that is clearly off the old surface.

Graphics gains two settings, both on as they come, so nothing changes out of
the box: Tint new surface in a preview draws a changed body as itself when it
is off, working out no face at all, and Show removed material in red leaves the
see-through cut and the ghost of the shape a feature replaces fully
transparent, drawing only their outlines.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-11-01 18:02:57 +00:00
github-actions[bot] 147d1cfadd Regenerate generated files and fold pending files [skip ci] 2026-11-01 18:35:09 +00:00
github-actions[bot] d135e1659c Regenerate generated files and fold pending files [skip ci] 2026-11-01 18:26:58 +00:00
froglemonade2 3ccf04d4b2 Edit Extrude: close on the first Escape (#9084) (#9757)
* claim: #9084

* Edit Extrude: close on the first Escape (#9084)

An edit dialog whose value names a parameter (`T` in Edit Extrude) opened with
that field focused and its completion list showing the longer names (`tan(`),
unarmed. The list took the first Escape, so the dialog needed two.

An unarmed list now leaves Escape to the window until the box is typed in, as
it already leaves Enter and Tab; once typed in, Escape dismisses the list first
as before. Its hint drops "Esc dismiss" in that state.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:02:39 +00:00
N3MI-DG 2014e60ae6 Sketch Offset: an Instances count stacks offsets one behind another (#9827) (#9828)
* claim: #9827

* Sketch Offset: an Instances count stacks offsets one behind another (#9827)

Offset made one offset at a time, so a set of evenly spaced outlines meant
running it again on the curves it had just made, once per ring: a separate undo
step and a separate driving dimension each time.

`sketch.offset` now takes `instances`, a whole number or a unitless expression
from 1 to 100 (1, today's behaviour, by default). The per-group work the command
did inline becomes `offset_rung`, and `offset_ladder` runs it once per instance,
each rung offsetting the one before it the same distance again, so instance n
stands n distances from the curves picked. Every rung of a side is driven by
that side's own dimension, so editing it moves the whole set and keeps the
spacing even, and each side of a Two Sides or Symmetric offset grows its own
ladder. A rung the shape has no room for refuses the whole command and names
which instance it was.

A later rung reads its side from the geometry, through the rung's own
normal-distance annotation, rather than from the distance's sign: an output's
stored ends need not run the way the chain through them is walked, and a
reversed walk reads the same sign as the other side. A circle is the exception
and keeps the sign, because it offsets concentrically with no chain to walk, and
a step past a ring smaller than the distance lands beyond the centre, where the
circle's "outside the current radius" side test reads as the outward side: an
inward ladder turned round and grew again (1, 4, 1, 4 mm) instead of running out
of room.

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

* Sketch Offset: the Instances row in the offset window (#9827)

The offset window gets an Instances row under Distance: a whole number with step
arrows, 1 by default, so the count can be walked up a ring at a time and the
preview follows each step. The count is not kept in the design, as Mirror's and
Scale's values are not, so the window sends the number it evaluates to rather
than the text.

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

* UI kit: a count's step arrows are not text (#9827)

The arrows stand inside the editor, which asks for the text cursor across its
whole rect, so hovering one showed an I-beam and the arrows read as part of the
number. They take the ordinary cursor back after it, and the pointer is read
from the arrow's rect rather than from the hover the editor underneath competes
for, which keeps the hover highlight in step with it.

The pattern Quantity rows share this field and had the same thing.

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

* Sketch Offset: Instances label in the complete catalogs and the other shipped languages

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

* Sketch Offset Instances: translations for the complete catalogs

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-11-01 18:02:32 +00:00
Nathan Dane c454c05f88 Preferences redesign 1/11: design reference in docs (#9873) (#9889)
* claim: #9873

* Add the Preferences design reference: pages, figures, measurements, tokens

* Fix the selection look in the design reference to match the app
2026-11-01 18:02:24 +00:00
merik 1cc90036fb File: a command for each export format, so each can have a key (#10176) (#10177)
File › Export STEP…, STL…, 3MF…, OBJ… and URDF… were UI actions around
the shared file.export, so Keyboard Shortcuts had no row for them.
file.export_step, _stl, _3mf, _obj and _urdf now do what those items do
when started from a key, command search or the menu (a save picker; Save
As Mesh for STL and 3MF), and take a path when called with JSON. The File
menu shows their keys. Each has a contract and a reply type
(schema/file.rs).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:02:16 +00:00
Dahak Builds 176c22acaa MCP: explain when an unknown command id is actually an MCP tool name (#10678) (#10680)
* claim: #10678

* MCP: explain when an unknown command id is actually an MCP tool name

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 15:36:04 +00:00
ABDULLAH 2ab5dedfc1 Arabic: pan term in the Creo navigation help (#10667) 2026-11-01 15:13:48 +00:00
Dahak Builds e77c55549f Sketch: a value box must not swallow a click on the reference line's endpoint (#10600) (#10639)
* claim: #10600

* Sketch: a value box must not swallow a click that lands on a sketch point (#10600)

The construction line tools' Length box waits beside the pointer. When the
pointer reaches the reference line's own endpoint it can land under that box
(the box is anchored to the tool hover, which freezes while the pointer rests
on the box), and the box's text edit takes the click, so the tool never sees
it. A release over the tool's own value box or construction/normal switch that
lands on a snapped sketch point now goes to the tool; a click on the box away
from any sketch point still edits the box.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 13:31:14 +00:00
github-actions[bot] e7bb038795 Regenerate generated files and fold pending files [skip ci] 2026-11-01 13:27:53 +00:00
Marcel Karger a5a561caae Add Creo Parametric navigation preset (#3507) (#9322)
* claim: #3507

Co-authored-by: Codex <codex@openai.com>

* Add Creo Parametric navigation preset (#3507)

Add middle-button orbit, Shift+middle pan and Ctrl+middle vertical drag
zoom, with downward dragging zooming in. Keep the default navigation
preset and existing presets unchanged.

Refs #3507

Co-authored-by: Codex <codex@openai.com>

* Navigation: Creo preset needs the NX preset's reverse_drag_zoom field

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

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:57:15 +00:00
Dahak Builds 462de5b9d2 Fix extrude preview placement in placed components (#2103) (#10596)
* claim: #2103

* Pin #2103: extrude-cut preview placement on placed component occurrences

* Format #2103 regression tests

* Satisfy clippy lints in #2103 regression tests

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 12:46:17 +00:00
kaltenbrunnermarkus be425c6102 Assemblies: use lookup maps when building the world state (#10497)
* claim: Assemblies: use lookup maps when building the world state

* Assemblies: use lookup maps when building the world state

world_state_reusing, made again after every geometry command in a design
with components, filtered all bodies for every placement, searched the
timeline for every sketch and construction of a placed part, and searched
all bodies twice per tap and placement. It now builds the feature to
component map, the bodies of each component (in body order) and each
tap's component and body offset once per call. The result and its order
are unchanged.

On a 100-part assembly placed 5 times (800 sketches, 200 taps) one world
state drops from 124 ms to 9.5 ms (release, median).

A test-only counter of searches through all features or all bodies
checks that the work no longer grows with the sketches and taps.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:39:41 +00:00
Colin Faulkingham bc4d35b097 Avoid rebuilding bodies when rename updates feature references (#6287) (#10588)
* claim: #6287

* fix: reuse body geometry when renaming references (#6287)
2026-11-01 12:15:47 +00:00
Colin Faulkingham 78c5b8de42 Fix fastener placement on transformed washers (#9648) (#10583)
* claim: #9648

* Fix fastener picks on transformed washers
2026-11-01 12:13:41 +00:00
kaltenbrunnermarkus 9738861d61 Sketch drag: the crash test cleans up its folder like its neighbours (#10602)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:13:19 +00:00
ABDULLAH 0cf4da606c Arabic: one term for parameters in the equation curve and text help and the joint move hint (#10604) 2026-11-01 12:09:56 +00:00
github-actions[bot] 074cc758ee Regenerate generated files and fold pending files [skip ci] 2026-11-01 12:12:00 +00:00
kaltenbrunnermarkus 47e8b96c2f Placement digests look up bodies by name once (#10492)
A join, cut or intersect without explicit targets reads where every body
of its component is placed. Its placement digest searched the model for
each of those bodies by name, then the timeline for its component and the
placements for that component's frame: O(B^2 + B*F) per feature, on every
evaluation, also when every feature comes from the cache.

The bodies, their components (Document::model_body_components) and each
component's frame are now looked up once per digest. The bodies are still
hashed in name order with the same values, so every digest stays the same
bit for bit and saved caches stay valid.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:45:22 +00:00
Dahak Builds 7b3d84fb33 Per-component History hiding (#10393) (#10578)
* claim: #10393

* Per-component History hiding: flag, commands, Browser menu, bar toggle (#10393)

* Carry the new History-hiding strings in the complete catalogs (#10393)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 11:42:33 +00:00
github-actions[bot] 59a40aabc0 Regenerate generated files and fold pending files [skip ci] 2026-11-01 11:43:09 +00:00
kaltenbrunnermarkus b557f22f97 Sketch: a construction/normal switch in the construction line tools (#10258) (#10551)
* Sketch: a construction line with a length can be a normal line (#10258)

`construction: false` on a construction line command that has a
`length` makes the segment a normal sketch line, ready to close a
profile, instead of construction geometry. The default stays
construction, and an infinite line is always construction.

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

* Sketch: a length box in the construction line tools (#10258)

The horizontal, vertical, parallel and perpendicular construction line
tools show a Length box beside the pointer from the start. It stays
empty (shown as infinite) and leaves the keyboard to the tool's
shortcuts until a digit is typed; then the box takes the keyboard.
Without a length the tools work as before: one click, an infinite line.
With a length the first click sets the start and a second click the side
the segment runs to, sent as the command's `length` and `near`, so the
segment gets its length dimension. The angle tool has a Length box
beside its Angle box. A typed length keeps the start's snap, and the
preview draws the segment the click will create.

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

* Sketch: a construction/normal switch in the construction line tools (#10258)

A Construction | Normal switch sits under the Length box of the
construction line tools. Normal sends `construction: false`, so a
segment with a length becomes a normal sketch line, ready to close a
profile, without converting it afterwards. The switch starts at
Construction with each start of a tool. An infinite line is always
construction, so without a length the switch is greyed out and its
tooltip says why. It belongs to the tool alone: the Sketch Palette's
drawing line type (X) is left as it is.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:15:21 +00:00
github-actions[bot] 29acaf8642 Regenerate generated files and fold pending files [skip ci] 2026-11-01 10:44:57 +00:00
kaltenbrunnermarkus 36bdf4ea3c Sketch drag: drop the second document copy per frame (#10507)
* claim: Sketch drag: drop the second document copy per frame

* Sketch drag: drop the second document copy per frame

edit_solved copied the document to edit it, then stored it with
`*s.doc_mut() = doc`. The command's undo snapshot still holds the old
document, so doc_mut copied the whole design a second time only to
overwrite it. Store the edited document with Session::set_doc instead,
which forgets the same caches and puts it in a new Arc. The design lands
at a new address as before and the command's end records it for the
crash journal (#7502).

A live drag frame on a design of 200 sketches (20k curves) drops from
19.2 ms to 9.4 ms; 50 sketches: 2.3 ms to 1.7 ms.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:13:17 +00:00
kaltenbrunnermarkus ddd65cc4bb Move: say a part's joint conflicts instead of 'no movable joint axis' (#10509)
* claim: Move: say a part's joint conflicts instead of 'no movable joint axis'

* Move: say a part's joint conflicts instead of 'no movable joint axis'

When a joint of the selected part conflicts with the others (the solver
can't close its loop), Move found no axis and said "This part has no
movable joint axis.", pointing at joint types and limits. It now names
the conflicting joint and suggests editing, suppressing or deleting it.
Parts with no free joint value and no conflict keep the old message.

Refs #10331

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:10:01 +00:00
kaltenbrunnermarkus 2a410c2d76 Sketch: Intersect can pick sketch lines (#10484) (#10512)
* claim: #10484

* Sketch: Intersect can pick sketch lines and model edges (#10484)

The Intersect dialog only took faces or whole bodies, so a click on a
line of another sketch (the sides of a rectangle crossed by a plane
through its middle) picked nothing, although sketch.intersect already
cuts edges and sketch curves with the sketch plane. The pick list now
takes faces, edges and other sketches' curves, and the selection filter
reads Geometry | Bodies like Project's.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:09:55 +00:00
ABDULLAH 01b43999a5 Arabic: unambiguous last sentence in the Plane Through Axis And Point help (#10561) 2026-11-01 10:09:49 +00:00
Dahak Builds bcb5efcaa4 Right-to-left: multi-word Hebrew/Arabic names read backwards; prefs search misses multi-word labels (#10471) (#10562)
* claim: #10471

* Show given names in bidi display order; find RTL prefs labels typed in reading order (#10471)

crate::bidi ran over catalog text only, so a multi-word Hebrew or Arabic
body name showed its words left to right in stored order, and Preferences
search compared display-order labels against reading-order queries.
given_name now returns right-to-left names in display order (browser rows,
tabs, status bar all draw names through it), and text_matches also scores
the query after the same bidi transform.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 10:09:44 +00:00
semmelrinde 74ee047d3f Configuration rules: Then says it lists only the table's columns, and how to add one (#10532) (#10544)
* Configuration rules: Then says it lists only the table's columns, and how to add one (#10532)

A rule drives or restricts a column of the table only, so Then leaves out a
feature without a Suppress column or a parameter without a column, where
When lists them all. A note under the list now says so and points at Add
Column.

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

* Configuration rules: the two notes in the complete catalogs (Arabic, Hebrew, Japanese, Vietnamese, Chinese)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:09:38 +00:00
kaltenbrunnermarkus ab6e6941c7 View: look hidden bodies up in a set (#10496)
* claim: View: work out consumed sketches and hidden bodies once per revision

* View: work out the sketches features use once per design

Each sketch_shown call walked every feature of the design to tell whether
the sketch is used, and the viewport asks about every sketch several
times a frame. The used sketches are now gathered into a set once per
design (keyed on the design itself, so any edit reads them again).

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

* View: look hidden bodies up in a set

body_visible walked the hidden-bodies list for every body asked about, and
the viewport asks about every body several times a frame. The hidden
bodies now join the hidden joints and folders in the view's visibility
index, built when the list changes.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:59:42 +00:00
github-actions[bot] a93d7b207c Regenerate generated files and fold pending files [skip ci] 2026-11-01 10:14:19 +00:00
kaltenbrunnermarkus 2b65164159 View: work out the sketches features use once per design (#10545)
* claim: View: work out consumed sketches and hidden bodies once per revision

* View: work out the sketches features use once per design

Each sketch_shown call walked every feature of the design to tell whether
the sketch is used, and the viewport asks about every sketch several
times a frame. The used sketches are now gathered into a set once per
design (keyed on the design itself, so any edit reads them again).

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:53:47 +00:00
kaltenbrunnermarkus fbb00dc7b2 Canvas: store reference image data as shared text so edits stop copying it (#10489)
* claim: Canvas: store reference image data as shared text so edits stop copying it

* Canvas: store reference image data as shared text so edits stop copying it

Every edit clones the document, because the undo history still holds the
old one, and the canvas image was a plain String: each of the up to 200
undo states owned its own copy of the base64 text. Canvas::data is now
Arc<str>, like the shared STEP text, meshes and point clouds; serde's rc
feature writes it as the same JSON string, so design files stay byte for
byte the same.

A 4.2 MB image with 250 edits: peak memory 1.09 GiB -> 42 MB, run time
1.16 s -> 0.12 s.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:46:17 +00:00
semmelrinde 7702316039 Bosnian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10445)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:44:28 +00:00
semmelrinde bb0b9a1d2f Browser: a folder that holds only a new group shows it (#10526) (#10529)
* Browser: a folder that holds only a new group shows it (#10526)

New Group from the root's context menu made an empty group in the Bodies
folder, but the Browser draws no folder without items, so in a design
with no bodies yet nothing appeared. A folder that holds groups is now
drawn with them, even before anything is in it.

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

* Browser: test that a new group in an empty folder shows and takes a dragged body (#10526)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:44:17 +00:00
kaltenbrunnermarkus 74eda80b5d Extrude dialog: work out the automatic operation once, not every frame (#10491)
* claim: Extrude dialog: work out the automatic operation once, not every frame

* Extrude dialog: work out the automatic operation once, not every frame

An open Extrude window with the automatic operation on asked the engine
for `auto_operation` every frame. That probes the bodies with
`Mesh::contains`, rays through every triangle, so a large part cost tens
of milliseconds per frame with nothing changing.

The window now keeps the answer with a key over what it depends on: the
extrude's parameters (participants included), the design's revision and
id, and the active component and occurrence path. The latch still runs
every frame as before.

Open Extrude over a 1M-triangle mesh body: 29.8 -> 0.87 ms per frame
(headless harness, median of 3).

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:43:06 +00:00
kaltenbrunnermarkus ea63eec845 Extrude dialog: keep the taper limits while the profile is unchanged (#10503)
* claim: Extrude dialog: keep the taper limits while the profile is unchanged

* Extrude dialog: keep the taper limits while the profile is unchanged

The taper handle is drawn every frame while the extrude window is open,
and every frame it worked out the inradius of each picked profile and of
each of its holes: a grid search over every side, about 200 ms a frame
for a plate with 64 round holes. Only the height changes between frames.

The radii are now kept in the window's state with the profiles they were
worked out for, and worked out again only when the profiles differ. The
limits come from the same arithmetic, so they are unchanged.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:37:47 +00:00
HiroKenEz 64a55392d8 Sketch: a Line chain survives Undo (#10016) (#10019)
* claim: #10016

* Sketch: a Line chain survives Undo (#10016)

Undo could take away the segment the chain was drawing on from, and every
further click was then refused with "unknown sketch point". The chain now
remembers where each segment started and goes back to the last one still there.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:19:23 +00:00
github-actions[bot] 3a6920d10f Regenerate generated files and fold pending files [skip ci] 2026-11-01 09:39:05 +00:00
kaltenbrunnermarkus 228b311a6f Raytracer: map faces to looks once (#10502)
* claim: Raytracer: map faces to looks once

* Raytracer: map faces to looks once per body

Preparing a body for the CPU raytracer walked the body's face look list
one entry per work step for every triangle, so a body cost T x (F + 1)
steps (each through the job host) instead of T. The face looks are now
sorted once per body, keeping the first entry per face as before, and
each triangle finds its look with one binary search. Pixels are
unchanged; with 10 000 faces and 50 to 500 face looks, preparation
drops by 40 to 88 %.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:16:23 +00:00
kaltenbrunnermarkus 16f00d4cf9 Opening: validate the document once when a model cache is present (#10501)
* claim: Opening: validate the document once when a model cache is present

* Opening: validate the document once when a model cache is present

Opening a design that keeps its model parsed and validated document.json
twice: reading the model cache checked it before decoding the cached
bodies, and read_document, which follows straight after, runs the same
migrations and validation and fails the open when they fail. The open
path now reads the model without that check; geometry(),
read_design_geometry and the preview keep it. A broken document fails the
open with the same error as before.

3,000-feature design with its model cache, bench-open parse step:
426 -> 293 ms (median of 8, heavy machine load). Saving the opened
design gives a byte-identical file.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:16:12 +00:00
Dahak Builds 9fa3c85c68 Invert the curved ViewCube roll arrows (option) (#10483) (#10539)
* claim: #10483

* ViewCube: an option to invert the curved roll arrows (#10483)

A new Navigation preference, "Invert the curved roll arrows"
(invert_curved_roll_arrows, off by default): with it on, the two curved
arrows at the cube's corner roll the view the other way within the
screen plane - the clockwise arrow rolls counterclockwise and the
counterclockwise arrow rolls clockwise. The sibling of the cardinal-arrow
inversion from #10286, which left these arrows alone.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 09:13:00 +00:00
github-actions[bot] cda0277c3e Regenerate generated files and fold pending files [skip ci] 2026-11-01 08:49:46 +00:00
kaltenbrunnermarkus f233f000e8 Mesh imports: compare by pointer before comparing contents (#10490)
* claim: Mesh imports: compare by pointer before comparing contents

* Mesh imports: compare by pointer before comparing contents

MeshData derived PartialEq, so the dirty check and the autosave check
walked every coordinate and index of a scanned mesh after each edit,
although the document and its saved copy share the same arrays. Compare
shared arrays by pointer first and value by value otherwise, as Points
and Colors already do. About 1.9 ms per comparison for a mesh of one
million triangles goes to nothing.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:17:11 +00:00
kaltenbrunnermarkus fcb0a925e2 Sketch: hash sets for the stale-pattern check before each solve (#10493)
* claim: Sketch: use a lookup map for curves in pattern upkeep

* Sketch: use a lookup map for curves in pattern upkeep

Pattern upkeep runs before every solve, each drag frame included, and
looked up every copy curve and its arc points with a linear search over
all ids. Dragging another line in a sketch with a 500-copy slot pattern
spent about two thirds of each frame there. The ids now resolve through
maps built once per call; the result is the same to the bit.

The solve bench gains a circular slot pattern section.

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

* Sketch: hash sets for the stale-pattern check before each solve

The check whether a pattern lost a seed, copy or relation built three
ordered sets of all ids before every solve. Only membership is asked, so
hash sets give the same answer; a drag frame in a sketch with a 500-copy
pattern takes another third less.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:17:05 +00:00
kaltenbrunnermarkus 71624cf7fa Sketch: use a lookup map for curves in pattern upkeep (#10524)
* claim: Sketch: use a lookup map for curves in pattern upkeep

* Sketch: use a lookup map for curves in pattern upkeep

Pattern upkeep runs before every solve, each drag frame included, and
looked up every copy curve and its arc points with a linear search over
all ids. Dragging another line in a sketch with a 500-copy slot pattern
spent about two thirds of each frame there. The ids now resolve through
maps built once per call; the result is the same to the bit.

The solve bench gains a circular slot pattern section.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:16:59 +00:00
PapaEcho ce72abacf9 Construct: plane through an axis and a point (#10178) (#10472)
* claim: #10178

* feat: add plane through axis and point (#10178)
2026-11-01 09:15:25 +00:00
Dahak Builds f25a8e6abc Fix tooltip delay bypass on sequential menu hovers (#10481) (#10482)
* claim: #10481

* Restart tooltip delay per menu row and place tips beside the drop-down (#10481)

* Format

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-11-01 09:10:54 +00:00
Leo 24b91bd453 Sketch Concentric: accept a circle or arc and a sketch point (#10487) 2026-11-01 07:57:43 +00:00
github-actions[bot] 22f12e80e4 Regenerate generated files and fold pending files [skip ci] 2026-11-01 08:13:20 +00:00
semmelrinde 1b02f38732 German: translate the 20 messages the catalog lacks (Move Sketches to Their Parts, the view cube's arrows, view angle locks and more) (#10488)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 07:45:55 +00:00
Frank 7239c95858 Sketch Project: round edges project as arcs, not short lines (#10312)
Projecting a whole body fitted its outline from mesh edges: a round wall seen
edge-on has triangles leaning a hair either way, so the outline zig-zagged
between both rims and broke into two-point pieces (lines), and seams where two
faces' triangles don't share vertices counted as open, outline edges. Faces
that face one way all over (planes, cylinders and cones about the sketch
normal) now decide for all their triangles, near vertices are welded, and a
seam between two faces facing the sketch is no outline. A STEP hook went from
159 lines to 10 lines and arcs.

A single rounded or chamfered rim of a cut curved face is a spline that only
follows its circle; read as Bezier pieces it came in as twenty splines. An
edge read as spline pieces that stays on a circle within 1e-5 of its radius,
in a plane parallel to the sketch, now comes in as that arc.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 07:13:00 +00:00
clark19990 f90b6116d5 Measure: datum planes, axes and points, at exact distances (#10157)
* Measure: datum planes, axes and points, at exact distances

The Measure tool took faces, edges, vertices and sketch geometry; a datum
plane or axis could not be picked into it, and a point set of an infinite
plane or axis would measure the wrong thing anyway.

Describe origin and construction planes, construction axes and construction
points as items, accept PLANES, AXES and CONSTRUCTION_POINTS (and the origin)
in the Measure dialog, and give the new shapes exact closest points:

- a point and a plane: the perpendicular distance;
- a point and a line: the perpendicular distance;
- two planes: parallel distance, or zero where they meet;
- a plane and a line: zero where they cross;
- two lines: the skew distance.

The panel and the quick readout show a plane's point and normal, and an
axis's point and direction.

Test: a point 10 above the XY plane, 5 from the Z axis, and a plane and an
axis that meet.

* Select: a straight sketch line is a curve in an input that takes curves

Adding axes to Measure's input made a straight sketch line match the "axis"
rule before the sketch-line rule, so a picked external sketch curve became a
SketchCurve instead of the ExternalSketchCurve Measure records. The axis rule
now applies only to inputs that take axes on their own (Move's direction and
the like), not to inputs that also take curves.

Test: measure_input_fits_external_sketch_geometry, and the Move axis picks.

* rustfmt: format the new selection filter and measure code

* Measure: drop an unused normalisation in the skew-line distance

---------

Co-authored-by: clarkliu <277482660@qq.com>
2026-11-01 07:04:03 +00:00
semmelrinde 38b9ff223b Frames: a renamed cut-off piece keeps its name when the design is rebuilt from its features (#10438) (#10466)
* Frames: test that a renamed cut-off piece keeps its name in a rebuild from the features alone (#10438)

solidcraft-cli eval rebuilds a saved design without its saved model; the
rename made after a cut then lands on the first line's member. The test
reproduces it on main; the fix follows.

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

* Frames: a renamed cut-off piece keeps its name when the design is rebuilt from its features (#10438)

Since #10132 a cut's pieces past the first belong to the cut and the piece
that keeps the body's name to the body's own feature. Ordering the pieces
by their faces then moved the names among them but left each body's
feature behind, so the cut-off piece said the frame member feature made
it. body.rename stored its new name there, in the first line's slot, and a
rebuild from the features alone (solidcraft-cli eval of the saved file)
named member A after it. A name now moves together with the feature it
belongs to.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 06:43:40 +00:00
github-actions[bot] d96d547f6b Regenerate generated files and fold pending files [skip ci] 2026-11-01 06:53:11 +00:00
github-actions[bot] 05a8911f83 Regenerate generated files and fold pending files [skip ci] 2026-11-01 06:43:56 +00:00
semmelrinde 1581d85cba Czech: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10446)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:50 +00:00
semmelrinde ad54bdf8eb Danish: translate the 69 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10447)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:32 +00:00
C.Vengels 7bafc99390 Send to Slicer: the Print face input takes one face for each body sent (#10468)
With several bodies to send (after Add all bodies, say), the Print face
input refused every face. It took a print face only when exactly one
body was sent; any other pick was cleared at once, with a note in the
status bar only. Each body's print face then had to be set with Ctrl+B,
or by sending that body alone.

The input now holds the print face of each body being sent. A face
picked in it becomes its body's print face, in place of the one it had.
A click on a body's print face takes it out and clears only that body's.
A face of a body that is not being sent is refused with a message
(`ui.print_face_body_not_sent`, in place of `ui.print_face_one_body`).
Each change is still one `body.print_face` command, so it undoes on its
own. Picks in this window are no longer widened to the other pieces of
the face's surface, so a click on a print face takes it out rather than
adding its neighbours.

The user guide (guide/files.md) says so. The new message is in
assets/locales/pending/print-face-each-body.json, in all 31 languages;
the old one is removed from the catalogs.

Test: dialogs_save_mesh::tests::the_print_face_input_takes_one_face_for_each_body_sent
(fails without the change: the second body's face is refused).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:15 +00:00
semmelrinde e537a63988 Croatian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10449)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:11 +00:00
semmelrinde 984a5de001 Galician: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10448)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:11 +00:00
semmelrinde a799318b0f Hungarian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10450)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:44:00 +00:00
github-actions[bot] f989b3309f Regenerate generated files and fold pending files [skip ci] 2026-11-01 06:18:58 +00:00
github-actions[bot] 8771b734e4 Regenerate generated files and fold pending files [skip ci] 2026-11-01 06:09:49 +00:00
semmelrinde ef081519d5 Italian: translate the 62 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10453)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:43:48 +00:00
semmelrinde 2a3d29766e Indonesian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10451)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:43:38 +00:00
semmelrinde 058f17cc37 Malay: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10454)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:43:32 +00:00
semmelrinde 6475418169 Norwegian Bokmål: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10455)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:43:20 +00:00
semmelrinde 1a76f80bae Dutch: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10456)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:36 +00:00
semmelrinde f7ceba9cca Romanian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10457)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:34 +00:00
semmelrinde 249ca9267a Russian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10458)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:32 +00:00
semmelrinde 119168cb3e Turkish: translate the 67 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10461)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:19 +00:00
semmelrinde 2eb81a79b5 Swedish: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10460)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:19 +00:00
semmelrinde b503340b06 Ukrainian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10462)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:18 +00:00
semmelrinde 63123e9237 Serbian: translate the 68 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10459)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:42:05 +00:00
semmelrinde c843d1fb28 Traditional Chinese: translate the 67 messages main gained after batch 10, the regrouped Preferences included (batch 11) (#10463)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:41:16 +00:00
semmelrinde 483e21ab1b Sketch: Break Link in the context menu of projected geometry (#10417) (#10421)
* Sketch: Break Link in the context menu of projected geometry (#10417)

Projected curves and points (model edges and vertices, another sketch's
curves) could only be unlinked through command search, although Trim and
other refusals say "use Break Link on it". The sketch context menu now
offers Break Link when the selection holds projected geometry; text keeps
Explode Text.

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

* Sketch: test Break Link in a projected edge's context menu (#10417)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 04:41:25 +00:00
kaltenbrunnermarkus 9f37003dfa Browser: Features folders scrolled out of view skip working out their mark (#10439)
* Browser: Features folders scrolled out of view skip their mark

A folded Features folder shows the worst error or warning mark of the
features it holds. Working that out scans the design's whole history, and
from 16 features builds a table of all of them: per placement, on every
frame, whether the row is on screen or not. In a long tree of placements of
a part with a long history this was most of the window's frame while
orbiting.

A row scrolled out of view paints nothing (`tree_row` only takes its space),
so its mark is now worked out only when the row is on screen, as the
placement rows already do for their own state. The rows on screen show the
same marks.

Refs #6083

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

* Scale tests: time each frame of an orbit around a large design

`scale_tests::orbit_frame_cost` (ignored, manual) turns the view with the
right button over a design (a STEP file inserted as the app opens one, a
design file, or the generated assembly) with the model scene built, as the
app builds it once a renderer is there. Per frame it times the window
thread's frame and its named steps (`frame_watch`), egui's tessellation,
and the renderer's prepare, draw recording and GPU work on an offscreen
device (`OffscreenFrames::frame_parts`); without an adapter it times the
window thread alone. `painted` fingerprints the tessellated picture once
the view rests, to compare two builds.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 04:40:39 +00:00
Christian Glombek 4c44126a4d Sketch: sketch.create says which way the origin planes' sketch axes run (#10397)
On XZ a sketch's y runs along world -Z, so a value written as a height lands
below the plane; only the modelling skill's reference said so. The command's
parameter text now gives the axes of all three origin planes (XY: +X, +Y;
XZ: +X, -Z; YZ: +Y, +Z), and a test pins them.

Assisted-by: LLM
2026-11-01 04:19:07 +00:00
Christian Glombek 5c1aad8a09 Assembly: Relink Source takes a .json design, as Insert Design does (#10394)
`component.insert_design` links a design saved as a .sc container or as plain
JSON (`.json`), but `component.relink` refused every path that was not a
container ("choose a saved .sc design"), so a component linked to a JSON
design could not be pointed at another one. Relink now takes any design path
Insert Design takes (`is_design_path`: .sc, a legacy extension or .json); the
file is read by the same `design_bodies`, which already reads both. The Relink
Source file dialog opens on a filter that lists .json among the designs, too.

Assisted-by: LLM
2026-11-01 04:18:49 +00:00
kaltenbrunnermarkus 9d01f6e005 Sketch: construction lines with a length (#10258) (#10427)
* claim: #10258

* Sketch: construction lines with a length (#10258)

The horizontal, vertical, angle, parallel and perpendicular construction
line commands take an optional `length`: the line becomes a construction
segment from `at`, running towards `near`, held by a length dimension
that follows its expression. Without `length` the line stays infinite,
as before.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 04:13:03 +00:00
ashxddaga 127373a950 fix(ui): fade or unfade browser row dynamically when visibility toggles (#9953) (#9967)
- In entry_row (crates/ui-egui/src/browser.rs), use live  rather than cached  for the  property
- Ensures eye toggle dynamically fades or unfades rows for sketches and bodies without requiring a cache rebuild
- Add regression test in browser_card_tests

Fixes #9953
2026-11-01 04:10:32 +00:00
kaltenbrunnermarkus 64fe82b6c1 Engine: open a design a feature at a time (#10198) (#10274)
OpenSteps splits doc.open of a design into steps for a caller that must
keep working meanwhile (the web app, which has one thread): start reads
the file, each step evaluates the next feature by moving a copy's history
marker one feature on (so every result before it is reused), and finish
puts the design in the session's place with doc.open's reply. The next
feature's imported STEP text is offered first, so a caller can read it
in parts. Session::opened and opened_at are split into small helpers so
both paths build the session the same way; doc.open is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:39:22 +00:00
kaltenbrunnermarkus fafa575c9f Find a saved model's sheets by their text, not a record built per state (#10418)
Saving a design with its model cache asked, for every state, for each sheet-metal body it holds,
and built that sheet's JSON record each time only to find it written already. A design of 906
states with 300 sheets spent most of its save there. The sheet's text written straight from it
is the key now; its record is built only the first time. The records and the file are the same.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:20:05 +00:00
kaltenbrunnermarkus 7e701aabc1 Save: write the model cache manifest without copying the model's records (#10419)
* Find a saved model's sheets by their text, not a record built per state

Saving a design with its model cache asked, for every state, for each sheet-metal body it holds,
and built that sheet's JSON record each time only to find it written already. A design of 906
states with 300 sheets spent most of its save there. The sheet's text written straight from it
is the key now; its record is built only the first time. The records and the file are the same.

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

* Write the model cache manifest without copying the model's records

The manifest was built as a JSON tree, which copied the whole model's records (13 to 33 MB of
records in large designs) before writing them, and a cache too big for the file wrote it again for
each try at fewer states. The manifest is now written from the records where they are, its fields
in the sorted order the tree wrote them, so the file is the same byte for byte.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:19:59 +00:00
acebear17 9475e0e1a4 Docs/design approach 10063 (#10409)
* claim: #10063

* docs: add a design approach and workflows chapter to the user guide (#10063)

Adds guide/design-approach.md after the window tour: the parametric idea, principles for designs that change well, the timeline and rollback marker, bodies vs components and occurrences, common workflows with links, a term map for users of other CAD software, and where to find help.

* docs: match the chapter to the app's labels and sketch colours (#10063)

Checked in the web app (0.29.0): the timeline strip is headed History, the toolbar shows New Sketch and Parameters for Create Sketch and Change Parameters, and fully constrained geometry turns white in the default dark theme rather than dark.

* docs: tighten the design approach chapter's wording (#10063)

The glossary covers most, not all, of the chapter's terms; Redo moves forward; the timeline is shown as the History strip; Report a bug drafts a report for the user to file and sends nothing itself.
2026-11-01 02:27:40 +00:00
kaltenbrunnermarkus a8be28563c Sketch: leave out the exact roundoff estimate away from the pixel bound (#10267)
A flat node's roundoff allowance took a hypot per control point. Twice
|x| + |y| bounds each hypot from above with room to spare for rounding,
and rounding keeps sums monotonic, so whenever the flatness test passes
with that larger allowance it passes with the exact one too. Only nodes
whose chord distance comes near the 0.5 px bound (or with a non-finite
term) now take the exact hypots, and they decide as before.

Same result for every node: the reference comparison test still matches
bit for bit.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:16:37 +00:00
NOWN-DEV a69a7c6ffe Inspect: measure names_only lists every world body without measuring (#10402)
A caller that only needs the bodies' names (assembly occurrences included,
e.g. "Body2 (Part:2)") had to measure every body, or read document.inspect,
which lists definition bodies only. `inspect.measure {names_only: true}`
returns each world body's name and its definition's name, honouring the
`bodies` filter; an unknown name is refused before anything is measured.
The MCP `measure` tool passes it through, directly and as a batch step;
`summary` leaves such a list as it is, since there is nothing to narrow.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:12:23 +00:00
Nathan Dane 7366fe0ecc Blur behind panels: render path, zero cost when off (#8876) (#9809)
* claim: #8876

* Add optional viewport-only Kawase blur and native UI composition

* Fix vendored panel hook imports and deprecated float constant import

* Drain texture updates in the full GPU blur fixture

* Hold the blur evidence fixture camera fixed across radii

* Address blur compositor lint checks

* Share animated Render preview background with blur source

* Use resolved Design background for blur source

* Exercise blur rendering independently of software adapter policy in CI

* Use stock egui hosts instead of vendoring the full crate

* Preserve feature-window open handling with stock egui hosts

* Target the native Window in close-handling regression fixture
2026-11-01 01:37:44 +00:00
MarkusRe 60d79e7ea2 Browser: show F2 shortcut on Rename context menu rows and route key when open (#10388)
Show F2 beside Rename on context menus where F2 already renames the
target (bodies, features and sketches, construction planes, components and
occurrences, browser groups, and canvases). Saved views and joints remain
without a shortcut label because F2 does not rename them without a menu.

When a context menu is open, pressing F2 routes through press_shortcut to
rename the menu's target (such as a clicked browser group or canvas) and
close the menu, rather than renaming the viewport selection.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gemini 3.8 Flash <noreply@google.com>
2026-11-01 01:37:25 +00:00
kaltenbrunnermarkus 653bc00be4 Sketch: work out each sampled point's clip distances and screen position once (#10343)
Each node of the display curve sampler recomputed a control point's
seven clip distances for every clip plane it tested (up to 7 times for
the reject test, 3 more for the depth test, once more for the cut), and
projected each point to the screen twice. They are now worked out once
per node into arrays and read from there.

Most nodes are split rather than drawn. A node is flat only when every
control point lies within the pixel bound of its chord after adding a
roundoff allowance that is never negative, so a point already farther
than the bound decides "split" on its own; the allowance (four hypot
and divisions per point) is now left out for those nodes.

Same values in the same order: the reference comparison test from the
previous commit still matches bit for bit.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:12:51 +00:00
kaltenbrunnermarkus 915dd9a83f Viewport: the comment above build_sketch_scene describes the sketch scene (#10367)
The doc comment of the ground shadow had slid onto build_sketch_scene when
that function was added above ground_shadow. Give it back to ground_shadow
and describe what build_sketch_scene builds.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:12:43 +00:00
semmelrinde b99d4ddde9 Sketch Coincident: a selected line midpoint is a point it takes (#10311) (#10315)
* Sketch Coincident: a selected line midpoint is a point it takes (#10311)

A selected midpoint goes to the command as {midpoint: line}, as
Horizontal/Vertical's alignment takes it, but Coincident wanted a point
id and refused it with "b must be a point or curve". The command now
puts a point at the line's middle, tied there by a Midpoint constraint
(or uses the one already tied there), and makes that point coincident
or puts it on the other curve, in one undo step. The toolbar button and
the context menu send a selected midpoint that way.

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

* Sketch Coincident: test a line midpoint in the command, the toolbar and the context menu (#10311)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:44:48 +00:00
kaltenbrunnermarkus 425417b36a Web app: open a large design without freezing the page, with progress and Cancel (#10198) (#10211)
* Engine: open a design a feature at a time (#10198)

OpenSteps splits doc.open of a design into steps for a caller that must
keep working meanwhile (the web app, which has one thread): start reads
the file, each step evaluates the next feature by moving a copy's history
marker one feature on (so every result before it is reused), and finish
puts the design in the session's place with doc.open's reply. The next
feature's imported STEP text is offered first, so a caller can read it
in parts. Session::opened and opened_at are split into small helpers so
both paths build the session the same way; doc.open is unchanged.

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

* Web app: open a design a few features a frame, with progress and Cancel (#10198)

On the web, a design to open is now a staged file load like a STEP file
(#1376): its features are evaluated for about 30 ms a frame through
OpenSteps, a feature's imported STEP file is read a solid per frame first
and held so the feature finds it read, the progress card counts the
design's features, Cancel works between steps, and the meshes are made
a few per frame afterwards. The desktop app keeps its worker thread.

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

* Web app: design links and the files window open designs with progress and Cancel (#10198)

Design links (?open=URL, as embedded viewers use), the files window and
designs opened from the computer went through the one-shot open_path,
which froze the page on a large design. They now use the staged open; a
design link becomes an unsaved copy under the link's name once the open's
reply arrives (SolveApp::open_path_answered).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:17:15 +00:00
kaltenbrunnermarkus b7a6967ef2 Web app: open a design a few features a frame, with progress and Cancel (#10275)
* Engine: open a design a feature at a time (#10198)

OpenSteps splits doc.open of a design into steps for a caller that must
keep working meanwhile (the web app, which has one thread): start reads
the file, each step evaluates the next feature by moving a copy's history
marker one feature on (so every result before it is reused), and finish
puts the design in the session's place with doc.open's reply. The next
feature's imported STEP text is offered first, so a caller can read it
in parts. Session::opened and opened_at are split into small helpers so
both paths build the session the same way; doc.open is unchanged.

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

* Web app: open a design a few features a frame, with progress and Cancel (#10198)

On the web, a design to open is now a staged file load like a STEP file
(#1376): its features are evaluated for about 30 ms a frame through
OpenSteps, a feature's imported STEP file is read a solid per frame first
and held so the feature finds it read, the progress card counts the
design's features, Cancel works between steps, and the meshes are made
a few per frame afterwards. The desktop app keeps its worker thread.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:16:01 +00:00
NOWN-DEV 68105938e2 Sketch: a text rebuilt with other words keeps the far-move walk (#10357)
A sketch holding a text whose words come from a text parameter is left
unsolved after the words change and its links then move far, with the
warning "the sketch constraints conflict (k1, ..., k10)". The same two
changes the other way round solve.

Repro: a sketch with three projected points, a construction rectangle on
them and a centred text along its far side, the text's words given by a
text parameter. Change the parameter's words, then move the source
geometry about 280 mm. Without the word change it solves; with it, the
sketch warns of conflicting constraints.

Other words are other outlines: the text is built anew with new points
and curves. The solve that walks a sketch from its stored state to a far
link move (and the check for sides the jump turned over) refused the two
sketches as not the same entities, and the single jump left alone failed.

`Sketch::stored_with_rebuilt_texts` gives the stored sketch as this one
has its entities when they differ only by rebuilt texts: the texts'
entities as they are now, every other point, radius, arc turn and
constraint value as stored. `Document::solve_sketch` walks from that, so
the points pair up again. Sketches that differ in anything else are
refused as before.

Test: sketch_project::side_tests::a_label_follows_its_source_far_after_a_text_parameter_gave_it_other_words
(fails without the fix with the conflict warning above).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:15:32 +00:00
ABDULLAH 11e283ba77 Arabic: reasoning effort label, view cube arrow note (roll term, tanween spelling), Look group heading (#10355) 2026-11-01 00:11:16 +00:00
kaltenbrunnermarkus c25c2f3a21 Sketch: sample display curves without a heap copy per subdivision (#10342)
The pixel-bounded curve sampler that draws sketch curves and profile
outlines ran again on every camera move, and each subdivision step
allocated: every span's control points were a Vec, splitting one in
half built a Vec per de Casteljau level, each node's projection was
collected into a Vec, and every drawn part grew from two vertices.

The control points now live inline in a fixed array (`Hull`, at most a
degree-9 Bezier), the halves are worked out in place with the same
`mix` level by level, the projection goes into a reused array, and a
new part starts with room for eight vertices. The arithmetic and its
order are unchanged, so the points are the same bit for bit.

Tests: `samples_the_same_points_bit_for_bit_as_the_reference` keeps the
previous sampler as a reference and compares paths, quality and budget
use over every segment kind, slanted and far-off planes, orthographic
and perspective views from far out to far in, clipped and unclipped,
with ample and spent budgets. Ignored timings:
`sampler_cost_against_the_reference` (render) and
`sketch_scene_cost_while_the_camera_moves` (ui-egui).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:10:37 +00:00
kaltenbrunnermarkus e0582668bb Web app: an opened design is drawn when its meshes cannot be made on a thread (#10362)
The web build cannot start the open-meshing thread. Its fallback counted
every body done but listed none ready, so whatever the file load opened
on the web drew no bodies and kept "Loading parts n/n" up for good. The
fallback now lists every body ready and the viewport meshes as it draws,
as the fallback meant to. Found while opening a design through the file
load in the browser (#10198).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:10:20 +00:00
Colin Faulkingham df04e7742f Assembly: let components extruded from reference faces move independently (#10289) (#10332)
* claim: #10289

* fix: preserve placement of face-derived components (#10289)
2026-10-31 23:51:18 +00:00
github-actions[bot] 56b77b405d Regenerate generated files and fold pending files [skip ci] 2026-11-01 00:09:18 +00:00
raykholo e4a67d1e30 Assembly: move sketches to the part they're drawn on (#10218) (#10219)
* claim: #10218

* Assembly: move sketches to the part they're drawn on (#10218)

`component.move_sketches.to_parts` moves each given sketch, or every sketch in the design, whose
plane is an exactly attached face of a part's body into that part through
`component.move_sketches`, as one undo step. Sketches with no single part to go to stay where they
are, and the reply lists what moved and what stayed, with the reason. The Sketches folder's menu
offers it for the folder's sketches (or the selected ones), and a sketch's menu for that sketch or
the selected sketches; the status bar says how many moved and why the others stayed.

`component.move_sketches` now keeps a face-attached sketch where it is on its face: the sketch is
authored in the new component's placement (the face's own when the component owns the face), and
its picked point and repair plane are carried into it. Before, a face-attached sketch moved out of
the design was left without an author placement and failed to evaluate.

* Tests: the moved part is free to move; face anchor path

* Tests: move the to_parts test to the component command tests

* Shorter label: Move Sketches to Their Parts
2026-10-31 23:42:47 +00:00
Dahak Builds ed9599d1e4 View angles card: per-axis zero buttons and locks (#4581) (#10344)
* claim: #4581

* View angles card: per-axis zero buttons and locks (#4581)

The readout card under the orientation gizmo already showed and edited the
view's yaw, pitch and roll in degrees. Each axis row now also has a zero
button that turns that angle to 0 degrees at once, and a lock: a locked
angle stays put while orbiting, turning with the arrow keys or using a 3D
mouse, and its field and zero button go inert, so a symmetrical view can
hold its yaw and roll at 0 while only the pitch is adjusted.

* clippy: drop needless borrow in keep_locked call

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 23:39:24 +00:00
bherbruck 3a5195b0a0 Release 0.29.0 2026-10-31 19:20:30 -04:00
bherbruck beb35029a4 Release 0.29.0 (#10347)
Fold the pending design format changes (align-axis-mask, equation-variable-unit, link-file-digests) into format 46.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:20:15 -04:00
semmelrinde 80fb02f0b8 Sketch Ellipse: Major radius and Minor radius boxes while drawing (#10279) (#10282)
* Sketch Ellipse: Major radius and Minor radius boxes while drawing (#10279)

The Ellipse tool showed no value boxes. After the centre it now shows
Major radius, after the end of the major axis Minor radius; a typed value
places the point at that size and becomes the ellipse's dimension, as for
a circle's Diameter. sketch.draw.dimensioned takes major_radius and
minor_radius for sketch.ellipse, so Dimension new shapes covers it too.

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

* Sketch Ellipse: test the typed radii in the tool and in sketch.draw.dimensioned, and say so in the guide (#10279)

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

* Sketch Ellipse: rustfmt the new engine test (#10279)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:18:35 +00:00
kaltenbrunnermarkus a5c8fa7be7 Viewport: faster highlight with many bodies selected (#10204)
* claim: faster highlight with many bodies selected

* Viewport: find selected bodies by name once per highlight build

With every body of a large assembly selected, each hover change built the
highlight again and looked every selected body up along the world's bodies
(`ModelState::body` scans them), which is quadratic: about 2.2 million name
comparisons on 2,116 bodies, some 3 ms of every hover change.

The selected bodies, faces and edges are now found in a map by name, made at
the first lookup. A name held by more than one body finds the first, as the
scan did, so the same bodies are lit in the same order.

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

* Viewport: measure hover changes with many bodies selected

An ignored scale test: every body of a design selected (a file named by
SOLIDCRAFT_SCALE_DESIGN, else a generated grid of 1,440 boxes), the pointer
moving from face to face, timing each hover change's highlight build headless
and as the app builds it on a GPU (bodies drawn from their plans, and from the
model scene's blocks). Run with `--ignored --nocapture`.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:14:05 +00:00
MarkusRe 4c3d1484c5 Parameters: show one rename tooltip per name (#10309)
Keep full names in the tooltip only when the name cell clips them, and keep
the double-click rename hint without stacking a second popup. Cover short
and long names, favorites, and double-click input with a regression test.

Co-authored-by: Codex <noreply@openai.com>
2026-10-31 22:48:33 +00:00
Colin Faulkingham e0c1b8adbd Sketch: snap to infinite construction-line crossings (#10011) (#10285)
* claim: #10011

* Fix snapping at infinite construction-line crossings (#10011)
2026-10-31 22:23:57 +00:00
bherbruck 604515cfb3 Test: hovering a large body checks the pick index doesn't block, without timing (#10325)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:23:51 +00:00
github-actions[bot] 57d789c0aa Regenerate generated files and fold pending files [skip ci] 2026-10-31 22:34:32 +00:00
github-actions[bot] 4e9656ea9a Regenerate generated files and fold pending files [skip ci] 2026-10-31 22:25:42 +00:00
Nathan Dane 07160d9366 Preferences redesign 3/11: page structure, aliases and profile card (#9875) (#9907)
* claim: #9875

* Preferences: split pages and preserve aliases and profiles

* Preferences: preserve moved-page regressions and complete translations

* Preferences: retain newly added settings in profiles

* Preferences: target Tools in tool-cursor persistence tests
2026-10-31 22:05:20 +00:00
Dahak Builds c111b5f2fb ViewCube: option to invert directional rotation arrows (#10254) (#10286)
* claim: #10254

* ViewCube: an option to invert the directional rotation arrows (#10254)

A new Navigation preference, "Invert the view cube rotation arrows"
(invert_navigator_arrows, off by default): with it on, an arrow around the
cube orbits the camera the way the arrow points, as in Fusion and Inventor,
instead of rotating the cube toward the side the arrow sits on. Left and
right, and up and down, trade places, and each tooltip names the action
actually performed. The curved roll arrows are not inverted: they name the
rotation they perform, not a direction.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 22:00:16 +00:00
Nathan Dane 4594d81f7a Gruvbox: light and dark themes with three contrast choices (#10292)
* claim: consolidate Gruvbox contrast variants

* Consolidate Gruvbox variants into light and dark contrast choices

* Check both original Gruvbox viewport gradient endpoints

* Reset Gruvbox scenario state before independent theme checks
2026-10-31 21:58:30 +00:00
raykholo 2e3448f348 Sketch: an option to make the part active when sketching on its face (#10215) (#10216)
* claim: #10215

* Sketch: an option to make the part active when sketching on its face (#10215)

Preferences › Sketch › "Sketching on a part's face makes that part active", off by default.
When it is on, a sketch started on a face of another component's body activates that component
at the picked placement first and is made there, as after an explicit activate. The part stays
active, so what is built from the sketch lands in the same part.

For scripts the same is `sketch.create` `activate_part: true`; the reply's `activated` names the
component and placement. On an error the active component goes back to what it was.

* Tests: a free part, no edge projection, the sketch on the face plane
2026-10-31 21:52:09 +00:00
github-actions[bot] e5c6e3ef1e Regenerate generated files and fold pending files [skip ci] 2026-10-31 21:57:31 +00:00
rugbymauri 57df47b763 Sketch: clicking the running tool's button ends it, so clicks select again (#10151) (#10263)
* claim: #10151

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

* Sketch: clicking the running tool's button ends it, so clicks select again (#10151)

The lit ribbon button of the running sketch tool started the tool over
when clicked again, so it gave no way out: every click in the view kept
drawing or measuring, and Ctrl+click never reached selection. Esc and
right-click already end a tool, but the pressed button looks like the
natural switch.

A second click on the running tool's button (large ribbon buttons and
the constraint grid) now ends the tool. Persistent analysis toggles keep
their own on/off. The old behaviour stays behind the Sketch preference
"Clicking the running tool's button ends it" (ribbon_click_ends_tool).

Fixes #10151

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-31 21:32:48 +00:00
PapaEcho 42145719a0 Measure: start with the current selection (#10169) (#10234)
* claim: #10169

* fix: initialize Measure from preselected items (#10169)
2026-10-31 21:26:40 +00:00
rugbymauri 64421a2cb9 3D mouse: turns a rolled or upside-down view the way the cap is pushed (#10083) (#10262)
* claim: #10083

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

* 3D mouse: turns a rolled or upside-down view the way the cap is pushed (#10083)

In the default upright (turntable) orbit the cap's Spin rolls the view,
but its Tilt and Turn went straight into yaw and pitch, which ignore the
roll. Rolled upside down, every push moved the model the opposite way on
screen; rolled on its side, tilt moved it sideways.

The device now takes Tilt and Turn back through the view's roll before
the orbit, for the cap and for a device app over the navigation bridge.
An unrolled view behaves exactly as before; Free orbit was already
view-relative and is unchanged.

Fixes #10083

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-31 21:20:10 +00:00
ABDULLAH 711e41233f Arabic: orbit/pan/zoom verbs in the NX navigation help, System language choice, measuring bodies progress (#10272) 2026-10-31 21:19:33 +00:00
kaltenbrunnermarkus cf0508e5c8 Sketch: held solves that cannot succeed give up sooner (#6606) (#10208)
* claim: #6606

* Sketch: held solves that cannot succeed give up sooner (#6606)

A status-only solve (the held solves of a drag) only asks whether it
succeeds. When LM sits in a least-squares valley of conflicting
constraints it crawled on to its 500-iteration limit, and a held solve
runs LM up to three times. Such a solve now leaves a component once the
iteration has stalled far from a solution: over the last 20 iterations
the sum of squared residuals fell by at most 1e-5 of itself while the
largest residual is still above 1e-4 mm.

Successful solves are unaffected: on about 62,000 LM runs that succeeded
(the sketch and engine tests and 120 generated sketch scripts with 2,900
drags) the slowest still fell by 1.4e-3 over any 20 iterations while
that far off. Solves with an analysis keep iterating (their report names
the violated constraints), and live frames are unchanged.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:51:32 +00:00
Dahak Builds bace1a1228 Assistant: set the reasoning effort per API agent (#10241) (#10259)
* claim: #10241

* Assistant: send a per-API-agent reasoning effort on /chat/completions (#10241)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 20:48:42 +00:00
Colin Faulkingham bda8b6b2e8 Fix Touchpad navigation with focused tool fields (#10231) (#10253)
* claim: #10231

* fix(ui): allow touchpad navigation with focused tool fields
2026-10-31 20:48:28 +00:00
P'tite Tête 52765304cd fix: Sketch rectangles and centre circles take expressions (#10251)
- sketch.rectangle.center and two_point: a corner given with expressions is a fixed point that follows its parameters, as with sketch.line
- sketch.circle.center: a radius or diameter naming a parameter is a driving dimension; a string naming none is just the number; bare numbers read in the design unit
- A radius that is not a number is refused with its own message and no longer passed over for diameter
- sketch.circle.two_tangent: a string radius reads bare numbers in the design unit

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-10-31 20:07:36 +00:00
Colin Faulkingham ec526d2b3d Sketch: keep polygon inputs clear of the preview (#10035) (#10199)
* claim: #10035

* Sketch: keep polygon inputs clear of the preview (#10035)
2026-10-31 20:06:03 +00:00
ABDULLAH 3b3d93f333 Arabic: selection wording for the freehand lasso option, clearer view cube fit note, tanween spelling (#10249) 2026-10-31 20:05:41 +00:00
kaltenbrunnermarkus 05f30c1674 Browser: a click just past a feature's name selects it instead of opening its sketch (#10079) (#10213)
* claim: #10079

* Browser: a click just past a feature's name selects it instead of opening its sketch (#10079)

The Edit Profile Sketch button on a browser feature row began 2 points after
the name, so a click that landed just past the name, or a press on the name
that slipped a few points before its release, opened the profile sketch.
The button now starts 8 points after the name, wider than egui's 6-point
click slop; the gap selects the row like the name does.

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

---------

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:05:28 +00:00
Colin Faulkingham bba2fc1400 Report omitted print-face selections in command-script exports (#10230) (#10235)
* claim: #10230

* fix: report omitted print-face assignments in script exports
2026-10-31 19:58:48 +00:00
Dahak Builds 538d7fcb43 Language: follow the system language by default (#10138) (#10183)
* claim: #10138

* Follow the system language on a first start (#10138)

The language preference defaults to a new 'system' value: the OS locale
(sys-locale) is matched against the catalog list - exact tag, base
language, then English - and a System choice sits at the top of the
Preferences language list. A saved language still wins.

* Drop the explicit deref clippy flagged; regenerate THIRD-PARTY-LICENSES.txt for sys-locale

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 19:50:08 +00:00
Ben Rog-Wilhelm 8be56316f6 Sketch Chamfer: several corners at once, as Fillet does (#8998)
The three sketch chamfers take `points: [corners]` (a point reference or
{a, b} each), as sketch.fillet does: every corner is cut alike in one
edit, and a corner that can't be cut, or is named twice, refuses them all.
The Chamfer window sends every picked corner instead of refusing a second.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:28:14 +00:00
github-actions[bot] eda51873aa Regenerate generated files and fold pending files [skip ci] 2026-10-31 19:33:49 +00:00
Colin Faulkingham d4144ddf9b Select: resolve faces and edges from a point or index (#10171) (#10191)
* claim: #10171

* fix(select): resolve faces and edges from a point or index
2026-10-31 19:11:54 +00:00
Dahak Builds c39f44e190 Orbit ignores floating input fields passing under the cursor (#10128) (#10206)
* claim: #10128

* Orbit ignores floating input fields passing under the cursor (#10128)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 19:04:33 +00:00
clark19990 5b8d8416e9 Navigation: an NX mouse mapping (#10156)
* Navigation: an NX mouse mapping

NX's CAD1 scheme drags the middle button to rotate, middle + left to zoom and
middle + right to pan. The mouse mappings here hold one button at a time, so
the preset takes the middle drag with Shift (pan) and Ctrl (zoom) for those two
drags, leaves the right button to the context menu, and fits on a middle
double-click as NX does.

The help text says which NX drags the modifiers stand for.

* Navigation NX note: translations for the complete catalogs

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

* Navigation NX note: use the message id nav.nx.help in the pending file

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

---------

Co-authored-by: clarkliu <277482660@qq.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:44:45 +00:00
github-actions[bot] 1f2356fa7b Regenerate generated files and fold pending files [skip ci] 2026-10-31 19:06:16 +00:00
github-actions[bot] e29e32f197 Regenerate generated files and fold pending files [skip ci] 2026-10-31 19:00:09 +00:00
kaltenbrunnermarkus 76e009ab10 Viewport: blend grid levels while zooming instead of popping (#9570) (#9608)
* claim: #9570

* Cross-fade the viewport grid between levels while zooming (#9570)

grid_step switches the spacing in a step (1, 2, 5 times a power of ten), so
the whole grid swapped at once as the view zoomed through a switch and
popped over and over while scrolling. Near a switch the drawn grid now
draws the outgoing and incoming levels together, weighted by a smoothstep
over about 19% of zoom either side, each level's lines on their own places
on the plane so a fading level never shifts. A line both levels share is
drawn once; its colour and width blend between minor and major.

grid_step itself is unchanged, so sketch snapping, dash lengths and the
tools keep their spacing; the dominant drawn level is always grid_step's.
The viewport and the headless picture share grid_lines, so they still draw
the same grid.

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

* Fade out the fine grid before a non-nested coarser level appears (#9570)

The levels go 1, 2, 5 times a power of ten. From 1 to 2 and from 5 to 10
every coarser line is also a finer one, but 5 is not a multiple of 2, so
cross-fading the 2 and 5 levels drew lines at 0, 2, 4, 5, 6, 8, 10 mm at
once, which looked restless.

At that switch the finer level's own lines now fade out over the first
half of the cross-fade and the coarser level's own lines fade in over
the second, so the two are never drawn together. The lines both levels
share (every 10 mm, the finer level's major lines) stay at full strength
throughout, so the grid never goes blank. The cross-fade spans the same
zoom range as before, and the nested switches cross-fade as they did.

grid_levels keeps its shares (adding up to 1, grid_step's level the
stronger one); grid_lines draws a line only one level has as strongly
as that level's own lines. The headless and viewport tests now check
half strength at the nested 5 to 10 switch and no 2 or 5 mm line right
at the 2 to 5 switch; a new test zooms through 2 to 5 in four decades.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:35:49 +00:00
froglemonade2 441d67f9ff View cube and axis gizmo: a standard view centres and fits the model (#10091) (#10092)
* claim: #10091

* View cube and axis gizmo: a standard view centres and fits the model (#10091)

A new Navigation preference, "Fit the model after a navigator view change"
(`navigator_fit`, off by default): with it on, a face, corner or arrow of
the view cube and an axis tip of the gizmo look at the middle of the model
from the one distance that fits its bounding sphere, so Home, an opened
design and every view chosen on a navigator share one zoom and one centre
and only the direction changes. Off, both navigators turn in place as
before.

"Home and Fit size" (`home_fit_size`, 10–100 %, default 100 %) is how much
of the view between the panels and the controls the model fills when Home,
Fit, an opened design or a navigator view change frames it; 100 % is the
framing as before.

`view.navigate` takes an optional `fit: true` with a direction or a turn,
so programmatic callers get the same framing.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 18:34:13 +00:00
semmelrinde 6541a4e980 Assistant: the agent list and the boxes under the switcher keep the panel's margin (#10172) (#10179)
* Assistant: the agent list and the boxes under the switcher keep the panel's margin (#10172)

The agent switcher above them sits in the panel's side margin, but what
the panel draws under it before a connection (the Agent row with Add
agent and Refresh, the agents found, what isn't installed, the New chat
confirmation and the sign-in box) started at the panel's very edge, and
Refresh was cut off at the right. They now keep the same side margin.

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

* Assistant: test that the agent list keeps the panel's side margin (#10172)

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

* Assistant: the margin test passes the labels without a needless borrow (#10172)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:31:55 +00:00
thocsmith 85197c1224 Selection: a freehand lasso that takes what is fully inside it (#4376) (#10185)
* claim: #4376

* Selection: a freehand lasso that takes what is wholly inside it (#4376)

The SELECT panel gets a Lasso checkbox (UiState::lasso_select, ui.set
lassoSelect). With it on, a primary drag from empty space draws a
freehand loop; the release selects through the same path as the window
box: items whose points are all inside the loop and no segment crosses
it, so a concave loop leaves out a curve that bulges out of it. Shift
and Ctrl add as with the box. ui.drag takes via waypoints (and scenario
drag steps take more than two points) so agents and tests can draw it.

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-10-31 18:31:21 +00:00
Colin Faulkingham ed66adac5c Keep renamed bodies visible when reusing hidden names (#10163) (#10175)
* claim: #10163

* fix(engine): preserve visibility when renaming into hidden names
2026-10-31 18:15:56 +00:00
ABDULLAH e88295ceba Arabic: isometric view wording in the Extrude view preference, equation curve range unit option and note, quoted menu names in the graphics card note, tanween spelling (#10189)
* Arabic: isometric view wording in the Extrude view preference

* Arabic: equation curve range unit option and note read naturally

* Arabic: tanween spelling in the new bold-font and delete-first messages, menu and option names quoted in the graphics card note
2026-10-31 18:15:34 +00:00
Kyle a.k.a. TechSquidTV aeca117045 Sketch: offset chains lines whose corners are coincident (#5019) (#8858)
* claim: #5019

* sketch offset: chain lines whose corners are coincident, not only shared

Lines drawn one by one meet at their own points held by Coincident
constraints. The offset walked shared points only, so each such line was
offset on its own, to the left of the way it was drawn: a closed
rectangle came apart, both horizontals moving up. The same split shows
next to a fillet or chamfer, whose other corners stay Coincident pairs.

Chain ends held by a Coincident constraint as one joint, the way
Sketch::chain() and axis_implied already do, so a closed outline offsets
as one closed shape.

Fixes #5019.

* test(sketch): cover coincident offset loops after trim and blends

* refactor(sketch): share and cache coincident endpoint groups
2026-10-31 17:37:38 +00:00
Dahak Builds 31326e9898 Frames: renaming a cut-off piece of a frame member renames a different member after a rebuild (#10123) (#10132)
* claim: #10123

* Frames: a renamed cut-off piece keeps its own name through a rebuild (#10123)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 17:32:32 +00:00
github-actions[bot] a9e9f8b82b Regenerate generated files and fold pending files [skip ci] 2026-10-31 17:36:03 +00:00
austin62jake 8558b37e44 Edit dialogs: a stored number shows with its unit, Fillet, Chamfer, Shell, Draft and more (#1936) (#8697)
* claim: #1936 (Fillet, Chamfer, Extrude, Shell and Draft edit dialogs)

* Fillet, Chamfer, Shell and Draft: a stored number edits with its unit (#1936)

Their edit dialogs showed a value stored as a bare number as it was, so a
fillet made with a radius of 2 opened as "2" and a chamfer angle of 30 as
"30", where the other dialogs, and since #2107 the Plastic ones, read
"2 mm" and "30 deg". The Fillet radius, the Chamfer distances and angle,
the Shell thickness and the Draft angle now go through shown_expr, as the
Plastic dialogs do.

Extrude is left as it is: its editor keeps an unchanged expression exactly
as stored (extrude_edit_tests), and writing the unit would change it.

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

* Offset Face, Stock, Offset Plane, Plane at Angle, Pipe and Emboss too (#1936)

The rest of the dialog_descriptors! table's length and angle fields that
copied the stored text as it was now go through shown_expr as well: the
Offset Face distance, the Stock margin, the Offset Plane offset, the Plane
at Angle angle, the Pipe diameter and wall, and the Emboss depth. Scale's
factor has no unit and still shows as stored.

The test also edits an offset face, a stock and an offset plane.

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

* Revolve, Thread, Rib and Web too (#1936)

The custom edit paths that copied a stored length or angle as it was now
go through shown_expr as well: the Revolve angle, the Thread length, and
the Rib and Web thickness and depth. The test also edits a revolve.

The Rectangular and Circular Patterns stay as they are, like Extrude:
their editors keep an unchanged definition exactly as stored
(preselect_tests::feature_mirror_and_patterns_reopen_as_feature_replays_even_for_body_creators).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 16:58:34 +00:00
Colin Faulkingham aa244bcee4 Fix copying drawn 3D sketch curves (#10134) (#10146)
* claim: #10134

* fix: copy drawn 3D sketch curves (#10134)
2026-10-31 16:53:47 +00:00
PaddleStroke eaf3acc297 Colour popover: step buttons, HSV fields and recent colours (#9234) (#9398)
* claim: #9234

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

* Colour popover: step buttons, H/S/V fields and recent colours (#9234)

Every colour field of the app opens the same ui_kit popover. It gains:
- step buttons beside each number (R, G, B and the new H, S, V): a press
  steps once, holding repeats, for live fine-tuning;
- H (degrees), S and V (percent) fields on the same gamma HSV as the square;
- a row of the last 12 colours picked in any colour field. A colour joins
  when an edit is finished; fine-tuning in one opening replaces its entry.
  The list is saved with the app's settings (`recent_colors`), hostile
  saved entries dropped and the list capped.

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

* Recent colours: keep the shared list outside ui_kit, crate-visible only (#9234)

The store and the per-frame sync with the saved settings move to
crate::recent_colors; the ui_kit colour popover only shows the list and
adds finished picks. No plain pub escape from the gallery's ui_kit rule.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:51:56 +00:00
semmelrinde c392791f2c Sketch drag: a step that throws points far from the sketch, or presses a line to no length, counts as failed (#8600)
* Sketch drag: a step that throws points far from the sketch counts as failed

A held step of a drag that moved a point more than 1000 times the sketch's
size plus the drag's way was taken as solved, so a drag could leave points
around 1.4e6 mm away while reporting it reached the cursor. Such a step now
fails like one the solver cannot solve, and the cursor correction refuses it.

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

* Sketch drag: a step that presses a line to no length counts as failed too

An end dragged past the point where its line would have to turn over (a
line held at its midpoint and by an angle, dragged past the midpoint; a
rectangle's corner dragged across the opposite side) pressed the line to no
length, where it has no direction left for its constraints. A step leaving a
line under 1/1000 of its length at the drag's start now fails like one that
throws points far away, so the drag stops just short of it (48 such drags in
4000 random sketches, none with this).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:45:35 +00:00
rvmzym5xv8-star 0fb2e9b06b MCP batch: refuse a command entry with an unknown key (#7854) (#7865)
* claim: #7854

* MCP batch: refuse a step with an unknown key (#7854)

A step's parameters under another key (`args`) were dropped and the
command ran without them. The batch is now refused before its first
step, naming the key.

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

* MCP batch: a step carrying `expect` is refused, saying it is the batch's argument

#9904 gave batch an `expect` argument and tested that a step carrying one runs with
nothing checked. That is the silent drop this branch refuses: the step is now refused
before it runs, and the error says `expect` belongs to the batch.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:26:23 +00:00
clark19990 b410c577e0 Light a feature's faces when it is selected, even after a later edit renamed them (#9848)
* Light a feature's faces after a later edit renamed them

Selecting a feature in the Browser or the timeline lights its geometry on the
model, by the face names the feature gave (`F<feature>:…`). A later edit that
moves or renames those faces (Offset Face, Replace Face, Delete Face) leaves
them named after whatever last touched them, so selecting the earlier feature
lit nothing.

When no face carries the feature's name, compare the model states either side
of it instead: a face whose name is new, or whose area, centroid or plane
moved, is a face the feature changed. Each is found on the body as it is now
by its name, or by a parallel plane and nearest centre, so a face a later
feature moved again is still lit.

The Browser row of the feature a picked face belongs to already lights; that
is unchanged (a body row lights too, as #3009 asks).

Test: a box raised twice with Offset Face; selecting the first raise lights
the face the second one renamed.

* Browser: light the feature of a picked face, not its whole body

Picking a face in the view lit the body's row as if the body were selected,
which in NX is not what a face pick shows: the Part Navigator marks the
feature the face belongs to. Faces, edges and vertices no longer light their
body row; the feature row of the picked face shows as selected instead
(`picked_feature`), while the session's selection stays the face or edge, so
Replace Face, sketches and the rest keep the pick they take.

The live face of the view still lights the face itself; the timeline already
marked the feature of a picked face and is unchanged.

Tests: the browser's 178 tests pass (selection arrays unchanged; only the
rows lit differ).

---------

Co-authored-by: clarkliu <277482660@qq.com>
2026-10-31 16:26:05 +00:00
semmelrinde d95b8208b3 Sketch Fillet: a radius of half a side rounds both its corners (#2500) (#8238)
The second fillet of a side whose radius is half its length needs all
that is left of the line, and was refused as too large. Such a fillet
now takes the whole line: the line goes, the arc ends where the line did
and is tangent to the curve that goes on from there. A 20 x 10 rectangle
filleted with r = 5 becomes a slot, a 10 x 10 square a circle. Several
corners in one call keep their arcs by id, as removing a line renumbers
the curves.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:15:20 +00:00
semmelrinde 99f8cff454 Sketch Fillet: a corner held by Coincident takes a fillet when a dimension measures to it (#7507) (#7778)
* claim: #7507

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

* Sketch Fillet: a corner held by Coincident takes a fillet when a dimension measures to it (#7507)

Before rounding a corner whose two lines meet only through a
Coincident constraint, Fillet and Chamfer weld the two points into
one, but not when anything else referred to the second line's point,
such as a dimension measuring to the corner: the fillet was then
refused ("the lines must share a corner point"). The point is now
welded as well, and what referred to it refers to the corner, the
same place. A point that a constraint names together with the corner,
or a linked point, is still not welded.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:15:04 +00:00
semmelrinde 3292cba4ad Sketch Fillet and Chamfer: two lines that share no end are brought to where they meet (#6912, #7759) (#8426)
Two selected lines that stop short of each other or cross were refused
with "the lines must share a corner point". Now each line's end on the
shorter side of their meeting point moves there first (extending a line
that stops short, cutting a line that runs past), and the corner is
rounded or cut as before. Parallel lines, ends held by other geometry
and lines whose length is held are refused and change nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:14:40 +00:00
semmelrinde 6fa023b3ab Sketch Break: refuse an offset's curves, as Trim and Extend do (#8645)
An offset's ends follow its source curves on every solve, so a break's new
ends were pulled back: a broken offset arc came out in three pieces going
the long way round, and the solve moved curves the break never touched.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:14:14 +00:00
lewis2e 510378d965 Sketch offset: an offset circle no longer reports a degree of freedom (#9264) (#9573)
The offset sets an offset circle's radius from its source after the solve,
but the solve still counted that radius as a free unknown, so a
dimensioned circle and its offset reported 1 DOF instead of 0. Lines and
arcs were not affected: the offset already holds their end points. The
solve now holds an offset circle's radius too.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:12:29 +00:00
github-actions[bot] c8a9159ac5 Regenerate generated files and fold pending files [skip ci] 2026-10-31 16:47:49 +00:00
github-actions[bot] fca86a414e Regenerate generated files and fold pending files [skip ci] 2026-10-31 16:39:00 +00:00
Colin Faulkingham b76fcae341 Preferences: retain Show tool at cursor between sessions (#10033) (#10049)
* claim: #10033

* fix(ui): persist tool cursor preference (#10033)
2026-10-31 16:10:37 +00:00
phanoob e64202417f Pattern and Mirror dialogs: explain Object Type, and say why a sketch is not taken (#10057) (#10097)
* claim: #10057

* Pattern and Mirror dialogs: explain Object Type, and say why a sketch is not taken (#10057)

The Object Type row of the Rectangular, Circular and Path Pattern and Mirror
dialogs gets a tooltip saying what Bodies and Features are and that a sketch
is neither. A sketch clicked for their objects, in the view, the Browser or
the History, now shows a hint naming what to pick and what to do with a
sketch instead of being ignored.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:10:09 +00:00
PaddleStroke 6a4b5c90c8 Properties: measure faster, show progress, and stop when closed (#8706) (#8730)
* claim: #8706

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

* Properties: measure faster, show progress, and stop when closed (#8706)

Properties on a large assembly measured every placed copy of every body
afresh (transform plus three fine tessellations each), so it took minutes.
#5203 (in #8103) already moved the measurement beside the window; this
builds on it.

- A rigid placement reuses the body's own cached measurement: volume, area
  and counts unchanged, centre placed, box from the placed corners (quarter
  turns) or from the body's shared mesh placed (other turns). Scaled or
  mirrored placements are still measured as placed.
- Bodies are grouped by component once and the subtree traversal groups
  occurrences by parent once (both were quadratic).
- The measurement beside the window is watched by a Progress: the card
  says "Measuring... n of N bodies" with a bar, and closing it (or
  switching designs) cancels the worker at its next body instead of
  leaving it running.
- component.properties and occurrence.properties are answered on a copy for
  control-channel requests too, as inspect.measure is.

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

* Properties: count the measuring work instead of timing it in the reuse test (#8706)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:04:38 +00:00
Colin Faulkingham 2e981d66e8 Sketch: allow dimension edits with tangent caps (#4823) (#9494)
* claim: #4823

* fix: resize sketches with tangent offset caps (#4823)
2026-10-31 16:04:29 +00:00
PaddleStroke ef0914ff94 Design tabs: optionally keep each design's dialog, tool and toolbar tab (#9360) (#9656)
A switch between design tabs cancels the open dialog and tool of the design
left and shows MODEL. With the new Interface preference "Keep dialogs and
tools open when switching designs" (keep_work_on_tab_switch, off by default)
each tab keeps its dialog (with its picks and values), its editing state, its
tool and its toolbar tab, and has them back when it is shown again. A feature
still computing after OK is cancelled as before. A feature edit's saved source
revision follows the view refresh of the switch, so its Cancel still restores
the history.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:04:22 +00:00
froglemonade2 0476154973 Update Links: linked designs that changed while the tab was away come back as one undo step (#9743) (#9753)
* claim: #9743

* Update Links: linked designs that changed while the tab was away come back as one undo step (#9743)

Coming back to an assembly's tab read each changed link in an Update Links
run of its own, so each became its own undo step: one Undo took back one
component and left another copy of the same part at the new version.

component.update_links now takes `components`, a list, and goes on past a
design it cannot read: that link keeps its bodies, is named in `failed` and
gets a warning. The tab return sends one request with every changed link,
records as read only the ones that were, and says on the status line which
one could not be read.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:04:05 +00:00
Thodoris Tsagkogeorgas 5e265089c7 Sketch: offset corners trim when they cross and extend when they fall short (#10089)
The chain walk follows ends that share a point, a coincident constraint, or
the same position. The offset then meets at the intersection.
2026-10-31 16:03:31 +00:00
semmelrinde 31a0c99a2c Delete: the delete runs with what its check computed ahead, instead of building the history again (#9917) (#10090)
* Delete: the delete runs with what its check computed ahead (#9917)

With the history rolled back, a delete builds the history after the
marker to see what it breaks, once in the check that runs off the window
thread (Session::ahead) and once more in the delete itself, on the window
thread. The check's results were lost on the way: the dry run's scratch
session and the whole-history copy dropped what the model keeps, and the
check's kept results never reached the delete. The scratch and the copy
now keep what their model keeps, and the delete (at once or after the
confirmation) runs with the check's kept results, so it finds the
history there instead of building it again.

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

* Delete: test that a confirmed delete runs with what its check kept (#9917)

With the history rolled back, deleting a side of the plate's outline is
checked off the window thread and confirmed first; the confirmation
carries the check's kept results, and the delete adds nothing to them.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:19:57 +00:00
Dahak Builds ce3b0d4086 Hide the origin point behind bodies like the axes and planes (#10050) (#10103)
* claim: #10050

* Hide the origin point behind bodies like the axes and planes

The origin point is a flat dot painted after the 3D scene, so it always sat
over bodies while the axes and planes respect the through-bodies rule
(#2697, #2618). Ask the meshes the same question the depth test asks the
axes: is a nearer kept face in front of the origin along the view ray.

Fixes #10050

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 15:14:46 +00:00
austin62jake d782d45cce Sketch: a centre rectangle's centre lines up with the axes when dragged (#10086) (#10087)
* Sketch: a centre rectangle's centre lines up with the axes when dragged (#10086)

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

* Sketch: a centre rectangle's centre lines up with the axes when dragged (#10086)

A centre rectangle's centre is held at the middle of a construction
diagonal (Midpoint), on none of its curves. Dragged by the centre, the
rectangle's own lines stayed snap targets and held the centre at their
middle, so it never lined up with the origin; dragged as a whole, the
centre wasn't a moving point, so only the corners lined up.

drag_snap::moving_points now takes all dragged entities at once and
treats a point held at a line's middle as going where the line's ends
go: it moves with a line whose two ends move, and when it is dragged
its line and the shape round it are no targets.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 14:31:19 +00:00
Dahak Builds 9a44641970 Fix Circular Pattern dialog showing two fields active at once (#10066) (#10078)
* claim: #10066

* Keep one dialog field active at a time (#10066)

Pick fields drew their accent ring from the dialog's active-input index
while text editors drew theirs from egui keyboard focus, so a Circular
Pattern dialog could show Axis and Quantity active together. A pick
field now drops its ring while a text editor holds the keyboard, and
clicking a pick field (or its clear button) takes the keyboard back.
Selection routing (d.active) is unchanged.

* Format editor_has_focus onto one line

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 14:01:09 +00:00
austin62jake 2b1ca94e2d Navigation: wheel and trackpad gestures go on under a value box (#10061) (#10073)
* Navigation: wheel and trackpad gestures go on under a value box (#10061)

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

* Navigation: wheel and trackpad gestures go on under a value box (#10061)

Any area above the model took navigation's wheel input, so a two-finger
pan, orbit or zoom stopped as the pointer crossed a floating value box
(Extrude's distance), and none could start over one. Model-anchored
canvas areas (Surface::Canvas: value boxes, a dimension's editor,
readouts) scroll nothing: navigation's wheel, trackpad pan and orbit and
pinch now go on under them. Their clicks stay theirs, and with value
stepping on the wheel stays with the value under the pointer.

Container::area records the canvas areas it builds; navigation asks
whether the top layer under the pointer is one (container::is_canvas).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 14:01:05 +00:00
github-actions[bot] f30fce0fc1 Regenerate generated files and fold pending files [skip ci] 2026-10-31 14:25:05 +00:00
github-actions[bot] f4b74fcb1b Regenerate generated files and fold pending files [skip ci] 2026-10-31 14:17:58 +00:00
semmelrinde 59efc86826 Rename Body: a pasted placement's body renames by the name the browser shows (#10017) (#10026)
* Rename Body: a pasted placement's body renames by the name the browser shows (#10017)

The browser lists the bodies of a component's second placement by their
placed names (`KB (K:2)`) and the context menu's Rename sends that name,
but body.rename looked it up only among the model's own bodies and
refused it ("no body"). It now takes a placed copy's name as the body it
shows, as appearance.assign does (#9740): the body is renamed in its
component, so every placement shows the new name.

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

* Rename Body: test renaming a pasted placement's body by its placed name (#10017)

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

* Rename Body: the test also hides the copy first and undoes the rename (#10017)

A copy hidden by its placed name stays hidden under its new name, and
one undo brings both names back (checks from the review on #10026).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:48:14 +00:00
leValdemar19 24ce89e0fc Modify ▸ Delete with nothing selected asks for a pick instead of a parameter error (#9079) (#9114)
* claim: #9079

* Modify ▸ Delete with nothing selected asks for a pick instead of a parameter error (#9079)

Starting `timeline.delete` from the ribbon, the command palette or the
control channel ran it with no parameters, so the notice showed the
engine's check ("`features` must list features, or `groups` timeline
folders"). It now deletes what is selected, as the Delete key does, and
with nothing selected shows a hint saying what to pick, changing nothing.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:35:33 +00:00
rugbymauri a476e645b0 Browser: F2 renames a selected group (#10005) (#10013)
* claim: #10005

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

* F2 renames a group clicked in the Browser (#10005)

A group has no selection of its own: clicking its Browser row only
records it as the picked folder, which V uses. F2's rename_selection
looked only at the selection, picked components and canvases, so on a
group it did nothing, or renamed a body that was still selected.

F2 now takes a clicked group row first, as V does, and opens the same
Rename box as the group's context-menu Rename, which commits through
browser.rename_group. This works for groups inside components as well.

Fixes #10005

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-31 13:35:26 +00:00
leValdemar19 3385d1c570 Viewport: dark parts keep their shape: linear-light shading and a sky fill (#7974) (#9028)
* claim: #7974

* Viewport: dark parts keep their shape: linear-light shading and a sky fill (#7974)

Preferences > Graphics "Keep the shape of dark parts" (off by default)
shades the viewport in linear light: the key light and eye fill over a
sky fill that grows as a face turns up, plus the 4% of that sky every
dielectric reflects (a little more toward grazing), encoded to sRGB
once. Near-black faces keep a gradient; light and mid-grey faces stay
within a few levels of the sRGB shading. The world's up direction and
the switch ride in a new `sky` vec4 at the end of the viewport uniform,
and the frame key, so turning it on redraws. Off, the shader takes the
old path unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:30:07 +00:00
austin62jake f0acea0992 Preferences: a shortcut recorded from a search result takes the key (#10043) (#10058)
* Preferences: a shortcut recorded from a search result takes the key (#10043)

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

* Preferences: a shortcut recorded from a search result takes the key (#10043)

A Preferences search lists matching shortcuts with their key buttons,
but a recording started there only counted while the Shortcuts page
itself was open, and the page skipped its recorder during a search: the
key went into the search box and ran its command behind Preferences.

keymap::capturing now counts a recording while a search shows, and the
recorder runs for it; clicking back into the search field stops the
recording, so the search keeps its own typing. The recording's message,
a taken key's Use anyway / Cancel choice and the next-step hint show
above the search's shortcut matches too (outcome, shared with the page).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 13:28:40 +00:00
Colin Faulkingham 6047c0d5a1 Viewport: keep the ground grid stable through grazing views (#10014) (#10031)
* claim: #10014

* fix(ui): stabilize the ground grid near the horizon
2026-10-31 13:28:34 +00:00
Dahak Builds 34c378c063 Save As: rename the design to the new file's name (#4907) (#10036)
* claim: #4907

* Save As: rename the design to the new file's name when its name was borrowed (#4907)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 13:22:10 +00:00
github-actions[bot] dc6e25250c Regenerate generated files and fold pending files [skip ci] 2026-10-31 13:48:06 +00:00
ashxddaga 2e22ff8035 fix(ui): disable bold in Edit Text when font has no bold face (#9940) (#9958)
* fix(ui): disable bold in Edit Text when font has no bold face (#9940)

- Check whether font has bold face via font_data_styled() before enabling bold toggle
- Automatically uncheck bold and disable bold button with explanatory tooltip for built-in font
- Prevent sending bold: true in sketch.edit_text when font lacks bold face
- Add unit regression test in sketch_text::tests

Fixes #9940

* style: format choice.rs in ui_kit

* i18n: move sketch_text.no_bold to pending locale catalog with complete translations

* test(sketch_text): assert disabled bold on built-in font without error (#9940)
2026-10-31 12:57:46 +00:00
rugbymauri cb9b50b11e Assistant: switching models no longer reports "Method not found" for session/set_model (#9970) (#10022)
* claim: #9970

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

* Agent chat: no error when an agent does not serve session/set_model (#9970)

An ACP agent can list models in the unstable `models` field of its
session/new reply without serving `session/set_model`; it answers
JSON-RPC -32601 and the Assistant showed that as an error each time the
model was switched. Agents that offer the model as a configuration option
already go through session/set_config_option. A -32601 reply to
session/set_model is now logged at debug level instead of shown; every
other error still shows.

Fixes #9970

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-31 12:51:26 +00:00
github-actions[bot] a22aeaaf93 Regenerate generated files and fold pending files [skip ci] 2026-10-31 13:19:38 +00:00
semmelrinde d4eeedbf95 Delete: in a rolled-back history, the check builds the history once when nothing breaks (#9917) (#9972)
* Delete: the check in a rolled-back history builds the history once when nothing breaks (#9917)

With the history rolled back, the check before a delete (which features
the delete would break) built the whole history twice, before and after
the change, with the marker removed. On a large design each build ran
every boolean after the marker: deleting twelve splines took half a
minute on the window thread. Only a feature that fails after the change
can be newly broken, so the history after it is built first, and when
nothing fails there the history before it isn't built at all.

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

* Delete: test that the check in a rolled-back history still reports what breaks (#9917)

Deleting a hole in the plate changes the plate and every cut after the
marker and breaks none of them: the check reports nothing. Deleting a
side of the outline breaks the plate, and the check still says so, with
a cache too small to keep a state.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:29:30 +00:00
Colin Faulkingham c512c8e86a Feature editor: close when its feature is removed (#9394) (#10027)
* claim: #9394

* Fix stale feature editors after Undo and deletion (#9394)
2026-10-31 12:26:46 +00:00
SilentWolf75 1bcc1125f0 Graphics: apply the chosen card, or say when it could not (#8341) (#9252)
* claim: #8341

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

* Graphics: keep a preferred card when surface marks it incompatible (#8341)

Prefer the saved adapter by exact then case-insensitive name, including when only listed as surface-incompatible, and explain in Preferences when another card is still in use.

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

* Format and clippy

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-10-31 12:00:52 +00:00
ashxddaga 7c013333db feat(engine,ui): allow restricting point-to-point align to chosen axes (#9946) (#10015)
* feat(engine,ui): allow restricting point-to-point align to chosen axes (#9946)

* style: apply cargo fmt across crates

* fix(clippy): use filter then map instead of bool::then in filter_map
2026-10-31 12:00:17 +00:00
kaltenbrunnermarkus 7e0f8a9a4c History: build the row plan once per revision and draw only the visible rows (#9616)
* claim: History: build the row plan once per revision and draw only the visible rows

* History: build the row plan once per change and draw only the rows in view

The vertical History worked out its rows from the design on every frame: for each row it
searched every folder's last feature (cubic in features and folders), cloned every name, and
looked up each row's result, sketch and selection by a linear search. It also drew and
hit-tested every row, including the ones scrolled out of view.

- The row plan (features, folders' depth, extent, header places, contents and joints, the rows
  with listed sketches, which sketches have patterns) is built once per design change, keyed
  by session, revision, marker, counts and the "Sketches: under their feature" choice, and kept
  in the egui context; the visible order is kept beside it until a folder or fold opens.
- A feature row outside the scroll area's clip takes its place and publishes its handles, but
  is not drawn or made interactive; one being found, dragged or focused, or under an open
  menu, is drawn as before.
- Selection and results are collected once per frame; folder rows read their contents from
  the plan.
- The horizontal strip collects the picked features (selection and dialog items) once per
  frame and writes a chip's tooltip only while it shows.

Tests compare the plan with the per-frame searches it replaced on tangled folders in every
fold state, the kept plan with a fresh one after each kind of change, and the History drawn
with every row against the culled one (same places, same text in view, fewer rows drawn).
An ignored test times the panel on a large design.

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

* Drop a needless borrow

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-10-31 11:54:50 +00:00
SilentWolf75 18e200f262 UI: recover chrome after sleep wakes a black window (#8749) (#8952)
* claim: #8749

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

* Recover egui chrome after a long pause between frames.

Sleep/wake can leave the window black while the GPU device stays up: re-upload
the font atlas, clear cached icon textures, and repaint when the frame clock
jumps so the ribbon and panels are not stuck blank (#8749).

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

* Format

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-10-31 11:54:16 +00:00
github-actions[bot] bd0f0f3471 Regenerate generated files and fold pending files [skip ci] 2026-10-31 12:39:18 +00:00
github-actions[bot] e39d741862 Regenerate generated files and fold pending files [skip ci] 2026-10-31 12:30:11 +00:00
austin62jake ea9ea7bba8 Sketch: a driven dimension nothing fixes any more takes a value again (#10018) (#10023)
* Sketch: a driven dimension nothing fixes any more takes a value again (#10018)

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

* Sketch: a driven dimension nothing fixes any more takes a value again (#10018)

A dimension placed while a constraint fixed what it measures goes in
driven, and stayed driven once that constraint was deleted: a
double-click only said it has no value to edit. The double-click now
tries making it driving on a scratch copy; when nothing else fixes it,
the value box opens at the measured value, and Enter makes it driving
(sketch.toggle_driven) at the typed value (parameters.change) in one
undo step. A refused value goes back to a checkpoint, so it stays
driven. While something still fixes it, it stays read-only as before.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 11:53:42 +00:00
sascha2101 07fa5e4f1f Sketch: Project gives its new curves the selected line type (#9945) (#10009)
* claim: #9945

* Sketch: Project gives its new curves the selected line type (#9945)

The sketch palette gives the drawing line type (construction, centerline, infinite) to the
curves a SKETCH › CREATE command reports under `curves`. Project reports what it made per link,
`links[].curves`, so its curves were never found and stayed normal lines whatever the palette
said. `apply_mode` now also reads the links' curves, so a projected line becomes construction,
centerline or infinite and a projected circle construction, in the projection's own undo step.

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

---------

Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:40:14 +00:00
semmelrinde 2a51f42495 Serbian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#10001)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:25:34 +00:00
HiroKenEz 8f259d5b83 Sketch: the Line tool can switch to a tangent arc and back without changing tool (#9489) (#9553)
* claim: #9489

* Sketch: the Line tool can switch to a tangent arc and back without changing tool (#9489)

With Preferences > Sketch > "Rest on a line's last point to switch to a
tangent arc" on (off by default), leaving the chain's last point and resting
back on it for 0.35 s makes the next click a tangent arc, by the existing
sketch.arc.tangent command, and the same gesture goes back to lines. A label
at the cursor says which. sketch.arc.tangent's end can now name an existing
point, so an outline drawn this way closes.

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

* Sketch: the cursor label names the command in words (#9489)

The label asked the catalog for a message id that doesn't exist, so it showed
the id. It now takes the command's translated label, and the test checks the
words it paints. Moved clear of the pointer's own badge.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:21:59 +00:00
semmelrinde a6b9d5a5b2 Browser: a sketch shown before an Extrude still hides once it is used (#9954) (#9971)
Show/Hide put every sketch it showed in `shown_sketches`, the list of
finished sketches kept shown although a feature uses them. Hiding and
showing a sketch nothing used yet put it on that list, so once an
Extrude used it, it stayed shown instead of hiding itself as a sketch
never hidden does. Show now marks only a sketch a feature already uses;
one nothing uses is shown anyway and leaves the list alone.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:19:34 +00:00
semmelrinde 18e4b9ea9f Bosnian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9999)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:19:27 +00:00
semmelrinde 7cccb2b52e Croatian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#10000)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:19:21 +00:00
semmelrinde 7d7a1ecfa5 Russian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#10002)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:19:04 +00:00
semmelrinde 1202a79e25 Danish: translate the 349 messages main gained after batch 9, the toolbar's command help included (batch 10) (#10003)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:18:58 +00:00
semmelrinde fbafdfdcb6 Ukrainian: translate the 349 messages main gained after batch 9, the toolbar's command help included (batch 10) (#10004)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:18:52 +00:00
semmelrinde 2744063631 Norwegian Bokmål: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9998)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:17:23 +00:00
Frank 8e53d5050e View corner Cancel: cancel an unfinished tool before the sketch (#6709) (#9584)
* claim: view corner Cancel cancels an unfinished tool first (#6709)

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

* View corner Cancel: cancel an unfinished tool before the sketch (#6709)

With OK and Cancel in the view's corner on, the cross took the whole sketch
even with a line half drawn. It now takes the steps one Esc takes first (a
drag, Select Other, the tool's points, then the tool) and cancels the sketch
only when nothing is unfinished. Esc and the cross share cancel_unfinished.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:12:36 +00:00
github-actions[bot] fde56d3c8b Regenerate generated files and fold pending files [skip ci] 2026-10-31 12:05:01 +00:00
github-actions[bot] 81a6a3dcde Regenerate generated files and fold pending files [skip ci] 2026-10-31 11:55:25 +00:00
Colin Faulkingham 3eb76a8b3b Agent chat: continue after creating or opening a design (#7526) (#9980)
* claim: #7526

* Agent chat: continue across agent-initiated design changes
2026-10-31 11:12:00 +00:00
semmelrinde 60de59d5a9 Czech: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9984)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:54 +00:00
semmelrinde ad0b5c9d05 Hungarian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9986)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:48 +00:00
semmelrinde ff08eb0043 Italian: translate the 371 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9987)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:41 +00:00
semmelrinde 6bfd53a55a Dutch: translate the 371 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9988)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:35 +00:00
semmelrinde 709cb62fe8 Romanian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9989)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:29 +00:00
semmelrinde ee4786cd8f Swedish: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9991)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:23 +00:00
semmelrinde 6a4fef151f Turkish: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9992)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:16 +00:00
semmelrinde d3574dd493 Traditional Chinese: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9993)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:10 +00:00
semmelrinde d2a3430147 Malay: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9995)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:11:04 +00:00
semmelrinde 598556bca9 Indonesian: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9996)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:10:58 +00:00
semmelrinde 80341ec13b Galician: translate the 373 messages main gained after batch 9, the toolbar's command help included (batch 10) (#9997)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:10:52 +00:00
github-actions[bot] 7be5d93e51 Regenerate generated files and fold pending files [skip ci] 2026-10-31 11:28:33 +00:00
HiroKenEz 5bb3b6717f Equation curves: the variable can carry its range's unit (#9333) (#9549)
* claim: #9333

* Equation curves: the variable can carry its range's unit (#9333)

A curve can set variable_unit "range" (or tick the new box in its window): the
variable is then a length, so a ^ 2 / (4 * t) is a length when a is one. Start
and end must both be lengths, or one a length and the other a plain number in
the design's unit; anything else is refused. Saved only when set, as one pending
format change; without it curves behave as before.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:54:06 +00:00
kaltenbrunnermarkus 5b6e5815a3 Engine: lower the per-command cost of evaluating large designs (#9615)
* claim: Engine: lower the per-command cost of evaluating large designs

* Engine: lower the per-command cost of evaluating large designs

- The fingerprint texts of features are kept in one store shared by every
  worker, not per worker: a command evaluated ahead runs on a new worker each
  time, where the per-worker store was always empty and every feature was
  written out again. Same texts, same hashes.
- Pinning body names after a command keeps, per feature, the names of the
  bodies it made with the state they were read from, so it no longer searches
  every result's bodies (features x bodies per command), and finds results by
  their index before searching.
- Pinning fingerprints the timeline again only from the first renamed feature
  (or a pattern or mirror of features replaying one) when the model proves its
  fingerprints are the design's; the fingerprints are those of the full pass.
- The evaluation cache's byte count looks bodies and sketches up instead of
  searching them per body (bodies squared per feature). Same counts.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:51:16 +00:00
github-actions[bot] 376c313214 Regenerate generated files and fold pending files [skip ci] 2026-10-31 10:41:12 +00:00
Colin Faulkingham ab665ee3b3 Edit Sketch: open an existing sketch from the ribbon (#9705) (#9829)
* claim: #9705

* Fix Edit Sketch ribbon selection and chooser (#9705)
2026-10-31 10:13:35 +00:00
sascha2101 f0d47e1d26 Preferences: turn the view to isometric when Extrude opens, and back afterwards (#9176) (#9948)
* claim: #9176

* Preferences: turn the view to isometric when Extrude opens, and back afterwards (#9176)

Two new preferences on the Design page, next to "Extrude to a picked target", both off by
default:

- `extrude_view_isometric`: each time Extrude opens, the view turns to the isometric view, so
  the depth can be seen right after a sketch seen face-on.
- `extrude_view_return` (only with the first): when Extrude closes, by OK, Cancel or Esc, the
  view goes back to where it was before it turned, keeping the projection as the end of a sketch
  does (#7038). The check is per frame, so every way the dialog closes is covered.

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

---------

Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:13:08 +00:00
Dahak Builds a2365228ab eval_more: one FaceSlice value for the face helpers (#2684) (#9960)
* claim: #2684

* eval_more: one FaceSlice value for the face helpers (#2684)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 09:52:29 +00:00
froglemonade2 e2a7a45c44 MCP: batch and execute take expect {volume, bodies, bbox} and flag a mismatch (refs #3380) (#9904)
* MCP: batch and execute take expect {volume, bodies, bbox} and flag a mismatch (refs #3380)

An optional `expect` argument on the `batch` and `execute` tools: the volume
(with a relative `tol`, default 0.002), body count and bounding box (0.05 mm)
the agent computed before building. Once the call has gone through the server
measures the design as the `measure` tool does and the reply gains
`expectation: {ok, measured, expected, mismatch}`. A mismatch flags the tool
result `isError` with the mismatch text first; the design is kept as built.
A malformed `expect` is refused before anything runs. Without `expect`
nothing changes, and a batch step carrying one is a step with a stray key.

In 29 logged agent sessions every agent computed these numbers beforehand
and then spent a `measure` call plus a comparison by hand after every stage;
one missed a 0.25 mm skin until it compared. Volume and body count are
kernel-independent checks.

Refs #3380

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

* MCP expect: filter then map for the bbox coordinates that are off (clippy)

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

* MCP: expect's coordinate text helper no longer shares a name with the JSON reader

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: bherbruck <tenbee91@gmail.com>
2026-10-31 09:48:08 +00:00
kaltenbrunnermarkus fa21db7df8 Save: hard-link the backup copy and check for unsaved changes once per revision (#9617)
* claim: Save: hard-link the backup copy and check for unsaved changes once per revision

* Save: keep the backup as a hard link and check for unsaved changes once per revision

The backup of a save is the old file under a second name (a hard link renamed over the
old .bak) instead of a copy written out again; where links are refused (FAT, shares,
cloud folders) or the old file cannot be flushed, it is copied as before. Every point a
save can stop at leaves the design file whole and the backup the version before it.

Session::is_dirty compared the whole document with the saved one each time it was asked,
which the window does every frame for every tab. The answer is kept per revision and pair
of documents; the cache holds both, so neither can change in place while it stands, and
doc_mut lets it go first so edits stay in place.

On the rover in two unsaved tabs an idle frame takes 15.0 ms instead of 19.3 ms; a save
with its backup of the 11.8 MB file writes in 9.6 ms instead of 18.9 ms.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:29:20 +00:00
github-actions[bot] 4fda59af80 Regenerate generated files and fold pending files [skip ci] 2026-10-31 09:45:43 +00:00
MarkusRe 776dfc9c18 Agent chat: the system note reaches agents beyond the Claude adapter (#9811) (#9818)
* claim: #9811

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

* Agent chat: the system note reaches agents beyond the Claude adapter

The note only went out as `_meta.systemPrompt.append`, an extension of the
Claude adapter, so Codex and Antigravity never saw it. It now also goes in
the standard ACP `systemPrompt` field for agents that offer it, and in
`_meta.antigravity.systemPrompt.append` for Antigravity.

Fixes #9811

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:41 +00:00
P'tite Tête 6583c28231 fix: French catalog complete, 342 missing messages translated (#9941)
- 247 help texts (help.*) and 95 UI messages: theme options, viewport status, quick measure, personal sheet-metal rules, analysis details, sketch report
- Terms aligned with fr.json; no-break space before : ; ? ! as in the catalog
- Pending file, folded on main after merge

Co-authored-by: P'tite Tête <asso@ptitetete.com>
2026-10-31 09:16:34 +00:00
N3MI-DG 6b24775413 Extrude: with every body hidden, a new extrude stays New Body instead of turning to Cut (#9919) (#9920)
* claim: #9919

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

* Extrude: with every body hidden, a new extrude stays New Body instead of turning to Cut (#9919)

Smart Extrude passed the shown bodies as `participants` to keep hidden
ones out (#6901), but with none shown that list was empty, and the
engine reads an empty list as the whole scope: a profile on a hidden
body chose Cut, which has no shown body to change, so nothing was
previewed and OK did nothing. With no body shown it now chooses New
Body.

The operation it latches for a side is also decided again when a body
is shown or hidden while the window is open, as a new start already
does (#467): showing the body an extrude goes into turns it to Cut,
hiding it again back to New Body.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:16:30 +00:00
austin62jake c4f9269409 Report a bug: the window can be dragged by its title (#9916) (#9934)
* Claim #9916

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

* Report a bug: the window can be dragged by its title (#9916)

The window was pinned at the top middle of the screen (ui_kit::Window
anchor), over the model, in a sketch or not. It now starts there and can
be moved (`placed`), as the file-load card can.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 09:16:04 +00:00
froglemonade2 9b31d29443 MCP: server instructions that say how to plan, build and verify (refs #3380) (#9903)
The `initialize` instructions were one paragraph of command shapes. Replace
them with version-neutral guidance: read `skill_reference solidcraft-modeling`
first, plan the numbers, build in a few `batch` calls, the primitive routes
and the fillet-shell-inside-holes order, what follows a parameter, `measure`
after each stage with a coalesced parameter check, and `timeline.edit` /
`parameters.change` / `edit.checkpoint` to fix instead of rebuilding. The
Assistant's system note gets the same pointer; docs/mcp.md says so.

`instructions_name_only_real_tools` now checks the command ids as well as the
tools, and `instructions_recipe_builds_a_parametric_shelled_box` builds a part
the way the instructions describe and checks its volume, axes and parameters.

Refs #3380

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 09:09:53 +00:00
froglemonade2 e16647a63f MCP: measure summary and body_topology faces: cylinders for small verification replies (refs #3380) (#9853)
* MCP: measure summary and body_topology faces: cylinders for small verification replies (refs #3380)

`measure {"summary": true}` replies one short object per body (name,
volume_mm3, bbox, faces, closed) with body_count and total.volume_mm3,
as a tool and as a batch step. `body_topology {"faces": "cylinders"}`
replies only the cylindrical faces, the kernel's split halves merged
by radius and axis line into {radius, origin, axis, extent, names},
and no edges. Both defaults are unchanged. The merge uses only what
model.faces and model.edges already reply.

Measured on a 13-feature enclosure, body_topology is 74 KB and measure
1.1 KB per body; agents filter them with scripts, and seven of 29
benchmark runs asked for exactly a cylinders-only listing.

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

* MCP: keep body_topology's cylinder merge next to check_parameters' without a name clash

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: bherbruck <tenbee91@gmail.com>
2026-10-31 08:56:37 +00:00
Wenyao Ma 5281734a22 UI: add theme-aware button hover and press feedback (#4984) (#5331)
* fix(ui): add theme-aware button interaction feedback (#4984)

* fix(ui): preserve footer button identity in overlapping dialogs

* fix(ui): preserve native button widths in toast actions
2026-10-31 08:32:45 +00:00
austin62jake b5fe8776b5 Assembly: default names skip names in use (part of #9738) (#9898)
* Claim part of #9738

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

* Assembly: default names skip names in use (part of #9738)

Contact sets, motion studies and exploded views were named by how many
there were, and a component's placements by how many it had, so after a
delete the next default name repeated one still in use, and commands
given that name acted on the first. They now take the first number no
item has, as joints' and the app's dialogs' default names already do; a
placement keeps the next number when it is free.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 08:28:50 +00:00
Dahak Builds 8986a670e5 3-Point Circle keeps snapped clicks as coincident constraints (#9858) (#9922)
* claim: #9858

* 3-Point Circle keeps snapped clicks as coincident constraints (#9858)

The tool sent only the three clicked positions, so a click snapped to a
point or curve left no constraint and the circle was free once drawn.
Each click now goes as its snap, as the 3-Point Arc's do, and the engine
adds a PointOnCurve for each referenced point, as the centre circle's
through click does (#3099).

* Fix #9858 test: assert the clicked midpoint point, not the line end

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 08:27:13 +00:00
froglemonade2 c528964363 MCP: check_parameters tries parameter values and puts the design back (refs #3380) (#9910)
A new tool that checkpoints the design, sets every value in one
parameters.change, reports measure, errors and cylinder axes, and
restores the checkpoint, so the loop set_parameter / measure /
body_topology / set back costs one call and one rebuild.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 08:24:41 +00:00
github-actions[bot] 251c20bef0 Regenerate generated files and fold pending files [skip ci] 2026-10-31 07:53:06 +00:00
austin62jake 3c0d4e120e Linked designs: one changed while the assembly was closed is read again when it opens (#9739) (#9872)
* Claim #9739

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

* Linked designs: one changed while the assembly was closed is read when it opens (#9739)

A link now keeps the SHA-256 of its design file's bytes as the assembly
last read them (DesignLink::file_sha256, a pending format change), set by
Insert Design, Relink, Extract to File and Update Links. component.links
reports `changed` from it, hashing a file again only when its time or
size changed (vfs::design_path_stamp, behind the design-path rule). The
app treats a link it meets for the first time whose file changed as not
read yet, so opening an assembly, in its own tab or in place of the blank
design, reads those parts again as a tab coming back does.

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

* Linked designs: read what changed last when a design opens in place of the blank one (#9739)

A read is taken up only by the design revision it started from, and
adopt bumped the revision after starting it, so the read was dropped.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 07:28:24 +00:00
Badger B bfd6aa9b7d Add German translations, batch 9 (#7237) (#9915)
Adds the last 79 German translations for messages that exist in en.json
but are missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the agent chat tool summaries,
the history, sending to a slicer, Perforate, the window system and the
web app.
2026-10-31 07:24:48 +00:00
ABDULLAH 44d7c1fb6f Arabic: viewport and camera terms in the new view help and tab colour options, parameter, K factor, trim strip, run-off and standoff wording (#9909) 2026-10-31 06:55:14 +00:00
github-actions[bot] db696ec24b Regenerate generated files and fold pending files [skip ci] 2026-10-31 07:05:30 +00:00
Badger B a69c5770a4 Add German translations, batch 8 (#7237) (#9902)
Adds 130 German translations for messages that exist in en.json but are
missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the Render studio (lighting,
environment, sun, depth of field), bounded volume and interior scattering,
saved shots, the timeline messages, thread direction and the agent chat
and endpoint settings.
2026-10-31 06:23:53 +00:00
momysnow da23eb1377 Sketch dialog follows "Dialog buttons at top" (#9860) (#9884)
* claim: #9860

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

* Sketch panel follows Dialog buttons at top (#9860)

The Sketch panel drew Finish Sketch, Look at and Slice in a footer under
its options even with Preferences > Interface > Dialog buttons set to
Top. Panel::palette now reads the same published preference as the
feature cards and draws its footer row under the header, above the
options, when it is Top. With the preference off nothing changes.

Changes a default: no

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

* Docs: Dialog buttons at top also moves the Sketch panel's Finish row (#9860)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 06:23:41 +00:00
austin62jake 5d9e4d5f95 joint.drive: a suppressed joint stores nothing, and the reply says what was asked and why a value differs (#9742) (#9862)
* claim: #9742

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

* joint.drive: a suppressed joint stores nothing; the reply says what was asked and why a value differs (#9742)

Driving a suppressed joint answered moved: false with "H is suppressed"
but wrote the values into the joint, with an undo step, so the part
jumped when the joint was unsuppressed. A suppressed joint outside any
motion link now stores nothing.

The reply gains `requested` (the values asked for, before limits and
links) and `held`: each driven value that ended up elsewhere, with why
(a motion link sets it, as a screw's travel follows its turn; a limit;
contact; a closed loop). A drive that moves nothing gives the first held
value's why as `reason`, so driving a screw's distance alone no longer
answers moved: false without one.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 05:57:24 +00:00
Dahak Builds 75b3fb678c Sketch: conflict messages no longer name a nonexistent id or claim full constraint wrongly (#9735) (#9859)
* claim: #9735

* Sketch: conflict messages name real constraints, not the refused one's id or a false 'fully constrained' (#9735)

* fmt + clippy: contains over iter().any (#9735)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 05:56:42 +00:00
github-actions[bot] 827713cc2f Regenerate generated files and fold pending files [skip ci] 2026-10-31 06:14:31 +00:00
ABDULLAH 4cf104ea2c Arabic: clearer wording in the new Sketch ribbon help (undo, mirror and revolve names, turned part, polygon sizes, normal direction) (#9870)
Co-authored-by: bherbruck <tenbee91@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:53:32 +00:00
Badger B 4fd8f8475c Add German translations, batch 7 (#7237) (#9864)
* Add German translations, batch 7 (#7237)

Adds 130 German translations for messages that exist in en.json but are
missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the parameter list setting and
the Render panel pages: GPU material overrides, nested dielectrics, saved
optics, output size, physical background, presentation filters, preview,
quality budget, regrade and the result area.

* German batch 7: render.result_sequence follows the catalog's Checkpoint term

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

---------

Co-authored-by: bherbruck <tenbee91@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:47:17 +00:00
github-actions[bot] 9fdf380286 Regenerate generated files and fold pending files [skip ci] 2026-10-31 05:35:14 +00:00
austin62jake affe37bcc3 Body names: inspect.interference refuses an unknown body; appearance.chain and appearance.list take a placed copy's name (#9740) (#9856)
* claim: #9740

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

* Body names: inspect.interference refuses an unknown body; appearance reads take a placed copy's name (#9740)

inspect.interference dropped a name that is no body, so a misspelled or
wrongly cased name left that body unchecked and two overlapping cubes
answered count 0. It now refuses the name, as inspect.clearance does,
naming the closest body when one is near enough to be a typo or another
case (cmd::no_body, on the existing edit_distance).

appearance.chain, appearance.list and appearance.replace's scope take a
placed copy's name (KB (K:2)) and a placement's (K:2) for the body and
component they show, as appearance.assign already does, through two
Session helpers: own_body_name (on body_frames) and component_or_placement,
which appearance.assign now uses too. A name that is neither is still
refused. select.set already refuses an unknown body on main (#9737).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 05:07:04 +00:00
PeppaWutz 9d8ab72da7 Ribbon tooltips: help for every remaining toolbar command (#9060) (#9868)
The last 106 commands on the backlog get the two-sentence help under
their tooltip, in English, German, Japanese, Chinese, Arabic, Hebrew and
Vietnamese: Surface, Sheet Metal, Plastic, Inspect, Construct,
Configure, Parameters, Library, View and the few single ones (Bill of
Materials, Frame Member, Pattern on Path, Convert Mesh, checkpoints).

With that the backlog is empty, so it goes: the test is now
every_toolbar_command_has_help, and the docs that mentioned the backlog
say so. The help tests use sketch.drag, which is not on the ribbon, as
the command without help.
2026-10-31 05:04:31 +00:00
Nathan Dane 160662ffec Tab colours: per-tab surfaces and fade (#8876) (#9808)
* claim: #8876

* Resolve theme tab surfaces with cached Oklab transitions

* Apply tab surfaces to unauthored Render preview backgrounds

* Allow quantized endpoint colours late in cubic Render fades

* Keep tab surface publication from restyling ordinary widgets
2026-10-31 04:39:12 +00:00
github-actions[bot] b5550d01c1 Regenerate generated files and fold pending files [skip ci] 2026-10-31 05:05:00 +00:00
PaddleStroke fd5d0e6738 Assemblies: mass, centre of mass and inertia per occurrence and rigid group (#9715) (#9717)
* claim: #9715

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

* Assemblies: mass, centre of mass and inertia per occurrence and rigid group (#9715)

assembly.mass_properties (and Session::mass_properties) gives, for every
occurrence and every rigidly-joined group, mass (kg), centre of mass (mm) and
the inertia tensor about it in world and frame axes (kg mm2), the input
the multibody dynamics work needs. Bodies are measured as Properties
measures them (kernel volume and centre, mesh gyration), once per body,
then moved by each placement and combined by the parallel-axis theorem.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:35:29 +00:00
PaddleStroke 0067dcab28 Settings: edit an added AI agent (#9619) (#9653)
* claim: #9619

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

* Settings: edit an added AI agent (#9619)

Agents you added in Preferences > AI get an Edit button between Test and
Remove. It reopens the Add agent dialog filled with the agent's name and
command, or its protocol, preset, endpoint, model and request timeout, and
Save changes the entry in place: same id, permission override and
always-allowed tools. The stored key is never read back into the dialog;
an empty key field keeps it (still only sent to HTTPS or this computer),
a new key replaces it. The control channel gets `edit_dialog`.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:33:18 +00:00
snowsh1ne 2f6ca7e46b Linux: optional system title bar (server-side decorations) (#9263)
On Linux the window always had no OS decorations and drew Windows-style
caption buttons in the application bar. SOLIDCRAFT_TITLEBAR=system, or
"titlebar": "system" in graphics.json, now opens the window with the
desktop's own title bar and buttons (server-side decorations where the
compositor offers them). The default is unchanged; Windows and macOS
are unaffected.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:12:39 +00:00
PaddleStroke c2f66107cf Sketch: paste copied sketch geometry into another design (#9258) (#9675)
* claim: #9258

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

* Sketch: paste sketch geometry copied in another design (#9258)

Sketch geometry copied or cut in a sketch (sketch.copy / sketch.cut) now pastes into a sketch of another open design with Ctrl+V or the context menu, at the cursor or at the copied coordinates, with the constraints and dimensions among it. A dimension whose expression names the copied design's parameters keeps its value, in its unit; a text keeps its numbers. SketchClip::for_another_design prepares the clipboard; the app keeps the last sketch copy beside the cross-design body/component copy.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:11:57 +00:00
github-actions[bot] ec5f4b8740 Regenerate generated files and fold pending files [skip ci] 2026-10-31 04:29:13 +00:00
Dahak Builds 91aefd5abc Refuse sketch values that collapse a curve in settle_value_edit (#9729) (#9837)
* claim: #9729

* Refuse value edits that collapse a sketch curve: settle_value_edit runs settle's shrunk/flung checks (#9729)

* rustfmt

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 04:08:20 +00:00
PaddleStroke f1c2fc8804 Assistant: a new tab keeps the agent's sign-in (#9293) (#9669)
* claim: #9293

* Assistant: a sign-in confirmed in one tab holds in every tab (#9293)

The latest sign-in evidence per agent is window state: keep it across
tab switches, new tabs and replaced designs instead of resetting it with
the tab's conversation, so a new tab no longer marks the agent as signed
out when discovery's login probe is older than the chat's proof.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:01:52 +00:00
Nathan Dane d7cba9e96d Preferences: theme options list and the opacity option (#8876) (#9807)
* claim: #8876

* claim: #8876

* Theme options: persist per-theme values and expose preferences contracts

* Restore selected theme mode after stopping a followed theme

* claim: #8876

* Preferences: render theme options and apply readable panel opacity

* Use floating-point scale expectation in theme preferences scenario

* Keep panel opacity colour construction inside theme tokens

* Complete theme options translations in Arabic Hebrew and Vietnamese

* Let standalone theme sliders fill their option cells
2026-10-31 04:01:29 +00:00
PeppaWutz 849bb392d2 Ribbon tooltips: help for the Sketch tools (#9060) (#9847)
* claim: #9060

* Ribbon tooltips: help for the Sketch tools (#9060)

84 sketch commands get the two-sentence help under their tooltip, in
English, German, Japanese, Chinese, Arabic, Hebrew and Vietnamese, and
leave the backlog: every Create tool (lines, rectangles, circles, arcs,
slots, polygons, splines, conic, ellipse, text, points, the 3D sketch
tools, Project, Intersect and the other reference tools), every Modify
tool (Trim, Extend, Break, Offset, Fillet, the chamfers, Move, Scale,
Transform, Blend Curve), the patterns and Mirror, every constraint,
AutoConstrain, Sketch Dimension, Create/Edit/Finish/Cancel Sketch and
copy, cut and paste. The help tests use inspect.zebra as the
undocumented example, since sketch.trim has help now.
2026-10-31 04:01:09 +00:00
thocsmith 3b77567932 Docs: joint flip needs x for point origins; checkpoints are single use, 16 kept (#9746) (#9844)
joint.create's flip text and the assemblies guide said two origins at one
point with the same z stay as they are; a point origin without x takes its
x from z, so a part turned about z turns back. Say so, and point to giving
x on both origins or joint.as_built.

edit.checkpoint and edit.restore_checkpoint now say that restoring or
forgetting uses the checkpoint (and later ones) up and that only the last
16 are kept, and the "no checkpoint N" error says why. The editing and
mechanisms skills no longer read as if a checkpoint can be restored twice.

Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 03:48:13 +00:00
Homero Thompson 810a9558bd perf(#3892): index the hidden joints and folders of the view (#9823)
The browser asks about every joint of the design on every frame, and
the answer walked the list of hidden joints: a nested STEP assembly
hides thousands of them, so a frame cost grew with the size of the
design.

Session keeps the hidden joints and folders as sets and answers from
them. The sets are built when the view changes and kept until it
changes again: the revision says the design changed, and the lists
say the view changed under it (the view is replaced outside the
engine too, without a revision of its own).

The behaviour is unchanged: the hidden joint stays hidden, the shown
one stays shown, and undo and redo keep the hiding right.
2026-10-31 03:01:33 +00:00
PaddleStroke d7ae568790 Insert SVG: say why a drawing has nothing to import (#9641) (#9652)
* claim: #9641

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

* Insert SVG: say why a drawing has nothing to import, and read UTF-16 files (#9641)

An SVG with nothing SolidCraft draws (only an embedded picture, shapes
pulled from another file with <use>, or not an SVG at all) was refused with
"invalid parameters for sketch.insert_svg: the drawing has no geometry
SolidCraft reads". It now names the file and says why, pointing pictures at
Insert Canvas and Canvas to Sketch. SVG files with a byte-order mark or in
UTF-16 now read, and a gzip-compressed (.svgz) file is refused clearly.

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

* Insert SVG: decode UTF-16 with as_chunks (clippy)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 03:00:32 +00:00
PaddleStroke 43a549d967 Render: start the GPU path tracer setup only when the GPU image method is chosen (#9041) (#9668)
* claim: #9041

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

* Render: start the GPU path tracer setup only when the GPU image method is chosen (#9041)

Entering the Render workspace started the GPU path tracer setup even with
the default Raster image method. The setup compiles the path shader, which
some Vulkan drivers (Mesa RADV/ANV, NVIDIA) take gigabytes over: the memory
climb reported while orbiting in Render. The setup now starts only while
Render is open with the GPU path method.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:59:05 +00:00
Dahak Builds 7228e218bf Preferences: unapplied changes no longer reach the settings file (#9730) (#9831)
* claim: #9730

* Preferences: unapplied changes no longer reach the settings file while the dialog is open (#9730)

* Preferences: hold unapplied changes out of the settings file while the dialog is open

While prefs_window.snapshot is set, SolveApp::prefs() writes the snapshot's
values - the ones Cancel restores - so the periodic autosave, quit, window
close, web save and control channel can no longer persist a change that was
never applied. Apply re-baselines the snapshot; settings the dialog cannot
change keep saving live. Adds regression tests and updates the tests that
pinned the old behavior.

Fixes #9730

* prefs: bind the snapshot's shadow outside json! so the ui-containers guard reads it

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 02:58:02 +00:00
ABDULLAH 67b491671f Arabic: viewport term in the viewport status preferences and details, tolerance, flip and colour key wording in the details cards (#9836) 2026-10-31 02:57:10 +00:00
momysnow 60b9e9d188 Edit Joint: show the two sides and pick them again (#9686) (#9692)
* claim: #9686

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

* Edit Joint: joint.edit takes the two sides

joint.edit accepts a and b in the form joint.create takes them (read by the same origin_param), so a joint can move its attachment without being deleted. A side left out keeps its place. The two sides must stay on different occurrences, checked as joint.create does. Limits, values, expressions and the name are kept; a relation's values start again where its parts are.

joint.edit gets an input contract and reply type (schema/joints.rs), and leaves schema/unconverted.txt.

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

* Edit Joint shows both sides and picks them again

The Edit Joint dialog now shows the two sides in the same selection inputs as Joint, each pre-filled with the face or edge of its own component under the origin. OK sends joint.edit a or b only for a side picked again, so an untouched side keeps its origin exactly.

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

* Edit Joint: a moved side turns the part as joint.create would

joint.edit kept the joint's old angle when a side changed, so the part could turn by a half turn about its axis when the new origin's x lay the other way. When a side moves and no angle is given, the angle is worked out again as joint.create does (the least turn from where the part is now); a relation's angle is its tilt and stays.

The mechanisms reference says joint.edit takes a and b.

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

* Edit Joint: pre-fill sides by name, send the angle only when changed

A side shows the face or edge its snap names, the piece nearest its stored pick when the name has several, else the one nearest that pick; it no longer searches outward from the frame origin, which could show the plate beside a hole for the hole's wall. A point or frame side shows nothing. OK sends the angle only when the user changed it, so a side moved without it turns the part as joint.create would.

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

* Edit Joint: a moved side leaves an angle expression alone (#9686)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-31 02:27:54 +00:00
Nathan Dane ca005237a1 Theme files: options, overlays and requires (#8876) (#9805)
* claim: #8876

* Theme files: add options, overlays and requirements
2026-10-31 02:25:34 +00:00
Colin Faulkingham 213e05da5f Shortcuts: normalize and validate text bindings (#9733) (#9800)
* claim: #9733

* Fix text shortcut normalization and current bindings (#9733)
2026-10-31 02:25:15 +00:00
bherbruck 251d18588a Release: build macOS on macos-15 runners (macos-14 is retired on Nov 2) (#9804)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:11:55 +00:00
github-actions[bot] 97f48a9f63 Regenerate generated files and fold pending files [skip ci] 2026-10-31 02:55:13 +00:00
PaddleStroke 72a265e4fc Nightly action matrix: a sketch on the sensor enclosure lid's top keeps its attachment (#9465) (#9670)
* claim: #9465

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

* Face attachments: a piece of a face its certificate finds the other pieces of keeps that certificate (#9465)

The nightly action matrix has refused a sketch and an offset plane on the
sensor enclosure lid's underside (`examples/sensor_enclosure :: Lid/F2:top`)
since #8278. The lid's lip, joined below it, parts the underside into a ring
and a panel; the ring rests on the case and is named after the case's top,
so the lid's cap certificate (via certified_pieces) finds only the panel.
#8278 then refused the certificate because it doesn't find the picked face,
and the history-name fallback can't take a name made by another body's
feature.

A face that is no pattern or mirror copy and lies on the same oriented plane
as the pieces the certificate finds is another piece of that face: the
certificate stands for it, as before #8278. Copies (the #8278 case) still
fall back to their own history name.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:02:24 +00:00
PaddleStroke 3877ef08b9 Measure: drag the labels away from the part (#8510) (#9395)
* claim: #8510

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

* Measure: drag the labels away from the part (#8510)

The distance and X/Y/Z labels in the view follow a drag of the pointer and keep
a leader to what they measure; a double-click or a new measurement puts them
back. A press on a label picks nothing, starts no box selection and opens no
Select Other.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:00:23 +00:00
PaddleStroke cce337f83f Sketch: alignment guides from any hovered point while drawing (#9105) (#9673)
* claim: #9105

* Sketch: alignment guides from any hovered point while drawing (#9105)

While a drawing tool waits for a point, hovering a line or arc end, a
rectangle corner or a free point wakes the same temporary horizontal/
vertical tracking guide as a circle centre or line midpoint. Idle
hovering keeps centres and midpoints only. Preference
sketch_tracking_points (on) turns it off.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 02:00:07 +00:00
PaddleStroke 5304294107 Browser: a Joints folder under each component lists the joints that attach it (#9130) (#9404)
* claim: #9130

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

* Browser: a Joints folder under each placement lists the joints that attach it (#9130)

Each component placement in the Browser gets a Joints folder (closed at
first) listing the joints whose Component 1 or Component 2 is that
placement, each beside the placement it joins it to. The rows are the
root folder's joints: click picks, the eye hides the glyph, double-click
edits and the menu is the joint's menu. The root Joints folder is
unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:59:53 +00:00
PaddleStroke b8eb8d262c Settings: saved to disk as soon as they change, not only when the app closes normally (#8275) (#9711)
* claim: #8275

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

* Settings: written to disk in the frame they change, not only at a clean exit (#8275)

eframe saves app.ron every 30 s and when the window closes normally. A
change made since the last save was lost whenever the app didn't close
normally: ended from the Task Manager, stopped by a sign-out or shutdown,
or crashed or hung in the graphics driver on the way out. The next start
then showed the old settings (View navigator back to Axes, Display quality
back to Normal).

The desktop app now writes its settings and flushes them in the frame they
change (frames with input and the one after), as the web app already does
(#8644). Unchanged settings are not written again.

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

* Settings: write at most once a second, and only after input that can change them (#8275)

Review: pointer motion came in every frame while the mouse moved, so the
settings were serialized every frame; and some settings change in every frame
(a panel's width while its edge is dragged), which would have flushed app.ron
on the UI thread each frame. Only clicks, keys, text, paste, wheel and pinch
count as input now, and the file is written at most once per second: a change
made in between waits, a repaint is asked for when its second is over, and it
is written then, so an idle app still writes it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:57:54 +00:00
github-actions[bot] 28cbca72c5 Regenerate generated files and fold pending files [skip ci] 2026-10-31 02:26:00 +00:00
PaddleStroke 9d18034e13 Sketch drag: a line is never shrunk to a point to follow the cursor (#9062) (#9728)
* claim: #9062

* Sketch drag: a line is never shrunk to a point to follow the cursor (#9062)

A line's end on a horizontal construction line through the origin, dragged
up off it, made the construction line vanish. A horizontal row is
b.y - a.y, which a line a hair long meets at any tilt, so the drag's held
solves met the point-on-line by shrinking the construction line: to 1e-5 mm
on the first frame, 1e-7 mm by the tenth, and the end went up.

A drag step (held solve, the tangent-ends-held solve, and each cursor
correction pose) that shrinks a line or circle from over 1e-3 to under
1e-4 now counts as failed, as a step that wraps an arc does. The end then
stays on the construction line, which keeps its length.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:54:19 +00:00
PaddleStroke d61ec60480 Assistant: say when an installed agent can't start, and how to reinstall it (#9034) (#9655)
* claim: #9034

* Assistant: say when an installed agent can't start, and how to reinstall it (#9034)

An agent whose program is found but fails its `--version` check without
printing a version (npm's opencode.exe left as a placeholder when the
package's setup script did not run, which Windows refuses with "not
compatible with the version of Windows you're running") was listed like
a working one, and connecting only said "The agent stopped." with the
system's message.

Discovery now keeps why the program did not start: the spawn error
(Windows' `%1` replaced with the program) or, through a `.cmd` shim, the
first stderr line or the exit status. A probe that times out still says
nothing. The picker shows "Installed, but it does not start: ...", and
connecting first explains it with the agent's install command and Refresh.
The control channel status carries `problem`.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:52:59 +00:00
Dahak Builds c4538c53e1 Fix Move feature editor prefill showing wrong values (#9696) (#9803)
* claim: #9696

* Show an edited Free Move its stored turn as the X/Y/Z angles that remake it (#9696)

* cargo fmt

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 01:51:40 +00:00
ABDULLAH b6b0d80332 Arabic: tanween spelling in the new ribbon help, fastener and standard part placement notes (#9820) 2026-10-31 01:51:32 +00:00
github-actions[bot] 42166bd6b7 Regenerate generated files and fold pending files [skip ci] 2026-10-31 01:56:59 +00:00
Nathan Dane 5bd5be9543 Viewport status item: standard details card (#8613) (#8614)
* Keep viewport summary menus below modal owners

* Build viewport status details from shared blocks

* Keep details focus checks outside context data locks

* Preserve native editor Escape ownership in details cards

* Initialize strict contract test sessions directly

* Translate viewport status detail cards

* Use inferred slider options and test legacy Section editing
2026-10-31 01:27:12 +00:00
PaddleStroke 2f09c90542 Exploded View: grey it out until the design has two components (#9142) (#9659)
* claim: #9142

* Exploded View: grey it out until the design has two components (#9142)

On a part made of plain bodies (the sample plate) Exploded View opened its
dialog and failed on OK with "an exploded view needs two or more
occurrences". The command is now disabled until the design has two or more
occurrences, and its tooltip says why and which commands make components.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:18:34 +00:00
Colin Faulkingham 8311ae5c5d BOM: group purchased children under inseparable parts (#9744) (#9794)
* claim: #9744

* Fix grouped BOM children for inseparable parts
2026-10-31 01:17:58 +00:00
Dahak Builds 4973638b56 Selection follows body rename so Delete targets the right body (#9737) (#9789)
* claim: #9737

* Selection follows body renames; stale body selections are dropped (#9737)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 01:17:28 +00:00
rugbymauri 8a61069fbb Control channel: document.inspect reports undo, redo and path in the app (#9624) (#9632)
* claim: #9624

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

* Control channel: document.inspect reports undo, redo and path in the app (#9624)

The app answers document.inspect on a scratch copy of the session, which
has no undo or redo history and no file path, so the reply always said
undo 0, redo 0, path null. Take those three values from the live session
and put them into the reply.

Fixes #9624

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-31 01:15:03 +00:00
PaddleStroke 55bea620c7 Continuous orbit turns about the Orbit around point (#1044) (#9690)
* claim: #1044

* Continuous orbit turns about the Orbit around point (#1044)

With Orbit around set to the point under the cursor (or the origin), Continuous
orbit still turned about the view's centre. Cursor orbits move that centre off
the model, so the model swung across and out of the view while it spun. The
spin now turns about the origin, or the model point in the middle of the view
(else the middle of the bodies in view), held from start to stop.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:14:03 +00:00
rugbymauri c755e067f5 Sketch pane: stays still at 125 % display scaling (#9457) (#9598)
* claim: #9457

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

* Keep the sketch pane still at fractional display scaling (#9457)

Right-docked cards were placed by their right edge from last frame's
measured width, which flipped by one physical pixel each frame at
scales such as 125 % in the Docked layout. Place them by their left
edge from the width they are held to instead.

Fixes #9457

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-31 00:49:10 +00:00
Colin Faulkingham f73479ddf2 Preserve multiline fields and accept a BOM in parameter CSV imports (#9732) (#9772)
* claim: #9732

* fix(params): preserve quoted CSV records and BOM headers (#9732)
2026-10-31 00:46:57 +00:00
Dahak Builds 772815b4c0 Sketch Extend: only an end on a circle counts as held (#9731) (#9773)
* claim: #9731

* Sketch Extend: only an end on a circle counts as held (#9731)

grow_line's on-circle test was true for every point inside the circle; test distance to the circle instead. Also pin the new end point of a held-end extension onto the curve it stops on, as the free-end path does.

* Fix regression test harness usage and held-end setup (#9731)

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-31 00:45:22 +00:00
Erlis Vidal 6d89de1f5a Construction planes, axes and points hide with their component's placement (#9790) (#9791)
construction_visible checked the item's own hide and its folder, but not
the placement its component is drawn at, so hiding Part:1 left Part's
planes, axes and points on screen. It now asks feature_placement_visible,
the check sketches already made; the placement path is computed in one
place for both.
2026-10-31 00:44:47 +00:00
ABDULLAH 6375529b44 Arabic: colour key wording in the analysis status help, browser eye note, print face highlight tip, constraint glyph note, parameters apply-live option (#9446)
* Arabic: colour key wording in the viewport analysis status help

* Arabic: tanween and direction-neutral wording in the browser eye position note

* Arabic: outline wording and no direction mark in the print face highlight tip

* Arabic: sketch term in the constraint glyph note

* Arabic: checkbox wording and discard verb in the parameters apply-live option
2026-10-31 00:43:02 +00:00
CaRLymx bdd7605d47 Spanish (es-ES): catch up with en.json (2026-10-31/01) (#9781)
Add 50 missing messages from upstream main (5f60d78):
- Sort/Order UI: 3 keys
- Parameters live apply: 4 keys
- Preferences viewport/parameters: 8 keys
- Render output profiles: 3 keys
- Sheet personal rules: 17 keys
- Sketch text formula: 1 key
- UI 3D mouse, agent chat, viewport status: 14 keys

Validations: i18n --check ✅, lists ✅, assets ✅
Placeholders: 0 mismatches
Keys: 4828/4828 (100%)

Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
2026-10-31 00:42:11 +00:00
momysnow ffbaf292f0 Browser: drag over the eyes to show or hide many rows at once (#9680) (#9681)
* claim: #9680

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

* Browser: drag over the eyes to show or hide many rows at once (#9680)

Press an eye and drag up or down over other eyes: each eye the pointer passes
takes the first eye's new state (shown or hidden), and the whole drag is one
undo step. A plain click is unchanged. Dragging near the list's top or bottom
edge scrolls it. A component's eye now goes through browser.visibility, so it
is undoable like a body's.

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

* Browser: fix the eye drag's undo merge and layering (#9680)

Merge the drag's steps from the undo depth noted at press, when the design
and visibility still match, instead of searching the history. Run the merge
every frame from logic(), so a release under an overlay is never missed. An
eye under a popup or tooltip no longer takes part in the drag. Adds a
regression test for a drag from the Origin's eye after a show.

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

* Browser: say when the eye drag's undo merge gives up (#9680)

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

* Gallery test: the eye-drag state key is not a renderable element

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <tenbee91@gmail.com>
2026-10-31 00:35:34 +00:00
github-actions[bot] 2b4f07a0e8 Regenerate generated files and fold pending files [skip ci] 2026-10-31 00:50:08 +00:00
PeppaWutz 4b1556d102 Ribbon tooltips: help for the Assemble and Insert tools and the Create primitives (#9060) (#9069)
* claim: #9060

* Ribbon tooltips: help for the Assemble and Insert tools and the Create primitives (#9060)

56 commands get the two-sentence help under their tooltip, in English,
German, Japanese, Chinese, Arabic, Hebrew and Vietnamese, and leave the
backlog: every Assemble tool (components, joints, contact, motion,
position), every Insert tool (files, images, 2D, library, links) and the
Model > Create drop-down primitives (box, cylinder, sphere, torus, cone,
coil, pipe, thread, rib, web, emboss, boundary fill, bounding solid).
Their drop-down rows now show a tooltip as well. The help tests use
sketch.trim as the undocumented example, since solid.rib has help now.
2026-10-31 00:22:31 +00:00
Colin Faulkingham 0fe5bf114c Timeline: resolve exact feature names before ID fallbacks (#9689) (#9748)
* claim: #9689

* Resolve timeline names before falling back to feature IDs
2026-10-31 00:08:24 +00:00
PaddleStroke 526cdfd247 Zebra: flat faces keep one stripe colour while the view turns (#2153) (#9761)
* claim: #2153

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

* Zebra: flat faces keep one stripe colour while the view turns (#2153)

A face whose normal lies in the upright plane through the view (every
horizontal face) reflects straight ahead, where the zebra's sideways
coordinate is zero: exactly on the edge between a black and a white
stripe. Rounding then picked one or the other from frame to frame, so
the face flickered while the camera orbited. Straight ahead is now white.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:03:59 +00:00
bherbruck ee6b85605a Release 0.28.0 2026-10-30 19:50:14 -04:00
bherbruck 045cfed6a7 Release 0.28.0 (#9766)
Fold the pending design format changes (browser-nested-groups, point-cloud-properties, print-faces) into format 45.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:50:02 -04:00
github-actions[bot] 5f60d78658 Regenerate generated files and fold pending files [skip ci] 2026-10-30 23:45:03 +00:00
Dahak Builds 646eb97eaf motion.list gives study keys in motion.study units (#9741) (#9760)
* claim: #9741

* motion.list gives study keys in motion.study units

Angle keys came back in radians while motion.study takes degrees, so an
agent that listed a study and wrote it back changed every angle by a
factor of 57.3, silently. Convert key values on the way out like
joint.list does (degrees for angles, mm for distances), give each key a
unit and its joint's name, and add a round-trip regression test.

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-30 23:03:40 +00:00
PaddleStroke 2cf8df2ef6 Section caps: a body touching another along a curved face keeps its own cap (#9407) (#9665)
* claim: #9407

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

* Section caps: a body touching another along a curved face keeps its own cap (#9407)

Seen through a cut, a far wall and another body's face lying on it (a shaft
in its bore, a ring in its housing) are the same surface faceted two ways, so
the marking pass let the other body's face win in streaks. The section's
passes now first count the faces behind the cut in the stencil (back +1,
front -1): where the count says the cut runs through a body but a front face
won, the back faces are drawn again pulled toward the eye by a few pixels and
take the cap back.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:01:28 +00:00
PaddleStroke 0096e694dd Parameters: an option to apply edits together, when you choose (#9279) (#9400)
* claim: #9279

* Parameters: an option to hold edits until Apply or OK (#9279)

"Apply each change as you leave its field" (on by default, remembered in
the preferences as parameters_apply_live). Off: an expression edit is
checked when its field is left but waits, without a preview; Apply or OK
sets every held edit in one parameters.change {changes}, so the design
rebuilds once and one undo takes them back. Cancel, the close button and
Escape drop them. Turning the option back on applies what waits.

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

* Docs: the parameters_apply_live preference in ui.prefs (#9279)

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

* Parameters: the hint says edits wait for Apply or OK when they do (#9279)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:57:45 +00:00
github-actions[bot] c29bd18190 Regenerate generated files and fold pending files [skip ci] 2026-10-30 23:16:06 +00:00
Badger B 8e9e270027 Add German translations, batch 6 (#7237) (#9759)
Adds 130 German translations for messages that exist in en.json but are
missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the remaining CPU lighting and
reflection controls, render diagnostics, lens and film effects, GPU path
tracer options, HDR source details, the resource library and the raw
linear EXR options.
2026-10-30 22:51:12 +00:00
fkirill 1b927421f6 Restore Browser and source-body context during Extrude editing (#5573) (#5576)
* claim: #5573

* Keep Browser and source bodies in the Extrude edit context (#5573)

* fix: keep Browser feature rows after manual rollback

---------

Co-authored-by: Kirill Frolov <k.a.frolov@vk.team>
2026-10-30 22:03:03 +00:00
PaddleStroke 8d22b3a41a DXF export: the full R2000 file structure strict readers need (#9144) (#9662)
* claim: #9144

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

* DXF export: the full release 2000 file structure strict readers need (#9144)

The view and flat pattern DXF left out parts of the AC1015 file that a
strict reader expects: the CLASSES section, the VPORT, VIEW, UCS, APPID
and DIMSTYLE tables (with *Active, ACAD and Standard) and the ACAD_GROUP
dictionary under the root dictionary. They are written now, along with
$DWGCODEPAGE; the active viewport looks at the whole drawing.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:01:27 +00:00
PaddleStroke edcf02c0d4 Crash reports: a window opened after startup no longer labels the run 'during startup' (#8776) (#9661)
* claim: #8776

* Crash reports: a window opened after startup no longer labels the run "during startup" (#8776)

egui-wgpu reports "window surface" and "surface configure" for every native
window, also the ones opened after startup, such as the agent chat in its own
window. Each one overwrote the journal's "running" phase, so a later unclean
exit was reported as "during startup (surface configure, N s after launch)".
crash::startup_step records a startup step only until the app runs, and the
app's egui-wgpu hook calls it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:00:19 +00:00
rugbymauri b85d5a665c Browser: the recover-unsaved-work card always shows Recover and Discard (#9472) (#9636)
* claim: #9472

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

* Browser: the recover-unsaved-work card always shows Recover and Discard

A window's scrolling body was only offered the height the card had the
frame before. A card first laid out while the page was still tiny (a
browser canvas not sized yet) kept a body of no height for good, so only
its title showed and the required choice could not be made. The body is
now offered the room the screen leaves and shrinks to its content, which
also covers the desktop recovery card and every other system window.

Fixes #9472

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-30 21:41:29 +00:00
github-actions[bot] a232df7751 Regenerate generated files and fold pending files [skip ci] 2026-10-30 22:20:32 +00:00
Badger B 4757c3cee6 Add German translations, batch 5 (#7237) (#9697)
Adds 130 German translations for messages that exist in en.json but are
missing from de.json, in a new pending file as described in
assets/locales/pending/README.md. Covers the quick measurement, a few
Preferences labels and a large part of the Render panel (adaptive
sampling, surface aging, camera background, CPU lighting and environment).
2026-10-30 21:27:08 +00:00
github-actions[bot] 736f334301 Regenerate generated files and fold pending files [skip ci] 2026-10-30 21:50:16 +00:00
semmelrinde e721004f62 Croatian: translate the 14 messages main gained after batch 8, new Preferences included (batch 9) (#9540)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:00:37 +00:00
rugbymauri 88d87729d4 Browser History: drag rows to reorder features (#9565) (#9599)
* claim: #9565

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

* feat(browser): reorder features by dragging rows of the History in the browser

Let users drag features in the History shown in the browser to move them, with the same drop bar and checks as the History at the left, while the rollback marker still drags on its own.

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-30 21:00:29 +00:00
ABDULLAH c7597ea66f Arabic: render term in the CPU resource profile size notes, pan and tilt verbs in the 3D mouse sketch orbit note (#9693) 2026-10-30 21:00:22 +00:00
github-actions[bot] f3389c3af7 Regenerate generated files and fold pending files [skip ci] 2026-10-30 21:19:55 +00:00
Janiz-zzz a0a52f6657 Add Finnish (fi) UI localization (#9626) (#9631)
* Add Finnish (fi) UI localization (#9626)

A fi catalog ("Suomi") translated from en.json, all 4801 messages,
registered in LOCALES between es and fr, with
finnish_catalog_is_registered_and_translates_keys, the language listed
in docs/localization.md (registered and partial) with its terms, and a
pending attribution row.

Fixes #9626

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

* Finnish: the 3D mouse Restore defaults button gets its own label

It shared its text with Restore defaults elsewhere on the page, so the layout test found
the wrong button.

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

---------

Co-authored-by: Janiz-zzz <267742026+Janiz-zzz@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-30 20:46:03 +00:00
github-actions[bot] cc9787f80e Regenerate generated files and fold pending files [skip ci] 2026-10-30 20:50:14 +00:00
fkirill 8e80d98610 Pre-highlight whole bodies for body selection inputs (#5572) (#5575)
* claim: #5572

* Highlight whole bodies for body selection inputs (#5572)
2026-10-30 20:24:45 +00:00
semmelrinde f8a8114843 Malay: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9535)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 20:24:22 +00:00
rugbymauri 98bfe1af9c Viewport: a docked Assistant beside an inverted view area no longer crashes (#9492) (#9499)
* claim: #9492

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

* fix(viewport): keep running when the view area comes out inside out

Make the layout beside the docked Assistant give way instead of closing the app when a window reconfigure leaves the view area inverted.

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

---------

Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
2026-10-30 20:06:19 +00:00
Alex Lytvynenko ffbf9836cc Agent chat: support ACP configuration options and OpenCode V2 (#3305) (#3867)
* claim: #3305

* fix(agent-chat): support ACP configuration options (#3305)

Handle current select options for model, thinking effort and mode, with confirmed snapshots and legacy compatibility. Full quality gate attempted; draft delivery authorized with unrelated geometry/property/timing failures documented in PR #3867.

* fix(agent-chat): keep configuration selectors inline (#3305)

Use compact value pickers and bounded wrapping to preserve conversation space; verified at normal and narrow widths with the installed OpenCode V2 server.

* style(agent-chat): format tool events
2026-10-30 20:00:26 +00:00
Homero Thompson 806adb6c46 A sketch curve id that breaks a name is refused (#5858) (#9606)
* claim: #5858

* fix(#5858): a curve id that cannot be written into a name is refused
2026-10-30 19:53:51 +00:00
phanoob 250ed2f266 Parameters: the expression field opens wide while you edit it (#9358) (#9384)
* claim: #9358

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

* Parameters: the expression field opens wide while you edit it (#9358)

A parameter's expression that is too long for its cell opens over the
cells to its right while it has focus, wraps instead of scrolling, and
shows the live value or error under it. Enter still applies it and the
field returns to its column; a short expression stays in its cell. The
add row's expression field does the same.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:47:44 +00:00
bherbruck 56f07a5598 Hover test: allow the first hover half of the index time, not a third (#9638)
On a loaded runner the hover alone has taken 70 ms against a 190 ms index, which failed
unrelated pull requests. Waiting for the index would still cost all of it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:41:24 +00:00
rlartot dfdad35d08 Extrude: a sketch picked in the Browser or History takes all its profiles (#6106) (#6154)
* claim: #6106

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

* Extrude: a sketch picked in the Browser or History takes all its profiles (#6106)

With Extrude, Revolve or Emboss open, a plain click on a sketch row in the
Browser or on a sketch in the History now fills Profiles with every profile of
that sketch (the island inside another profile's hole is left out). Before, the
click only changed the selection, which the dialog never reads.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 19:29:34 +00:00
github-actions[bot] 1cef0d83df Regenerate generated files and fold pending files [skip ci] 2026-10-30 19:48:48 +00:00
PaddleStroke a99c57ffc7 3D mouse: optional orbit while a sketch is open (#2227) (#9406)
* claim: #2227

* 3D mouse: optional orbit while a sketch is open (#2227)

A new Navigation preference, "Turn sketches with the 3D mouse"
(space_mouse_sketch_orbit, off by default), lets the cap and a device
app turn the view while a sketch is open and seen square on, as the
mouse already can. Off keeps today's behaviour: pan and zoom until the
view is tilted away from the sketch (#6687). Restore defaults on the
3D mouse section resets it.

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

* 3D mouse: keep the settings list in order after the rebase

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-10-30 19:22:58 +00:00
Colin Faulkingham cdffdcbbef Selection mode: the Modelling SELECT panel shows and clears component picking (#9382) (#9485)
* claim: #9382

* Show component picking in the Modelling Select panel (#9382)
2026-10-30 19:03:10 +00:00
ABDULLAH 4f05716f69 Arabic: follow the shortened English of the quick measurement preference note (#9604) 2026-10-30 19:03:07 +00:00
semmelrinde f0567d6a48 Hungarian: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9528)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:57:35 +00:00
semmelrinde 6e9ffe83d2 Romanian: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9531)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:57:28 +00:00
semmelrinde 2647ec51fc Ukrainian: translate the 95 messages the catalog lacks (#9591)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:57:17 +00:00
AsTechEvolves 23587433e2 Assistant: say when a Claude reply hit the length limit or was declined, not that the model lacks tools (#9495) (#9500)
* claim: #9495

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

* Assistant: say when a reply hit its length limit or was declined, not that the model lacks tools (#9495)

The API client never read why an answer stopped, so a Claude reply cut
off at max_tokens while thinking (its thinking text is empty by default),
or a refusal, ended the turn as normal with nothing said. The chat then
blamed tool support: "The model didn't use SolidCraft's tools".

Reply now carries the stop reason: Anthropic stop_reason max_tokens and
refusal, or OpenAI finish_reason length and content_filter and a message
refusal. Any other value is a normal end. The turn ends with that reason,
which already keeps the tool-calling notice away, and the chat shows
what happened in place of the raw "(max_tokens)", for ACP agents too. A
tool call cut off at the length limit has incomplete input, so it is
neither run nor kept in the history.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:56:14 +00:00
Daniel Dimitrov 0414de7b98 Hole: each dropdown option shows the hole it makes while hovered (#9577)
* Hole: each dropdown option shows the hole it makes while hovered

Extents, Hole Type, Hole Tap Type and Drill Point list names only, so seeing
what an option makes meant picking it. Hovering an option now shows a small
section of that hole beside the list, drawn by the dialog's own section
painter from a copy of the form with the option applied.

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

* Hole: give the dropdown previews a gallery entry

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

---------

Co-authored-by: danieldimitrov86 <danieldimitrovdev@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-30 18:54:18 +00:00
Kevin Ravensberg 295bd0e165 Render: fit the product inside the picture's frame, after the saved setup loads (#5763) (#5764)
While Frame shows the picture's frame, the preview projects into that frame
rather than the viewport, but Fit Product and the fit on entering Render
framed the product for the viewport's free area, so a picture taller than
wide cut the product at its sides. The fit now uses the viewport layout
seen from the frame (the frame as the viewport, the free area inside it
with the usual 16-point margin).

Entering Render fitted before the saved setup replaced the draft, so a cold
workspace with a portrait setup fitted for the old draft's aspect; the fit
now runs once the setup in effect is loaded.

render.camera.fit takes the picture's aspect (0.000001 to 1000000) through
Camera::fit_aspect, which publishes only a valid fitted camera: an aspect
that would divide the distance into infinity is refused and the previous
camera, document and undo stay as they were.
2026-10-30 18:30:45 +00:00
github-actions[bot] 0b34d3590d Regenerate generated files and fold pending files [skip ci] 2026-10-30 19:12:20 +00:00
austin62jake aedbf0b1d4 Preferences: an option to keep value boxes out of the view (#9479) (#9547)
* Claim #9479

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

* Preferences: an option to keep value boxes out of the view (#9479)

Design > "Show values beside their handles in the view", on by default.
Off, a command's value box (Extrude's Distance, Fillet's Radius, a hole's
reference distances...) isn't drawn over the model; the command window's
value field takes the keyboard that opening the window or a pick gives the
box, its text selected, so a number typed after a pick still sets it. The
handles stay and still drag the value.

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

* Preferences: the value box option in Arabic, Hebrew and Vietnamese too (#9479)

The complete catalogs must carry every English message.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-30 18:30:04 +00:00
semmelrinde a13e0e0ceb Danish: translate the 1055 messages the catalog lacks outside the Render workspace (catch-up 1 of 2) (#9589)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:29:51 +00:00
semmelrinde 6f05c19c42 Danish: translate the Render workspace's 1245 messages (catch-up 2 of 2) (#9590)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:29:44 +00:00
HiroKenEz db0c25522e Render: a size the CPU profile can't render names the profile that can (#8941) (#8943)
* claim: #8941

* Render: a size the CPU methods refuse names the profile that renders it (#8941)

The Shots strip offers 4K UHD whatever the method. Under the default CPU
raster, and under CPU ray tracing's default Compact profile, Render was then
refused with English engine text that didn't say how to render that size.

The refusal is now translated and names the smallest CPU resource profile that
admits the size (with CPU ray tracing, for raster, which has no profiles). The
size lists mark such presets. Nothing is switched: method, profile and
resolution still change only when chosen, and GPU path tracing is unaffected.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:16:36 +00:00
CaRLymx e6c999bbcd Spanish (es-ES): catch up with en.json (2026-10-30) (#9569)
- Base: origin/main (71b3a70, post #9448)
- en.json: 4,778 keys → es.json: 4,778 keys (100% complete)
- 1,033 reusable translations from previous catch-up branch
- 88 newly translated keys (construction lines, point cloud, print face, quick measure, recovery, UI hints, update checks)
- Terminology per glossary: 'Servicio de API', 'Desactivar/Reactivar', 'alma/ala', 'Ángulo de plegado/Ángulo incluido', 'vinculación de superficie', 'toma/escenario/estudio/configuración', 'X-Líneas' (construction lines), 'cara de impresión', 'bisectriz', 'tangente común'
- 0 placeholder mismatches, catalog sorted alphabetically
- cargo xtask i18n --check ✅, cargo xtask lists ✅, cargo xtask assets ✅

Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
2026-10-30 18:07:52 +00:00
PaddleStroke 180a725c0e Parameters: sort the list by name within each section (#8563) (#8725)
* claim: #8563

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

* Parameters: sort each section by name (#8563)

The Name heading of every Parameters table steps through A to Z, Z to A and the
listed order. Each section sorts on its own: Favorites, User Parameters and each
model group (the groups keep the design's order). Names compare in natural order
(d2 before d10, letters ignoring case). The choice is kept in egui's persisted
memory like a section's open state. While sorted, a user parameter's move arrows
are disabled. The sortable heading and natural_cmp live in ui_kit::table.

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

* Parameters sort: gallery entry for the sortable heading, natural_cmp private

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:02:06 +00:00
Steven Waters 4ba6a9744b Browser: allow nesting groups and re-ordering groups & components (#9109) (#9116)
* Browser: nest groups and reorder components

Groups can hold other groups (up to 8 levels) and be reordered with the
items around them. Sub-components can be reordered too, through a new
`components` list in `browser_order` that is not drawn as a folder.

Document: `BrowserGroup` gains `parent`; group items and `browser_order`
lists hold `BrowserEntry` values (a number is a group, a string an item).
One pending format file; older designs read unchanged. Loading and
refreshing drop what the lists name that is gone, and cut cycles and
over-deep nesting to the top level.

Engine: `browser.group` takes `parent`; `browser.move`, `browser.order`
and `browser.ungroup` handle groups. A change to only the browser's
groups and order no longer re-evaluates the model.

UI: nested rows, dragging a group row (top of a row: before it, the rest:
into it), New Group inside a group, and dropping a placement on the top
of a sibling to reorder it.

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

* Fixed formatting

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

* Fixed unintended side effect in render scene browser test

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

* Preserve group order in render workspace

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

* Increased test coverage

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

* Updated clipboard test from upstream main for group item type

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 17:40:33 +00:00
Nathan Dane ae998906ff Viewport: fold three status items into a summary badge (#8549) (#8558) 2026-10-30 17:33:23 +00:00
phanoob 634f0a2308 Sketch text: number parameters in the words, and = for a formula in Edit Text (#9281) (#9386)
* claim: #9281

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

* Sketch text: a number parameter in the words shows its value (#9281)

A text's words expression can now name a number parameter: `'W' + Width`
reads "W85 mm" (lengths in the design's units, angles in degrees, up to
three decimals). `number(Width)` drops the unit and `number(Width, 2)`
fixes the decimals. The words and the cut follow a parameter edit in the
same rebuild, a rename reaches inside number(), and deleting the parameter
is refused while a text shows it.

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

* Sketch text: a Text starting with = builds its words from parameters (#9281)

In Edit Text, a Text that starts with = is the words' expression
(='W' + number(Width)): OK keeps it, the dialog shows it again with its =,
and an untouched OK no longer turns it into typed words. Typed words still
replace it, and a saved text that happens to start with = stays typed
until it is edited. The field's tooltip says how.

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

* Sketch text: the formula hint in every complete catalog (#9281)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:28:31 +00:00
phanoob 306b78ce16 Expressions: Down arrow in an empty field lists the parameters (#9246) (#9385)
* claim: #9246

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

* Expressions: Down arrow in an empty field lists every named parameter (#9246)

In an empty expression field, Down opens the completion list with every
named parameter (not just the first eight), armed on the first one; Up and
Down walk it, the list scrolls to keep the chosen row in view, and Enter
inserts it. A field that already holds text keeps Down for itself.

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

* Expressions: Preferences caps the Down-arrow parameter list (#9246)

Down arrow in an empty field lists the parameters up to "Parameters
listed by Down arrow" in Preferences > Design (default 50, 5 to 200),
favourites and the design's own parameters first, so a large design
does not build a list of hundreds of rows.

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

* Expressions: a label that fits for the parameter list cap (#9246)

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

* Expressions: the parameter list cap in every complete catalog (#9246)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:27:56 +00:00
Badger B 678da425da Add German translations, batch 4 (#7237) (#9554)
Claude-Session: https://claude.ai/code/session_01YVbV9BQxn5xzooBo8yEt9a

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:27:13 +00:00
froglemonade2 fc499441e2 Lost GPU: the old process exits instead of hanging in the driver, after the settings are saved (#9044) (#9054)
After a lost device the next start began from on_exit, while eframe was
still writing app.ron, and the old process then hung in
vkDestroySwapchainKHR on NVIDIA. Start it from Drop for App instead, after
eframe has joined the settings save and before it drops the window's GPU
surface, and end the process there after a loss. The update restart moves
there too. The loss reason is passed to the next start and shown in its
note.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:26:33 +00:00
github-actions[bot] 1cf5f83924 Regenerate generated files and fold pending files [skip ci] 2026-10-30 18:38:33 +00:00
bherbruck 6a465abee9 Quick measurement: stop the off-thread test failing when the thread is fast (#9566)
The test asserted the readout was still empty right after the first poll, but the measuring thread can finish first, so it failed now and then in merge groups and on unrelated pull requests. It now asserts only that the measurement arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:37:42 -04:00
github-actions[bot] 89a3e89d62 Regenerate generated files and fold pending files [skip ci] 2026-10-30 18:31:05 +00:00
Cyryl Sochacki 58c0fc396f i18n(pl): translate 1155 missing UI messages (#9102)
Everything en.json gained since the last catch-up except the render.*
messages: Preferences, navigation bar, assistant settings, materials,
hole presets, joint move, edit in place, point clouds, view angles,
tool help texts and error hints. Added as one pending locale file.
2026-10-30 17:19:37 +00:00
GavinSchmidt 0952686c1b Sheet metal: personal Sheet Metal Rules, saved in the profile and offered by every design's rule manager (#9346) (#9520)
* Sheet metal: personal rules library in the profile, offered by every design's rule manager

Adds personal_sheet_rules.rs and its tests, the personal_sheet_rules preference, and the rule manager wiring.

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

* Sheet metal: pending strings for personal rules

English and the complete catalogs (ar, he, ja, vi, zh), plus de and ko.

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

* Docs: personal rules in the sheet metal guide

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

* Docs: glossary entry for personal sheet metal rule

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:12:12 +00:00
semmelrinde 64b506dccf Sketch: the coordinate readout starts right of the pointer arrow (#9480) (#9484)
* Sketch: the coordinate readout starts right of the pointer arrow (#9480)

The readout of a sketch tool's point (2D and 3D sketches) started 10
points below and right of the snapped point, under the system arrow,
which hangs about 12 points wide below the hot spot and covered the
first value. It now starts 18 points right and 10 below the pointer
(the point snaps away from it), from the snapped point only when there
is no pointer over the view.

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

* Sketch: the readout clears the next larger pointer too (#9480)

The reporter uses the system's pointer size 2, whose arrow is about
18 × 28 points: start the readout 26 points right of the hot spot
instead of 18, and test against that arrow.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:57:45 +00:00
semmelrinde 61195235d8 Layout: three more edge clamps give way in an inside-out view (#9504)
The clamp that crashed #9492 (f32::clamp between a view's edges, which
panics once the view comes out inside out) also sits in the room tool
windows get beside the docked Assistant, the Render selection bar's
anchor and a left-anchored panel's anchor. Use shell::within there,
the clamp that gives the lower edge for an empty range (#9271).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:57:38 +00:00
semmelrinde 6241decaf9 Galician: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9537)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:57:22 +00:00
semmelrinde 3288adf3dd Czech: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9525)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:50:24 +00:00
semmelrinde 5b53a1354a French: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9527)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:50:17 +00:00
semmelrinde 579dddaea0 Italian: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9529)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:50:10 +00:00
semmelrinde 9052b69e81 Dutch: translate the 31 messages main gained after batch 8, new Preferences included (batch 9) (#9530)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:50:04 +00:00
semmelrinde 2c5d875ab7 Swedish: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9532)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:57 +00:00
semmelrinde 04efc68578 Turkish: translate the 32 messages main gained after batch 8, new Preferences included (batch 9) (#9533)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:51 +00:00
semmelrinde 0e137b947f Traditional Chinese: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9534)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:44 +00:00
github-actions[bot] 97a24941d2 Regenerate generated files and fold pending files [skip ci] 2026-10-30 17:34:50 +00:00
github-actions[bot] c2a19bf375 Regenerate generated files and fold pending files [skip ci] 2026-10-30 17:27:38 +00:00
semmelrinde e40d7aaee7 Indonesian: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9536)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:30 +00:00
semmelrinde 2b18b68307 Norwegian Bokmål: translate the 35 messages main gained after batch 8, new Preferences included (batch 9) (#9538)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:23 +00:00
semmelrinde b9ae8814d4 Bosnian: translate the 14 messages main gained after batch 8, new Preferences included (batch 9) (#9539)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:16 +00:00
semmelrinde f3a4c62240 Serbian: translate the 14 messages main gained after batch 8, new Preferences included (batch 9) (#9541)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:10 +00:00
semmelrinde dbf38d84ef Russian: translate the 14 messages main gained after batch 8, new Preferences included (batch 9) (#9542)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:49:03 +00:00
github-actions[bot] f913a2a459 Regenerate generated files and fold pending files [skip ci] 2026-10-30 17:00:07 +00:00
Nathan Dane f504c82216 Viewport: integrate Section status with current UI changes (#8548) (#8557) 2026-10-30 16:23:37 +00:00
SilentWolf75 df18823d0a Start: tell same-named recent designs apart (#9081) (#9174)
* claim: #9081

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

* Tell same-named recent designs apart (#9081)

Show the file name with extension, ellipsize long paths in the middle so the end stays visible, and label imports by kind (STEP, STL, …) instead of always Design.

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

* Start: Recent paths take their ink from the theme, and the middle-ellipsis helper is exported for tests only

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-10-30 16:21:26 +00:00
austin62jake 0396b2d00a Model cache: a design with mesh bodies keeps its saved model (#9170) (#9218)
* claim: #9170

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

* Model cache: a design with mesh bodies keeps its saved model (#9170)

One mesh body (an inserted STL or 3MF, or a STEP part shown as a mesh of
its faces) left the model cache out for the whole design, so opening it
evaluated every feature again. The doc records refused mesh bodies, and
the native B-rep codec the io cache stores bodies with refuses them too.

Kernel work is paused, so the kernel is left alone: the io cache stores a
mesh body as its own slice kind (SCMESH01, a JSON header with the counts,
colour and paint, then the vertices as f64 and triangles as u32), read
back through mesh_body, with_color and with_paint. A mesh body is kept
only when its slice reads back as the same vertices bit for bit, the same
triangles, colour and paint. The decoder checks the counts against the
bytes before allocating. The picture shown while opening draws a mesh
body with its own triangles (native_preview_mesh refuses mesh bodies).

Cache version 4 -> 5; docs/design-container.md describes the mesh slice.

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

* Model cache: decode mesh slices with as_chunks, as clippy asks

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-10-30 16:17:27 +00:00
AsTechEvolves b1c132bc9a Assistant: drop earlier thinking blocks when history is trimmed, so Claude requests stop failing (#9417) (#9448)
* claim: #9417

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

* Assistant: drop earlier thinking blocks when history is trimmed (#9417)

Claude signs each thinking block against the conversation before it. Once
drop_old_views swaps an older view for a note, or trim_history drops the
oldest messages, every later block stops matching and the API refuses the
request with HTTP 400 "Invalid signature in thinking block". For the
Anthropic protocol, a trim now also removes the thinking and
redacted_thinking blocks from earlier replies, which the API allows. A
reply that was only thinking keeps a short note. Requests where nothing
was trimmed are sent unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:13:35 +00:00
github-actions[bot] 71b3a70254 Regenerate generated files and fold pending files [skip ci] 2026-10-30 16:24:57 +00:00
github-actions[bot] bcdecd19f4 Regenerate generated files and fold pending files [skip ci] 2026-10-30 16:17:21 +00:00
austin62jake 3b3f860adc Sketch: Project takes a construction axis, not only X, Y and Z (#9324) (#9471)
* claim: #9324

* Sketch: Project takes a construction axis, not only X, Y and Z (#9324)

sketch.project with {"axis": "Axis1"} upper-cased the name, and the axis link knew only the
origin axes, so a construction axis failed with "unknown axis `AXIS1`". X, Y and Z (any case)
are still the origin axes; any other name must be a construction axis, kept as its feature names
it. The link finds the axis where the timeline before the sketch puts it (construction_axis_in,
as Revolve does) and projects it as an origin axis: its line in the sketch plane, or a point
when the axis stands square to it. Renaming the axis renames the link, as for a construction
plane's projection.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-30 15:51:57 +00:00
PaddleStroke f0d5666a74 Closing: stop the Home picture builds instead of waiting for every cold recent file (#8398) (#9416)
* claim: #8398

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

* Closing: stop the Home picture builds instead of waiting for them (#8398)

Dropping SolveApp waited up to 10 s for the Home picture threads to end
(#6063). The thumbnails thread imports and rebuilds every cold recent
file (STEP and mesh imports, designs without a stored picture) one after
another, and its channel only closes once all are done, so closing the
window froze until every cold recent was built or the 10 s ran out.

The picture threads now build under a shared progress watch that
finish_pictures cancels first: the design being built stops at its next
feature (a STEP import at its next step), the remaining cold files are
skipped, and a build stopped part way draws and caches no picture.
Pictures already on the GPU are still waited for, so the driver crash
#6063 fixed stays fixed.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:46:06 +00:00
Kai Evans f1c2f67d63 Quick measurement: the selection's length, area or distance in the view's corner (#7000) (#7513)
* Quick measurement: the selection's length, area or distance in the view's corner (#7000)

With edges, faces, sketch curves or points selected, the view's bottom right corner above History
shows what they measure, as the Measure tool lists it: an edge's length (Ø and R for a round
one), a face's area, a point's position; two items' distance and angle; several edges' or faces'
count and total. It is computed off the window thread from a copy of the session whenever the
selection or the design changes, and a click copies it. Preferences > Interface turns it off. The
Measure dialog's rows come from the same function.

* Quick measurement: off by default, turned on under Preferences > Interface (#7000)

The quick_measure preference now defaults to false, so nothing changes until it is turned on.
Its tests turn it on, and a new test checks that nothing shows until it is.
2026-10-30 15:46:01 +00:00
Homero Thompson 32900556e0 Control server: a subscribed connection is told of design changes (#8315) (#8333)
* claim: #8315

* fix(control-server): release the watch seat deterministically (#8315)

The seat a subscribed connection holds on the DesignWatch lived in the
events thread alone, so it left the watch only when that thread ended:
on the first write a closed client refuses, which needs the design to
move, or when the thread noticed the end of the stream, which waits for
the connection's own thread to come out of its blocking read. A closed
connection could therefore keep counting on the watch for a second or
more, and the test that asks for the count to be gone could lose its
race with the ten seconds it waits.

The seat is now shared: it leaves the watch once, whichever thread sees
the connection's end first, the connection's own thread at the end of
the stream or the events thread at the first refused write. The count
of subscribed connections is right as soon as the connection is gone,
and the events a subscribed connection is told of do not change.
2026-10-30 15:45:51 +00:00
Frank bf14bf3678 Inspect Sketch: clicking it shows a Sketch report window (#6262) (#6263)
* claim: #6262

* Inspect Sketch: the ribbon button opens a Sketch report window (#6262)

sketch.inspect gets a place on the Sketch tab (Inspect panel). ui.start opens a
read-only Sketch report: solver status, degrees of freedom left, counts of
curves, points, constraints and profiles, the curves and points that are not
fully constrained, and Copy JSON. engine.execute still only returns the JSON.

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

* Inspect Sketch: a Sketch Report button on the Sketch tab while a sketch is open (#6262)

SKETCH/INSPECT placed it in the Inspect tab's Surface Quality drop-down, which
is hidden while a sketch is edited. It now has its own Inspect panel on the
Sketch tab, labelled Sketch Report; the test clicks the ribbon button.

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

* Sketch Report: strings in a pending locale file, help for sketch.inspect (#6262)

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

* Sketch Report: the Sketch tab fits at 1366 px; the width test closes the report (#6262)

The panels left of the Finish group keep the gap before it when they are fitted,
so they collapse before they reach it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Frank <plexi32@gmail.com>
2026-10-30 15:39:25 +00:00
DuBearte f0a5fc5016 Open: a DWG file says what it is and what to do instead (#9328) (#9487)
Opening a DWG stopped with "design file is not UTF-8 JSON". It is told
apart by its signature, whatever the file is called, and the error points
to STEP or DXF.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:18:40 +00:00
github-actions[bot] ad1652683b Regenerate generated files and fold pending files [skip ci] 2026-10-30 15:48:25 +00:00
github-actions[bot] 0d778e074b Regenerate generated files and fold pending files [skip ci] 2026-10-30 15:39:35 +00:00
PaddleStroke 0b2d51d301 Sketch: a projected face follows a resize that moves its corners far (#9001) (#9453)
* claim: #9001

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

* Sketch: a projected face follows a resize that moves its corners far (#9001)

A sketch on the face of an angle kept its projected face boundary (and
reported "projected face boundary identity is ambiguous") when the wall
thickness changed: the inner corner moved by more than a tenth of the face,
past the nearest-point match, and the same-order fallback refused faces.

Accept the same-order layout when, for every point, the point read in that
order is also clearly the nearest one on the same loop with lines the same
way. Rims and holes, and rotated readings of a rectangle, stay refused.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:05:36 +00:00
Frank bf7979d4a7 Insert Part: seat a fastener in several holes at once (#9341) (#9342)
* claim: #9341

* Insert Part: seat a fastener in several holes at once (#9341)

parts.insert / parts.fastener take `at` as one pick or a list of picks.
The part is built once as one component; each hole gets its own
placement and rigid seat joint. All placements are made before any is
seated, since joint.create takes the part's seating point where the
placement is. The insert now runs on a scratch session like
parts.resize, so it is one undo step, and a pick off every body fails
it before anything is made.

The Insert Part dialog's hole input is multi-select ("Holes
(optional)") and OK passes every pick.

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

* Insert Part: picks on one hole's rim seat one part (#9341)

A hole's rim is drawn as two half-circle edges, and one click on it
picks both, so four clicks gave eight picks and two screws per hole.
Picks whose wall point lies level on the same hole axis, on the same
body placement, are one hole.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:37:02 +00:00
DuBearte 5439bfc0b5 Expressions: a decimal comma inside plain parentheses (#9408) (#9447)
A comma separates arguments only directly in a function call, so plain
parentheses take a decimal comma like the rest of an expression:
`-(width-0,5)/2`. The UI no longer rewrites commas before wrapping a value
in the design unit's parentheses.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:34:56 +00:00
austin62jake c2ea096abb Assistant on Windows: find Node in its install folder when the launcher's PATH lacks it (#9297) (#9458)
* claim: #9297

* Assistant on Windows: find Node in its install folder when the launcher's PATH lacks it (#9297)

An npm-installed agent starts on Windows through its .cmd shim, which runs `node`, with the PATH
search_dirs makes: SolidCraft's own PATH, then npm's and other installers' folders. Node's own
install folder was not among them, so started from a launcher that leaves it off the PATH (the
ArtCraft launcher), the agent stopped with "'node' is not recognized". The search now also takes
%ProgramFiles%\nodejs, %ProgramFiles(x86)%\nodejs and nvm-windows' %NVM_SYMLINK%, after
everything it took before. search_dirs reads its variables through search_dirs_in, so a test can
give it a launcher's environment.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-30 14:27:10 +00:00
Colin Faulkingham 085853315d Sketch: copy completed sketches between designs (#5427) (#8962)
* claim: #5427

* Sketch: paste completed sketches across design tabs
2026-10-30 14:09:37 +00:00
Mario Geiger 332f96c26a Point clouds: colour and filter by any property the file carries (#8816) (#8819)
* Point clouds: colour and filter by any property of a PLY file, on range tracks (#5151)

A PLY file's other vertex properties are kept as named numbers on the
points (whole-numbered for integer types; a comment `property: 0 name,
1 name` names a label's values). A cloud is coloured by any of them, a
ramp or a palette for whole numbers, and filtered by up to eight, each a
range or a set of named values. The GPU colours and filters the points
itself, so editing a range uploads nothing.

In the Edit Point Cloud card each range is a track, as in an audio
editor: a histogram of the property over what the other filters keep,
stacked by colour, under an overview strip. Ranges are drawn, moved,
snapped to colour changes, typed (Start, End, Length, relative values)
and played (Space or Play). The automatic colour range is exactly the
values the property takes over what the filters keep.

pointcloud.insert and pointcloud.edit take color_by: <property>,
color_range and filters; pointcloud.list reports them with the
properties. docs/point-clouds.md describes it all.

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

* Point clouds: note the saved properties, colouring and filters as a pending format change (#8816)

A design now saves a cloud's per-point columns and their value names, colouring by a property and
filters; an older release must refuse it instead of dropping them.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:02:29 +00:00
PaddleStroke 56e752c182 Toasts: a close (×) with a countdown ring instead of a Close button; a shorter 3D mouse driver label (#7971) (#8763)
* claim: #7971

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

* Toasts: a close (×) with a countdown ring instead of a Close button (#7971)

Every toast the user may close (the status, the 3D mouse offer and the
stand-up note) has a close (×) at its top right, handle toast:<name>:close,
in place of a Close button; it does what the button did, is a kit-drawn
button with an accessible name and hover text, reachable by Tab, and dead
behind an issue report. Toasts that go by themselves draw a thin ring
around it that shrinks over their time; pointing at the offer or the note
starts their time again, as before, so their ring is full while pointed at.

The 3D mouse offer keeps three buttons, Calibrate…, Use Direct Driver (the
detail in its tooltip) and Never show again, on one row: the corner
toasts are 400 points wide, as the row's estimated widths need 356.

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

* Toasts: lay buttons out at their drawn widths; corner toasts 360 wide (#7971)

The toast's button rows used an estimate of each button's width that ran
about 40 points over the kit button as drawn, which wrapped the 3D mouse
offer's three buttons and left the main action short of the right edge.
Each button's drawn width is now remembered and the rows laid out with it
(the estimate stands in for the one frame before a button was first
drawn), so the main action is flush right; the corner width is the
smallest round one that fits the offer's three buttons in a row: 360.

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

* Toasts: keep the analysis status test whole after rebasing onto main

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-10-30 13:48:29 +00:00
github-actions[bot] 014ccf5ff4 Regenerate generated files and fold pending files [skip ci] 2026-10-30 15:12:22 +00:00
github-actions[bot] ab309f2219 Regenerate generated files and fold pending files [skip ci] 2026-10-30 15:05:46 +00:00
TaeHeon Kim 6c9afc6f7e Start the window on OCLP Macs with SYSTEM_VERSION_COMPAT=1 (#3080) (#8930)
On Macs patched with OpenCore Legacy Patcher the OS reports a newer
macOS version than the GPU's Metal compiler supports, so wgpu picks a
shader standard the compiler refuses (`invalid value 'metal3.2'`) and
the window never draws.

The variable has to be set before the process starts, and Rust 2024
makes `set_var` unsafe (this crate denies unsafe code), so main()
now re-launches the binary with SYSTEM_VERSION_COMPAT=1 when the OCLP
NVRAM key (OCLP-Version) is present, and exits with the child's status.
A process that already has the variable set never re-launches, so
there is no loop and a user's own value is respected. If the check or
the spawn fails, startup continues normally.

Tested on an OCLP Mac: one re-launch message, graphics render
correctly; with SYSTEM_VERSION_COMPAT=1 set beforehand it does not
re-launch.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 13:48:19 +00:00
SvenCarstensen aff8106205 Help: check for updates and show results in the selected language (#8896)
* Help: add manual update checks and status window

* Test manual update checks and localized results

* Add update check to the native Help menu

* Localize Help update checks in all app languages

* Document manual update checks from Help

* Document ui.help updates control action
2026-10-30 13:41:00 +00:00
semmelrinde 8ced905843 macOS: quitting on a Mac with a Touch Bar no longer crashes (#7535) (#7762)
* claim: #7535

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

* Vendor accesskit_macos 0.26.3 as published (#7535)

The published package, unchanged, so the next commit shows the fix as a diff.

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

* macOS: quitting on a Mac with a Touch Bar no longer crashes (#7535)

eframe's accessibility adapter (accesskit_macos 0.26.3) swaps the window's
view to a subclass of its own. When AppKit's Touch Bar observes the first
responder (nextResponder), key-value observing subclasses the view again.
Dropping the adapter on quit reset the view's class, which threw away KVO's
subclass, so AppKit's removal of its observer raised NSRangeException ("not
registered as an observer") and the app ended with EXC_BREAKPOINT.

accesskit_macos 0.27.2 fixes this (AccessKit/accesskit#835), but egui 0.36
is on accesskit 0.24, so vendor 0.26.3 with 0.27.2's src/subclass.rs and its
tests: the adapter leaves the class alone when KVO subclassed the view.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:35:37 +00:00
SilentWolf75 21340ba019 Web: Chinese UI loads a Chinese font (#8894) (#8981)
* claim: #8894

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

* Web: load Simplified/Traditional Chinese fonts for zh and zh-TW (#8894)

BIZ UDPGothic only covers Japanese, so Chinese UI showed tofu. Ship locale-glyph Craft UI SC/TC subsets and fetch the matching face on demand.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 13:33:23 +00:00
Colin Faulkingham 8b25fa0ae4 Configurations: reject invalid parameter expressions when saving rows (#9088) (#9414)
* claim: #9088

* Configurations: reject invalid row expressions (#9088)
2026-10-30 13:32:50 +00:00
ReDead c0b0369aa2 Sketch: hide constraint glyphs when zoomed out (#8980) (#9023)
* claim: #8980

Hide constraint glyphs when zoomed out (claim).

Co-authored-by: ReDead <ReDead@users.noreply.github.com>

* Sketch: hide constraint glyphs when zoomed out (#8980)

Fixed-size constraint glyphs stayed visible at every zoom and cluttered the
view when far out. Hide them below a sketch-plane scale threshold, with the
old always-on behaviour behind hide_constraint_glyphs_zoomed_out.

Co-authored-by: ReDead <ReDead@users.noreply.github.com>

* i18n: complete-catalog strings for zoom-hide preference (#8980)

Add ar/he/vi translations for prefs.hide_constraint_glyphs_zoomed_out
(and its note) so cargo xtask i18n --check no longer reports 6 gaps in
complete catalogs.

Co-authored-by: ReDead <ReDead@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
2026-10-30 13:31:09 +00:00
Frank 44a9f9ee42 GPU path tracer: the path shader compiles on NVIDIA's Vulkan driver (#6705) (#6850)
Native compilers inline every call, and `main` inlined 33 BVH walks: three
sample_path calls, and a shadow walk per light source family. NVIDIA's
compiler (driver 610.88) crashed in nvgpucomp64.dll or ran out of memory.

- main settles the adaptive budget first, then samples at one call site.
- direct_light takes over direct_studio, visible and the fixed-light loop:
  one loop over the sources with one shadow-ray call site, drawing random
  numbers in the same order.
- next_vertex (a select) replaces (a+1u)%3u: after a BVH walk that index
  crashed NVIDIA's compiler within seconds.

main now inlines 5 BVH walks; main_inlines_few_bvh_walks counts them with
naga, so the limit holds without an NVIDIA GPU.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:28:57 +00:00
kaltenbrunnermarkus f331a0740b Model cache: write each set of sketch keys once, and build the states on threads (#9010)
Problem: every state of a saved model wrote the evaluation key of each sketch before it, so a
design of many sketches and many features after them repeated the same map over and over. In
the SELENE rover (5,484 features, 2,116 bodies) that was 27 of the 33.5 MB manifest: the
manifest hit its 32 MiB limit, the save thinned the cache (#3220) to 2,140 of 5,484 states, and
every open evaluated the 3,344 left-out features again. Reading the kept states one after
another took a few seconds more.

Solution: as for sheet bodies (#6152), the records keep a list of sketch-key sets, each distinct
set written once, as the keys it sets and drops relative to the set written before it; a state
names its set by place. A thinned model keeps the whole list and counts it once against its
budget. The reader counts every set's keys, and each state's, towards MAX_HELD before it copies
them (a short record can stand for a large set), then builds the states on threads
(kernel::map_on_threads) once the shared records are read. The cache format is now /5: an older
cache is a miss and the design is evaluated.

SELENE's manifest is 15.9 MB with every state kept; opening it evaluates nothing. Parse and
evaluate on opening, release build, median of 5 on a shared machine: 7.36 s -> 1.14 s. The
evaluated bodies are identical (inspect.measure of all 2,116 bodies).

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

Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
2026-10-30 13:28:16 +00:00
fkirill 12636b67be Select hidden geometry with Select Other and fit the list to the window (#5574) (#5577)
* claim: #5574

* Pick hidden geometry near long clicks in a five-row list (#5574)

* fix: make Select Other configurable and fit its list to the window

---------

Co-authored-by: Kirill Frolov <k.a.frolov@vk.team>
2026-10-30 13:25:56 +00:00
semmelrinde 9f5ac92faa Saved model cache: keep fast opening after an update (#7168) (#7354)
* claim: #7168

* Saved model cache: keep fast opening after an update (#7168)

The model cache was read only by the exact build that wrote it (same app
version and commit), so every update evaluated every design again on its
first open. It is now keyed by the evaluation source instead: a digest of
the committed source the model is computed from (doc and the workspace
crates it is built from, the patched truck crates, locked external
dependencies, workspace build settings without the version, and the
cache reader), with the platform. An update that changed none of that
opens designs from their cache; one that changed any of it evaluates
them again, as before.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:25:42 +00:00
PaddleStroke 6e39efd4ac Theme: System follows the desktop's dark/light setting on Linux GNOME (#7944) (#8123)
* claim: #7944

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

* Theme: System follows the desktop's dark/light setting on Linux (#7944)

On GNOME (Wayland, or X11 through XWayland) the window toolkit never reports
the desktop's dark or light preference, nor its changes, so Theme: System kept
its colours. The Linux app now reads org.freedesktop.appearance color-scheme
through the XDG Desktop Portal's Settings interface (then GNOME's color-scheme
and gtk-theme when it has no preference) on a thread of its own, follows the
portal's SettingChanged signal, and falls back to gsettings or a slow poll.
The window reads the answer through Services::desktop_dark; the decision from
the raw values is prefs::desktop_prefers_dark. Windows and macOS keep the
window toolkit's theme.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:25:23 +00:00
C.Vengels 3c40edd12a Print face: Send to Slicer lays a body on its marked face (#8429) (#8433)
* claim: #8429

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

* Print face: Send to Slicer lays a body on its marked face (#8429)

A body can have one flat print face (Document::print_faces, held by the
certified target a sketch on a face holds). body.print_face {body, point?}
marks or clears it. Send to Slicer, Save Mesh with a slicer and EasyPrint
turn and move the exported copy so the face lies on the bed at Z = 0 with
the centre of mass over the origin; a face that can't be found is a
warning and that body is exported as it lies. Move/Copy and Mirror give a
copy its source's print face. One pending format file; the command
has its own icon (print_face).

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

* Print face: Ctrl+B, the face menu and the Send to Slicer row (#8429)

Ctrl+B and the face context menu mark the selected flat face as its
body's print face, or clear it. Send to Slicer shows the print face of
the one body being sent in a Print face input, takes a flat face picked
in the viewport and clears it when emptied.

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

* Print face: a light tint in the viewport and its strength in Preferences (#8429)

Each visible body's print face is tinted towards the theme's section
colour and outlined, at the strength set under Preferences > General >
Slicer (print_face_highlight, 0 to 1, 20 % by default; 0 hides it). The
user guide describes the print face, and the known limitation after a
shell of a shelled body is listed.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:25:18 +00:00
github-actions[bot] 81b620d7aa Regenerate generated files and fold pending files [skip ci] 2026-10-30 14:36:14 +00:00
SixSolid 9af2ae9a0f Agent chat window: an off-screen chat window no longer stalls the main window (#9118) (#9119)
* claim: #9118

* Agent chat window: an off-screen chat window no longer stalls the main window (#9118)

The chat popped out into its own window is an immediate viewport: eframe draws
and presents it inside the main window's frame. Both surfaces waited for vsync,
and a Wayland compositor sends no frame callbacks to a window it isn't showing,
so the main window stalled behind the chat window's present.

A surface other than the main window's is now configured with Mailbox, else
Immediate, when the configured mode waits for vsync and the surface offers one
(viewport_present_mode in the vendored egui-wgpu). The main window keeps vsync
and still paces both.
2026-10-30 13:24:30 +00:00
Kai Evans 1754318df7 Browser: the visibility eye at the right end of each row (#8718) (#8720)
* claim: #8718

* Browser: the eye sits in a column at each row's right end (#8718)

Tree rows draw the visibility eye right-aligned at the row's end, clear of
the fold arrow and the radio and of a floating scroll bar; names stop short
of it. Preferences > Interface > Eye next to the fold arrow
(browser_eye_left) keeps the old position. Rows report their control
positions so placement handles and inline rename follow the layout.

* Docs: the browser's eye column and browser_eye_left (#8718)

* Browser: the eye's tip shows by the eye at the row's end (#8718)

* Browser: a gap keeps the fold arrow and the radio apart; the eye clears a floating scroll bar (#8718)

The eye's old slot leaves a 10 pt gap instead of 4 pt, so the radio's hit
area is 7.5 pt from the fold arrow's and History pattern rows step in from
their sketch as far as browser children do. The eye's inset follows the
scroll style: only a floating bar laid over the rows takes room. Rows
publish indent:<label> where their depth puts them.

* Tests: browser depth checks run on the default layout, by indent handles (#8718)
2026-10-30 13:24:22 +00:00
semmelrinde f273195144 Czech: translate the 29 messages main gained after batch 7, construction lines included (batch 8) (#9427)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:55 +00:00
semmelrinde 247a77e1b1 French: translate the 25 messages main gained after batch 7, construction lines included (batch 8) (#9428)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:48 +00:00
semmelrinde ce4212a33a Hungarian: translate the 29 messages main gained after batch 7, construction lines included (batch 8) (#9429)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:41 +00:00
semmelrinde 522bd95254 Italian: translate the 29 messages main gained after batch 7, construction lines included (batch 8) (#9430)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:34 +00:00
semmelrinde b0b1e6832e Dutch: translate the 29 messages main gained after batch 7, construction lines included (batch 8) (#9431)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:27 +00:00
semmelrinde 3cb9aa07b5 Romanian: translate the 29 messages main gained after batch 7, construction lines included (batch 8) (#9433)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:19 +00:00
semmelrinde 60f6836ab7 Swedish: translate the 29 messages main gained after batch 7, construction lines included (batch 8) (#9434)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:23:12 +00:00
github-actions[bot] 43f0438fbb Regenerate generated files and fold pending files [skip ci] 2026-10-30 13:58:26 +00:00
bherbruck aa1fe28ddf Release 0.27.1 2026-10-29 23:34:31 -04:00
bherbruck 5b0eb08473 Release 0.27.0 2026-10-29 19:53:24 -04:00
bherbruck 9df8837d43 Release 0.26.1 2026-10-29 06:33:09 -04:00
bherbruck 409ab1ed45 Release 0.26.0 (zip 9.1.3) 2026-10-28 19:41:38 -04:00
bherbruck 5e03fe2f4b Release 0.26.0 2026-10-28 19:32:17 -04:00
bherbruck 37a586dd61 Release 0.25.1 2026-10-27 20:58:30 -04:00
bherbruck a79f40a711 Release 0.25.0 2026-10-27 18:05:39 -04:00
bherbruck b5728f284b Release 0.24.1 2026-10-27 08:34:27 -04:00
bherbruck 5965439a15 Release 0.24.0 2026-10-26 19:23:21 -04:00
bherbruck 586a358ed4 Release 0.23.1 2026-10-26 07:07:21 -04:00
bherbruck 7770f8982d Release 0.23.0 2026-10-25 18:18:39 -04:00
bherbruck 72ad6383ef Release 0.22.1 2026-10-25 10:38:39 -04:00
bherbruck 0537623a75 Release 0.22.0 2026-10-24 18:53:43 -04:00
bherbruck 65d288227e Release 0.21.0 (rc.2's code, version only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:51:08 -04:00
bherbruck fedb3bcdb1 Release 0.21.0-rc.2 2026-10-23 17:14:38 -04:00
bherbruck 097b8b7550 Release 0.21.0-rc.1
The tree is main at f120285c9 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-23 13:48:05 -04:00
bherbruck af7ca3a8a2 Release 0.21.0-rc.1
The tree is main at b31fddb98 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-23 13:37:43 -04:00
bherbruck 27ddb725c7 Release 0.21.0-rc.1
The tree is main at 2bf3d4e11 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-23 12:10:02 -04:00
bherbruck a06bdf5cfc Release 0.20.0
The tree is main at 030187452 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-22 18:34:40 -04:00
bherbruck 33fdcdb3a0 Release 0.19.0
The tree is main at 5899e5b6c exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-21 21:13:33 -04:00
bherbruck c07ab5f8f9 Release 0.18.4
The tree is main at 728f753b8 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-21 12:26:34 -04:00
bherbruck fea32f8aac Release 0.18.3
The tree is main at 034aa60d4 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-21 08:46:37 -04:00
bherbruck 4927214976 Release 0.18.2
The tree is main at 57bcc4083 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-20 22:09:18 -04:00
bherbruck b621819c0c Release 0.18.1
The tree is main at 522ae7755 exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-20 17:39:11 -04:00
bherbruck ec552a5300 Release 0.18.0
The tree is main at c3151fb7f exactly; the previous release commit is kept as a parent so release only moves forward.
2026-10-20 16:28:59 -04:00
bherbruck e0cc740cc3 Release 0.17.0
The tree is main at 511faedcf exactly; the 0.16.x release commits are kept as a parent so release only moves forward.
2026-10-20 11:41:48 -04:00
bherbruck 5de48e916d Release 0.16.2 2026-10-19 00:39:30 +00:00
bherbruck 80af7bbe3e Release 0.16.2
Version 0.16.2: 0.16.1 with the rigid orbit fix (#2753). Same design format (27).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 00:38:53 +00:00
bherbruck 191e983423 Fix orbit about the picked point (regression in 0.16.0) (#2753)
Orbiting about the point under the cursor (or the origin) turned the camera
about its target and then panned to put the pivot back. The pan works at the
target's depth with the parallel-view scale, so in a perspective view the
pivot slid off its pixel and the model swung about the camera.

The camera now turns rigidly about the pivot: Camera::orbit_about keeps the
pivot's coordinates in the camera's frame and rebuilds the target from the
new orientation, so the pivot stays exactly on its pixel in both projections.
The Shift snap spring and view.navigate's pivot orbit use the same.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e88779218)
2026-10-19 00:38:52 +00:00
bherbruck 2b9445b049 Release 0.16.1 2026-10-18 20:16:21 +00:00
bherbruck 7ddf48ca16 Release 0.16.1
Version 0.16.1: 0.16.0 with the orbit fix (reverts #2142). Same design format (27).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:12:04 +00:00
bherbruck d2f6848377 Revert #2142: orbit about the picked point like 0.15.0
#2142 made orbiting about the point under the cursor turn the camera in place instead of turning the part about that point. Restore the 0.15.0 behaviour.

This reverts commit cac4bccea8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:12:00 +00:00
bherbruck 0b6d6fecd8 Release 0.16.0 2026-10-18 18:16:11 +00:00
bherbruck fa691707c2 Release 0.15.0 2026-10-17 13:54:24 +00:00
bherbruck b9a55c74e2 Release 0.15.0
Version 0.15.0; the pending design-format changes are folded into format 26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:53:44 +00:00
bherbruck 2bb9c81069 Release 0.14.0 2026-10-16 09:19:14 +00:00
bherbruck cedb51b8ea Release 0.14.0
Version 0.14.0; the pending design-format changes are folded into format 25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:19:10 +00:00
bherbruck 66d13059dd Release 0.13.1 2026-10-15 21:56:43 +00:00
bherbruck 800ea40c0d Release 0.13.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:56:35 +00:00
bherbruck 20442eecd3 Agent chat on Windows: start npm agents through their .cmd shim
npm puts a Unix shell script with the bare name (codex-acp,
claude-code-acp, gemini) beside the .cmd shim. The lookup tried the bare
name first, so every npm-installed agent failed with "not a valid Win32
application (os error 193)". Windows now only considers .exe/.cmd/.bat,
and a command saved with the bare path runs the shim beside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 60ea8c69d2)
2026-10-15 21:51:01 +00:00
bherbruck 22d1cd7032 Release 0.13.0 2026-10-15 20:34:13 +00:00
bherbruck 6a0dc18655 Fix the Windows 32-bit and macOS release builds
On 32-bit Windows SetWindowLongPtrW is SetWindowLongW, which takes and
returns i32, so the window-procedure hook didn't compile for i686.
The macOS app bundle now signs the bundled solidcraft-cli before the
bundle itself; codesign refuses a bundle with an unsigned helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 94e76cd97e)
2026-10-15 20:34:12 +00:00
bherbruck e006dbf1af Release 0.13.0
The tree is the 0.13.0 version commit (8241a750fa) on main exactly; the previous release is kept as a parent so release only moves forward.
2026-10-15 19:59:50 +00:00
bherbruck 8241a750fa Release 0.13.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:59:42 +00:00
bherbruck 41779cf7bb Release 0.12.0: release branch follows main again after the 0.11.1 patch
The tree is main at c36a6f9f exactly; the 0.11.1 patch commit is kept as a parent so release only moves forward.
2026-10-15 13:32:01 +00:00
bherbruck b01b9689c8 Release 0.11.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:41:41 +00:00
bherbruck 4426da6f5d Windows: start Direct3D 12 first, and Vulkan only without a D3D12 GPU (#1190)
* Windows: start Direct3D 12 first, and Vulkan only without a D3D12 GPU

wgpu started Vulkan, D3D12 and OpenGL together and preferred Vulkan, so
every Windows start loaded all installed Vulkan drivers. An old Intel
Vulkan driver (igvk64.dll) crashed 0.10.1 at startup inside the driver.

The app now probes D3D12, then Vulkan, then OpenGL, one backend at a
time, and starts only the first with a hardware adapter (WARP when none
has one). WGPU_BACKEND still wins; graphics.json's "backend" sets the
order by hand; the software renderer (safe mode, the retry) uses D3D12
alone. A new Windows CI job starts the real app under several settings.

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

* CI: a test filter YAML parses

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

* CI: the startup smoke test writes graphics.json where the app reads it

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

* CI: run the WARP shader tests one at a time

WARP's shader JIT (d3d10warp.dll) crashed twice in 124 runs of the gpu
tests with four test threads, with a null read inside its own optimizer
while several devices compiled compute pipelines at once. Serial runs
did not crash.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 95ea05e7c8)
2026-10-14 20:41:37 +00:00
bherbruck 5ead82e627 Release 0.11.0: release branch follows main again after the 0.10.1 patch
The tree is main at cccbe125 exactly; c29b0d2f (0.10.1) is kept as a parent so release only moves forward.
2026-10-14 20:07:41 +00:00
bherbruck c29b0d2f13 Release 0.10.1 2026-10-14 14:41:19 +00:00
bherbruck 480fd8d517 Fix the startup crash on D3D12 with FXC: CPU fallback for the tessellation pipeline, FXC-friendly shader (#1098) (#1099)
* GPU tessellation: a pipeline the device cannot build falls back to CPU meshing instead of crashing (#1098)

Build the tessellation compute pipeline inside error scopes; TessGpu::new returns an error,
and the viewport logs it and meshes bodies on the CPU. A Windows CI job builds the pipeline
with D3D12's FXC on the WARP adapter.

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

* Tessellation shader: basis functions in one-dimensional local arrays, so D3D12's FXC compiles it (#1098)

FXC has to unroll loops that write an array inside a struct (Basis.n0/n1) or a 2-D array
(ndu) by a counter, and cannot with run-time trip counts (X3511). A2.2 basis functions in a
local 1-D array returned whole; derivatives from the degree p-1 functions; the pole step
offsets computed rather than indexed.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:41:16 +00:00
bherbruck 4c09fa5355 Hovered and selected faces light solidly again over GPU-drawn bodies (#1064)
Since the model's mesh went back to the reference mesher (#940), the GPU viewport drew
opaque bodies from their display plans while hover and selection lit faces with the
reference mesh's triangles. Two triangulations of the same face at equal depth fought, so
a hovered face shimmered in wedges instead of lighting whole.

Faces lit over a body drawn from its plan now use the plan's own triangles
(ModelBody::plan_mesh, evaluated once on the CPU), and the highlight pass gets a small
depth bias toward the eye so f32 rounding between the GPU and CPU evaluations of the same
triangles cannot speckle it either.

Test: every face of a box with a pocket and a round boss, hovered and rendered offscreen
through the viewport's real pipelines on the GPU path, lights the same pixels as on the
CPU path (skipped without a GPU adapter).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:56:47 +00:00
825 changed files with 113068 additions and 9356 deletions
+3 -1
View File
@@ -1,6 +1,6 @@
## Change
Describe the problem and resulting behavior. Link the issue when applicable.
Start with what this is, in plain words someone new to the project would understand: what it does and who it's for. Don't assume anyone knows the name of your tool, project or feature. Then describe the problem and resulting behavior. Link the issue when applicable.
Changes a default: no
@@ -14,6 +14,8 @@ What did you run, and what were the results? Include expected and measured value
## Screenshots of the behavior (when observable)
Put the picture or video here in the description. A file in the diff doesn't count: a maintainer has to be able to see what this is without opening the changed files.
For observable CAD, UI or model behavior, include actual descriptive screenshots or a short recording from a small reproduction. This includes kernel, geometry, assembly and backend effects that can be shown in the app. Coding agents should run the app and capture the evidence themselves, rather than asking a human for evidence they can produce. Show the actual failure/result, preferably before and after, with brief steps, expected/actual behavior and annotations where helpful.
Flowcharts and Mermaid diagrams cannot substitute for evidence of visible behavior. Use diagrams for architectural reasoning when actual screenshots cannot illustrate the point. Changes with no useful observable behavior may omit screenshot/video evidence. Exclude private designs and sensitive details; attach media or link to an accessible recording rather than committing generated review artifacts.
+4 -3
View File
@@ -1,6 +1,7 @@
# While a new geometry kernel is evaluated, new issues, pull requests and discussions about kernel
# work are labelled `kernel-paused` for a maintainer to answer (CONTRIBUTING.md, "Kernel work is
# paused"). The logic is in scripts/kernel-paused/ (tested in ci.yml).
# While a new geometry kernel is evaluated, new issues and discussions about kernel work, and new
# pull requests that change the kernel directories, are labelled `kernel-paused` for a maintainer to
# answer (CONTRIBUTING.md, "Kernel work is paused"). The logic is in scripts/kernel-paused/ (tested
# in ci.yml).
name: Kernel paused
on:
+18 -3
View File
@@ -1,6 +1,6 @@
# Two quick checks on every pull request (CONTRIBUTING.md, "Sending a change"): no second open PR
# for an issue a lower-numbered one already closes, and the "Changes a default:" line is filled in.
# Neither is a required status check. The logic is in scripts/pr-checks/pr-checks.js (tested in ci.yml).
# Three quick checks on every pull request (CONTRIBUTING.md, "Sending a change"): no second open PR
# for an issue a lower-numbered one already closes, the "Changes a default:" line is filled in, and
# a PR adding to the showcase shows a picture. None is a required status check. The logic is in scripts/pr-checks/pr-checks.js (tested in ci.yml).
name: PR checks
on:
@@ -48,6 +48,21 @@ jobs:
const r = await checks.checkDefaults({ github, context, core });
core.info(JSON.stringify(r));
picture:
name: Picture check
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: scripts/pr-checks
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const checks = require('./scripts/pr-checks/pr-checks.js');
const r = await checks.checkPicture({ github, context, core });
core.info(JSON.stringify(r));
# Labelling needs write access, which a fork PR's token never has, so it is its own job and best
# effort: a fork PR just doesn't get the `changes-default` label from here.
label:
+2 -2
View File
@@ -350,7 +350,7 @@ jobs:
name: macOS build ${{ matrix.arch }}
needs: version
if: needs.version.outputs.build == 'true'
runs-on: macos-14
runs-on: macos-15
strategy:
fail-fast: false
matrix:
@@ -384,7 +384,7 @@ jobs:
name: macOS universal
needs: [version, macos-build]
if: needs.version.outputs.build == 'true'
runs-on: macos-14
runs-on: macos-15
env:
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+132 -4
View File
@@ -84,6 +84,8 @@ Editable drawing code, native projects and shipped exports are collected under `
| docs/book/src/generated/tool-preview.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/tool-preview.json`; gitignored) | MIT OR Apache-2.0 |
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
| assets/fonts/craft-ui-sc/CraftUI-SC.otf | Adobe / Google Inc. (Noto Sans SC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/SC Regular, glyph-subset for assets/locales/zh.json and renamed Craft UI SC (OFL reserved-name); licence text in assets/fonts/craft-ui-sc/OFL.txt; web UI Simplified Chinese | SIL OFL 1.1 |
| assets/fonts/craft-ui-tc/CraftUI-TC.otf | Adobe / Google Inc. (Noto Sans TC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/TC Regular, glyph-subset for assets/locales/zh-TW.json and renamed Craft UI TC (OFL reserved-name); licence text in assets/fonts/craft-ui-tc/OFL.txt; web UI Traditional Chinese | SIL OFL 1.1 |
| tests/step-conformance/cone_point.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
| tests/step-conformance/cone_point_vertex.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
| tests/step-conformance/cylinder.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
@@ -125,6 +127,8 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/appearances/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/appearances/opaque_finishes.rs | SolidCraft contributors | original illustrative opaque finish values; no external dataset, textures or measured engineering data | MIT OR Apache-2.0 |
| assets/fonts/biz-udp-gothic/OFL.txt | The BIZ UDGothic Project Authors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/craft-ui-sc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-SC.otf | SIL OFL 1.1 |
| assets/fonts/craft-ui-tc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-TC.otf | SIL OFL 1.1 |
| assets/fonts/ibm-plex-mono/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/ibm-plex-sans/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
@@ -185,11 +189,7 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/shaders/viewport.wgsl | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/ui/palettes.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-dark.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-dark-hard.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-dark-soft.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-light.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-light-hard.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-light-soft.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/solidcraft-95.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
| assets/styles/ui/themes/platinum-9.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
| assets/styles/ui/themes/cupertino.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
@@ -628,6 +628,133 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
| docs/book/src/generated/equation-parabola.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-parabola.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-spiral.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-spiral.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/equation-wave.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/equation-wave.json`; gitignored) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/print_face.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/locales/fi.json | SolidCraft contributors | Original Finnish translation of SolidCraft's English catalog, AI-assisted (Claude Code) clean-room from the English and not yet reviewed line by line by a native speaker; no commercial CAD text used | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/plane_axis_point.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-2x.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-2x.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-4x.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-4x.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-8x.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-8x.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-off.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/aa-off.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/agent-pointer-speed.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/agent-pointer-speed.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/ao-on.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/ao-on.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/assembly-move.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/assembly-move.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/browser-card.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/browser-card.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/browser-column.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/browser-column.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-axes.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-axes.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-bends.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-bends.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-draft.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-draft.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-failed.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-failed.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-fixed.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-fixed.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-projected.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/colour-vision-projected.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/direct-interaction.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/direct-interaction.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/frame-rate.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/frame-rate.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-bottom.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-bottom.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-in-browser.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-in-browser.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-left.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-left.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-under-browser.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/history-under-browser.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/layout-schematic.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/layout-schematic.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/measure-axis-distances.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/measure-axis-distances.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/mouse-mapping.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/mouse-mapping.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-axes.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-axes.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-cube.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/navigator-cube.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-centre.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-centre.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-cursor.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-cursor.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-origin.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orbit-origin.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orientation-y-up.dark.svg | SolidCraft contributors | original SolidCraft artwork: the Orientation figure copied from the app and extracted from the Preferences redesign board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orientation-y-up.light.svg | SolidCraft contributors | original SolidCraft artwork: the Orientation figure copied from the app and extracted from the Preferences redesign board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orientation-z-up.dark.svg | SolidCraft contributors | original SolidCraft artwork: the Orientation figure copied from the app and extracted from the Preferences redesign board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/orientation-z-up.light.svg | SolidCraft contributors | original SolidCraft artwork: the Orientation figure copied from the app and extracted from the Preferences redesign board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/quick-measure.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/quick-measure.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/select-bodies.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/select-bodies.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/select-faces-edges.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/select-faces-edges.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-conflict.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-conflict.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-exit-keep.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-exit-keep.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-exit-previous.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-exit-previous.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-exit-unless-turned.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-exit-unless-turned.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-guides.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-guides.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-hide-glyphs.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-hide-glyphs.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-state-icons.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/sketch-state-icons.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/style-shaded-edges.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/style-shaded-edges.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-coarse.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-coarse.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-fine.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-fine.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-normal.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-normal.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-ultra.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tess-ultra.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-dark.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-dark.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-gruvbox.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-gruvbox.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-imported.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-imported.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-light.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-light.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-system-dark-half.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-system-dark-half.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-system-light-half.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/theme-system-light-half.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-cursor.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-cursor.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-extrude.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-extrude.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-hole.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-hole.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-placement.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/tool-placement.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/vertex-markers.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/vertex-markers.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-angles.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-angles.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-grid.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-grid.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-perspective.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-perspective.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-shadow.dark.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| docs/design/preferences/figures/view-shadow.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
| `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 |
## Code adapted from sibling projects
@@ -687,6 +814,7 @@ 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) |
| vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: bounded parameter searches, collapsed-boundary projection, subdivision and provisional fixed-grid previews (vendor/README.md) |
| vendor/truck-stepio | truck-stepio 0.3.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | STEP writer: intersection-curve surfaces written at unique ids (vendor/README.md) |
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; face and edge history of booleans; trace macro; corner choice at vertices several intersection curves end on; reuse of an operand's meshes (vendor/README.md) |
+14 -6
View File
@@ -19,12 +19,13 @@ Questions, ideas or want to say what you're working on? Join the [Discord](https
## Kernel work is paused
We're evaluating a new geometry kernel. Until that's decided, please don't open issues, pull
requests or discussions about kernel work: booleans, fillets, chamfers, shell, offsets, sweeps and
lofts, threads and holes, STEP/IGES import and export, tessellation, or anything else that changes
`crates/kernel/`. The pinned issue on the tracker explains it. New ones get the `kernel-paused`
label (`.github/workflows/kernel-paused.yml`) and a maintainer answers them. Crashes and data loss
are still taken as issues, and everything outside the kernel carries on as normal.
We're moving to a new geometry kernel. Until that's done, please don't open pull requests that
change `crates/kernel/`, `crates/geom/` or `vendor/truck*`, or issues and discussions about kernel
work: booleans, fillets, chamfers, shell, offsets, sweeps and lofts, threads and holes, STEP/IGES
import and export, or tessellation. The pinned issue on the tracker explains it. New ones get the
`kernel-paused` label (`.github/workflows/kernel-paused.yml`) and a maintainer answers them. Crashes
and data loss are still taken as issues. Pull requests outside those directories carry on as normal,
including the dialogs and tools for these features.
## Get it running (about 10 minutes on a fast machine)
@@ -346,6 +347,13 @@ without pushes. These automatic cleanup deadlines are separate from the daily pr
`/unclaim` releases the bot claim early (only you or a maintainer can). Issues a maintainer has
taken are never freed automatically.
**A PR has to explain itself.** Start the description with what it is, in plain words someone new
to the project would understand: what it does and who it's for. Don't assume anyone knows the name
of your tool, project or feature. If there's anything to look at, show it in the description: a
picture or video has to be visible there without opening the changed files. "The GIF is the change"
doesn't count. A PR that doesn't do this waits until it does. For a PR that adds to
`docs/showcase.md` or `docs/screenshots/`, the Picture check fails until the description shows one.
Help reviewers see what the words mean. For observable CAD, UI or model behavior, please
include actual descriptive screenshots or a short recording in the issue or PR. This evidence
is required for changes or bugs that can be demonstrated in the app, including kernel,
Generated
+39 -20
View File
@@ -60,8 +60,6 @@ dependencies = [
[[package]]
name = "accesskit_macos"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076"
dependencies = [
"accesskit",
"accesskit_consumer 0.38.0",
@@ -4210,7 +4208,7 @@ dependencies = [
[[package]]
name = "solidcraft"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"block2 0.6.2",
"eframe",
@@ -4242,15 +4240,16 @@ dependencies = [
"windows-sys 0.61.2",
"winit",
"x11rb",
"zbus 5.19.0",
]
[[package]]
name = "solidcraft-3dx-client"
version = "0.27.1"
version = "0.30.1"
[[package]]
name = "solidcraft-cli"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"image",
"log",
@@ -4262,7 +4261,7 @@ dependencies = [
[[package]]
name = "solidcraft-derive"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"proc-macro2",
"quote",
@@ -4271,7 +4270,7 @@ dependencies = [
[[package]]
name = "solidcraft-doc"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"log",
"serde",
@@ -4288,7 +4287,7 @@ dependencies = [
[[package]]
name = "solidcraft-engine"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"image",
"log",
@@ -4309,7 +4308,7 @@ dependencies = [
[[package]]
name = "solidcraft-geom"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"serde",
"serde_json",
@@ -4318,7 +4317,7 @@ dependencies = [
[[package]]
name = "solidcraft-gpu"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"image",
"log",
@@ -4338,7 +4337,7 @@ dependencies = [
[[package]]
name = "solidcraft-io"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"cadmpeg-core",
"cadmpeg-ir",
@@ -4356,13 +4355,14 @@ dependencies = [
"solidcraft-doc",
"solidcraft-geom",
"solidcraft-kernel",
"solidcraft-testkit",
"thiserror 2.0.21",
"zip",
]
[[package]]
name = "solidcraft-kernel"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"log",
"proptest",
@@ -4382,8 +4382,9 @@ dependencies = [
[[package]]
name = "solidcraft-mcp"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"image",
"serde_json",
"solidcraft-engine",
"solidcraft-gpu",
@@ -4391,7 +4392,7 @@ dependencies = [
[[package]]
name = "solidcraft-render"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"image",
"moxcms",
@@ -4400,12 +4401,13 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"solidcraft-geom",
"web-time",
"weezl",
]
[[package]]
name = "solidcraft-sketch"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"harfrust",
"serde",
@@ -4418,14 +4420,18 @@ dependencies = [
[[package]]
name = "solidcraft-space-mouse"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"serde",
]
[[package]]
name = "solidcraft-testkit"
version = "0.30.1"
[[package]]
name = "solidcraft-ui-egui"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"arboard",
"egui",
@@ -4444,6 +4450,7 @@ dependencies = [
"solidcraft-gpu",
"solidcraft-space-mouse",
"syn 2.0.119",
"sys-locale",
"tiny-skia",
"toml",
"ttf-parser",
@@ -4455,7 +4462,7 @@ dependencies = [
[[package]]
name = "solidcraft-web"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"eframe",
"egui",
@@ -4559,6 +4566,18 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
dependencies = [
"js-sys",
"libc",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -4566,7 +4585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.5",
"windows-sys 0.61.2",
@@ -5998,7 +6017,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.27.1"
version = "0.30.1"
dependencies = [
"proc-macro2",
"serde",
+6 -1
View File
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
exclude = ["vendor"]
[workspace.package]
version = "0.27.1"
version = "0.30.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -28,6 +28,8 @@ solidcraft-space-mouse = { path = "crates/space-mouse" }
solidcraft-mcp = { path = "crates/mcp" }
solidcraft-derive = { path = "crates/derive" }
solidcraft-3dx-client = { path = "crates/threedx-client" }
# test tooling: a dev-dependency only (xtask/src/layers.rs)
solidcraft-testkit = { path = "crates/testkit" }
# foundation
thiserror = "2"
@@ -165,6 +167,9 @@ egui-wgpu = { path = "vendor/egui-wgpu" }
# winit 0.30 never binds and eframe 0.36 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]
+3 -1
View File
@@ -662,7 +662,9 @@ completed drawings publish atomically. The browser still exports synchronously.
pieces survive unfold/refold as one logical sheet group, including split flanged sheets.
Cut-outs made in the flat pattern cross bends at any angle or curved: their edges fold to
helices and curves on the bend, their walls square to the sheet. Free-form bases use bounded
sampling.
sampling. A fold crosses the sheet's own outer edges the same way, slanted or on any curve,
with the outline exact in the flat pattern and its DXF, and flanges and other folds still go
on the panels.
Parallel prismatic partial-depth cuts retain pocket side and depth through transfer and
unfold/refold, with separate pocket contours in preview and DXF. Planar Intersect preserves
exact holes and retained islands, including the union of multiple selected profiles, and
+40 -12
View File
@@ -3,7 +3,7 @@
## Supported versions
SolidCraft is young and moves quickly, so only the latest release gets security fixes. Please
update to the [latest release](https://github.com/bherbruck/solidcraft/releases/latest) before
update to the [latest release](https://github.com/tangency-labs/solidcraft/releases/latest) before
reporting, and check whether the problem is still on `main`.
| Version | Supported |
@@ -15,18 +15,46 @@ reporting, and check whether the problem is still on `main`.
## Reporting a vulnerability
Please **don't open a public issue** for a security problem. Report it privately through GitHub:
open the repository's [Security tab](https://github.com/bherbruck/solidcraft/security) and choose
open the repository's [Security tab](https://github.com/tangency-labs/solidcraft/security) and choose
**Report a vulnerability**
([direct link](https://github.com/bherbruck/solidcraft/security/advisories/new)).
([direct link](https://github.com/tangency-labs/solidcraft/security/advisories/new)).
Please include what you found, how to reproduce it (a design file, command or MCP request), the
version and platform, and the impact you expect. I'll acknowledge the report as soon as I can,
keep you updated in the advisory, and credit you when the fix is published unless you'd rather
stay anonymous.
Please include what you found, the steps you ran to reproduce it (a design file, command or MCP
request), the version and platform, and what an attacker gains. I'll acknowledge the report as soon
as I can, keep you updated in the advisory, and credit you when the fix is published unless you'd
rather stay anonymous.
Areas where reports are especially useful:
## What counts as a security issue
- opening untrusted design, STEP, STL or 3MF files (crashes, excessive memory or CPU, path tricks)
- the MCP server and the control API (file access outside the allowed folders, command injection)
- the web app (anything reaching beyond the page's own storage)
- release packages and the update paths (AppImage, Flatpak, MSI, deb, rpm, snap)
Advisories are for problems where someone gets something they shouldn't, without already having
access to your computer or your account. For example:
- opening a design, STEP, STL or 3MF file from someone else runs code, or reads or overwrites files
it shouldn't
- a website or another computer can reach or control SolidCraft
- an agent gets past a prompt that promised to ask you first
- the web app reaches beyond the page's own storage
- a release package or an update can be tampered with
- an API key or token gets sent somewhere it shouldn't go
## What to report as a normal bug
Please [open an issue](https://github.com/tangency-labs/solidcraft/issues/new) for these. They're
still worth fixing, but they aren't security issues:
- anything that needs a program already running on your computer, or the control token. A program
running as you can already do what you can.
- MCP's allowed folders. They guard against accidents, they aren't a security boundary, and an agent
with a shell of its own can already reach those files. Getting around them isn't a vulnerability.
- crashes, hangs, or heavy memory or CPU use, unless they also lead to something in the list above
- anything that only happens in an unusual setup you'd have to choose on purpose
## Before you send it
- **Run it.** Reproduce the problem on the latest release. If you only read the code, say so, and
expect it to wait.
- **Check it yourself.** If a scanner or an AI agent found it, read the code and confirm it first.
These tools tend to overrate what they find.
- **Say what's gained.** Name what the attacker needs and what they get. I set the final severity.
Reports that don't fit will be closed with a short note, or moved to the issue tracker.
+38 -3
View File
@@ -1,6 +1,6 @@
Third-party software in SolidCraft
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 560 crates below.
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 561 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.
@@ -403,6 +403,7 @@ syn 1.0.109 (MIT OR Apache-2.0)
syn 2.0.119 (MIT OR Apache-2.0)
syn 3.0.6 (MIT OR Apache-2.0)
synstructure 0.13.2 (MIT)
sys-locale 0.3.2 (MIT OR Apache-2.0)
tempfile 3.27.0 (MIT OR Apache-2.0)
termcolor 1.4.1 (Unlicense OR MIT)
thiserror 1.0.69 (MIT OR Apache-2.0)
@@ -1836,8 +1837,8 @@ approx 0.4.0, as-raw-xcb-connection 1.0.1, atspi 0.29.0, atspi-common 0.13.0, at
cgmath 0.18.0, chardetng 0.1.17, codespan-reporting 0.13.1, encoding_rs 0.8.42, kurbo 0.13.1,
multiversion_no_op 1.0.0, nohash-hasher 0.2.0, peniko 0.6.1, polycool 0.4.0, raw-window-metal 1.1.0,
rustls-platform-verifier 0.7.1, self_cell 1.3.0, siphasher 1.0.4, spade 2.15.1, static_assertions
1.1.0, ureq 3.4.2, ureq-proto 0.6.4, utf8_iter 1.0.4, x11rb 0.13.2, x11rb-protocol 0.13.2, zeroize
1.9.1, zeroize_derive 1.5.0, zune-core 0.5.3, zune-jpeg 0.5.15
1.1.0, sys-locale 0.3.2, ureq 3.4.2, ureq-proto 0.6.4, utf8_iter 1.0.4, x11rb 0.13.2, x11rb-protocol
0.13.2, zeroize 1.9.1, zeroize_derive 1.5.0, zune-core 0.5.3, zune-jpeg 0.5.15
================================================================================
Apache License
@@ -13570,6 +13571,32 @@ The above copyright notice and this permission notice shall be included in all c
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.
================================================================================
sys-locale 0.3.2
================================================================================
MIT License
Copyright (c) 2021 1Password
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.
================================================================================
tempfile 3.27.0
================================================================================
@@ -16799,6 +16826,14 @@ Bundled fonts (SIL Open Font License, Version 1.1)
Copyright 2022 The BIZ UDGothic Project Authors (https://github.com/googlefonts/morisawa-biz-ud-mincho)
Source: https://github.com/googlefonts/morisawa-biz-ud-gothic
- CraftUI-SC.otf (assets/fonts/craft-ui-sc/CraftUI-SC.otf)
Copyright 2014-2021 Adobe / Google Inc. (Noto Sans SC subset for SolidCraft web zh)
Source: https://github.com/notofonts/noto-cjk (Sans SubsetOTF/SC)
- CraftUI-TC.otf (assets/fonts/craft-ui-tc/CraftUI-TC.otf)
Copyright 2014-2021 Adobe / Google Inc. (Noto Sans TC subset for SolidCraft web zh-TW)
Source: https://github.com/notofonts/noto-cjk (Sans SubsetOTF/TC)
- IBMPlexSans-Variable.ttf (assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf)
Copyright 2017 IBM Corp. with Reserved Font Name "Plex"
Source: https://github.com/IBM/plex
+5 -1
View File
@@ -172,7 +172,11 @@ fn main() -> ExitCode {
Some("oracle") => oracle::run(&args[1..]),
Some("fuzz") => fuzz::run(&args[1..]),
Some("fuzz-one") => fuzz::one(&args[1..]),
Some("mcp") => cmd_mcp(&args[1..]),
Some("mcp") => {
let result = cmd_mcp(&args[1..]);
solidcraft_mcp::cleanup_scratch_dir();
result
}
Some("crashes") => crashes::run(&args[1..]),
Some("icon") => cmd_icon(&args[1..]),
Some("licences" | "licenses") => {
+107
View File
@@ -263,3 +263,110 @@ mod setup {
assert!(json_of(&pkg.join("claude_desktop_config.json"))["mcpServers"]["solidcraft"].is_object());
}
}
/// Scratch files belong to the CLI process; exercise real startup, requests and shutdown.
mod scratch_cleanup {
use super::*;
use std::path::{Path, PathBuf};
fn command(case: &str) -> (PathBuf, Command) {
let dir = std::env::temp_dir().join(format!("solidcraft-mcp-lifecycle-{case}-{}", std::process::id()));
std::fs::create_dir(&dir).unwrap();
let mut command = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
command
.args(["mcp", "--headless"])
.env("TMPDIR", &dir)
.env("TMP", &dir)
.env("TEMP", &dir)
.env("SOLIDCRAFT_RECOVERY_DIR", dir.join("recovery"))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
(dir, command)
}
fn scratch_folders(dir: &Path) -> Vec<PathBuf> {
std::fs::read_dir(dir)
.unwrap()
.map(|entry| entry.unwrap().path())
.filter(|path| path.file_name().unwrap().to_string_lossy().starts_with("solidcraft-mcp-"))
.collect()
}
fn initialize() -> Value {
json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}})
}
fn run(mut command: Command, request: Value) -> std::process::Output {
let mut child = command.spawn().unwrap();
writeln!(child.stdin.take().unwrap(), "{request}").unwrap();
child.wait_with_output().unwrap()
}
#[test]
fn eof_removes_only_the_process_scratch_folder() {
let (dir, command) = command("eof");
let unrelated = dir.join("solidcraft-mcp-unrelated");
std::fs::create_dir(&unrelated).unwrap();
let out = run(command, initialize());
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
let reply: Value = serde_json::from_slice(&out.stdout).unwrap();
assert!(reply.get("result").is_some(), "{reply}");
assert_eq!(scratch_folders(&dir), vec![unrelated]);
assert!(dir.join("recovery").is_dir(), "cleanup must leave the recovery folder alone");
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn startup_error_removes_the_process_scratch_folder() {
let (dir, mut command) = command("startup-error");
let out = command.args(["--param-check", "invalid"]).stdin(Stdio::null()).output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success());
assert!(stderr.contains("--param-check wants off, warn or strict"), "{stderr}");
assert!(scratch_folders(&dir).is_empty(), "startup error left a scratch folder in {dir:?}");
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn shutdown_preserves_nonempty_scratch_folder() {
let (dir, mut command) = command("nonempty");
let mut child = command.spawn().unwrap();
let mut input = child.stdin.take().unwrap();
writeln!(input, "{}", initialize()).unwrap();
let mut reply = String::new();
BufReader::new(child.stdout.take().unwrap()).read_line(&mut reply).unwrap();
let reply: Value = serde_json::from_str(&reply).unwrap();
assert!(reply.get("result").is_some(), "{reply}");
let scratch = scratch_folders(&dir);
assert_eq!(scratch.len(), 1);
let saved = scratch[0].join("keep.txt");
std::fs::write(&saved, "saved output").unwrap();
drop(input);
let out = child.wait_with_output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
assert_eq!(std::fs::read_to_string(saved).unwrap(), "saved output");
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn failed_screenshot_scratch_folder_is_removed_without_a_path_scope() {
let (dir, mut command) = command("screenshot");
command.arg("--allow-any-path");
// A headless server cannot capture a window. The request still allocates a scratch
// path before the backend refuses it, without relying on a GPU or installed renderer.
let out = run(
command,
json!({
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "screenshot", "arguments": {"source": "window"}},
}),
);
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
let reply: Value = serde_json::from_slice(&out.stdout).unwrap();
assert_eq!(reply["result"]["isError"], true, "{reply}");
assert!(reply["result"]["content"][0]["text"].as_str().unwrap().contains("the window gave no picture"), "{reply}");
assert!(scratch_folders(&dir).is_empty(), "failed screenshot left a scratch folder in {dir:?}");
std::fs::remove_dir_all(dir).unwrap();
}
}
+3 -1
View File
@@ -229,7 +229,9 @@ fn known_pending_concepts_and_released_files_round_trip_with_inert_metadata() {
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
let reopened = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
assert_eq!(reopened.metadata, document.metadata);
assert_eq!(reopened.name, document.name);
// #4907: `run --save` maps to Save As, and a borrowed design name
// follows the destination file's stem instead of keeping the old name.
assert_eq!(reopened.name, format!("resaved-{index}"));
assert_eq!(reopened.features, document.features);
assert_eq!(std::fs::read(&source).unwrap(), bytes);
}
@@ -123,3 +123,42 @@ fn a_design_naming_a_sketch_and_its_extrude_alike_replays_from_its_script() {
assert_eq!(names, ["Block", "Block"], "{first:#}");
assert_eq!(second["commands"], first["commands"], "the replayed design's script has the same steps");
}
/// #10230: script export must disclose the print-face state that replay leaves out.
#[test]
fn script_export_warns_about_omitted_print_faces_on_stderr() {
let dir = std::env::temp_dir().join(format!("solidcraft-print-face-script-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let saved = dir.join("block.sc");
let exported = dir.join("block.json");
let mut s = Session::default();
s.execute("solid.box", &json!({"length": 20, "width": 10, "height": 5, "body_name": "Block"})).unwrap();
s.execute("body.print_face", &json!({"body": "Block", "point": [10, 5, 0]})).unwrap();
s.execute("file.save", &json!({"path": saved})).unwrap();
let native = std::fs::read(&saved).unwrap();
for to_file in [false, true] {
let mut cmd = std::process::Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
cmd.arg("script").arg(&saved);
if to_file {
cmd.arg("--out").arg(&exported);
}
let out = cmd.output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success(), "{stderr}");
assert!(stderr.contains("note: the script leaves out print-face assignments (body.print_face)"), "{stderr}");
let script = if to_file {
assert!(out.stdout.is_empty(), "--out must keep stdout empty");
std::fs::read(&exported).unwrap()
} else {
out.stdout
};
let script: Value = serde_json::from_slice(&script).unwrap();
let mut replayed = Session::default();
replayed.run_script(&script).unwrap();
assert!(replayed.doc.print_faces.is_empty());
assert_eq!(replayed.doc.features, s.doc.features);
assert_eq!(std::fs::read(&saved).unwrap(), native, "export must not change the source design");
}
std::fs::remove_dir_all(dir).unwrap();
}
+4
View File
@@ -22,6 +22,10 @@
<link data-trunk rel="rust" data-bin="solidcraft-web" data-wasm-opt="z" data-weak-refs />
<link data-trunk rel="copy-file" href="../../assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf" data-target-path="fonts" />
<link data-trunk rel="copy-file" href="../../assets/fonts/biz-udp-gothic/OFL.txt" data-target-path="fonts" />
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-sc/CraftUI-SC.otf" data-target-path="fonts" />
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-sc/OFL.txt" data-target-path="fonts/craft-ui-sc" />
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-tc/CraftUI-TC.otf" data-target-path="fonts" />
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-tc/OFL.txt" data-target-path="fonts/craft-ui-tc" />
<link data-trunk rel="css" href="../../assets/styles/web/loading.css" />
<link data-trunk rel="copy-file" href="../../assets/styles/web/loading-logo.svg" />
<!-- Installable as an app (manifest.webmanifest); the icons are the Linux exports of the app mark. -->
+27 -8
View File
@@ -15,6 +15,9 @@ type Reply = Result<(String, Vec<u8>), String>;
pub struct Download {
reply: Rc<RefCell<Option<Reply>>>,
pending: Option<(String, Vec<u8>)>,
/// The downloaded design being opened (a few features a frame, #10198): the answer to come,
/// the name it takes and the file it was read from.
opening: Option<(std::sync::mpsc::Receiver<serde_json::Value>, String, String)>,
}
impl Download {
@@ -33,13 +36,14 @@ impl Download {
}
pub fn tick(&mut self, app: &mut SolveApp, ctx: &egui::Context) {
self.opened(app);
if let Some(reply) = self.reply.borrow_mut().take() {
match reply {
Ok(file) => self.pending = Some(file),
Err(error) => app.set_status(format!("Could not open design link: {error}"), true),
}
}
if self.pending.is_none() || app.file_picker_busy() {
if self.pending.is_none() || self.opening.is_some() || app.file_picker_busy() {
return;
}
if solidcraft_ui_egui::documents::any_dirty(app) {
@@ -71,18 +75,33 @@ impl Download {
}
}
let Some((name, bytes)) = self.pending.take() else { return };
// The extension selects the design loader even when the URL has no extension.
let path = &format!("/linked/design.{}", solidcraft_engine::io::DESIGN_EXT);
vfs::insert(path, bytes);
app.open_path(path);
if app.session.path.as_deref() == Some(path.as_str()) {
// The extension selects the design loader even when the URL has no extension. Named
// after the link, as the progress card shows the file's name.
let file: String = name.chars().map(|c| if matches!(c, '/' | '\\') { '_' } else { c }).collect();
let path = format!("/linked/{}.{}", if file.is_empty() { "design" } else { &file }, solidcraft_engine::io::DESIGN_EXT);
vfs::insert(&path, bytes);
// Opened as any design is, with its progress and Cancel; finished in `opened`.
self.opening = Some((app.open_path_answered(&path), name, path));
self.opened(app);
}
/// The downloaded design is in: it is an unsaved copy under the link's name.
fn opened(&mut self, app: &mut SolveApp) {
let Some((rx, _, _)) = &self.opening else { return };
let (ok, cancelled) = match rx.try_recv() {
Ok(reply) => (reply["ok"].as_bool() == Some(true), reply["error"].as_str().is_some_and(|e| e.contains("cancelled"))),
Err(std::sync::mpsc::TryRecvError::Empty) => return,
Err(std::sync::mpsc::TryRecvError::Disconnected) => (false, false),
};
let Some((_, name, path)) = self.opening.take() else { return };
if ok && app.session.path.as_deref() == Some(path.as_str()) {
app.session.path = None;
app.session.doc_mut().name = name;
app.session.mark_unsaved();
} else {
} else if !ok && !cancelled {
app.set_status("Could not open design link: the downloaded file is not a valid SolidCraft design", true);
}
let _ = vfs::remove(path);
let _ = vfs::remove(&path);
}
}
+79 -30
View File
@@ -273,8 +273,10 @@ mod web {
}
const JAPANESE_FONT_PATH: &str = "./fonts/BIZUDPGothic-Regular.ttf";
const CHINESE_SC_FONT_PATH: &str = "./fonts/CraftUI-SC.otf";
const CHINESE_TC_FONT_PATH: &str = "./fonts/CraftUI-TC.otf";
type FontLoadResult = Rc<std::cell::RefCell<Option<Result<Vec<u8>, String>>>>;
type FontLoadResult = Rc<std::cell::RefCell<Option<Result<(String, Vec<u8>), String>>>>;
struct Shell {
app: SolveApp,
@@ -292,11 +294,17 @@ mod web {
rename_focus: bool,
/// The start page's recent designs were filled from the stored ones.
recents_synced: bool,
/// On-demand CJK font: fetched only when a CJK language is selected.
/// On-demand script fonts: fetched only when a CJK language is selected.
font: solidcraft_ui_egui::fonts::CjkFontLoader,
font_result: FontLoadResult,
/// The fetched CJK font's bytes: kept so a language change can re-order the fallbacks.
/// The fetched Japanese font's bytes: kept so a language change can re-order the fallbacks.
cjk_bytes: Option<Vec<u8>>,
/// The fetched Chinese font's bytes (Simplified or Traditional for this session).
chinese_bytes: Option<Vec<u8>>,
/// Which Chinese face `chinese_bytes` is: `"zh"` or `"zh-TW"`.
chinese_kind: Option<&'static str>,
/// Kind requested by the in-flight fetch (`"ja"`, `"zh"`, or `"zh-TW"`).
font_fetch_kind: Option<&'static str>,
/// The language the fonts were last set up for (a CJK UI leads with the CJK face).
font_language: String,
space_mouse: SpaceMouse,
@@ -367,7 +375,8 @@ mod web {
// Designs opened from the computer's disk (the files window): open them.
let opened: Vec<_> = self.files.opened.borrow_mut().drain(..).collect();
for path in opened {
self.app.open_path(&path);
// A few features a frame, with progress and Cancel (#10198).
self.app.open_path_async(&path);
self.files.panel.set(false);
}
// Recent designs whose file is gone are dropped from the list.
@@ -408,7 +417,7 @@ mod web {
path
};
match intent {
Intent::Open => self.app.open_path(&path),
Intent::Open => self.app.open_path_async(&path),
Intent::Insert | Intent::Theme => self.app.drop_path(&path),
}
self.files.panel.set(false);
@@ -572,7 +581,7 @@ mod web {
let path = format!("{}{name}", files::OPFS);
match what {
"open" => {
self.app.open_path(&path);
self.app.open_path_async(&path);
self.files.panel.set(false);
}
"insert" => {
@@ -674,65 +683,102 @@ mod web {
files::autosave(&self.files, None);
}
/// Fetch and install the CJK font when a CJK language is selected. If that fails, this
/// 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)) {
Some("zh") => self.chinese_bytes.is_some() && self.chinese_kind == Some("zh"),
Some("zh-TW") => self.chinese_bytes.is_some() && self.chinese_kind == Some("zh-TW"),
Some("ja") => self.cjk_bytes.is_some(),
_ => true,
}
}
/// Fetch and install a script font when a CJK language is selected. If that fails, this
/// session falls back to English (no tofu); the saved language preference is kept.
fn tick_font(&mut self, ctx: &egui::Context) {
let done = self.font_result.borrow_mut().take();
if let Some(res) = done {
let kind = self.font_fetch_kind.take();
let installed = match res {
Ok(bytes) => {
self.cjk_bytes = Some(bytes);
Ok((path, bytes)) => {
match kind {
Some("zh") => {
self.chinese_bytes = Some(bytes);
self.chinese_kind = Some("zh");
}
Some("zh-TW") => {
self.chinese_bytes = Some(bytes);
self.chinese_kind = Some("zh-TW");
}
_ => {
self.cjk_bytes = Some(bytes);
}
}
log::info!("loaded web script font from {path}");
true
}
Err(err) => {
log::warn!("failed to fetch CJK font from {JAPANESE_FONT_PATH}: {err}");
let path = match kind {
Some("zh") => CHINESE_SC_FONT_PATH,
Some("zh-TW") => CHINESE_TC_FONT_PATH,
_ => JAPANESE_FONT_PATH,
};
log::warn!("failed to fetch script font from {path}: {err}");
false
}
};
if installed {
log::info!("loaded web CJK font from {JAPANESE_FONT_PATH}");
} else if solidcraft_ui_egui::fonts::needs_cjk_font(&self.app.preferences.language) {
log::warn!("CJK font unavailable; showing English for this session");
let effective = solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language);
if !installed && solidcraft_ui_egui::fonts::needs_cjk_font(effective) {
log::warn!("script font unavailable; showing English for this session");
}
self.font.finished(installed, &mut self.app.preferences.language);
self.apply_fonts(ctx);
ctx.request_repaint();
}
if self.font.tick(&mut self.app.preferences.language) {
self.fetch_font(ctx);
let ready = self.script_font_ready();
let effective = solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language);
if self.font.tick(&mut self.app.preferences.language, ready) {
self.fetch_font(ctx, solidcraft_ui_egui::fonts::web_script_font_kind(effective).unwrap_or("ja"));
}
// The CJK face leads only a CJK UI: its Cyrillic glyphs are full-width, so switching
// to Ukrainian (or English) must put the Latin face back in front.
if self.app.preferences.language != self.font_language {
if solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language) != self.font_language {
self.apply_fonts(ctx);
}
}
/// Set up the UI fonts for the live language, keeping the fetched CJK font as a fallback
/// (it leads only for a CJK UI, see [`solidcraft_ui_egui::fonts::install_cjk_font_for`]).
/// Set up the UI fonts for the live language, keeping fetched script fonts as fallbacks
/// (Chinese leads a Chinese UI; see [`solidcraft_ui_egui::fonts::install_web_script_fonts`]).
fn apply_fonts(&mut self, ctx: &egui::Context) {
match self.cjk_bytes.clone() {
Some(bytes) => {
solidcraft_ui_egui::fonts::install_cjk_font_for(ctx, bytes, &self.app.preferences.language);
}
None => solidcraft_ui_egui::fonts::setup_fonts(ctx),
let effective = solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language);
if self.cjk_bytes.is_none() && self.chinese_bytes.is_none() {
solidcraft_ui_egui::fonts::setup_fonts(ctx);
} else {
solidcraft_ui_egui::fonts::install_web_script_fonts(ctx, self.cjk_bytes.clone(), self.chinese_bytes.clone(), effective);
}
self.font_language.clone_from(&self.app.preferences.language);
self.font_language = effective.to_owned();
}
/// CJK text on screen with no CJK font yet (an English UI showing a Japanese name):
/// fetch the font, so the text does not stay tofu (#720).
/// CJK text on screen with no Japanese font yet (an English UI showing a Japanese name):
/// fetch the Japanese font, so the text does not stay tofu (#720).
fn watch_text_for_font(&mut self, ctx: &egui::Context) {
if self.font.watching_text() && solidcraft_ui_egui::fonts::painted_cjk(ctx) && self.font.text_needs_font() {
self.fetch_font(ctx);
self.fetch_font(ctx, "ja");
}
}
fn fetch_font(&self, ctx: &egui::Context) {
fn fetch_font(&mut self, ctx: &egui::Context, kind: &'static str) {
let path = match kind {
"zh" => CHINESE_SC_FONT_PATH,
"zh-TW" => CHINESE_TC_FONT_PATH,
_ => JAPANESE_FONT_PATH,
};
self.font_fetch_kind = Some(kind);
let slot = self.font_result.clone();
let ctx = ctx.clone();
let path = path.to_owned();
wasm_bindgen_futures::spawn_local(async move {
let res = fetch_font_bytes(JAPANESE_FONT_PATH).await;
let res = fetch_font_bytes(&path).await.map(|bytes| (path, bytes));
*slot.borrow_mut() = Some(res);
ctx.request_repaint();
});
@@ -962,6 +1008,9 @@ mod web {
font: Default::default(),
font_result: Rc::new(std::cell::RefCell::new(None)),
cjk_bytes: None,
chinese_bytes: None,
chinese_kind: None,
font_fetch_kind: None,
font_language,
space_mouse: SpaceMouse::default(),
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
+5
View File
@@ -104,5 +104,10 @@ winit = { version = "0.30", default-features = false, features = ["x11"] }
raw-window-handle = "0.6"
x11rb = { version = "0.13", default-features = false }
[target.'cfg(target_os = "linux")'.dependencies]
# The desktop's dark or light preference through the XDG Desktop Portal (src/desktop_theme.rs,
# #7944); already in the tree through eframe's accessibility support (accesskit_unix).
zbus = { version = "5", default-features = false, features = ["async-io", "blocking-api"] }
[build-dependencies]
embed-resource = "3.0.11"
+219 -16
View File
@@ -22,6 +22,10 @@ const MAX_BAD_LINES: usize = 8;
/// How long a subscribed connection waits after a `design.changed` line before the next: what
/// changes meanwhile (a parameter dragged) goes out as one line, the latest (#6985).
const EVENT_GAP: Duration = Duration::from_millis(100);
/// How long a new connection has to show the token. A client connects when it has a request to
/// send, so this is plenty; one that doesn't send it loses the connection, and its seat among
/// [`MAX_CONNECTIONS`] goes to someone else.
const TOKEN_WAIT: Duration = Duration::from_secs(5);
static OPEN: AtomicUsize = AtomicUsize::new(0);
/// The token files this launch wrote (its control port, the agent chat's endpoint) and the
@@ -225,23 +229,53 @@ fn unauthorized() -> Value {
/// Refuse the connection: the reply, then the end of what we send, then what the client had
/// already sent read and dropped (a little, briefly). Closing a socket with unread input resets
/// the connection, and a reset can reach the client before it has read the refusal.
fn refuse(out: &mut TcpStream, reader: &mut BufReader<TcpStream>) {
fn refuse(out: &mut TcpStream, reader: &mut BufReader<ClientInput>) {
use std::io::Read;
let _ = send(out, &unauthorized());
let _ = out.shutdown(std::net::Shutdown::Write);
let _ = reader.get_ref().set_read_timeout(Some(Duration::from_millis(500)));
reader.get_mut().deadline = Some(Instant::now() + Duration::from_millis(500));
let _ = std::io::copy(&mut reader.by_ref().take(64 * 1024), &mut std::io::sink());
}
/// What a client sends. Until it shows the token, all of its reads share one deadline, so neither
/// blank lines nor a line sent a byte at a time buy it more time. After that it may stay idle as
/// long as it likes.
struct ClientInput {
socket: TcpStream,
deadline: Option<Instant>,
}
impl ClientInput {
/// No deadline any more: the client showed the token.
fn admit(&mut self) -> std::io::Result<()> {
self.deadline = None;
self.socket.set_read_timeout(None)
}
}
impl std::io::Read for ClientInput {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if let Some(deadline) = self.deadline {
let left = deadline.saturating_duration_since(Instant::now());
if left.is_zero() {
return Err(std::io::ErrorKind::TimedOut.into());
}
self.socket.set_read_timeout(Some(left))?;
}
self.socket.read(buf)
}
}
/// How long a request waits for the app's reply: most requests, and a batch (or every request,
/// on a patient connection).
/// on a patient connection); and how long a new connection has to show the token.
#[derive(Clone, Copy)]
struct Waits {
short: Duration,
long: Duration,
token: Duration,
}
const WAITS: Waits = Waits { short: Duration::from_secs(60), long: Duration::from_secs(3600) };
const WAITS: Waits = Waits { short: Duration::from_secs(60), long: Duration::from_secs(3600), token: TOKEN_WAIT };
/// A request that timed out but is still running in the app (#1618): its id and method, and where
/// its reply will land.
@@ -413,11 +447,37 @@ impl Lines {
}
}
/// A subscribed connection's seat on the watch: it leaves exactly once, whichever thread sees
/// the connection's end first — the connection's own thread at the end of the stream, or the
/// events thread at the first write a closed client refuses. The seat used to wait for the
/// events thread alone, and that thread only leaves its wait when the design moves, so the
/// count could outlive the connection by a second or more (#8315).
struct Seat(Mutex<Option<Subscriber>>);
impl Seat {
fn new(counted: Subscriber) -> Self {
Seat(Mutex::new(Some(counted)))
}
/// Leave the watch now: the seat goes once, whoever gets here first.
fn release(&self) {
let counted = self.0.lock().unwrap_or_else(PoisonError::into_inner).take();
drop(counted);
}
}
impl Drop for Seat {
fn drop(&mut self) {
self.release();
}
}
/// A connection's `control.subscribe` (#6985): its `design.changed` lines, sent from a thread of
/// their own until the connection ends.
struct Subscription {
lines: Arc<Lines>,
stop: Arc<AtomicBool>,
seat: Arc<Seat>,
}
impl Subscription {
@@ -433,15 +493,17 @@ impl Subscription {
) -> Option<(Self, std::io::Result<()>)> {
let lines = Arc::new(Lines { socket: out.try_clone().ok()?, turn: Mutex::new(()) });
let stop = Arc::new(AtomicBool::new(false));
let seat = Arc::new(Seat::new(counted));
let first = lines.turn.lock().unwrap_or_else(PoisonError::into_inner);
let (l, s) = (lines.clone(), stop.clone());
let (l, s, watch_seat) = (lines.clone(), stop.clone(), Arc::clone(&seat));
// The connection's seat on the watch rides the events thread: it leaves when the thread
// does, so the next failed write to a closed client retires it even while the connection's
// own thread is slow to see the end of the stream (#7226).
std::thread::Builder::new().stack_size(64 * 1024).spawn(move || send_events(design, &l, &s, seen, counted)).ok()?;
// own thread is slow to see the end of the stream (#7226). The connection's own thread
// leaves it too, so a closed connection stops counting at once (#8315).
std::thread::Builder::new().stack_size(64 * 1024).spawn(move || send_events(design, &l, &s, seen, watch_seat)).ok()?;
let sent = send(&mut &lines.socket, reply);
drop(first);
Some((Subscription { lines, stop }, sent))
Some((Subscription { lines, stop, seat }, sent))
}
}
@@ -454,6 +516,9 @@ fn design_of(reply: &Value) -> Option<(String, u64)> {
impl Drop for Subscription {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
// The seat on the watch goes now: the connection is over, and nothing counts it any more
// (#8315).
self.seat.release();
// The socket closes now, not when the thread next looks; a line blocked on a client that
// stopped reading ends too.
let _ = self.lines.socket.shutdown(std::net::Shutdown::Both);
@@ -461,10 +526,10 @@ impl Drop for Subscription {
}
/// The `design.changed` lines of a subscribed connection, each time the design differs from the
/// last one it was told of (`seen`), until `stop`. Owns the connection's seat on the watch
/// (`counted`), so the seat leaves exactly when this thread does: on `stop`, or on the first
/// write a closed client refuses (#7226).
fn send_events(design: &DesignWatch, lines: &Lines, stop: &AtomicBool, mut seen: (String, u64), _counted: Subscriber) {
/// last one it was told of (`seen`), until `stop`. Holds the connection's seat on the watch
/// (`seat`), which it leaves when the thread ends: on `stop`, or on the first write a closed
/// client refuses (#7226).
fn send_events(design: &DesignWatch, lines: &Lines, stop: &AtomicBool, mut seen: (String, u64), seat: Arc<Seat>) {
while !stop.load(Ordering::Relaxed) {
let Some(now) = design.wait(&seen, stop, Duration::from_secs(1)).filter(|now| *now != seen) else { continue };
if lines.send(&json!({"event": "design.changed", "params": {"document_id": now.0, "revision": now.1}})).is_err() {
@@ -473,6 +538,9 @@ fn send_events(design: &DesignWatch, lines: &Lines, stop: &AtomicBool, mut seen:
seen = now;
std::thread::sleep(EVENT_GAP);
}
// The client is gone: it is not counted on the watch any more, whatever the connection's own
// thread still has to notice (#8315).
seat.release();
}
/// What this app's control channel does beyond the methods every version has, for a client to
@@ -509,7 +577,7 @@ fn serve_with(
let connection = running.connections.fetch_add(1, Ordering::Relaxed) + 1;
let Ok(read) = stream.try_clone() else { return };
let mut out = stream;
let mut reader = BufReader::new(read);
let mut reader = BufReader::new(ClientInput { socket: read, deadline: Some(Instant::now() + waits.token) });
let mut bad = 0usize;
let mut authenticated = false;
let mut pending: Option<Pending> = None;
@@ -520,6 +588,10 @@ fn serve_with(
let line = match read_line(&mut reader, MAX_LINE) {
Ok(Some(l)) => String::from_utf8_lossy(&l).into_owned(),
Ok(None) => break,
// Closed without a word: a client that connected early and only now sends its first
// request would read a line here as the reply to it. It finds the connection closed
// and opens another.
Err(e) if !authenticated && matches!(e.kind(), std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock) => break,
Err(e) => {
let _ = answer(&mut out, events.as_ref(), &json!({"ok": false, "error": e.to_string()}));
break;
@@ -531,7 +603,12 @@ fn serve_with(
// The first request must carry the token; later ones on the same connection may.
if !authenticated {
match token_of(&line) {
Some(t) if control_auth::tokens_match(&t, token) => authenticated = true,
Some(t) if control_auth::tokens_match(&t, token) => {
if reader.get_mut().admit().is_err() {
break;
}
authenticated = true;
}
_ => {
refuse(&mut out, &mut reader);
break;
@@ -641,6 +718,80 @@ fn serve_with(
mod tests {
use super::*;
/// A connection that doesn't show the token in time is closed without a word, however it fills
/// the time. One that does keeps working when it idles for longer than that.
#[test]
fn the_token_has_a_time_limit_and_an_authenticated_client_may_idle() {
use std::io::Read;
let token = "0123456789abcdef0123456789abcdef";
let limit = Duration::from_millis(500);
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = channel::<ControlRequest>();
std::thread::spawn(move || {
for req in rx {
let _ = req.reply.send(json!({"ok": true, "result": req.method}));
}
});
static RUNNING: Running = Running::new();
let waits = Waits { short: Duration::from_secs(10), long: Duration::from_secs(10), token: limit };
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let tx = tx.clone();
std::thread::spawn(move || serve_with(stream, token, tx, egui::Context::default(), false, waits, &RUNNING, &DESIGN_WATCH));
}
});
let open = || {
let s = TcpStream::connect(addr).unwrap();
s.set_read_timeout(Some(Duration::from_secs(20))).unwrap();
s
};
// Sends `drip` every 50 ms until the server closes; what it read, and when it closed.
let closed_after = |drip: &[u8]| {
let mut s = open();
let start = Instant::now();
s.set_read_timeout(Some(Duration::from_millis(50))).unwrap();
let mut got = Vec::new();
loop {
let mut buf = [0u8; 256];
match s.read(&mut buf) {
Ok(0) => break,
Ok(n) => got.extend_from_slice(buf.get(..n).unwrap()),
Err(e) if matches!(e.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut) => {
assert!(start.elapsed() < Duration::from_secs(20), "still open");
let _ = s.write_all(drip);
}
Err(_) => break,
}
}
(got, start.elapsed())
};
for drip in [&b""[..], b"\n", b"\r\n \n", b" ", b"{"] {
let (got, took) = closed_after(drip);
assert!(got.is_empty(), "{drip:?} was answered: {}", String::from_utf8_lossy(&got));
assert!(took >= limit.saturating_sub(Duration::from_millis(50)), "{drip:?} closed after {took:?}");
}
let mut s = open();
let mut lines = BufReader::new(s.try_clone().unwrap());
let mut ask = |line: Value| -> Value {
writeln!(s, "{line}").unwrap();
let mut l = String::new();
lines.read_line(&mut l).unwrap();
serde_json::from_str(&l).unwrap()
};
assert_eq!(ask(json!({"id": 1, "method": "ui.view", "token": token}))["ok"], true);
std::thread::sleep(limit * 3);
assert_eq!(ask(json!({"id": 2, "method": "ui.view"}))["ok"], true, "idle for longer than the limit");
// A wrong token is still answered, at once.
let mut wrong = open();
writeln!(wrong, "{}", json!({"id": 1, "method": "ui.view", "token": "nope"})).unwrap();
let mut l = String::new();
BufReader::new(wrong).read_line(&mut l).unwrap();
assert!(l.contains("unauthorized"), "{l}");
}
/// #1618: a request that outlasts its wait says it is still running; the connection keeps its
/// reply for `control.pending`, and refuses other requests as `busy` until it lands, so none
/// is queued to run unseen behind it.
@@ -664,7 +815,7 @@ mod tests {
let _ = req.reply.send(json!({"ok": true, "result": req.method}));
}
});
let waits = Waits { short: Duration::from_millis(100), long: Duration::from_millis(100) };
let waits = Waits { short: Duration::from_millis(100), long: Duration::from_millis(100), token: TOKEN_WAIT };
static RUNNING: Running = Running::new();
std::thread::spawn(move || {
if let Some(stream) = listener.incoming().flatten().next() {
@@ -730,7 +881,7 @@ mod tests {
let _ = req.reply.send(json!({"ok": true, "result": req.method}));
}
});
let waits = Waits { short: Duration::from_millis(100), long: Duration::from_secs(60) };
let waits = Waits { short: Duration::from_millis(100), long: Duration::from_secs(60), token: TOKEN_WAIT };
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let tx = tx.clone();
@@ -905,6 +1056,58 @@ mod tests {
assert!(!WATCH.subscribed());
}
/// #8315: a closed connection leaves the watch at once. The seat of a subscribed connection
/// goes when the connection's own thread sees the end of the stream, not only when the
/// events thread notices: that thread waits for the design to move, so a connection that
/// closed while the design stood still kept the watch counting it for a second or more.
#[test]
fn a_closed_connection_leaves_the_watch_at_once() {
static RUNNING: Running = Running::new();
static WATCH: DesignWatch = DesignWatch::new();
let token = "0123456789abcdef0123456789abcdef";
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = channel::<ControlRequest>();
// The window names the design once and then leaves it alone: nothing moves it, so the
// events thread has nothing to write and only the end of the connection can retire it.
std::thread::spawn(move || {
for req in rx {
if req.method == "control.subscribe" {
WATCH.publish("d-1", 3);
let _ = req.reply.send(json!({"ok": true, "result": {"document_id": "d-1", "revision": 3}}));
} else {
let _ = req.reply.send(json!({"ok": true, "result": req.method}));
}
}
});
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let tx = tx.clone();
std::thread::spawn(move || serve_with(stream, token, tx, egui::Context::default(), false, WAITS, &RUNNING, &WATCH));
}
});
let s = TcpStream::connect(addr).unwrap();
s.set_read_timeout(Some(Duration::from_secs(10))).unwrap();
let mut out = s.try_clone().unwrap();
let mut lines = BufReader::new(s);
writeln!(out, "{}", json!({"id": 1, "method": "control.subscribe", "token": token})).unwrap();
let mut l = String::new();
lines.read_line(&mut l).unwrap();
let reply: Value = serde_json::from_str(&l).unwrap();
assert!(reply["ok"] == true, "{reply}");
assert!(WATCH.subscribed(), "the connection is subscribed");
// Let the events thread settle into its wait: the design stands still, so it has nothing
// to write, and only the end of the connection can retire it.
std::thread::sleep(Duration::from_millis(200));
// The client goes away without asking anything else.
drop((out, lines));
let deadline = Instant::now() + Duration::from_millis(400);
while WATCH.subscribed() && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(10));
}
assert!(!WATCH.subscribed(), "a closed connection is not counted on the watch");
}
/// A timed-out request that only reads is not said to "still apply", nor is one that only
/// writes a file (an export, #1989); a change is, by name.
#[test]
+195
View File
@@ -0,0 +1,195 @@
//! Linux: the desktop's dark or light preference, for Theme: System (#7944).
//!
//! The window toolkit does not pass it on everywhere: on GNOME (Wayland, or X11 through XWayland)
//! the window never learns it, nor when it changes, so the app kept its colours. This reads the
//! freedesktop setting through the XDG Desktop Portal's Settings interface on the session bus
//! (`org.freedesktop.appearance color-scheme`, then GNOME's `color-scheme` and `gtk-theme`), the
//! same setting other applications follow, and inside a Flatpak too. It runs on a thread of its
//! own: the first reading, then the portal's `SettingChanged` signal; where the signal can't be
//! had, a reading every few seconds. Without a portal it asks `gsettings` instead, on the same
//! thread. The window reads the last answer with an atomic load
//! (`solidcraft_ui_egui::Services::desktop_dark`), and the decision from the raw values is
//! `solidcraft_ui_egui::prefs::desktop_prefers_dark`.
use std::io::Read;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;
use zbus::zvariant::{OwnedValue, Value};
const UNKNOWN: u8 = 0;
const DARK: u8 = 1;
const LIGHT: u8 = 2;
/// How often to read again where the portal's change signal is not available.
const POLL: Duration = Duration::from_secs(5);
/// How long one `gsettings` reading may take before it is given up.
const GSETTINGS_TIMEOUT: Duration = Duration::from_secs(2);
const PORTAL: &str = "org.freedesktop.portal.Desktop";
const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop";
const SETTINGS: &str = "org.freedesktop.portal.Settings";
const APPEARANCE: &str = "org.freedesktop.appearance";
const GNOME: &str = "org.gnome.desktop.interface";
/// Start following the desktop's preference; the closure answers the latest reading (`None`
/// until there is one, or when the desktop says nothing).
pub fn start(ctx: &egui::Context) -> Box<dyn Fn() -> Option<bool>> {
let state = Arc::new(AtomicU8::new(UNKNOWN));
let (thread_state, ctx) = (state.clone(), ctx.clone());
if let Err(e) = std::thread::Builder::new().name("desktop-theme".into()).spawn(move || follow(&thread_state, &ctx)) {
log::warn!("desktop theme: no thread to follow it: {e}");
}
Box::new(move || match state.load(Ordering::Relaxed) {
DARK => Some(true),
LIGHT => Some(false),
_ => None,
})
}
/// Keep `state` on the desktop's preference, asking for a frame whenever it changes.
fn follow(state: &AtomicU8, ctx: &egui::Context) {
let publish = |dark: Option<bool>| {
let code = match dark {
Some(true) => DARK,
Some(false) => LIGHT,
None => UNKNOWN,
};
if state.swap(code, Ordering::Relaxed) != code {
ctx.request_repaint();
}
};
let portal = match portal() {
Ok(p) => Some(p),
Err(e) => {
log::info!("desktop theme: no settings portal ({e}); asking gsettings");
None
}
};
// The portal first; gsettings where there is none or it says nothing (its backend missing).
let read = |portal: Option<&zbus::blocking::Proxy<'static>>| portal.and_then(read_portal).or_else(read_gsettings);
let from_portal = portal.as_ref().and_then(read_portal);
let first = from_portal.or_else(read_gsettings);
publish(first);
// Wait for the portal's signal only when the portal answers: else the reading is gsettings',
// which only a new reading notices.
if let Some(p) = portal.as_ref().filter(|_| from_portal.is_some()) {
match p.receive_signal("SettingChanged") {
Ok(changes) => {
for message in changes {
if relevant(&message) {
publish(read(Some(p)));
}
}
log::info!("desktop theme: the portal's change signal ended; reading every few seconds");
}
Err(e) => log::info!("desktop theme: no change signal ({e}); reading every few seconds"),
}
} else if portal.is_none() && first.is_none() && !gsettings_available() {
// Neither the portal nor gsettings: nothing to follow; the window toolkit's theme stays.
return;
}
loop {
std::thread::sleep(POLL);
publish(read(portal.as_ref()));
}
}
/// The portal's Settings interface on the session bus.
fn portal() -> zbus::Result<zbus::blocking::Proxy<'static>> {
let connection = zbus::blocking::Connection::session()?;
zbus::blocking::Proxy::new(&connection, PORTAL, PORTAL_PATH, SETTINGS)
}
/// One reading through the portal.
fn read_portal(portal: &zbus::blocking::Proxy<'static>) -> Option<bool> {
let appearance = read_setting(portal, APPEARANCE, "color-scheme").and_then(|v| as_u32(&v));
// The GNOME settings only matter when the freedesktop one has no preference.
if matches!(appearance, Some(1 | 2)) {
return solidcraft_ui_egui::prefs::desktop_prefers_dark(appearance, None, None);
}
let gnome = read_setting(portal, GNOME, "color-scheme").and_then(|v| as_string(&v));
let gtk_theme = read_setting(portal, GNOME, "gtk-theme").and_then(|v| as_string(&v));
solidcraft_ui_egui::prefs::desktop_prefers_dark(appearance, gnome.as_deref(), gtk_theme.as_deref())
}
/// A setting through `ReadOne` (portal version 2), else the older `Read`.
fn read_setting(portal: &zbus::blocking::Proxy<'static>, namespace: &str, key: &str) -> Option<OwnedValue> {
let one: zbus::Result<OwnedValue> = portal.call("ReadOne", &(namespace, key));
one.or_else(|_| -> zbus::Result<OwnedValue> { portal.call("Read", &(namespace, key)) }).ok()
}
/// Whether a `SettingChanged` signal is about a setting the preference is read from.
fn relevant(message: &zbus::Message) -> bool {
let body = message.body();
let Ok((namespace, key, _)) = body.deserialize::<(String, String, OwnedValue)>() else { return false };
matches!((namespace.as_str(), key.as_str()), (APPEARANCE, "color-scheme") | (GNOME, "color-scheme" | "gtk-theme"))
}
/// The value inside the variants `Read` wraps it in (a portal answers one or two deep).
fn unwrap_variants<'v, 'a>(value: &'v Value<'a>) -> &'v Value<'a> {
let mut value = value;
for _ in 0..4 {
match value {
Value::Value(inner) => value = &**inner,
_ => break,
}
}
value
}
fn as_u32(value: &Value<'_>) -> Option<u32> {
match unwrap_variants(value) {
Value::U32(n) => Some(*n),
Value::I32(n) => u32::try_from(*n).ok(),
Value::U8(n) => Some(u32::from(*n)),
_ => None,
}
}
fn as_string(value: &Value<'_>) -> Option<String> {
match unwrap_variants(value) {
Value::Str(s) => Some(s.as_str().to_string()),
_ => None,
}
}
/// Without a portal: GNOME's settings through `gsettings` (`None` when it is not there).
fn read_gsettings() -> Option<bool> {
let gnome = gsettings("color-scheme");
let gtk_theme = gsettings("gtk-theme");
solidcraft_ui_egui::prefs::desktop_prefers_dark(None, gnome.as_deref(), gtk_theme.as_deref())
}
fn gsettings_available() -> bool {
gsettings("gtk-theme").is_some()
}
/// `gsettings get org.gnome.desktop.interface <key>`, its quoted answer, or `None` when the
/// program is missing, fails or does not answer in time.
fn gsettings(key: &str) -> Option<String> {
let mut child = std::process::Command::new("gsettings")
.args(["get", GNOME, key])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.ok()?;
let started = std::time::Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) if status.success() => break,
Ok(None) if started.elapsed() < GSETTINGS_TIMEOUT => std::thread::sleep(Duration::from_millis(20)),
_ => {
let _ = child.kill();
let _ = child.wait();
return None;
}
}
}
let mut out = String::new();
child.stdout.take()?.take(4096).read_to_string(&mut out).ok()?;
let value = out.trim().trim_matches('\'').to_string();
(!value.is_empty()).then_some(value)
}
+93 -10
View File
@@ -5,12 +5,25 @@
//! for recovery as a forced quit does, and starts again ([`restart`]), and the new start says what
//! happened ([`note`]). A device lost again soon after such a restart starts the next one on the
//! software renderer, which doesn't depend on the graphics driver.
//!
//! The old process ends without tearing down its GPU objects ([`end`]): on a lost NVIDIA device
//! destroying the window's swap chain busy-waits in the driver and never returns (#9044).
use crate::startup;
/// Set on the start after a lost device: when the device was lost (seconds since 1970).
pub const LOST_ENV: &str = "SOLIDCRAFT_DEVICE_LOST";
/// Set on the start after a lost device: why it was lost, as wgpu said it. The old process's
/// session journal, which recorded it, is gone by then.
pub const REASON_ENV: &str = "SOLIDCRAFT_DEVICE_LOST_REASON";
/// The longest reason passed on ([`REASON_ENV`]) and shown, in characters.
const REASON_CHARS: usize = 300;
/// How long the process may take to end ([`end`]) before it is aborted.
const END_GRACE: std::time::Duration = std::time::Duration::from_secs(3);
/// A loss this recent (seconds) counts for the start after it: its note, and a second loss
/// within it starts on the software renderer. Inherited by later starts, an older stamp is
/// ignored.
@@ -32,27 +45,54 @@ pub fn restart_on_software(after_recent_loss: bool, cpu_renderer: bool) -> bool
after_recent_loss || cpu_renderer
}
/// `reason` on one line, at most [`REASON_CHARS`] long; None when there is nothing to say.
pub fn reason(reason: Option<&str>) -> Option<String> {
let line = reason?.split_whitespace().collect::<Vec<_>>().join(" ");
(!line.is_empty()).then(|| line.chars().take(REASON_CHARS).collect())
}
/// What the start after a lost device tells the user, `software` when it runs on the software
/// renderer.
pub fn note(software: bool) -> String {
/// renderer, with what the driver reported ([`REASON_ENV`]).
pub fn note(software: bool, reason: Option<&str>) -> String {
let mut note = String::from(
"The graphics device was reset (the graphics driver stopped responding), so SolidCraft closed and started again. Unsaved changes were kept for recovery.",
);
if software {
note.push_str(" This start uses software rendering, which doesn't depend on the graphics driver.");
}
if let Some(reason) = self::reason(reason) {
note.push_str(&format!(" The graphics driver reported: {reason}"));
}
note
}
/// Start SolidCraft again with the same arguments, the loss stamped at `lost_at`, on the software
/// renderer when `software`.
pub fn restart(software: bool, lost_at: u64) {
let Ok(exe) = std::env::current_exe() else { return };
/// The command that starts `exe` again with `args`, the loss stamped at `lost_at` and its
/// `reason`, on the software renderer when `software`.
fn command(
exe: std::path::PathBuf,
args: impl IntoIterator<Item = std::ffi::OsString>,
software: bool,
lost_at: u64,
reason: &str,
) -> std::process::Command {
let mut command = std::process::Command::new(exe);
command.args(std::env::args_os().skip(1)).env(LOST_ENV, lost_at.to_string());
command.args(args).env(LOST_ENV, lost_at.to_string());
match self::reason(Some(reason)) {
Some(reason) => command.env(REASON_ENV, reason),
// Not one inherited from an earlier loss.
None => command.env_remove(REASON_ENV),
};
if software {
command.env(startup::RENDERER_ENV, "cpu");
}
command
}
/// Start SolidCraft again with the same arguments, the loss stamped at `lost_at` and its `reason`,
/// on the software renderer when `software`.
pub fn restart(software: bool, lost_at: u64, reason: &str) {
let Ok(exe) = std::env::current_exe() else { return };
let mut command = command(exe, std::env::args_os().skip(1), software, lost_at, reason);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
@@ -64,6 +104,23 @@ pub fn restart(software: bool, lost_at: u64) {
}
}
/// End the process now, without tearing down what is left on the lost device: the window's swap
/// chain, the viewport's buffers and pipelines. On NVIDIA's Vulkan driver destroying the swap
/// chain of a lost device busy-waits forever, which left the old window frozen at 100% of a core
/// beside the new one (#9044). Should the exit itself get stuck in a library's exit handler, the
/// process is aborted after [`END_GRACE`].
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");
std::process::abort();
});
if let Err(e) = watchdog {
log::warn!("graphics: no exit watchdog: {e}");
}
std::process::exit(0)
}
#[cfg(test)]
mod tests {
/// Only a recent, well-formed stamp counts: a later start that inherited it says nothing.
@@ -84,8 +141,34 @@ mod tests {
assert!(!super::restart_on_software(false, false));
assert!(super::restart_on_software(true, false));
assert!(super::restart_on_software(false, true));
assert!(super::note(true).contains("software rendering"));
assert!(!super::note(false).contains("software rendering"));
assert!(super::note(false).contains("kept for recovery"));
assert!(super::note(true, None).contains("software rendering"));
assert!(!super::note(false, None).contains("software rendering"));
assert!(super::note(false, None).contains("kept for recovery"));
}
/// The start after a loss is told why (#9044): the old process's journal held the reason, and
/// it is deleted as that process closes.
#[test]
fn the_next_start_is_told_why_the_device_was_lost() {
let exe = std::path::PathBuf::from("solidcraft");
let args = ["--control".into(), "7979".into()];
let command = super::command(exe.clone(), args, true, 1_000, "Lost: Device lost\n (vkQueueSubmit)");
let env = |command: &std::process::Command, name: &str| {
command.get_envs().find(|(k, _)| *k == name).map(|(_, v)| v.map(|v| v.to_string_lossy().into_owned()))
};
assert_eq!(env(&command, super::LOST_ENV), Some(Some("1000".into())));
assert_eq!(env(&command, super::REASON_ENV), Some(Some("Lost: Device lost (vkQueueSubmit)".into())), "on one line");
assert_eq!(env(&command, crate::startup::RENDERER_ENV), Some(Some("cpu".into())));
assert_eq!(command.get_args().collect::<Vec<_>>(), ["--control", "7979"]);
// No reason: none inherited from an earlier loss either.
let command = super::command(exe, Vec::new(), false, 1_000, " ");
assert_eq!(env(&command, super::REASON_ENV), Some(None));
assert_eq!(env(&command, crate::startup::RENDERER_ENV), None);
let note = super::note(false, Some("Lost: Device lost"));
assert!(note.ends_with("The graphics driver reported: Lost: Device lost"), "{note}");
assert!(!super::note(false, Some(" \n ")).contains("reported"));
let long = "x".repeat(1_000);
assert_eq!(super::reason(Some(&long)).map(|r| r.chars().count()), Some(300));
}
}
+311 -10
View File
@@ -22,6 +22,8 @@
mod app_icon;
mod come_forward;
mod control_server;
#[cfg(target_os = "linux")]
mod desktop_theme;
mod device_lost;
#[cfg(target_os = "windows")]
mod native_crash;
@@ -97,6 +99,86 @@ struct App {
nav_bridge: nav_bridge::NavBridge,
/// Where the next file dialog opens: it follows the open design (#7756).
dialog_folder: DialogFolder,
/// Settings go to `app.ron` in the frame they change (#8275).
prefs_writer: PrefsWriter,
}
/// Settings written to the settings file soon after they change (#8275). eframe saves them
/// only every 30 s and when the window closes normally, so a change was lost whenever the app
/// did not get that far: ended from the Task Manager, stopped by a sign-out or shutdown, or
/// crashed or hung in the graphics driver on the way out.
///
/// Some settings change continuously (a panel's width while its edge is dragged), so the file
/// is written at most once per [`PrefsWriter::INTERVAL`], and a change made in between is written
/// when that interval ends.
#[derive(Default)]
struct PrefsWriter {
/// The settings written (or read at start) last.
written: Option<String>,
/// The previous frame had input that can change settings: what it changed shows in this
/// frame's settings.
input_before: bool,
/// When the settings were last written by this writer.
last_write: Option<std::time::Instant>,
/// A change waits until then to be written.
pending_until: Option<std::time::Instant>,
}
impl PrefsWriter {
/// The least time between two writes of the settings file.
const INTERVAL: std::time::Duration = std::time::Duration::from_secs(1);
/// The settings as they were read at start: they are on disk already.
fn read(prefs: String) -> Self {
Self { written: Some(prefs), ..Self::default() }
}
/// Whether this frame's settings are worth comparing: input that can change them came in this
/// frame or the one before, or a waiting change's time has come. Pointer motion alone isn't
/// such input; eframe's own save still catches anything else.
fn due(&mut self, input: bool, now: std::time::Instant) -> bool {
let due = input || self.input_before || self.pending_until.is_some_and(|at| now >= at);
self.input_before = input;
due
}
/// Write `prefs` and flush them to disk if they differ from the settings written last and the
/// last write is at least [`Self::INTERVAL`] old. Returns how long a change waits otherwise:
/// the caller asks for a frame then, so an idle app still writes it.
fn store(&mut self, storage: &mut dyn eframe::Storage, prefs: String, now: std::time::Instant) -> Option<std::time::Duration> {
if self.written.as_deref() == Some(prefs.as_str()) {
self.pending_until = None;
return None;
}
if let Some(wait) = self.last_write.map(|at| Self::INTERVAL.saturating_sub(now.saturating_duration_since(at))).filter(|w| !w.is_zero()) {
self.pending_until = Some(now + wait);
return Some(wait);
}
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs.clone());
storage.flush();
self.written = Some(prefs);
self.last_write = Some(now);
self.pending_until = None;
None
}
}
/// Input that can change a setting: a click, a key, typed or pasted text, a wheel or a pinch.
/// Pointer motion, which comes every frame while the mouse moves, cannot by itself.
fn changes_settings(event: &egui::Event) -> bool {
matches!(
event,
egui::Event::PointerButton { .. }
| egui::Event::Key { .. }
| egui::Event::Text(_)
| egui::Event::Paste(_)
| egui::Event::Cut
| egui::Event::Ime(_)
| egui::Event::MouseWheel { .. }
| egui::Event::Zoom(_)
| egui::Event::Rotate(_)
| egui::Event::AccessKitActionRequest(_)
)
}
impl eframe::App for App {
@@ -184,11 +266,21 @@ impl eframe::App for App {
solidcraft_ui_egui::space_mouse::buttons_hook(&mut self.solve, ctx, self.space_mouse.buttons(), raw);
self.solve.raw_input_hook(raw);
}
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
content_ui(&mut self.solve, ui);
// After the frame's UI, so a setting changed in it (Preferences' OK) is written now.
let now = std::time::Instant::now();
if self.prefs_writer.due(ui.input(|i| i.events.iter().any(changes_settings)), now)
&& let Some(storage) = frame.storage_mut()
&& let Some(wait) = self.prefs_writer.store(storage, self.solve.prefs(), now)
{
ui.ctx().request_repaint_after(wait);
}
}
fn save(&mut self, storage: &mut dyn eframe::Storage) {
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, self.solve.prefs());
let prefs = self.solve.prefs();
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs.clone());
self.prefs_writer.written = Some(prefs);
}
fn on_exit(&mut self) {
// The agent chat's agent and everything it started end with the app.
@@ -202,13 +294,29 @@ impl eframe::App for App {
}
solidcraft_ui_egui::recovery_ui::close(&mut self.solve);
solidcraft_ui_egui::open_elsewhere::close(&mut self.solve);
// Before a restart below starts the next instance, which would otherwise get nothing from
// the 3D mouse driver (`solidcraft_3dx_client::stop`).
// Before a restart (`Drop for App`) starts the next instance, which would otherwise get
// nothing from the 3D mouse driver (`solidcraft_3dx_client::stop`).
#[cfg(target_os = "macos")]
solidcraft_3dx_client::stop();
solidcraft_engine::crash::end_session();
if let Some(lost_at) = self.gpu.as_ref().and_then(|g| g.lost_at) {
device_lost::restart(self.gpu.as_ref().is_some_and(|g| g.restart_on_software), lost_at);
// The next start (after a lost device, or into an update) begins once the app is dropped
// (`Drop for App`), when the settings are on disk.
}
}
impl Drop for App {
/// eframe drops its settings storage first, which waits for `app.ron` to be written, then the
/// app, then the window and its GPU surface. So the next start begins here: started from
/// `on_exit`, it read `app.ron` while the old process was still writing it, found it
/// unreadable and moved it aside, resetting every setting (#9044).
fn drop(&mut self) {
if let Some(gpu) = &self.gpu
&& let Some(lost_at) = gpu.lost_at
{
device_lost::restart(gpu.restart_on_software, lost_at, &gpu.reason);
// The window's GPU surface is still to be dropped, and on a lost device that may
// never return (#9044).
device_lost::end();
}
// An update asked to restart into the new version or to run its installer.
if let Some((program, args)) = solidcraft_ui_egui::update::take_launch(&mut self.solve) {
@@ -236,6 +344,8 @@ struct LostWatch {
restart_on_software: bool,
/// When the loss was seen: the app is closing to start again.
lost_at: Option<u64>,
/// Why, as wgpu said it: passed to the next start ([`device_lost::REASON_ENV`]).
reason: String,
}
impl App {
@@ -249,6 +359,7 @@ impl App {
eprintln!("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;
self.solve.quit_requested = true;
}
}
@@ -422,6 +533,12 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
graphics_adapters: Vec::new(),
graphics_path: graphics_path().map(|p| p.to_string_lossy().to_string()),
window_system: window_system_now(),
// Linux: the desktop's dark or light setting, which the window is not told on every
// desktop (#7944). Windows and macOS: the window's own theme.
#[cfg(target_os = "linux")]
desktop_dark: Some(desktop_theme::start(&cc.egui_ctx)),
#[cfg(not(target_os = "linux"))]
desktop_dark: 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.
@@ -701,6 +818,24 @@ fn msaa() -> Option<u16> {
/// (`solidcraft_ui_egui::titlebar`). macOS keeps its traffic lights over the integrated bar.
const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos");
/// Linux: the window manager's own decorations (server-side, or the compositor's frame) can be
/// asked for instead of the application bar ([`titlebar_choice`]). Elsewhere always `true`.
fn app_titlebar_wanted() -> bool {
if !cfg!(all(unix, not(target_os = "macos"))) {
return true;
}
let setting = graphics().and_then(|v| v.get("titlebar").and_then(serde_json::Value::as_str).map(str::to_string));
titlebar_choice(solidcraft_engine::geom::env::var("TITLEBAR").as_deref(), setting.as_deref())
}
/// Linux: whether the application bar is the title bar. `SOLIDCRAFT_TITLEBAR` (else
/// `graphics.json`'s `titlebar`) set to `system` gives the window the desktop's own title bar and
/// buttons; `app` (or anything else, or nothing) keeps the application bar as the title bar.
fn titlebar_choice(env: Option<&str>, setting: Option<&str>) -> bool {
let choice = env.map(str::trim).filter(|v| !v.is_empty()).or(setting.map(str::trim));
!choice.is_some_and(|v| v.eq_ignore_ascii_case("system"))
}
const DEPTH_BITS: u8 = 24;
/// A 32-bit float depth buffer, drawn with reversed depth (1 near, 0 far) so its precision follows
/// the distance from the eye (#1434). Only where every adapter offers it with a stencil
@@ -750,6 +885,16 @@ fn main() -> eframe::Result {
#[cfg(target_os = "macos")]
objc2_foundation::NSProcessInfo::processInfo().setProcessName(objc2_foundation::ns_string!("SolidCraft"));
mark("main");
// On OCLP-patched Macs, wgpu picks a Metal Shading Language standard that the system's
// compiler refuses (`invalid value 'metal3.2'`), because the patched OS reports a newer
// macOS version than the GPU's Metal compiler supports. Re-launching with
// SYSTEM_VERSION_COMPAT=1 makes the OS report the real (older) version so wgpu selects
// a standard the compiler accepts. The variable must be set before the process starts, so
// we detect OCLP here and re-exec immediately, before any macOS framework call (#3080, #4605).
#[cfg(target_os = "macos")]
if let Err(e) = startup::relaunch_with_version_compat() {
log::warn!("startup: OCLP check failed (continuing normally): {e}");
}
// Validate before legacy adoption, crash markers or any user preference read.
let settings = settings_dir().map_err(|error| eframe::Error::AppCreation(error.into()))?;
if std::env::var_os("SOLIDCRAFT_SETTINGS_DIR").is_none() {
@@ -821,7 +966,11 @@ fn main() -> eframe::Result {
// The launch before this one closed because its graphics device was lost (`App::watch_device`).
let after_loss = device_lost::recent(std::env::var(device_lost::LOST_ENV).ok().as_deref(), device_lost::now_secs());
if after_loss {
notes.push(device_lost::note(cpu_renderer));
let reason = std::env::var(device_lost::REASON_ENV).ok();
if let Some(reason) = device_lost::reason(reason.as_deref()) {
log::warn!("graphics: the last launch lost its device ({reason})");
}
notes.push(device_lost::note(cpu_renderer, reason.as_deref()));
}
if safe_mode {
notes.push("Safe mode: default settings (yours are kept for the next normal start), no recovery offer, software rendering.".into());
@@ -857,7 +1006,7 @@ fn main() -> eframe::Result {
eframe::egui_wgpu::WgpuSetup::CreateNew(setup) => setup.instance_descriptor.backends,
_ => eframe::wgpu::Backends::all(),
};
let custom_titlebar = CUSTOM_TITLEBAR && !startup::system_title_bar(cfg!(target_os = "windows"), started);
let custom_titlebar = CUSTOM_TITLEBAR && app_titlebar_wanted() && !startup::system_title_bar(cfg!(target_os = "windows"), started);
eframe::egui_wgpu::set_startup_hook(startup_step);
solidcraft_engine::crash::phase("window");
let options = eframe::NativeOptions {
@@ -961,6 +1110,7 @@ fn main() -> eframe::Result {
lost: rs.device_lost.clone(),
restart_on_software: device_lost::restart_on_software(after_loss, cpu_renderer),
lost_at: None,
reason: String::new(),
});
if !safe_mode {
app.preferences.graphics_adapter =
@@ -976,6 +1126,12 @@ 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 graphics: preferred adapter {:?} was not used; using {:?} instead",
app.preferences.graphics_adapter, info.name
);
}
let samples = scene_msaa_request(saved_samples);
let supported = scene_sample_choices(depth_bits, STENCIL_BITS, |depth| {
eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth)
@@ -1080,6 +1236,8 @@ fn main() -> eframe::Result {
mark("app created");
solidcraft_engine::crash::phase("first frame");
Ok(Box::new(App {
// Before `app` moves: the settings as read, already on disk.
prefs_writer: PrefsWriter::read(app.prefs()),
solve: app,
starting,
frames: 0,
@@ -1103,12 +1261,13 @@ fn main() -> eframe::Result {
}
/// egui-wgpu is about to start a step of getting the graphics device and the window's surface up:
/// the crash journal keeps it, and the adapter once it is picked.
/// the crash journal keeps it until the app runs (a window opened later goes through the same
/// steps), and the adapter once it is picked.
fn startup_step(step: &'static str, detail: &str) {
if step == eframe::egui_wgpu::STEP_ADAPTER_CHOSEN {
solidcraft_engine::crash::set_environment(&format!("Renderer: {detail}"));
}
solidcraft_engine::crash::phase(step);
solidcraft_engine::crash::startup_step(step);
}
/// The renderer settings: with `cpu`, the software adapter (WARP, lavapipe, llvmpipe). The
@@ -1270,6 +1429,148 @@ mod backend_tests {
assert!(!prefer_x11(true, true, Some("GNOME"), over(Some("wayland"))));
assert!(prefer_x11(true, true, Some("niri"), over(Some("x11"))));
}
#[test]
fn the_desktops_title_bar_only_when_asked_for() {
use super::titlebar_choice;
// Nothing set: the application bar is the title bar, as before.
assert!(titlebar_choice(None, None));
// graphics.json's `titlebar`.
assert!(!titlebar_choice(None, Some("system")));
assert!(titlebar_choice(None, Some("app")));
// SOLIDCRAFT_TITLEBAR wins over the file; empty counts as unset; case and spaces don't matter.
assert!(!titlebar_choice(Some(" System "), Some("app")));
assert!(titlebar_choice(Some("app"), Some("system")));
assert!(!titlebar_choice(Some(""), Some("system")));
// Anything unknown keeps the application bar.
assert!(titlebar_choice(Some("ssd"), None));
}
}
#[cfg(test)]
mod prefs_writer_tests {
use super::PrefsWriter;
use solidcraft_ui_egui::prefs::{NavigatorStyle, STORAGE_KEY};
use std::collections::HashMap;
use std::time::{Duration, Instant};
/// eframe's settings file: what was flushed to disk is what the next start reads.
#[derive(Default)]
struct Disk {
pending: HashMap<String, String>,
flushed: HashMap<String, String>,
flushes: usize,
}
impl eframe::Storage for Disk {
fn get_string(&self, key: &str) -> Option<String> {
self.pending.get(key).cloned()
}
fn set_string(&mut self, key: &str, value: String) {
self.pending.insert(key.into(), value);
}
fn remove_string(&mut self, key: &str) {
self.pending.remove(key);
}
fn flush(&mut self) {
self.flushed = self.pending.clone();
self.flushes += 1;
}
}
fn new_app() -> solidcraft_ui_egui::SolveApp {
solidcraft_ui_egui::SolveApp::new(solidcraft_engine::Session::default(), solidcraft_ui_egui::Services::default())
}
/// #8275: Preferences' View navigator and Display quality, changed and confirmed, are on disk
/// in that frame, so the next start has them even when this one never closes normally (ended
/// from the Task Manager, a sign-out, a hang or crash in the graphics driver on the way out).
#[test]
fn changed_settings_reach_the_disk_in_the_frame_they_change() {
let mut app = new_app();
let mut disk = Disk::default();
let mut writer = PrefsWriter::read(app.prefs());
let now = Instant::now();
assert_eq!(writer.store(&mut disk, app.prefs(), now), None);
assert_eq!(disk.flushes, 0, "the settings read at start are not written again");
app.preferences.navigator_style = NavigatorStyle::Cube;
app.preferences.display_quality = "fine".into();
assert!(writer.due(true, now), "a frame with input is checked");
assert_eq!(writer.store(&mut disk, app.prefs(), now), None);
assert_eq!(disk.flushes, 1, "flushed at once, not left for eframe's next save");
assert_eq!(writer.store(&mut disk, app.prefs(), now + Duration::from_secs(5)), None);
assert_eq!(disk.flushes, 1, "unchanged settings are not written again");
// The next start reads them.
let mut next = new_app();
let stored = solidcraft_ui_egui::prefs::stored(|k| disk.flushed.get(k).cloned()).expect("settings on disk");
next.load_prefs(&stored);
assert_eq!(next.preferences.navigator_style, NavigatorStyle::Cube);
assert_eq!(next.preferences.display_quality, "fine");
assert!(disk.flushed.contains_key(STORAGE_KEY));
}
/// A frame without input is checked only right after one with input (whose clicks the UI
/// handled in that frame); after that, idle frames cost nothing. Pointer motion, which comes
/// every frame while the mouse moves, doesn't count as input.
#[test]
fn frames_are_checked_with_input_and_once_after_it() {
let now = Instant::now();
let mut writer = PrefsWriter::default();
assert!(!writer.due(false, now));
assert!(writer.due(true, now));
assert!(writer.due(false, now), "the frame after input");
assert!(!writer.due(false, now));
let click = egui::Event::PointerButton {
pos: egui::Pos2::ZERO,
button: egui::PointerButton::Primary,
pressed: false,
modifiers: egui::Modifiers::NONE,
};
assert!(super::changes_settings(&click));
assert!(super::changes_settings(&egui::Event::Text("5".into())));
assert!(!super::changes_settings(&egui::Event::PointerMoved(egui::Pos2::ZERO)));
assert!(!super::changes_settings(&egui::Event::MouseMoved(egui::Vec2::X)));
}
/// A setting that changes in every frame (a panel's width while its edge is dragged) doesn't
/// write the file in every frame: at most once a second, and the last change is on disk at
/// most a second after it was made, with a frame asked for then so an idle app writes it.
#[test]
fn a_continuous_change_is_written_once_a_second_and_its_end_soon_after() {
let mut app = new_app();
let mut disk = Disk::default();
let mut writer = PrefsWriter::read(app.prefs());
let start = Instant::now();
let frame = Duration::from_millis(16);
let mut asked = None;
// 1.5 s of dragging at 60 frames a second, with input in every frame.
for n in 0..94u32 {
let now = start + frame * n;
app.preferences.browser_width = 200.0 + n as f32;
assert!(writer.due(true, now));
asked = writer.store(&mut disk, app.prefs(), now);
}
assert_eq!(disk.flushes, 2, "the first change at once, then one a second later");
let wait = asked.expect("the last width waits, and a frame is asked for when it can be written");
assert!(wait <= PrefsWriter::INTERVAL);
// The drag ends: no more input. Frames before the wait is over don't even look.
let end = start + frame * 93;
assert!(writer.due(false, end + frame), "the frame right after input");
assert!(!writer.due(false, end + frame * 2), "an idle frame before the wait is over");
// The frame asked for writes the last width.
let then = end + wait;
assert!(writer.due(false, then));
assert_eq!(writer.store(&mut disk, app.prefs(), then), None);
assert_eq!(disk.flushes, 3);
let mut next = new_app();
let stored = solidcraft_ui_egui::prefs::stored(|k| disk.flushed.get(k).cloned()).expect("settings on disk");
next.load_prefs(&stored);
assert_eq!(next.preferences.browser_width, 293.0, "the width the drag ended at");
assert!(!writer.due(false, then + frame), "nothing waits any more");
}
}
#[cfg(test)]
+1
View File
@@ -101,6 +101,7 @@ impl NativeMenu {
])?;
for (action, label) in [
("docs", "Documentation"),
("updates", app.tr("update.check_for_updates")),
("shortcuts", "Keyboard Shortcuts"),
("messages", "Messages\u{2026}"),
("report", "Report an Issue\u{2026}"),
+111 -10
View File
@@ -137,8 +137,11 @@ pub struct AdapterChoice {
pub compatible: bool,
}
/// Choose only surface-compatible adapters. Names saved in preferences are exact; the
/// environment override follows wgpu's case-insensitive substring convention.
/// Choose an adapter by name. The environment override follows wgpu's case-insensitive
/// substring convention among surface-compatible adapters. A saved preference prefers an
/// exact (then case-insensitive) name among compatible adapters, and if the named card is
/// only listed as incompatible with the surface still picks it (#8341: otherwise the
/// preference is kept in the dialog while wgpu silently uses another GPU).
/// No match leaves selection to the normal wgpu request.
pub fn choose_adapter(adapters: &[AdapterChoice], environment: Option<&str>, preference: Option<&str>) -> Option<usize> {
// wgpu's environment helper panics on a missing name; an override must remain recoverable.
@@ -149,12 +152,14 @@ pub fn choose_adapter(adapters: &[AdapterChoice], environment: Option<&str>, pre
return Some(index);
}
}
if let Some(name) = preference.filter(|s| !s.is_empty())
&& let Some((index, _)) = compatible().find(|(_, a)| a.name == name)
{
return Some(index);
}
None
let name = preference.filter(|s| !s.is_empty())?;
let find = |pred: &dyn Fn(&AdapterChoice) -> bool, only_compatible: bool| {
adapters.iter().enumerate().find(|(_, a)| (!only_compatible || a.compatible) && pred(a)).map(|(i, _)| i)
};
find(&|a| a.name == name, true)
.or_else(|| find(&|a| a.name.eq_ignore_ascii_case(name), true))
.or_else(|| find(&|a| a.name == name, false))
.or_else(|| find(&|a| a.name.eq_ignore_ascii_case(name), false))
}
/// Leave Automatic on wgpu's normal request path; named choices may defer to it too.
@@ -292,6 +297,71 @@ pub fn window_instance_flags(flags: eframe::wgpu::InstanceFlags, intel_mac: bool
if intel_mac && env.map(str::trim).is_none_or(str::is_empty) { flags - eframe::wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL } else { flags }
}
/// The environment variable that makes the macOS compatibility shim report an older OS
/// version to wgpu's Metal backend. On OCLP-patched machines the Metal compiler accepts
/// only up to the MSL standard of the original GPU, not the one the patched OS reports,
/// so wgpu picks a standard that the compiler refuses (`invalid value 'metal3.2'`).
/// Setting this variable before the process starts causes the OS to report the older
/// version and wgpu selects a supported MSL standard (#3080, #4605).
#[cfg(target_os = "macos")]
pub const SYSTEM_VERSION_COMPAT_ENV: &str = "SYSTEM_VERSION_COMPAT";
/// The NVRAM variable that OpenCore Legacy Patcher writes on every OCLP-booted system.
/// Its presence is the primary signal that `SYSTEM_VERSION_COMPAT=1` is needed.
#[cfg(target_os = "macos")]
pub const OCLP_NVRAM_KEY: &str = "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:OCLP-Version";
/// `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.
#[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)
}
/// Whether to re-launch with `SYSTEM_VERSION_COMPAT=1`: it is not set yet and OCLP shows through
/// the NVRAM key. The probe runs only when it is needed, so a re-launched process (variable
/// set) never starts `nvram`.
#[cfg(target_os = "macos")]
fn compat_wanted(compat_set: bool, nvram: impl FnOnce() -> bool) -> bool {
!compat_set && nvram()
}
/// If this process is running under OCLP and `SYSTEM_VERSION_COMPAT` has not been set,
/// re-launch the current binary with `SYSTEM_VERSION_COMPAT=1` added to the environment,
/// then exit with the child's status code.
///
/// `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).
///
/// Returns `Ok(false)` immediately when:
/// - `SYSTEM_VERSION_COMPAT` is already set (re-launched process, no loop), or
/// - the OCLP NVRAM key is absent.
///
/// Returns `Err` only when `std::env::current_exe()` fails or the child process cannot
/// be spawned; the caller should log the error and continue normally.
///
/// Never returns `Ok(true)`: a successful re-launch exits the current process.
#[cfg(target_os = "macos")]
pub fn relaunch_with_version_compat() -> Result<bool, String> {
// A set variable means this is the re-launched process: no loop.
if !compat_wanted(std::env::var_os(SYSTEM_VERSION_COMPAT_ENV).is_some(), oclp_nvram_present) {
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);
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))
.env(SYSTEM_VERSION_COMPAT_ENV, "1")
.status()
.map_err(|e| format!("could not re-launch with {}: {e}", SYSTEM_VERSION_COMPAT_ENV))?;
std::process::exit(status.code().unwrap_or(1));
}
/// Windows with OpenGL: the window keeps the system title bar (#2063). The window without OS
/// decorations keeps its caption style while having no frame, and some OpenGL drivers (older
/// Intel HD/UHD) then show the picture a caption's height lower than the window, so it no longer
@@ -351,7 +421,8 @@ mod tests {
assert_eq!(choose_adapter(&cards, None, Some("removed")), None);
assert_eq!(choose_adapter(&cards, Some("missing"), Some("Discrete small")), Some(1));
assert_eq!(choose_adapter(&cards, Some(""), Some("Discrete small")), Some(1));
assert_eq!(choose_adapter(&cards, None, Some("Discrete")), None, "saved names are exact");
assert_eq!(choose_adapter(&cards, None, Some("discrete small")), Some(1), "preference matches without regard to case");
assert_eq!(choose_adapter(&cards, None, Some("Discrete")), None, "a preference is the full name, not a substring");
}
#[test]
@@ -360,11 +431,20 @@ mod tests {
assert_eq!(choose_adapter(&cards, None, None), None);
assert_eq!(choose_adapter(&cards, Some(""), Some("")), None);
cards[2].compatible = false;
assert_eq!(choose_adapter(&cards, Some("large"), Some("Discrete large")), None);
// Environment still needs a compatible adapter; a preference keeps the named card (#8341).
assert_eq!(choose_adapter(&cards, Some("large"), Some("Discrete large")), Some(2));
assert_eq!(choose_adapter(&cards, Some("small"), None), Some(1));
assert_eq!(choose_adapter(&[], None, None), None);
}
#[test]
fn preference_keeps_a_named_card_even_when_surface_says_incompatible() {
let mut cards = cards();
cards[1].compatible = false;
assert_eq!(choose_adapter(&cards, None, Some("Discrete small")), Some(1));
assert_eq!(choose_adapter(&cards, None, Some("discrete SMALL")), Some(1));
}
#[test]
fn automatic_adapter_preserves_the_previous_request_configuration() {
use eframe::egui_wgpu::WgpuSetupCreateNew;
@@ -414,6 +494,27 @@ mod tests {
assert_eq!(window_instance_flags(on, false, None), on);
}
#[cfg(target_os = "macos")]
#[test]
fn oclp_relaunch_needs_the_signal_and_no_compat_variable() {
use super::compat_wanted;
let probed = std::cell::Cell::new(false);
let probe = |found: bool| {
let probed = &probed;
move || {
probed.set(true);
found
}
};
// The re-launched process has the variable: no probe, no second re-launch.
assert!(!compat_wanted(true, probe(true)));
assert!(!probed.get());
// The OCLP NVRAM key is the signal.
assert!(compat_wanted(false, probe(true)));
// No signal: an ordinary Mac starts as it is.
assert!(!compat_wanted(false, probe(false)));
}
#[test]
fn switches_read_from_the_environment() {
assert!(env_on(Some("1")) && env_on(Some("yes")) && !env_on(Some("0")) && !env_on(Some(" ")) && !env_on(None) && !env_on(Some("false")));
Binary file not shown.
+96
View File
@@ -0,0 +1,96 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'.
Copyright 2014-2021 Google Inc. with Reserved Font Name 'Noto'.
Subset for SolidCraft web UI (glyphs used by the zh locale); renamed Craft UI SC (no Reserved Font Name).
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
+96
View File
@@ -0,0 +1,96 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'.
Copyright 2014-2021 Google Inc. with Reserved Font Name 'Noto'.
Subset for SolidCraft web UI (glyphs used by the zh-TW locale); renamed Craft UI TC (no Reserved Font Name).
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -181,6 +181,7 @@ pipe false
plane false
plane_along_path false
plane_angle false
plane_axis_point false
plane_midplane false
plane_perpendicular false
plane_tangent false
@@ -201,6 +202,7 @@ polygon false
polygon_circumscribed false
polygon_edge false
presspull false
print_face false
project false
project_surface false
rack_pinion false
@@ -235,6 +237,7 @@ settings_monitor false
settings_mouse false
settings_palette false
settings_plug false
settings_profile false
sheet false
sheet_rules false
shell false
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<title>Settings profile</title>
<circle cx="32" cy="19" r="8" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 53C12 44.7157 20.9543 38 32 38C43.0457 38 52 44.7157 52 53" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 413 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<title>Plane through axis and point</title>
<path d="M26 15L56 32.32L38 42.71L8 25.39Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="0.1 5"/>
<path d="M15 18.4L53 40.34" fill="none" stroke="currentColor" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="22" cy="29" r="3.1" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 475 B

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<title>Print Face</title>
<path d="M9.72 26.17L4.5 20.95L4.5 40.85L9.72 46.07Z" fill="#b8c3cf"/>
<path d="M9.72 26.17L4.5 20.95L4.5 40.85L9.72 46.07Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M18.77 23.15L9.72 26.17L9.72 46.07L18.77 43.05Z" fill="#328bd3"/>
<path d="M18.77 23.15L9.72 26.17L9.72 46.07L18.77 43.05Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M13.55 17.93L18.77 23.15L9.72 26.17L4.5 20.95Z" fill="#e2e8ef"/>
<path d="M13.55 17.93L18.77 23.15L9.72 26.17L4.5 20.95Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M50.45 37.54L38.27 25.35L38.27 33.88L50.45 46.07Z" fill="#b8c3cf"/>
<path d="M50.45 37.54L38.27 25.35L38.27 33.88L50.45 46.07Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M59.5 34.53L50.45 37.54L50.45 46.07L59.5 43.05Z" fill="#8e9faf"/>
<path d="M59.5 34.53L50.45 37.54L50.45 46.07L59.5 43.05Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M47.31 22.34L59.5 34.53L50.45 37.54L38.27 25.35Z" fill="#e2e8ef"/>
<path d="M47.31 22.34L59.5 34.53L50.45 37.54L38.27 25.35Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M50.45 46.07L38.27 33.88" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M59.5 43.05L50.45 46.07" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M24.17 34.2L28.31 34.2" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M35.31 34.2L27.31 38.7L27.31 29.7Z" fill="#236ead"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+571 -14
View File
File diff suppressed because it is too large Load Diff
+501
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "PRELAZNA POVRŠ",
"BOUNDARY SURFACE": "GRANIČNA POVRŠ",
"BROWSER": "STABLO MODELA",
"Back to the original order": "Nazad na izvorni redoslijed",
"Ball": "Kuglasti",
"Bar": "Šipka",
"Base Center": "Centar osnove",
@@ -595,6 +596,8 @@
"Snap to face and edge centres": "Privlači na centre lica i ivica",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft ne može slati izvještaje direktno. Pošaljite ih na GitHubu; u aplikaciji nije potreban token.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft se zatvorio prije nego što su ovi dizajni sačuvani. Može se otvoriti njihovo posljednje automatsko čuvanje.",
"Sort A to Z": "Sortiraj od A do Ž",
"Sort Z to A": "Sortiraj od Ž do A",
"Spacing 2 must be a non-zero number": "Razmak 2 mora biti broj različit od nule",
"Spacing must be a non-zero number": "Razmak mora biti broj različit od nule",
"Specialise": "Specijalizuj",
@@ -775,6 +778,8 @@
"at the face": "na licu",
"bom.group_children": "Poddijelovi u redu svog dijela",
"browser.body_name": "Tijelo{number}",
"browser.calibrate_click_a": "Kliknite sliku da zadate tačku A ili upišite njene koordinate.",
"browser.calibrate_click_b": "Kliknite sliku da zadate tačku B ili upišite njene koordinate.",
"browser.calibrate_title": "Kalibriši: {name}",
"browser.capture_many": "Pomjerene komponente bez zabilježenog položaja: {count}.",
"browser.capture_note": "Zabilježi zadržava novi položaj; Vrati ga vraća nazad.",
@@ -788,6 +793,13 @@
"browser.sketch_attachment_lost": "{name} (pričvršćenje izgubljeno)",
"browser.sketch_editing": "{name} (uređuje se)",
"browser.sketch_name": "Skica{number}",
"browser.sketches_to_parts_already_in_part": "već u svom dijelu",
"browser.sketches_to_parts_design_body": "na tijelu samog dizajna",
"browser.sketches_to_parts_moved": "Skice premještene u dio na kojem se nalaze: {moved}",
"browser.sketches_to_parts_no_exact_attachment": "na licu bez tačnog pričvršćenja",
"browser.sketches_to_parts_not_on_face": "ne na licu dijela",
"browser.sketches_to_parts_part_missing": "njihov dio više ne postoji",
"browser.sketches_to_parts_result": "Skice premještene u dio na kojem se nalaze: {moved}; ostale na mjestu: {stayed} ({why})",
"browser.units": "Jedinice: {units}",
"cam.flat": "Ravno lice",
"cam.follower": "Pratilac",
@@ -817,6 +829,8 @@
"cmd.appearance_pick_up": "Preuzmi izgled",
"cmd.appearance_replace": "Zamijeni izgled",
"cmd.assembly_bom": "Sastavnica",
"cmd.assembly_mass_properties": "Svojstva mase",
"cmd.body.print_face": "Površina za štampu",
"cmd.body_rename": "Preimenuj tijelo",
"cmd.browser_collapse_all": "Skupi sve",
"cmd.browser_expand_all": "Proširi sve",
@@ -853,6 +867,7 @@
"cmd.component_move_bodies": "Premjesti tijela u komponentu",
"cmd.component_move_features": "Premjesti operacije u komponentu",
"cmd.component_move_sketches": "Premjesti skice u komponentu",
"cmd.component_move_sketches_to_parts": "Premjesti skice u njihove dijelove",
"cmd.component_paste_from_design": "Zalijepi iz drugog dizajna",
"cmd.component_paste_new": "Zalijepi kao novo",
"cmd.component_properties": "Fizička svojstva komponente",
@@ -1188,6 +1203,7 @@
"cmd.sketch_spline_control_point_5": "Splajn kontrolnih tačaka (stepen 5)",
"cmd.sketch_spline_fit_point": "Splajn kroz tačke",
"cmd.sketch_spline_insert_point": "Dodaj tačku splajna",
"cmd.sketch_spline_remove_point": "Ukloni tačku splajna",
"cmd.sketch_spline_reverse": "Obrni smjer splajna",
"cmd.sketch_spun_profile": "Rotirani profil",
"cmd.sketch_toggle_diameter": "Prebaci poluprečnik / prečnik",
@@ -1241,6 +1257,7 @@
"cmd.view.render_browser": "Stablo renderovanja",
"cmd.view.render_dock": "Premjesti panel renderovanja",
"cmd.view.render_focus": "Prikaz fokusa renderovanja",
"cmd.view.status_details": "Detalji statusa prozora prikaza",
"cmd.view.status_items": "Stavke statusa prozora prikaza",
"cmd.view_ambient_occlusion": "Ambijentalna okluzija",
"cmd.view_angle_readout": "Uglovi prikaza",
@@ -1279,8 +1296,14 @@
"color.hex_hint": "#rrggbb",
"color.hue": "Nijansa",
"color.linear": "Prikaz vrijednosti boje u linearnom prostoru 0-1",
"color.recent": "Nedavne boje",
"color.recent_help": "Ovdje se pojavljuju boje koje izaberete u bilo kojem polju boje",
"color.saturation": "Zasićenost (procenat)",
"color.saturation_value": "Zasićenost i vrijednost",
"color.selected": "Izabrana boja",
"color.step_down": "Smanji za korak",
"color.step_up": "Povećaj za korak",
"color.value": "Vrijednost, svjetlina (procenat)",
"config.column_filter_empty": "Nema odgovarajućih kolona",
"contact.components_count": "Komponente: {count}",
"contact.enabled_label": "omogućeno",
@@ -1327,6 +1350,7 @@
"dialog.dimension_name": "Kota: {name}",
"dialog.edit_title": "UREDI {title}",
"dialog.no_shown_body": "Nema prikazanog tijela za izmjenu: prikažite jedno ili izaberite Novo tijelo",
"dialog.pattern_sketch_not_object": "Izaberite tijela ili operacije: skica nije ni jedno ni drugo. Ponovite njene krive pomoću alata Niz ili Preslikaj u samoj skici ili je prvo ekstrudirajte.",
"dialog.select": "↖ Izaberi",
"dialog.selected_count": "↖ Izabrano: {count}",
"dialog.selection_guidance": "Izaberite ulaz da ga promijenite. Kliknite izabrane objekte da ih uklonite; Ctrl/Cmd-klik ih također uključuje i isključuje. Pogledajte pregled, a zatim pritisnite U redu.",
@@ -1384,26 +1408,243 @@
"from the rule": "iz pravila",
"help.apache": "Apache licenca 2.0",
"help.appearance.assign": "Daje izabranim tijelima, licima ili komponentama boju ili izgled materijala, bez promjene njihove težine. Koristite ga da razlikujete dijelove ili pregledate završnu obradu.",
"help.assembly.bom": "Navodi dijelove dizajna ili komponente s njihovim količinama, po broju dijela, a popis može sačuvati kao CSV ili JSON. Koristite je kao popis dijelova za naručivanje i montažu.",
"help.attributions": "Atribucije (ATTRIBUTION.md)",
"help.build": "Verzija {version} · commit {commit} · izgrađeno {date}",
"help.canvas.decal": "Postavlja sliku na ravno lice modela, poput naljepnice. Koristite je da prikažete etiketu, logotip ili štampani uzorak na dijelu.",
"help.canvas.edit": "Mijenja platno: njegov položaj, veličinu, ugao, neprozirnost, preslikavanje, vidljivost ili naziv. Koristite ga da poravnate referentnu sliku sa skicom kojom je precrtavate.",
"help.canvas.insert": "Postavlja PNG ili JPEG sliku na ravan ili lice kao prozirnu referencu. Koristite je da u skici precrtate fotografiju ili crtež, uz sliku skaliranu na poznatu veličinu.",
"help.component.break_link": "Odvaja povezanu komponentu od njene datoteke dizajna, a njena tijela ostavlja kakva jesu. Koristite ga kada dio više ne treba pratiti izmjene izvorne datoteke.",
"help.component.capture_position": "Zadržava položaje dijelova koje ste ručno pomjerili, tako da to postaju njihovi stvarni položaji. Koristite ga nakon što ručno namjestite dijelove koje nijedan spoj ne drži.",
"help.component.create": "Dodaje novu, praznu komponentu u dizajn, unutar aktivne komponente osim ako izaberete drugog roditelja. Koristite je da novi dio sklopa dobije vlastito mjesto za tijela i skice.",
"help.component.finish_edit": "Čuva dio koji uređujete na mjestu, vraća se u njegov sklop i ažurira sklop prema njemu. Upotrijebite kada završite izmjene povezanog dijela.",
"help.component.from_bodies": "Pretvara svako izabrano tijelo u zasebnu komponentu, nazvanu po tijelu. Koristite ga kada dizajn izgrađen od odvojenih tijela treba postati sklop čiji se dijelovi mogu spajati i pomjerati.",
"help.component.insert_design": "Umeće drugi sačuvani dizajn kao komponentu povezanu s njegovom datotekom, s njegovim punim tijelima postavljenim onako kako su tamo. Koristite ga da ponovo upotrijebite dio u sklopu i kasnije izmjene preuzmete pomoću Ažuriraj veze.",
"help.component.properties": "Prikazuje zapreminu, masu, centar mase i momente inercije komponente, prema materijalima njenih tijela. Koristite ga da provjerite težinu i uravnoteženost sklopa.",
"help.component.revert_position": "Vraća dijelove koje ste ručno pomjerili tamo gdje su bili, odbacujući pomjeranja koja nisu zadržana. Koristite ga da poništite probna pomjeranja u sklopu.",
"help.component.update_links": "Ponovo čita povezane datoteke dizajna i zamjenjuje tijela tamo gdje su se promijenila. Koristite ga nakon uređivanja dijela koji ovaj sklop koristi.",
"help.config.configure": "Dodaje parametre, operacije, materijale ili izglede kao kolone tabele konfiguracija. Koristite ga da napravite varijante jednog dizajna, npr. veličine nekog dijela.",
"help.config.preview": "Provjerava može li se red tabele konfiguracija ponovo izgraditi, bez promjene dizajna ili njegove vremenske trake. Koristite ga prije prelaska na varijantu u koju niste sigurni.",
"help.config.rules": "Prikazuje pravila tabele konfiguracija, njene redove i koji je red aktivan. Koristite ga da vidite kako su postavljene varijante dizajna.",
"help.config.table": "Prikazuje tabelu konfiguracija: jedan red po varijanti, jednu kolonu po parametru ili operaciji i da li dizajn još odgovara aktivnom redu. Koristite je da pregledate varijante dizajna.",
"help.connect_note": "Ponovo pokrenite svaki asistent da učita SolidCraft server. Svaka datoteka je sigurnosno kopirana kao .bak prije promjene.",
"help.connect_title": "Poveži AI asistente",
"help.construct.axis.edge": "Pravi pomoćnu osu duž ravne ivice ili linije skice. Koristite je kao osu za rotaciju, kružni niz ili spoj.",
"help.construct.axis.normal_to_face": "Pravi pomoćnu osu kroz tačku, okomitu na lice. Koristite je kao osu koja stoji pravo iz površine.",
"help.construct.axis.through_cylinder": "Pravi pomoćnu osu kroz centar cilindričnog, konusnog ili torusnog lica, ili kružne ivice. Koristite je da operacije okrećete ili ih postavljate u niz oko okruglog dijela.",
"help.construct.axis.two_planes": "Pravi pomoćnu osu gdje se sastaju dvije ravni ili ravna lica. Koristite je kada je osa koja vam treba linija između dva lica.",
"help.construct.axis.two_points": "Pravi pomoćnu osu kroz dvije tačke, vrhove ili tačke skice. Koristite je za osu koja prolazi između dva poznata položaja.",
"help.construct.plane.along_path": "Pravi pomoćnu ravan okomitu na ivicu ili krivu, na nekom položaju duž nje. Koristite je da skicirate profil za povlačenje ili presjek putanje.",
"help.construct.plane.at_angle": "Pravi pomoćnu ravan zakrenutu za ugao u odnosu na ravan ili lice, oko ose ili ivice. Koristite je za nagnute skice, npr. kutni nosač.",
"help.construct.plane.midplane": "Pravi pomoćnu ravan na pola puta između dvije ravni ili ravna lica, ili koja ih raspolavlja tamo gdje se sastaju pod uglom. Koristite je kao ravan simetrije za preslikavanje.",
"help.construct.plane.offset": "Pravi pomoćnu ravan paralelnu s ravni ili ravnim licem, na određenoj udaljenosti ili kroz tačku. Koristite je da skicirate na zadatoj visini iznad lica.",
"help.construct.plane.perpendicular": "Pravi pomoćnu ravan koja sadrži liniju ili ivicu i okomita je na ravan ili lice. Koristite je da skicirate uspravno na modelu duž ivice.",
"help.construct.plane.tangent": "Pravi pomoćnu ravan koja u jednoj tački dodiruje cilindar, konus, sferu ili torus. Koristite je da skicirate na boku okruglog dijela.",
"help.construct.plane.three_points": "Pravi pomoćnu ravan kroz tri tačke. Koristite je za ravan određenu položajima na modelu, npr. s tri ugla.",
"help.construct.plane.two_edges": "Pravi pomoćnu ravan kroz dvije ravne ivice ili linije koje leže u jednoj ravni. Koristite je za ravan kroz dio, npr. kroz dvije suprotne ivice.",
"help.construct.point.along_path": "Pravi pomoćnu tačku na nekom položaju duž ivice ili krive. Koristite je kao referencu negdje duž putanje.",
"help.construct.point.center": "Pravi pomoćnu tačku u centru kružne ivice, kruga ili luka skice, sfere ili torusa. Koristite je kao referencu za rupe, ose i spojeve.",
"help.construct.point.edge_plane": "Pravi pomoćnu tačku gdje ivica ili linija prolazi kroz ravan ili ravno lice. Koristite je da označite gdje putanja siječe ravan.",
"help.construct.point.three_planes": "Pravi pomoćnu tačku gdje se sastaju tri ravni ili ravna lica. Koristite je kao referencu ugla, čak i tamo gdje model nema vrh.",
"help.construct.point.two_edges": "Pravi pomoćnu tačku gdje se dvije ivice ili linije sastaju ili su najbliže. Koristite je kao referencu u virtualnom uglu.",
"help.construct.point.vertex": "Pravi pomoćnu tačku na vrhu ili tački skice. Koristite je kao referencu koja ostaje kada se skica ili tijelo oko nje sakrije.",
"help.contact.check": "Navodi parove dijelova u skupovima kontakata koji se u trenutnom položaju međusobno presijecaju. Koristite ga da pronađete sudare prije nego što pokrenete spojeve ili studiju kretanja.",
"help.contact.create": "Grupiše dva ili više dijelova u skup kontakata čija se tijela sprečavaju da prolaze jedno kroz drugo dok se spojevi kreću. Koristite ga za dijelove koji treba da se zaustave kad se dodirnu, npr. poluga uz svoj graničnik.",
"help.contact.disable_all": "Isključuje kontakt, pa se pokretani spojevi i studije kretanja više ne zaustavljaju tamo gdje se dijelovi dodiruju. Koristite ga da ponovo slobodno pomjerate dijelove ili da veliki sklop ostane brz u radu.",
"help.contact.edit": "Mijenja skup kontakata: dijelove koje sadrži, njegov naziv ili da li je potisnut; može i izbrisati skup. Koristite ga da podesite koji dijelovi ne smiju prolaziti jedan kroz drugi.",
"help.contact.enable_all": "Stavlja svaki dio sklopa u jedan skup kontakata, pa nijedna dva dijela ne mogu prolaziti jedan kroz drugi dok se spojevi kreću. Koristite ga za brzu provjeru cijelog mehanizma.",
"help.contact.enable_sets": "Uključuje kontakt za skupove kontakata koje ste definisali, pa se pokretani spojevi i studije kretanja zaustavljaju tamo gdje bi njihovi dijelovi prošli jedan kroz drugi. Koristite ga da vratite svoje skupove nakon isključivanja kontakta.",
"help.contact.list": "Navodi skupove kontakata dizajna, dijelove u svakom od njih i da li je kontakt uključen. Koristite ga da provjerite kako je kontakt postavljen prije nego što pokrenete sklop.",
"help.document.inspect": "Prikazuje pregled dizajna: njegove komponente, tijela, skice, operacije i parametre. Koristite ga da se snađete u dizajnu koji niste sami napravili.",
"help.document.units": "Mijenja jedinice u kojima dizajn prikazuje dužine, npr. mm ili inče. Geometrija zadržava veličinu; mijenjaju se samo prikaz i brojevi bez jedinice.",
"help.edit.checkpoint": "Pamti dizajn, aktivnu skicu i historiju poništavanja onakve kakvi su sada. Koristite ga prije nego što isprobate nešto rizično od čega ćete se možda htjeti vratiti.",
"help.edit.restore_checkpoint": "Vraća se na kontrolnu tačku, uključujući historiju poništavanja. Koristite ga da jednim korakom odbacite sve urađeno od kontrolne tačke.",
"help.explode.create": "Pravi rastavljeni prikaz koji odguruje dijelove od centra sklopa ili prema pomjeranjima koja zadate. Koristite ga da pokažete kako se sklop sastavlja, za uputstva ili prezentacije.",
"help.face.delete": "Uklanja izabrana lica i pušta da susjedna lica zatvore otvor, ili ostavlja otvor otvorenim kao površ. Koristite ga da uklonite zaobljenu ivicu, rupu ili ispupčenje koje smeta.",
"help.file.insert_mesh": "Dodaje 3MF, STL, OBJ, PLY ili glTF datoteku u dizajn kao mrežna tijela u novoj komponenti. Koristite ga da pored svog dizajna kao referencu postavite preuzeti ili skenirani model.",
"help.file.insert_step": "Dodaje STEP, IGES, FreeCAD ili Rhino datoteku u dizajn kao puna tijela u novoj komponenti; dijelovi sklopa postaju ugniježđene komponente. Koristite ga da uvezete modele od dobavljača ili iz drugih CAD programa.",
"help.file.save_mesh": "Čuva tijela kao STL, 3MF ili OBJ mrežu, s glatkoćom zakrivljenih lica koju izaberete. Koristite ga da pripremite dijelove za 3D štampu ili za programe koji čitaju mreže.",
"help.file.send_to_slicer": "Čuva izabrana tijela kao 3MF datoteku i otvara je u slajseru koji izaberete. Koristite ga da od modela do štampe dođete jednim korakom.",
"help.font_details": "Interfejs koristi ugrađene fontove biblioteke egui (Ubuntu Light i Hack, pod licencama Ubuntu Font Licence i MIT/Bitstream Vera; Noto Emoji i font emoji ikona, pod licencama OFL i MIT). Tekst skice koristi Liberation Sans (SIL OFL 1.1). Neobavezni paket fontova Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), nije uključen u izdanja SolidCrafta.",
"help.fonts": "Fontovi",
"help.frame.member": "Postavlja profil, npr. cijev ili aluminijski ekstruzioni profil, duž linija skice konstrukcije, po jedan konstrukcioni profil na liniju. Koristite ga za izradu okvira, regala i zavarenih konstrukcija.",
"help.inspect.accessibility": "Boji lica do kojih alat može doći iz jednog smjera i ona do kojih ne može. Koristite ga da provjerite dio za obradu ili izradu u kalupu s te strane.",
"help.inspect.center_of_mass": "Prikazuje centar mase tijela, svakog posebno i svih zajedno, uz ujednačenu gustoću. Koristite ga da provjerite ravnotežu i na koju stranu će se dio prevrnuti.",
"help.inspect.colored_components": "Daje svakoj komponenti vlastitu privremenu boju, bez promjene izgleda. Koristite ga da na prvi pogled razlikujete dijelove sklopa.",
"help.inspect.compare": "Upoređuje dva tijela i pokazuje gdje jedno ima materijala kojeg drugo nema, uz zapreminu i odstupanje. Koristite ga da provjerite izmijenjen ili uvezen dio u odnosu na original.",
"help.inspect.continuity": "Provjerava koliko glatko se lica sastaju duž svojih ivica: otvorene krajeve, pregibe ili skokove zakrivljenosti, i boji spojeve. Koristite ga da pronađete gdje se površi ne spajaju čisto.",
"help.inspect.curvature_comb": "Crta češljeve duž kriva, ivica ili lica čiji zupci pokazuju koliko se jako savijaju. Koristite ga da uočite ravna mjesta, pregibe i neujednačenu zakrivljenost.",
"help.inspect.curvature_map": "Boji lica prema tome koliko su zakrivljena, crveno gdje je poluprečnik mali. Koristite ga da pronađete mjesta oštre zakrivljenosti i procijenite koliko je površ glatka.",
"help.inspect.draft": "Boji lica prema uglu nagiba u odnosu na smjer vađenja iz kalupa i označava ona s premalim nagibom. Koristite ga da provjerite dio prije brizganja ili livenja.",
"help.inspect.environment_map": "Prikazuje model sa sjajnom završnom obradom koja reflektuje poput ogledala. Koristite ga da procijenite tok površi prema načinu na koji se refleksije protežu preko njih.",
"help.inspect.interference": "Pronalazi tijela koja se preklapaju i prikazuje svako preklapanje s njegovom zapreminom. Koristite ga da u sklopu provjerite ima li dijelova koji se sudaraju.",
"help.inspect.isocurve": "Crta izokrive lica (izoparametarske krive), mrežu na kojoj je površ izgrađena. Koristite ga da vidite kako površ teče i gdje se zgušnjava.",
"help.inspect.measure": "Mjeri dužine, udaljenosti, uglove, površine i poluprečnike između onoga što izaberete, ili zapreminu i masu tijela. Koristite ga da provjerite veličine i zazore.",
"help.inspect.minimum_radius": "Pronalazi najmanji poluprečnik zakrivljenosti kriva, ivica ili lica i gdje se nalazi. Koristite ga da provjerite može li alat ili mlaznica pratiti oblik.",
"help.inspect.section": "Otvara prikaz presjekom duž ravni i šrafira presječena lica, bez promjene modela. Koristite ga da pogledate unutrašnjost dijelova i sklopova.",
"help.inspect.zebra": "Prikazuje crno-bijele pruge reflektovane na licima modela. Koristite ga da procijenite glatkoću: pruge koje se prekidaju ili lome na ivici pokazuju loš prelaz.",
"help.joint.as_built": "Spaja dva dijela tamo gdje već jesu, bez pomjeranja ijednog od njih, i određuje kako se još smiju kretati. Koristite ga za dijelove koje ste već postavili u konačni položaj.",
"help.joint.cam": "Povezuje brijeg koji se okreće s pratiocem, pa pratilac klizi ili se ljulja dok obris brijega prolazi ispod njega. Koristite ga za brijegove, ekscentre i pogone ventila.",
"help.joint.create": "Spaja dva dijela u ishodištima spoja koja izaberete i pomjera drugi dio na mjesto; vrsta spoja određuje kako se još smiju kretati. Koristite ga da napravite sklop čiji se dijelovi kreću kao pravi mehanizam.",
"help.joint.drive": "Zadaje ugao ili udaljenost spoja i s njim pomjera dijelove, u okviru njegovih granica; povezani spojevi i zatvorene petlje ga prate. Koristite ga da provjerite kreće li se mehanizam kako je zamišljeno.",
"help.joint.gear": "Povezuje dva rotaciona spoja prijenosnim omjerom, pa pokretanje jednog okreće drugi. Koristite ga za čeone i prstenaste zupčanike, s omjerom iz podionih poluprečnika ili broja zubaca.",
"help.joint.limits": "Zadaje koliko daleko se spoj smije okrenuti ili pomjeriti, kao minimum i maksimum, ili uklanja te granice. Koristite ga da šarku, klizač ili polugu zadržite unutar njihovog stvarnog raspona kretanja.",
"help.joint.motion_link": "Veže dva spoja omjerom i pomakom, pa pokretanje jednog pomjera drugi. Koristite ga za čvrst odnos između spojeva koji Zupčanik, Zupčasta letva i zupčanik ili Vijak ne pokrivaju.",
"help.joint.origin": "Postavlja imenovano ishodište spoja na dio: u centar lica ili okruglog kraja, sredinu ivice, ugao ili tačku. Koristite ga da pripremite gdje se spojevi vezuju; ishodište izabrano na licu prati ga kada dio promijeni veličinu.",
"help.joint.rack_pinion": "Povezuje mali zupčanik koji se okreće sa zupčastom letvom koja klizi, pa se letva pomjera za dužinu podione kružnice koju mali zupčanik prevali. Koristite ga za pogone sa zupčastom letvom, upravljanje i linearne stolove.",
"help.joint.reach": "Pomjera tačku na dijelu prema cilju mijenjajući spojeve u njegovom lancu, u okviru njihovih granica. Koristite ga da namjestite mehanizam prema tome gdje treba biti njegov kraj, npr. vrh robotske ruke.",
"help.joint.rigid_group": "Zaključava izabrane dijelove zajedno onakve kakvi jesu, pa se kreću kao jedan. Koristite ga za dijelove koji zajedno pripadaju i između kojih nema kretanja.",
"help.joint.screw": "Povezuje okretanje i klizanje korakom, pa se svakim okretajem napreduje za tu udaljenost. Koristite ga za navojna vretena, vijke i navojne osovine.",
"help.library.apply": "Primjenjuje materijal ili izgled iz lokalne datoteke biblioteke na tijela, lica ili komponente. Koristite ga da ponovo upotrijebite materijale koje ste sačuvali iz drugog dizajna.",
"help.library.export": "Čuva materijale i izglede dizajna u datoteku biblioteke. Koristite ga da ih dijelite ili ponovo koristite u drugim dizajnima.",
"help.library.inspect": "Provjerava datoteku biblioteke i navodi njene materijale i izglede, bez promjene dizajna. Koristite ga da vidite šta biblioteka sadrži prije nego što je primijenite.",
"help.licences": "Licence",
"help.material.assign": "Određuje od čega su izabrana tijela napravljena, npr. čelik, aluminij ili ABS. Koristite ga da masa i težina odgovaraju stvarnom dijelu.",
"help.material.properties.validate": "Provjerava karticu podataka materijala na usklađenost brojeva i vrijednosti koje nedostaju. Koristite ga prije nego što se oslonite na podatke za masu ili krutost.",
"help.mesh.convert": "Ponovo gradi mrežno tijelo, npr. STL, kao uredivo puno tijelo s ravnim i cilindričnim licima. Koristite ga da izmijenite uvezene mehaničke dijelove kao modelirano tijelo.",
"help.mit": "MIT licenca",
"help.motion.study": "Zadaje vrijednosti spojeva u koracima na vremenskoj traci i reprodukuje ih kao animaciju. Koristite ga da posmatrate kako se mehanizam kreće kroz niz pokreta i usput provjerite zazore.",
"help.motion.trace": "Prati putanju kojom ide tačka na dijelu dok se reprodukuje studija kretanja ili se pokreće spoj i, kada potvrdite, zadržava je kao krivu 3D skice. Koristite ga da vidite dokle mehanizam dosiže ili da nastavite graditi na njegovoj putanji.",
"help.notice": "NOTICE",
"help.occurrence.ground": "Učvršćuje dio na mjestu, ili ga ponovo oslobađa, tako da spojevi pomjeraju ostale dijelove oko njega. Koristite ga za okvir ili osnovu na kojoj je izgrađen ostatak sklopa.",
"help.occurrence.move": "Pomjera dio sklopa samo duž stepeni slobode koje dozvoljavaju njegovi spojevi. Izaberite lice dijela, a zatim prevucite dostupnu strelicu ili luk.",
"help.parameters.change": "Otvara tabelu imenovanih vrijednosti i izraza koji upravljaju kotama dizajna. Koristite je da promijenite mjeru na jednom mjestu, a sve operacije koje je koriste je slijede.",
"help.parameters.export": "Čuva parametre dizajna kao CSV ili JSON. Koristite ga da ih uredite u proračunskoj tabeli ili prenesete u drugi dizajn.",
"help.parameters.graph": "Prikazuje koji parametri i operacije koriste koje druge, te sve petlje ili greške među njima. Koristite ga da vidite šta sve zahvata promjena nekog parametra.",
"help.parameters.import": "Učitava parametre iz CSV ili JSON u dizajn, sve ili nijedan. Koristite ga da unesete vrijednosti iz proračunske tabele ili drugog dizajna.",
"help.parameters.move": "Pomjera korisnički parametar ispred ili iza drugog na popisu. Koristite ga da povezane parametre držite zajedno.",
"help.parts.fastener": "Dodaje standardni vijak, zavrtanj, maticu, podlošku, zatik ili ležaj u izabranoj veličini. Postavljen na rupu, naliježe u rupu pomoću spoja, pa spojne elemente ne morate sami modelirati.",
"help.parts.insert": "Umeće standardni dio u porodici, veličini i dužini koje izaberete. Postavljen na rub ili zid rupe, naliježe na rupu krutim spojem.",
"help.parts.library": "Otvara standardne dijelove: vijke, zavrtnjeve, matice, podloške, bezglave vijke, cilindrične zatike i ležajeve u standardnim veličinama. Koristite ga da dodate standardni dio umjesto da ga sami modelirate.",
"help.parts.resize": "Mijenja veličinu ili dužinu umetnutog standardnog dijela i ponovo ga gradi; njegovi spojevi ga drže na mjestu. Koristite ga kada se vijak pokaže predugim ili rupa traži drugu veličinu.",
"help.plastic.assign_rule": "Dodjeljuje pravilo za plastiku tijelima, pa njihove plastične operacije preuzimaju njegov nagib i zazor kao podrazumijevane vrijednosti. Koristite ga da jednom podesite brizgani dio za sve njegove operacije.",
"help.plastic.boss": "Dodaje ispust za vijak na licu: stub s neobaveznom rupom, nagibom, zaobljenjem u korijenu i potpornim rebrima. Koristite ga za tačke za vijke u brizganim ili štampanim kućištima.",
"help.plastic.lip": "Dodaje usnu ili odgovarajući utor duž ruba izdubljenog tijela. Koristite ga da se dvije polovine kućišta međusobno pozicioniraju i čvrsto zatvore.",
"help.plastic.manage_rules": "Pravi i uređuje pravila za plastiku: materijal, debljinu zida, poluprečnike, zazor i nagib. Koristite ih da operacije brizganog dijela ostanu usklađene.",
"help.plastic.rest": "Dodaje oslonac na licu: okruglu ili pravougaonu podlogu, punu ili šuplju, s nagibom. Koristite ga kao potporu ili odstojnik u brizganom kućištu.",
"help.plastic.snap_fit": "Dodaje kuku škljocajućeg spoja na licu, s njenom dužinom, debljinom i zapinjačem. Koristite ga da dijelove spojite zaklapanjem bez vijaka.",
"help.pointcloud.insert": "Umetnite PLY, XYZ ili PTS skeniranje kao oblak tačaka uz model. Koristite ga kao referencu pri izradi modela iz izmjerenih podataka.",
"help.sheet.convert": "Pretvara ploču ili izdubljeno tijelo ujednačene debljine u tijelo od lima, sa savijanjima na ivicama koje izaberete. Koristite ga da postojeći dio učinite razvijivim.",
"help.sheet.export_bend_table": "Čuva tabelu savijanja tijela od lima kao CSV ili JSON: uglove, poluprečnike i položaje savijanja u razvijenom obliku. Koristite ga za presu za savijanje ili radionicu.",
"help.sheet.export_dxf": "Čuva razvijeni oblik kao DXF, s obrisima, linijama savijanja i neobaveznim napomenama o savijanju. Koristite ga za rezanje laserom, plazmom ili vodenim mlazom.",
"help.sheet.flange": "Dodaje prirubnicu: osnovnu ploču iz skice, zid duž otvorene konture skice ili savijeni zid na ivicama lima. Koristite je za izgradnju dijelova od lima.",
"help.sheet.flat_pattern": "Pravi razvijeni oblik tijela od lima, s njegovom veličinom, izrezima i linijama savijanja. Koristite ga da provjerite pripremak prije rezanja.",
"help.sheet.fold": "Savija lim duž linije skice za zadati ugao i poluprečnik. Koristite ga da presavijete ravan lim nakon što u njemu izrežete operacije.",
"help.sheet.hem": "Presavija ivicu lima natrag preko sebe. Koristite ga za sigurne, krute ivice bez oštrih srha.",
"help.sheet.manage_rules": "Pravi i uređuje pravila za lim: debljinu, poluprečnik savijanja, K-faktor, rasterećenja i razmake. Koristite ih da se uskladite s materijalom i alatima svoje radionice.",
"help.sheet.path_flange": "Dodaje lanac prirubnica duž ivice lima, prateći otvoreni lanac linija skice. Koristite ga za profile poput U ili Z oblika u jednom koraku.",
"help.sheet.perforate": "Dodaje polje rupa preko cijelog ravnog lima, u izmaknutom ili kvadratnom rasporedu. Koristite ga za otvore za ventilaciju, rešetke i lakše ploče.",
"help.sheet.refold": "Ponovo presavija razvijeno tijelo od lima. Koristite ga nakon dodavanja operacija na ravan lim.",
"help.sheet.unfold": "Razvija sva savijanja tijela od lima. Koristite ga da dodate rupe ili rezove koji moraju prelaziti preko savijanja, a zatim ga ponovo presavijte.",
"help.sketch.arc.center_point": "Crta kružni luk oko tačke centra, od početne do završne tačke. Koristite ga kada je centar luka poznat, npr. poluprečnik oko rupe ili osovine.",
"help.sketch.arc.tangent": "Crta luk koji počinje na kraju linije ili luka i nastavlja se tangentno na njega. Koristite ga da lanac nastavite glatkim prelazom umjesto ugla.",
"help.sketch.arc.three_point": "Crta luk od početne do završne tačke kroz treću tačku. Koristite ga kada luk mora proći kroz zadate tačke, a ne oko poznatog centra.",
"help.sketch.arc3d": "Crta tačan kružni luk u prostoru kroz tri tačke, uz uključenu 3D skicu. Koristite ga za zavoje putanja i vodilica koji izlaze iz ravni skice.",
"help.sketch.auto_constrain": "Dodaje ograničenja koja skica već gotovo ima, npr. horizontalno, paralelno ili podudarno, a zatim je kotira dok ne bude potpuno definisana. Koristite ga da grubo nacrtanu skicu zaključate u jednom koraku.",
"help.sketch.auto_constrain_from_datum": "Radi kao Automatska ograničenja, ali položaje mjeri od referentne tačke koju izaberete umjesto od ishodišta. Koristite ga kada je dio raspoređen od jedne referentne tačke, npr. ugla ili rupe.",
"help.sketch.blend_curve": "Spaja krajeve dviju kriva glatkim splajnom koji je tangentan na obje, ili i kontinuiran po zakrivljenosti ako tako izaberete. Koristite ga da premostite razmak između kriva bez pregiba.",
"help.sketch.break": "Dijeli krivu na dijelove tamo gdje je presijecaju druge krive, ili na tačkama koje postavite na nju. Koristite ga kada dio krive treba vlastito ograničenje ili ga treba zasebno izbrisati.",
"help.sketch.cancel": "Napušta skicu i odbacuje sve što je promijenjeno otkako je napravljena ili otvorena za uređivanje. Pomoću Poništi možete vratiti promjene.",
"help.sketch.centerline": "Prebacuje linije između običnih linija i središnjih linija, osa koje profili izostavljaju. Središnju liniju koristite kao osu za rotaciju, preslikavanje ili ograničenje simetrije.",
"help.sketch.chamfer.distance_angle": "Skošava ugao između dvije linije, zadat udaljenošću duž prve linije i uglom. Koristite ga kada crtež daje skošenje kao dužinu i ugao, npr. 2 mm × 30°.",
"help.sketch.chamfer.equal_distance": "Skošava ugao između dvije linije za istu udaljenost unazad duž obje. Koristite ga za uobičajeno skošenje od 45° na obrisu.",
"help.sketch.chamfer.two_distance": "Skošava ugao između dvije linije, zadat posebnom udaljenošću duž svake linije. Koristite ga za skošenja koja nisu 45°, zadana kao dvije dužine.",
"help.sketch.circle.center": "Crta krug iz njegovog centra i prečnika. Koristite ga za rupe, ispuste i druge okrugle oblike čiji je centar poznat.",
"help.sketch.circle.three_point": "Crta krug kroz tri tačke. Koristite ga kada krug mora proći kroz zadate tačke, npr. postojeće geometrije.",
"help.sketch.circle.three_tangent": "Crta krug koji dodiruje tri elementa: linije, krugove ili lukove. Koristite ga da krug uklopite u prostor između triju kriva, npr. upisani krug trougla.",
"help.sketch.circle.two_point": "Crta krug iz dva kraja njegovog prečnika. Koristite ga kada su poznate tačke na rubu kruga, a ne njegov centar.",
"help.sketch.circle.two_tangent": "Crta krug zadate veličine koji dodiruje dva elementa: linije, krugove ili lukove. Koristite ga za zaobljenje stalnog poluprečnika između dvije krive.",
"help.sketch.conic": "Crta konusnu krivu između dvije krajnje tačke, oblikovanu tjemenom tačkom i vrijednošću rho. Koristite je za glatke prelaze čiju punoću želite podesiti, od ravnog do gotovo oštrog ugla.",
"help.sketch.constrainer": "Primjenjuje ograničenje koje jedna ili dvije izabrane krive već gotovo imaju, npr. horizontalno, paralelno, tangentno ili jednako. Koristite ga da uredite ručno nacrtanu skicu, jedan odnos po jedan.",
"help.sketch.constraint.coincident": "Čini da tačka leži na drugoj tački, krivoj ili osi skice. Koristite ga da zatvorite otvore u profilu ili da tačku postavite na liniju.",
"help.sketch.constraint.collinear": "Čini da dvije linije leže na jednoj pravoj. Koristite ga da poravnate prekinute ivice, npr. obje strane zareza.",
"help.sketch.constraint.concentric": "Daje dva kruga ili luka isti centar. Koristite ga za prstenove, cilindrična upuštenja i osovine s ramenima.",
"help.sketch.constraint.curvature": "Čini da dvije krive koje se sastaju u krajnjoj tački nastave istom zakrivljenošću (G2). Koristite ga tamo gdje tangentni spoj nije dovoljno gladak, npr. na vidljivim površima.",
"help.sketch.constraint.equal": "Izjednačava dužine linija, ili poluprečnike krugova i lukova. Koristite ga da jedna kota upravlja s više istovjetnih oblika.",
"help.sketch.constraint.fix": "Fiksira tačke ili krive tamo gdje jesu, ili ih ponovo oslobađa. Koristite ga za geometriju koja se ne smije pomjerati kada povlačite ili rješavate skicu.",
"help.sketch.constraint.horizontal_vertical": "Čini liniju, ili liniju između dvije tačke, horizontalnom ili vertikalnom, zavisno od toga što je bliže. Koristite ga da obris poravnate s osama skice.",
"help.sketch.constraint.midpoint": "Postavlja tačku na sredinu linije, ili spaja središnje tačke dviju linija. Koristite ga da geometriju centrirate na ivici.",
"help.sketch.constraint.parallel": "Čini dvije linije paralelnima. Koristite ga za stranice koje moraju ostati paralelne kada se promijene druge kote.",
"help.sketch.constraint.perpendicular": "Čini da se dvije linije sastaju pod pravim uglom. Koristite ga za prave uglove između linija koje nisu na osama skice.",
"help.sketch.constraint.polygon": "Čini zatvoreni lanac linija pravilnim poligonom: jednake stranice, a svaki ugao na jednom krugu. Koristite ga da ručno nacrtan poligon učinite pravilnim i takvim ga zadržite.",
"help.sketch.constraint.symmetry": "Čini da dvije tačke ili krive budu međusobno preslikane preko linije ili oko tačke. Koristite ga da profil ostane simetričan kada promijenite jednu stranu.",
"help.sketch.constraint.tangent": "Čini da se dvije krive dodiruju tangentno, npr. linija i luk. Koristite ga za glatke spojeve bez pregiba.",
"help.sketch.construction": "Prebacuje krive između obične geometrije i pomoćne geometrije, koju profili izostavljaju. Pomoćne krive koristite kao pomagala za raspored, reference i kote.",
"help.sketch.construction_line": "Napravite beskonačnu pomoćnu pravu kroz dvije tačke. Koristite je da rasporedite skicu bez razdvajanja profila.",
"help.sketch.construction_line.angle": "Napravite beskonačnu pomoćnu pravu pod uglom od ose X skice. Izaberite tačku i upišite ugao ili izaberite smjer.",
"help.sketch.construction_line.bisector": "Izaberite dvije prave linije skice koje se sijeku, pa jednu od dvije simetrale ugla. Rezultat prati izvorne linije.",
"help.sketch.construction_line.horizontal": "Napravite beskonačnu horizontalnu pomoćnu pravu kroz tačku. Njeno ograničenje horizontalnosti ostaje na snazi pri kasnijim izmjenama.",
"help.sketch.construction_line.parallel": "Izaberite pravu liniju skice, pa postavite paralelnu beskonačnu pomoćnu pravu. Desnim klikom izaberite drugu referencu.",
"help.sketch.construction_line.perpendicular": "Izaberite pravu liniju skice, pa postavite okomitu beskonačnu pomoćnu pravu. Desnim klikom izaberite drugu referencu.",
"help.sketch.construction_line.tangent_at": "Napravite tangentu u tački na krugu ili kružnom luku, ili u krajnjoj tački otvorenog splajna ili konike. Izaberite krivu, pa tačku dodira.",
"help.sketch.construction_line.tangent_external": "Izaberite krug ili kružni luk, pa vanjsku tačku i rješenje tangente. Pregledi prikazuju samo dodirne tačke na nacrtanom luku.",
"help.sketch.construction_line.tangent_two": "Izaberite dva kruga ili kružna luka, pa u pregledu izaberite zajedničku tangentu. Dodirne tačke moraju ležati na nacrtanim krivama.",
"help.sketch.construction_line.vertical": "Napravite beskonačnu vertikalnu pomoćnu pravu kroz tačku. Njeno ograničenje vertikalnosti ostaje na snazi pri kasnijim izmjenama.",
"help.sketch.copy": "Kopira izabrane krive i tačke skice, zajedno s ograničenjima i kotama među njima. Zalijepite ih u ovu skicu ili u drugu.",
"help.sketch.create": "Počinje novu 2D skicu na ravni ili na ravnom licu modela. Svako ekstrudiranje, rotiranje i povlačenje počinje skicom.",
"help.sketch.cut": "Kopira izabranu geometriju skice i briše je iz skice, u jednom koraku poništavanja. Zalijepite je drugdje u ovoj skici ili u drugoj.",
"help.sketch.dimension": "Dodaje pogonsku kotu: dužinu, udaljenost, ugao, poluprečnik ili prečnik, zavisno od onoga što izaberete. Upišite vrijednost ili naziv parametra, a skica ih prati.",
"help.sketch.edit": "Otvara postojeću skicu za uređivanje. Operacije izgrađene na njoj se ažuriraju kada završite skicu.",
"help.sketch.ellipse": "Crta elipsu iz njenog centra, kraja velike ose i tačke na maloj strani. Koristite je za ovalne otvore, brijegove i druge izdužene okrugle oblike.",
"help.sketch.equation_curve": "Crta krivu iz formula: x(t) i y(t) ili y = f(x), u zadanom rasponu. Formule mogu koristiti parametre i kriva ih prati: koristite je za sinusoidu, spiralu ili evolventu zupca zupčanika.",
"help.sketch.extend": "Produžava liniju ili luk do najbliže krive na putu, ili izdužuje dvije krive dok se ne sastanu. Koristite ga da zatvorite otvore u profilu nakon odsijecanja ili uređivanja.",
"help.sketch.fillet": "Zaobljuje ugao gdje se dvije linije ili lukovi sastaju lukom zadatog poluprečnika. Više uglova zaobljenih zajedno dijeli jedan poluprečnik.",
"help.sketch.finish": "Napušta skicu i vraća se na model. Njeni zatvoreni profili tada su spremni za Ekstrudiraj, Rotiraj i druge operacije.",
"help.sketch.finish_auto_constrain": "Pokreće Automatska ograničenja na skici, a zatim je završava. Koristite ga da skicu ostavite potpuno definisanu u jednom koraku.",
"help.sketch.fit_curves_to_section": "Siječe mrežno ili puno tijelo ravni skice i uklapa linije i lukove u presjek. Koristite ga da skenirani ili uvezeni dio ponovo izgradite kao uredivu skicu.",
"help.sketch.include_3d": "Unosi ivice modela, vrhove i krive drugih skica u ovu skicu kao povezane reference, spljoštavajući one izvan njene ravni na nju. Koristite ga da gradite na geometriji koja ne leži u ravni skice.",
"help.sketch.infinite": "Prebacuje linije između običnih linija i beskonačnih pomoćnih pravih koje se protežu preko cijelog prikaza. Koristite ih kao reference za raspored koje nikada ne završe prekratko.",
"help.sketch.insert_dxf": "Učitava krive DXF crteža u skicu, skalirane, zakrenute i postavljene gdje zadate. Koristite ga da gradite na 2D crtežu iz drugog programa, npr. na obrisu za rezanje laserom.",
"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.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.",
"help.sketch.line3d": "Crta povezane prave linije između tačaka u prostoru, uz uključenu 3D skicu. Koristite ga za putanje i okvire koji izlaze iz ravni skice.",
"help.sketch.mirror": "Preslikava krive i tačke skice preko linije ili ose i dodaje ograničenja simetrije. Koristite ga da nacrtate pola simetričnog profila i dovršite ga.",
"help.sketch.move": "Pomjera ili rotira krive i tačke skice, ili njihovu kopiju. Koristite ga da promijenite položaj geometrije uz zadržavanje njenog oblika i ograničenja.",
"help.sketch.offset": "Pravi kopiju lanca kriva na zadatoj udaljenosti, s jedne strane ili s obje. Koristite ga za zidove, obrise i zazore; udaljenost ostaje kota koju možete uređivati.",
"help.sketch.paste": "Lijepi geometriju skice koju ste kopirali ili izrezali u aktivnu skicu. Postavite je na tačku ili je pomjerite za odmak.",
"help.sketch.pattern.circular": "Kopira geometriju skice oko tačke centra, preko punog kruga ili zadatog ugla. Koristite ga za kružne rasporede vijaka, paoke i zupce.",
"help.sketch.pattern.rectangular": "Kopira geometriju skice u redovima i kolonama na zadatim razmacima. Koristite ga za mreže rupa, žlijebova ili otvora za ventilaciju.",
"help.sketch.point": "Postavlja tačku skice. Koristite je kao referencu za kote, ograničenja, centre rupa ili kasnije operacije.",
"help.sketch.point3d": "Postavlja tačku u prostoru, uz uključenu 3D skicu. Koristite je kao referencu za 3D krive i putanje.",
"help.sketch.polygon.circumscribed": "Crta pravilan poligon oko kruga, zadat od centra do sredine stranice. Koristite ga kada je poznata širina ključa, npr. za šestougaonu maticu.",
"help.sketch.polygon.edge": "Crta pravilan poligon iz dva kraja jedne stranice. Koristite ga kada su poznate dužina i položaj stranice.",
"help.sketch.polygon.inscribed": "Crta pravilan poligon unutar kruga, zadat od centra do ugla. Koristite ga kada je poznata mjera preko uglova.",
"help.sketch.project": "Unosi ivice modela, lica, vrhove i krive drugih skica u ovu skicu, projektovane na njenu ravan. Ostaju povezani i prate model kada se promijeni.",
"help.sketch.project_to_surface": "Projektuje krive drugih skica duž normale ove skice na lice, kao povezane 3D krive. Koristite ga da logotip, liniju razdvajanja ili putanju postavite na zakrivljenu površ.",
"help.sketch.rectangle.center": "Crta pravougaonik iz njegovog centra i jednog ugla. Koristite ga za obrise centrirane na tački, npr. na ishodištu.",
"help.sketch.rectangle.rounded": "Crta pravougaonik sa sva četiri ugla zaobljena istim poluprečnikom. Koristite ga za ploče, poklopce i otvore sa zaobljenim uglovima.",
"help.sketch.rectangle.three_point": "Crta pravougaonik iz jedne ivice i tačke na suprotnoj strani. Koristite ga za pravougaonike pod uglom u odnosu na ose skice.",
"help.sketch.rectangle.two_point": "Crta pravougaonik iz dva suprotna ugla. Koristite ga za obrise ploča, blokova i džepova.",
"help.sketch.scale": "Skalira krive i tačke skice faktorom oko bazne tačke. Koristite ga da promijenite veličinu dijela skice bez ponovnog crtanja.",
"help.sketch.slot.arc_center": "Crta lučni žlijeb zadate širine duž luka određenog centrom, početkom i krajem. Koristite ga za žlijebove za podešavanje oko osovine.",
"help.sketch.slot.arc_three_point": "Crta lučni žlijeb zadate širine duž luka kroz tri tačke. Koristite ga kada putanja žlijeba mora proći kroz zadate tačke.",
"help.sketch.slot.center_point": "Crta ravni žlijeb zadate širine od njegovog centra do centra jednog krajnjeg luka. Koristite ga za žlijebove centrirane na tački.",
"help.sketch.slot.center_to_center": "Crta ravni žlijeb zadate širine između centara njegova dva krajnja luka. Koristite ga za žlijebove kotirane od centra do centra, npr. žlijebove za podešavanje vijaka.",
"help.sketch.slot.overall": "Crta ravni žlijeb zadate širine od jednog vanjskog kraja do drugog. Koristite ga kada je bitna ukupna dužina žlijeba.",
"help.sketch.spline.control_point": "Crta kubni splajn oblikovan kontrolnim tačkama kroz koje ne prolazi. Koristite ga za glatke, tečne krive koje oblikujete pomjeranjem tačaka.",
"help.sketch.spline.control_point_5": "Crta splajn petog stepena oblikovan kontrolnim tačkama. Koristite ga tamo gdje se krive moraju spajati glađom zakrivljenošću, npr. na vidljivim dizajnerskim površima.",
"help.sketch.spline.fit_point": "Crta glatki splajn koji prolazi kroz svaku tačku koju postavite, otvoren ili zatvoren. Koristite ga da oblik provučete kroz poznate tačke.",
"help.sketch.spline3d": "Crta glatku krivu u prostoru kroz tačke, uz uključenu 3D skicu. Koristite ga za putanje povlačenja i vođenje kablova koji izlaze iz ravni skice.",
"help.sketch.spun_profile": "Pravi u ravni skice obris koji tijelo opisuje kada se okreće oko ose. Koristite ga da dobijete siluetu tokarenog dijela za rotiranje ili crtež.",
"help.sketch.text": "Upisuje tekst u skicu izabranim fontom, visinom i uglom; njegove riječi mogu pratiti parametre. Ekstrudirajte ga ili ga napravite reljefom za etikete i natpise.",
"help.sketch.transform": "Skalira, okreće i pomjera geometriju skice oko tačke centra u jednom koraku. Koristite ga da istovremeno promijenite položaj i veličinu izbora.",
"help.sketch.trim": "Uklanja dio krive između najbližih presjeka oko mjesta na koje kliknete. Koristite ga da preklapajuće linije očistite u zatvoren profil.",
"help.solid.align": "Pomjera izabrana tijela tako da se tačka, lice ili ravan na njima poklopi s tačkom, licem ili ravni negdje drugdje. Koristite ga da postavite dio uz drugi bez mjerenja odmaka.",
"help.solid.boundary_fill": "Pravi puna tijela od područja zatvorenih preklapajućim alatnim tijelima, popunjavajući samo područja koja izaberete. Koristite ga da oblik izgradite iz prostora koji nekoliko tijela zajedno ograničava.",
"help.solid.bounding_solid": "Pravi kutiju koja obuhvata izabrana tijela, s neobaveznom marginom. Koristite ga da vidite ukupnu veličinu dijela ili kao pripremak iz kojeg se dio obrađuje.",
"help.solid.box": "Pravi kutiju iz dužine, širine i visine, iz tačke ugla ili centriranu na tački. Koristite je za brz blok, privremeni oblik ili tijelo u koje se urezuju operacije.",
"help.solid.chamfer": "Odsijeca izabrane ivice ravnim skošenjem zadate udaljenosti. Koristite ga da ublažite oštre ivice ili olakšate uvođenje zatika ili vijka.",
"help.solid.coil": "Pravi zavojno tijelo, npr. oprugu, iz prečnika, broja zavoja, visine ili koraka i okruglog ili kvadratnog presjeka. Koristite je za opruge, zavojnice i zavojne grebene.",
"help.solid.combine": "Spaja tijela, oduzima ih od ciljnog tijela ili zadržava samo zajedničku zapreminu, a može zadržati i alatna tijela. Koristite ga da spojite dijelove, da jednim oblikom odsiječete drugi ili da pronađete gdje se dva tijela preklapaju.",
"help.solid.cone": "Pravi konus iz kruga osnove i visine, ili zarubljeni konus kada i vrh ima poluprečnik. Koristite ga za suženja, lijevke, konusna upuštenja i šiljke.",
"help.solid.cylinder": "Pravi cilindar iz poluprečnika ili prečnika i visine, postavljen na tačku duž ose. Koristite ga za osovine, zatike, ispuste i okrugle pripremke.",
"help.solid.draft": "Naginje izabrana lica za ugao koji zadate, mjereno od neutralne ravni. Koristite ga na livenim ili brizganim dijelovima kako bi izašli iz kalupa.",
"help.solid.emboss": "Izdiže profile skice, npr. tekst ili logotip, iz lica, ili ih udubljuje u njega. Koristite ga za natpise, etikete i oznake na ravnim ili okruglim licima.",
"help.solid.extrude": "Izvlači profil skice u 3D duž zadate udaljenosti, dodajući ili uklanjajući materijal. Koristite ga da ravni obris pretvorite u blok, ispust ili džep.",
"help.solid.fillet": "Zaobljuje izabrane ivice glatkim kružnim prelazom zadatog poluprečnika. Koristite ga da omekšate oštre ivice radi čvrstoće, udobnosti ili izgleda.",
"help.solid.hole": "Buši okruglu rupu zadatog prečnika na izabranoj tački lica. Koristite je za prolaze vijaka, zatike i druge jednostavne okrugle otvore.",
@@ -1412,20 +1653,60 @@
"help.solid.move": "Pomjera ili rotira izabrana tijela, ili njihovu kopiju, za udaljenost i ugao koje zadate. Koristite ga da premjestite tijelo ili napravite drugo na drugom mjestu.",
"help.solid.offset_face": "Gura izabrana lica unutra ili van za udaljenost koju zadate, a okolna lica ih prate. Koristite ga da podebljate zid, produbite džep ili povisite ispupčenje bez uređivanja skice.",
"help.solid.pattern.circular": "Ponavlja izabrane objekte oko ose, za zadati broj i ukupni ugao. Koristite ga za krugove vijaka, žbice, rebra hladnjaka i zupce.",
"help.solid.pattern.path": "Kopira operacije ili tijela duž putanje skice, prema broju i razmaku. Koristite ga za rupe duž zakrivljene ivice ili dijelove duž vodilice.",
"help.solid.pattern.rectangular": "Ponavlja izabrane objekte u jednom ili dva smjera sa zadatim brojem i razmakom. Koristite ga za redove i mreže rupa, rebara, zatika ili nožica.",
"help.solid.pipe": "Pravi okruglu cijev duž putanje od kriva skice, punu ili šuplju sa zadatom debljinom zida. Koristite je za cijevi, žice, kablove i šine.",
"help.solid.press_pull": "Dodaje ili uklanja materijal dok povlačite lice, ili zaobljava ivice dok ih povlačite. Koristite ga za brze izmjene direktno na modelu.",
"help.solid.remove": "Uklanja izabrana tijela iz dizajna od ove tačke vremenske trake nadalje; raniji koraci ih zadržavaju. Koristite ga da odbacite pomoćno tijelo, npr. alat za rezanje koji vam više ne treba.",
"help.solid.replace_face": "Premješta izabrana ravna lica na ravan ili na paralelno lice, a tijelo raste ili se smanjuje da ga dosegne. Koristite ga da poravnate lice s drugim dijelom ili pomoćnom ravni.",
"help.solid.revolve": "Okreće profil skice oko ose i pravi okruglo tijelo. Koristite ga za vratila, dugmad, posude i svaki dio koji je okrugao oko središnje linije.",
"help.solid.rib": "Dodaje tanak zid duž otvorene krive skice, popunjen do sljedećih lica tijela. Koristite ga da ojačate zidove i uglove, npr. u brizganim ili štampanim dijelovima.",
"help.solid.scale": "Povećava ili smanjuje izabrana tijela za faktor, jednako u svim smjerovima ili posebno po osi. Koristite ga da promijenite veličinu uvezenog oblika ili uračunate skupljanje.",
"help.solid.shell": "Izdubljuje tijelo i ostavlja zidove zadate debljine, a može otvoriti lica koja izaberete. Koristite ga za kutije, poklopce i druge tankozidne dijelove.",
"help.solid.sphere": "Pravi sferu iz poluprečnika ili prečnika oko tačke centra. Koristite je za kugle, ručke i zaobljene krajeve.",
"help.solid.split_body": "Reže tijelo na zasebna tijela ravni, licem drugog tijela ili linijom skice. Koristite ga za dijelove koji se zasebno štampaju, obrađuju ili boje.",
"help.solid.split_face": "Dijeli izabrana lica po ravni, licu drugog tijela ili krivama skice, bez rezanja tijela. Koristite ga da dijelu lica date vlastiti nagib, boju ili odmak.",
"help.solid.sweep": "Povlači profil skice duž putanje i pravi tijelo. Koristite ga za cijevi, šine, ručke i svaki dio konstantnog presjeka koji prati krivu.",
"help.solid.thread": "Dodaje ISO metrički navoj na cilindrično lice, kao kozmetički navoj ili urezan u tijelo. Koristite ga za vijke, navojne rupe i štampane dijelove s navojem.",
"help.solid.torus": "Pravi prsten iz velikog poluprečnika oko njegove ose i malog poluprečnika za okrugli presjek. Koristite ga za O-prstenove, ručke i okrugle perle.",
"help.solid.web": "Dodaje tanak zid duž svake otvorene krive skice, svaki popunjen do sljedećih lica tijela. Koristite ga za mrežu potpornih zidova unutar kućišta i posuda.",
"help.surface.blend": "Pravi površ koja premošćuje ivice dvaju tijela, tangentnu ili kontinuiranu po zakrivljenosti s oba. Koristite je da dvije površi glatko spojite preko razmaka.",
"help.surface.boundary": "Pravi površ koja ispunjava petlju od dvije do četiri krive skice, ili mrežu kriva. Koristite je za slobodne panele određene svojim ivicama.",
"help.surface.extend": "Produžava površ preko njenih ivica, za neku udaljenost ili do ravni ili lica. Koristite ga da se površi preklope prije nego što ih odsiječete ili sašijete.",
"help.surface.extrude": "Izvlači krive ili profile skice na zadatu udaljenost u površ, samo zidove, bez poklopaca. Koristite ga za tanke zidove i za alate za rezanje ili razdvajanje.",
"help.surface.fair": "Izglađuje unutrašnjost slobodnih lica dok njihove ivice ostaju na mjestu. Koristite ga da izravnate izbočine i valovite refleksije u površi.",
"help.surface.loft": "Pravi površ koja redom prolazi kroz dva ili više presjeka, bez poklopaca. Koristite je za ljuske i prelaze između različitih oblika.",
"help.surface.match": "Ponovo gradi ivicu površi tako da se sastaje s ivicom drugog tijela, uz podudarnu tangentu ili zakrivljenost. Koristite ga da glatko zatvorite šav između dvije površi.",
"help.surface.offset": "Pravi kopiju lica na udaljenosti duž njihovih normala, kao novo tijelo površi. Koristite ga za omotače, zazore ili drugu stranu zida.",
"help.surface.patch": "Ispunjava područje skice ili zatvorenu petlju ivica licem, ravnim ili glatko zakrivljenim. Koristite ga da zatvorite rupe u tijelu površi.",
"help.surface.reverse_normal": "Okreće lica tijela površi, pa njihova vanjska strana gleda na drugu stranu. Koristite ga prije podebljanja ili šivenja kada lica gledaju prema unutra.",
"help.surface.revolve": "Okreće krive ili profile skice oko ose u površ, bez poklopaca. Koristite ga za okrugle ljuske poput abažura, zdjela i mlaznica.",
"help.surface.ruled": "Pravi traku pravih linija iz lanca ivica, duž normale lica, tangente ili pod uglom. Koristite ga za zidove s nagibom, odvodne površi i površi razdvajanja.",
"help.surface.stitch": "Šije tijela površi zajedno duž njihovih zajedničkih ivica, u puno tijelo kada se zatvore. Koristite ga da skup površi pretvorite u jedno tijelo.",
"help.surface.sweep": "Pomjera krive ili profile skice duž putanje u površ, bez poklopaca. Koristite ga za žljebove, lajsne i druge tanke oblike koji prate putanju.",
"help.surface.thicken": "Daje tijelu površi debljinu i pretvara ga u puno tijelo. Koristite ga da od površi napravite dijelove pogodne za štampu ili obradu.",
"help.surface.trim": "Siječe površ ravni, drugom površi ili krivama skice i zadržava stranu koju izaberete. Koristite ga da površi oblikujete prije šivenja.",
"help.surface.unstitch": "Razdvaja lica tijela u zasebna tijela površi; puno tijelo se raspada na površi. Koristite ga da preradite pojedinačna lica tijela.",
"help.surface.untrim": "Uklanja rupe iz lica, ili vraća njegovu vanjsku granicu na prirodne granice površi. Koristite ga da nakon odsijecanja vratite cijelu površ.",
"help.third_party": "Licence trećih strana",
"help.timeline.compute_all": "Ponovo gradi svaku operaciju na vremenskoj traci od početka. Koristite ga da osvježite model i vidite koje operacije ne uspijevaju.",
"help.timeline.delete": "Briše izabrane operacije s vremenske trake; brisanje skice briše i operacije napravljene iz nje. Koristite ga da uklonite korak koji više ne želite.",
"help.timeline.dependents": "Navodi operacije koje bi bile izbrisane zajedno s nekom operacijom i one koje bi bez nje otkazale. Koristite ga prije brisanja ili mijenjanja operacije.",
"help.timeline.play": "Reprodukuje preostale operacije jednu po jednu, s pauzom od pola sekunde nakon svake ponovne izgradnje. Pokrenite ovu komandu ponovo ili pritisnite Escape da zaustavite; Idi na kraj skače odmah.",
"help.view.apply": "Okreće prikaz u standardni smjer, npr. sprijeda, odozgo ili izometrijski, ili u sačuvani imenovani prikaz. Koristite ga da model posmatrate iz utvrđenog ugla.",
"help.view.delete": "Briše sačuvani imenovani prikaz. Model se ne mijenja.",
"help.view.fit": "Zumira i pomjera prikaz tako da cijeli model, ili izbor, ispuni prikaz. Koristite ga kada model nestane iz vidokruga.",
"help.view.grid": "Prikazuje ili sakriva mrežu u prozoru prikaza. Koristite je kao vodič za veličinu i orijentaciju tokom modeliranja.",
"help.view.ground_shadow": "Prikazuje ili sakriva sjenu modela na podlozi. Koristite je da lakše procijenite visinu i položaj.",
"help.view.home": "Vraća prikaz na početni smjer i uklapa model. Koristite ga da se brzo vratite na poznat prikaz.",
"help.view.list": "Navodi imenovane prikaze sačuvane u dizajnu. Koristite ga da pronađete prikaz koji ste ranije sačuvali.",
"help.view.navigate": "Okreće, pomjera ili zumira prikaz u koracima, npr. četvrtinu okreta ulijevo. Koristite ga da kameru pomjerate precizno umjesto povlačenjem.",
"help.view.navigation_bar": "Prikazuje ili sakriva navigacijsku traku na dnu prozora prikaza. Sakrijte je za više prostora, prikažite je da alatima prikaza pristupate mišem.",
"help.view.rename": "Daje sačuvanom imenovanom prikazu novi naziv. Sam prikaz ostaje isti.",
"help.view.save": "Čuva trenutni položaj kamere kao imenovani prikaz u dizajnu. Koristite ga da se kasnije vratite na isti ugao, npr. za snimke ekrana.",
"help.view.spin": "Neprekidno orbitira prikaz oko modela do sljedećeg klika ili tipke. Koristite ga da model predstavite sa svih strana.",
"help.view.tool": "Zaključava alat kamere, orbitiranje, pomjeranje ili zumiranje, tako da povlačenje lijevim dugmetom miša pomjera prikaz dok ga ne isključite. Koristite ga na touchpadu ili bez srednjeg dugmeta miša.",
"help.view.visual_style": "Mijenja način crtanja modela: osjenčeno sa ili bez ivica, žičani model ili sa skrivenim ivicama. Koristite žičani model da vidite unutra, a osjenčeno za čist izgled.",
"history.sketch_under_constrained": "Nedovoljno ograničeno (stepeni slobode: {count})",
"history.sketch_under_constrained_one": "Nedovoljno ograničeno ({count} stepen slobode)",
"hole_presets.confirm_delete": "Potvrdi brisanje",
@@ -1602,6 +1883,10 @@
"material.selection.mixed": "Različiti materijali",
"measure.at": "Na",
"measure.item": "Stavka {number}",
"measure.quick_copied": "Mjerenje kopirano",
"measure.quick_copy": "Kliknite da kopirate mjerenje",
"measure.quick_edges": "Ivice: {count}",
"measure.quick_faces": "Lica: {count}",
"messages.ago_h": "prije {n} h",
"messages.ago_min": "prije {n} min",
"messages.ago_s": "prije {n} s",
@@ -1635,6 +1920,7 @@
"nav.hfss.help": "Alt+lijevo povlačenje pomjera prikaz, Shift+Alt+lijevo zumira, Ctrl+lijevo povlačenje na praznom prostoru orbitira",
"nav.inventor.help": "Povlačenje srednjim dugmetom pomjera, Shift+srednje orbitira, Ctrl+srednje zumira, povlačenje desnim orbitira",
"nav.maya.help": "Alt+lijevo povlačenje orbitira, Alt+srednje pomjera, Alt+desno zumira; srednje povlačenje također pomjera",
"nav.nx.help": "Povlačenje srednjim dugmetom orbitira, Shift+srednje pomjera (u NX-u srednje + desno), Ctrl+srednje zumira (u NX-u srednje + lijevo); povlačenje desnim dugmetom ne dira kameru",
"nav.openscad.help": "Povlačenje lijevim dugmetom po praznom prostoru orbitira, Shift+povlačenje lijevim bira okvirom, povlačenje geometrije skice je uređuje; povlačenje desnim ili srednjim dugmetom pomjera, Shift+desno ili Shift+srednje zumira",
"nav.perspective.sketch": "Uređivanje skice koristi ortografski prikaz. Završite skicu da prebacite na perspektivu.",
"nav.right_drag_orbits.help": "Isključeno: povlačenje desnim dugmetom ne dira kameru, a desno dugme samo otvara kontekstni meni. Izbor mapiranja miša SolidWorks to isključuje.",
@@ -1754,6 +2040,11 @@
"not a move": "nije pomjeranje",
"not a number": "nije broj",
"open a sketch first": "prvo otvorite skicu",
"parameters.apply_live": "Primijeni svaku promjenu kad napustite njeno polje",
"parameters.apply_live_tip": "Isključeno: promjene čekaju na Primijeni ili U redu, a zatim se dizajn jednom ponovo gradi za sve njih. Otkaži ili Escape ih odbacuje.",
"parameters.held": "Promjene na čekanju za Primijeni: {count}",
"parameters.held_hint": "Uređujte izraze i jedinice. Promjene čekaju na Primijeni ili U redu.",
"parameters.held_one": "1 promjena na čekanju za Primijeni",
"parts.automatic_name": "automatski",
"parts.dimension.across_flats": "Širina ključa",
"parts.dimension.bore": "Provrt",
@@ -1776,6 +2067,8 @@
"parts.family.set_screw": "Bezglavi vijak s imbusom, ravni vrh",
"parts.family.socket_head_cap_screw": "Vijak s cilindričnom glavom i imbusom",
"parts.family.washer": "Obična podloška",
"parts.holes": "Rupe (neobavezno)",
"parts.placement_hint": "izaberite jednu ili više rupa da u svaku postavite dio, ili ga U redu postavlja u ishodište",
"pattern.alignment_2": "Poravnanje 2",
"pattern.alignment_hint": "Broj uključuje original. Centar ravnomjerno raspoređuje kopije; Obrni bira dodatnu stranu za parne ukupne brojeve.",
"pattern.direction_1": "Smjer 1",
@@ -1828,6 +2121,8 @@
"prefs.bottom_centre": "Dolje u sredini",
"prefs.bottom_right": "Dolje desno",
"prefs.browser": "Stablo modela",
"prefs.browser_eye_left": "Oko pored strelice za skupljanje",
"prefs.browser_eye_left_note": "Redovi stabla modela prikazuju oko za prikazivanje/skrivanje pored strelice za skupljanje, kao ranije, umjesto u koloni na desnom kraju redova.",
"prefs.browser_reveal_on_hover": "Prikaži pri prelasku mišem",
"prefs.browser_show_note": "Prikazano drži stablo modela na ekranu. Prikaži pri prelasku mišem drži ga skrivenim dok pokazivač ne pređe preko njegovog mjesta ili tankog jezička na njegovom lijevom rubu, a ostaje otvoreno dok je pokazivač u njemu ili dok je otvoren neki meni iz njega.",
"prefs.browser_shown": "Prikazano",
@@ -1847,6 +2142,8 @@
"prefs.ctrl_cmd": "Ctrl / Cmd",
"prefs.curved_surfaces": "Zakrivljene površi",
"prefs.curves": "Zakrivljene površi",
"prefs.dark_parts_shading": "Zadrži oblik tamnih dijelova",
"prefs.dark_parts_shading_help": "Sjenči prikaz u linearnom svjetlu s mekim dopunskim svjetlom neba, tako da tamna lica zadrže gradijent umjesto da pocrne",
"prefs.dead_zone_note": "Šrafirana traka je mrtva zona: kretanje unutar nje se zanemaruje.",
"prefs.default": "podrazumijevano",
"prefs.design": "Podrazumijevane postavke dizajna",
@@ -1861,6 +2158,8 @@
"prefs.edges": "Ivice i sjenčenje",
"prefs.extend": "Produži",
"prefs.extra_navigation": "Ctrl + povlačenje lijevim dugmetom po praznom prostoru orbitira; držite razmaknicu za pomjeranje.",
"prefs.extrude_view_isometric": "Okreni prikaz u izometrijski kada se otvori Ekstrudiraj",
"prefs.extrude_view_return": "Vrati se na prethodni prikaz kada se Ekstrudiraj zatvori",
"prefs.follows_os": "Prati operativni sistem",
"prefs.font": "Font",
"prefs.font_fallback": "Znakovi koji nedostaju izabranom fontu prikazuju se ugrađenim fontom, tako da nijedna oznaka ne prikazuje prazan kvadrat.",
@@ -1876,8 +2175,11 @@
"prefs.graphics_card_auto": "Automatski",
"prefs.graphics_card_missing": "Sačuvana grafička kartica nije dostupna; koristi se Automatski.",
"prefs.graphics_card_restart": "Stupa na snagu nakon ponovnog pokretanja SolidCrafta. WGPU_ADAPTER_NAME ima prednost nad ovim izborom.",
"prefs.graphics_card_unused": "Sačuvana grafička kartica nije korištena za ovaj prozor; kartica koja se koristi prikazana je u Pomoć › O programu i u statusnoj traci. Pokušajte s opcijom Automatski ili postavite WGPU_ADAPTER_NAME na jedinstveni dio naziva kartice.",
"prefs.graphics_note": "Žrtvujte brzinu za glatkoću. Svaki izbor pokazuje svoj učinak na primjeru.",
"prefs.graphics_presets_note": "Unaprijed postavljene vrijednosti podešavaju sve izbore ispod odjednom.",
"prefs.hide_constraint_glyphs_zoomed_out": "Sakrij simbole ograničenja kada je prikaz umanjen",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Simboli ograničenja nestaju iz prikaza kada je skica toliko umanjena da bi je zatrpali. Isključeno: ostaju fiksne veličine na ekranu pri svakom zumiranju.",
"prefs.history": "Vremenska traka",
"prefs.history_origin_bodies": "Prikaži ishodište i tijela na vrhu",
"prefs.history_origin_bodies_note": "Vremenska traka lijevo ili ispod stabla modela počinje folderom Tijela aktivne komponente i Ishodištem, s njihovim okom i menijima kao u stablu modela. Vremenska traka na dnu ostaje nepromijenjena.",
@@ -1888,6 +2190,8 @@
"prefs.history_sketches_note": "Kada je Skice postavljeno na Ispod svoje operacije, vremenska traka lijevo prikazuje svaku skicu jedan nivo uvučeno, ispod prve operacije napravljene iz nje, kad god je dovoljno široka za nazive. Vremenska traka na dnu uvijek zadržava redoslijed nastanka.",
"prefs.history_sketches_under_feature": "Ispod svoje operacije",
"prefs.hold_stop_snapping": "Držite da isključite privlačenje",
"prefs.home_fit_size": "Veličina za Početni prikaz i Uklopi",
"prefs.home_fit_size_note": "Koliki dio prikaza model ispunjava kada ga uokviri Početni prikaz, Uklopi, otvaranje dizajna ili promjena prikaza u navigatoru, bez obzira na to je li postavka ispod uključena. 100 %: ispunjava prikaz između panela, kompasa i navigacijske trake.",
"prefs.icons_only": "Samo ikone",
"prefs.imported_file": "Uvezena datoteka",
"prefs.in_browser": "U stablu modela",
@@ -1896,13 +2200,20 @@
"prefs.interface_language": "Jezik interfejsa",
"prefs.interface_note": "Kako SolidCraft izgleda i gdje su njegovi dijelovi.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_navigator_arrows": "Obrni strelice za rotaciju kocke prikaza",
"prefs.invert_navigator_arrows_note": "Strelice oko kocke orbitiraju kameru u smjeru u kojem pokazuju, kao u programima Fusion i Inventor: strelica s lijeve strane pokazuje udesno i zakreće kameru udesno. Isključeno: strelica okreće kocku prema strani na kojoj se nalazi, pa lijeva strelica zakreće kameru ulijevo. Postavka ne utiče na zakrivljene strelice za valjanje.",
"prefs.keep_work_on_tab_switch": "Zadrži dijaloge i alate otvorenima pri prebacivanju između dizajna",
"prefs.keep_work_on_tab_switch_note": "Svaka kartica dizajna zadržava svoj otvoreni dijalog ili alat, s onim što ste u njemu izabrali i upisali, te svoju karticu trake s alatima. Kada se vratite na dizajn, ponovo su tu. Isključeno: prebacivanje između dizajna otkazuje ih i prikazuje karticu Model.",
"prefs.labels": "Oznake",
"prefs.language_system": "Sistem",
"prefs.layout": "Raspored",
"prefs.layout_note": "Kompaktno pričvršćuje panele uz ivice prozora. Prozirnost stabla modela dostupna je u opciji Plutajuće. Stranu Asistenta promijenite na stranici Asistent.",
"prefs.left": "Lijevo",
"prefs.left_click": "Lijevi klik",
"prefs.line_drag_draws_one": "Pritisak i povlačenje crta jednu liniju",
"prefs.line_drag_draws_one_note": "U alatu Linija pritisak, povlačenje i puštanje crta jednu liniju od mjesta pritiska do mjesta puštanja i tu se završava. Klikovi i dalje crtaju lanac. Isključeno: povlačenje postavlja tačku tamo gdje se pusti, kao i klik.",
"prefs.line_hover_tangent_arc": "Zadržite pokazivač na posljednjoj tački linije da pređete na tangentni luk",
"prefs.line_hover_tangent_arc_note": "U alatu Linija, nakon što je nacrtan segment, odmaknite pokazivač od posljednje tačke lanca, vratite ga na nju i držite ga mirno: sljedeći klik završava tangentni luk, a isti pokret vraća na linije. Isključeno: zadržavanje na toj tački ne radi ništa.",
"prefs.linked_designs": "Povezani dizajni",
"prefs.live_input": "Ulaz uživo",
"prefs.live_preview": "Pregled uživo",
@@ -1916,6 +2227,9 @@
"prefs.max_file_size_step": "STEP i uvezene datoteke",
"prefs.max_file_size_web": "Web aplikacija otvara STEP i uvezene datoteke do 512 MB i dizajne do 256 MB: kartica preglednika ne može držati veće datoteke. Desktop aplikacija može povećati ove granice.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Velike ikone",
"prefs.menu_list": "Popis",
"prefs.menus": "Meniji",
"prefs.minutes": "minuta",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Miš i trackpad",
@@ -1923,6 +2237,8 @@
"prefs.mouse_mapping": "Mapiranje miša",
"prefs.navigation": "Navigacija",
"prefs.navigation_note": "Kako miš, trackpad i 3D miš pomjeraju prikaz.",
"prefs.navigator_fit": "Uklopi model nakon promjene prikaza u navigatoru",
"prefs.navigator_fit_note": "Lice, ugao ili strelica kocke prikaza, odnosno osa alata za orijentaciju, također usmjerava pogled na sredinu modela s jedne udaljenosti na kojoj se uklapa iz svakog smjera (njegova granična sfera). Početni prikaz koristi isti zum i centar dok je ova opcija uključena, pa se prikazi koje izaberete poslije njega samo okreću. Isključeno: prikaz se okreće na mjestu i zadržava svoj cilj i zum.",
"prefs.network_host_remove": "Ukloni",
"prefs.network_locations": "Dozvoli mrežne lokacije u dizajnima",
"prefs.network_locations_note": "Isključeno: povezane datoteke dizajna na drugom računaru (\\\\server\\share) otvaraju se samo sa servera navedenih ovdje ili nakon što ga dozvolite kada se to zatraži.",
@@ -1935,10 +2251,16 @@
"prefs.origin_plane_names": "Nazivi ravni ishodišta",
"prefs.origin_plane_names_note": "Nazivi prikazani u stablu modela, dijalozima i listama. {top} je XY, {front} je XZ, a {right} je YZ, sa Z prema gore. Komande, skripte i sačuvani dizajni zadržavaju XY, XZ i YZ.",
"prefs.overall_speed": "Ukupna brzina",
"prefs.parameter_list_rows": "Redovi popisa parametara",
"prefs.parameter_list_rows_tip": "Strelica prema dolje u praznom polju vrijednosti navodi parametre, favorite prvo, do ovog broja. Manji broj održava popis laganim u velikom dizajnu.",
"prefs.persistent_ribbon_pressed": "Istakni aktivne komande na traci",
"prefs.persistent_ribbon_pressed_help": "Ističe komande na traci dok su njihovi efekti uključeni. Isključite da biste zadržali prethodni neutralni izgled i pomoć za komande.",
"prefs.perspective_fov": "Vidno polje",
"prefs.placement": "Postavljanje",
"prefs.prefer_free_sketch_points": "Daj prednost pokretnim tačkama skice kada se tačke preklapaju",
"prefs.preview_choices": "Pregled vaših izbora",
"prefs.print_face_highlight": "Isticanje površine za štampu",
"prefs.print_face_highlight_tip": "Koliko se jako površina za štampu tijela boji i obrubljuje u prikazu. 0 % ne prikazuje isticanje.",
"prefs.profile_active": "Aktivni profil",
"prefs.profile_applied": "Profil je primijenjen.",
"prefs.profile_changed": "Postavke su promijenjene otkako je profil sačuvan. Sačuvaj ih upisuje u profil.",
@@ -1960,9 +2282,13 @@
"prefs.quality_fastest": "najbrže",
"prefs.quality_slowest": "najsporije",
"prefs.quality_smoother": "glađe",
"prefs.quick_measure": "Brzo mjerenje u uglu prikaza",
"prefs.quick_measure_note": "Veličina izbora u donjem desnom uglu prikaza: dužina ivice, ili Ø i R za okruglu, površina lica, položaj tačke, udaljenost i ugao dviju stavki, ukupna vrijednost više ivica ili lica. Klik je kopira.",
"prefs.render_previous_geometry_budget": "Koristi prethodni budžet geometrije za pregled renderovanja",
"prefs.render_previous_geometry_budget_help": "Vraća prethodno fiksno ograničenje geometrije (512 MiB na 64-bitnim desktop računarima, 128 MiB drugdje) umjesto budžeta prema uređaju.",
"prefs.reports_saved": "Sačuvano: {n}",
"prefs.ribbon_click_ends_tool": "Klik na dugme aktivnog alata završava ga",
"prefs.ribbon_click_ends_tool_note": "Klik na istaknuto dugme na traci alata za skiciranje koji je u upotrebi završava taj alat, pa klikovi u prikazu ponovo služe za biranje. Isključeno: klik ponovo pokreće alat.",
"prefs.ribbon_reveal": "Prikaži rezultate pretrage na traci",
"prefs.ribbon_reveal_note": "Zadržavanje pokazivača iznad rezultata u Pretraži komande pokazuje gdje se komanda nalazi na traci: njenu karticu, njeno dugme ili njen red u padajućem meniju panela. Komanda pokrenuta iz pretrage tamo trepće, a pokrenuta komanda iz padajućeg menija označava svoj panel.",
"prefs.right_click": "Desni klik",
@@ -1973,6 +2299,8 @@
"prefs.search_empty": "Nema odgovarajućih postavki",
"prefs.select": "Izbor",
"prefs.select_one_item": "Izaberi jednu stavku",
"prefs.select_other_hidden": "Izaberi skrivenu geometriju pomoću Izaberi drugo",
"prefs.select_other_hidden_help": "Uključeno: obuhvata skrivena tijela, skice i zaklonjene ivice u blizini pokazivača. Isključeno: koristi se izvorni popis vidljive geometrije ispod pokazivača.",
"prefs.selecting": "Biranje",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift samo dodaje; Ctrl / Cmd uključuje i isključuje",
@@ -1984,11 +2312,16 @@
"prefs.sketch_align_points_note": "Tokom crtanja tačka se istovremeno poravnava vodoravno s jednom tačkom skice i okomito s drugom, uz tačkastu pomoćnu liniju, a klik zadržava oba poravnanja. Isključeno: samo s prethodnom tačkom, po jedna osa.",
"prefs.sketch_conflict_colour": "Geometrija u sukobu u boji sukoba",
"prefs.sketch_conflict_colour_note": "Dok su ograničenja skice u sukobu, krive i tačke koje drži ograničenje koje ne uspijeva crtaju se također u boji sukoba, ne samo njegov simbol ili kota. Isključeno: zadržavaju uobičajene boje.",
"prefs.sketch_construction_lines": "Prošireni alati za pomoćne prave",
"prefs.sketch_exit_view": "Kada se skica završi",
"prefs.sketch_exit_view.keep": "Zadrži trenutni prikaz",
"prefs.sketch_exit_view.previous": "Vrati se na prethodni prikaz",
"prefs.sketch_exit_view.unless_rotated": "Vrati se osim ako je rotirano",
"prefs.sketch_exit_view_note": "Kuda prikaz ide nakon Završi skicu, Otkaži ili dvostrukog Esc.",
"prefs.sketch_face_activates_part": "Skiciranje na licu dijela čini taj dio aktivnim",
"prefs.sketch_face_activates_part_note": "Skica započeta na licu tijela druge komponente prvo aktivira tu komponentu, pa skica pripada njoj i u stablu modela ostaje uz taj dio. Taj dio nakon toga ostaje aktivan. Isključeno: skica pripada aktivnoj komponenti.",
"prefs.sketch_open_look_at": "Okreni kameru prema skici kad se otvori",
"prefs.sketch_open_look_at_note": "Isključeno: kad napravite ili uredite skicu, prikaz ostaje onakav kakav jest. Pogledaj okreće prikaz prema skici kad to želite.",
"prefs.sketch_recent_snap": "Automatski privlači na nedavne centre i središnje tačke",
"prefs.sketch_recent_snap_note": "Tokom crtanja pokazivač se privlači tamo gdje se sijeku pomoćne linije dvaju posljednjih centara krugova ili središnjih tačaka pravih ivica. Isključeno: te pomoćne linije služe samo za prikaz i pokazivač se ne pomjera.",
"prefs.sketch_selection": "Skica i izbor",
@@ -2000,6 +2333,8 @@
"prefs.sketch_tab_always_note": "Kartica Skica ostaje na traci, odmah iza Modela, dok nijedna skica nije otvorena. Njeni alati za crtanje tada pokreću skicu na izabranoj ravni ili licu, ili na ravni koju sljedeću izaberete, s alatom spremnim u njoj.",
"prefs.sketch_tracking_guides": "Pomoćne linije poravnanja centra i središnje tačke",
"prefs.sketch_tracking_guides_note": "Zadržite pokazivač iznad centra kruga ili središnje tačke prave ivice, pa se pomjerite vodoravno ili okomito da se prikaže privremena tačkasta pomoćna linija.",
"prefs.sketch_tracking_points": "Pomoćne linije poravnanja iz krajnjih tačaka i uglova tokom crtanja",
"prefs.sketch_tracking_points_note": "Dok alat za crtanje čeka tačku, prelazak mišem preko kraja linije ili luka, ugla pravougaonika ili slobodne tačke prikazuje istu privremenu tačkastu pomoćnu liniju kao centar ili središnja tačka. Isključeno: samo centri i središnje tačke.",
"prefs.sketching": "Skiciranje",
"prefs.snap_drawing_note": "Tokom crtanja ili povlačenja tačke skice.",
"prefs.source": "Izvor",
@@ -2016,6 +2351,24 @@
"prefs.status_error": "Greška",
"prefs.status_selected": "Izabrano",
"prefs.status_warning": "Upozorenje",
"prefs.structure.appearance": "Izgled",
"prefs.structure.appearance_note": "Boje, tekst i pristupačnost.",
"prefs.structure.in_profile": "U profilu",
"prefs.structure.layout": "Raspored",
"prefs.structure.layout_note": "Gdje se nalaze dijelovi prozora.",
"prefs.structure.look": "Vizuelno",
"prefs.structure.measure_pointer": "Mjerenje i pokazivač",
"prefs.structure.mouse3d": "3D miš",
"prefs.structure.mouse3d_note": "SpaceMouse i SpaceNavigator.",
"prefs.structure.navigation_note": "Miš i trackpad. 3D miš ima vlastitu stranicu.",
"prefs.structure.profile": "Profil",
"prefs.structure.profile_edited": "Promijenjeno otkako je sačuvano",
"prefs.structure.profile_hover": "Sačuvano u profilima postavki",
"prefs.structure.scale_percent": "{percent}%",
"prefs.structure.sketch": "Skica",
"prefs.structure.tools": "Alati",
"prefs.structure.tools_note": "Kako se ponašaju alati za modeliranje.",
"prefs.structure.work": "Rad",
"prefs.supersampling_brief": "Iscrtava prikaz većim i smanjuje ga filtriranjem: vrlo oštro i zahtjevnije.",
"prefs.tab_accent_note": "Naglasna boja prati aktivnu karticu",
"prefs.theme_file": "datoteka teme",
@@ -2027,6 +2380,16 @@
"prefs.theme_follow_off": "Isključeno",
"prefs.theme_follow_stop": "Prestani pratiti",
"prefs.theme_import_hint": "Kliknite da izaberete datoteku teme, ispustite je ovdje ili pritisnite Ctrl+V (Cmd+V na Macu) kada je tema kopirana",
"prefs.theme_option_opacity": "Neprozirnost",
"prefs.theme_option_opacity_help": "Podesite neprozirnost plutajućih panela. Donja granica čitljivosti štiti tekst preko boja prozora prikaza.",
"prefs.theme_option_tab_colours": "Boje kartica",
"prefs.theme_option_tab_colours_help": "Koristi boje panela i prozora prikaza ove teme za svaku karticu trake.",
"prefs.theme_option_tab_fade_ms": "Prelaz kartica",
"prefs.theme_option_tab_fade_ms_help": "Boje panela i prozora prikaza postepeno se mijenjaju pri prelasku na drugu karticu. Nula mijenja odmah.",
"prefs.theme_options_badge": "Opcije",
"prefs.theme_options_more": "Više · {count}",
"prefs.theme_options_reset": "Resetuj",
"prefs.theme_options_unavailable": "Nedostupno na ovom uređaju",
"prefs.theme_paste": "Zalijepi temu",
"prefs.theme_paste_none": "Međuspremnik ne sadrži SolidCraft temu",
"prefs.timeline_half_hour": "−30 min",
@@ -2048,13 +2411,23 @@
"prefs.under_cursor": "Ispod pokazivača",
"prefs.units_precision": "Jedinice i preciznost",
"prefs.updates": "Ažuriranja",
"prefs.value_boxes_in_view": "Prikaži vrijednosti pored njihovih ručica u prikazu",
"prefs.value_boxes_in_view_tip": "Vrijednost komande (Udaljenost, Poluprečnik…) u polju pored njene ručice, preko modela. Isključeno: upisujete je u prozoru komande, koji preuzima tastaturu nakon izbora, i ništa ne prekriva model.",
"prefs.view": "Ponašanje prikaza",
"prefs.view_confirm_corner": "U redu i Otkaži u uglu prikaza",
"prefs.view_confirm_corner_note": "Dok je otvoren dijalog ili skica, kvačica i krstić gore desno u prikazu rade isto što i dugmad U redu i Otkaži u dijalogu, odnosno završavaju i otkazuju skicu. Navigator prikaza pomjera se ispod njih.",
"prefs.viewport": "Prozor prikaza",
"prefs.viewport_analysis_status": "Prikaži status analize u prozoru prikaza",
"prefs.viewport_analysis_status_help": "Prikazuje aktivne analize iznad prozora prikaza, otvara njihove legende iz svake stavke, a tipka Esc isključuje najnoviju analizu. Isključite da biste vratili legende u uglovima i prethodno ponašanje tipke Esc.",
"prefs.viewport_background": "Pozadina",
"prefs.viewport_background_colour": "Boja",
"prefs.viewport_background_theme": "Gradijent teme",
"prefs.viewport_section_status": "Prikaži status presjeka u prozoru prikaza",
"prefs.viewport_section_status_help": "Prikazuje postavku presjeka u prozoru prikaza. Escape i aktivno dugme na traci je isključuju. Isključite da zadržite prethodne kontrole presjeka.",
"prefs.viewport_status_details": "Detalji prozora prikaza uživo",
"prefs.viewport_status_details_help": "Uređujte postavke aktivne analize i presjeka u standardnim karticama s detaljima. Isključite da vratite legende analize samo za čitanje i dijalog Presjek.",
"prefs.viewport_status_summary": "Sažmi stavke statusa prozora prikaza",
"prefs.viewport_status_summary_help": "Sažima tri ili više stavki statusa prozora prikaza u jednu sažetu oznaku. Isključite da svaka stavka ostane u punom redu.",
"prefs.wheel": "Točkić",
"prefs.wheel_trackpad": "Točkić i trackpad",
"prefs.workspace": "Radni prostor",
@@ -2064,6 +2437,10 @@
"properties.title": "Svojstva: {name}",
"recovery.days_ago": "prije {count} dana",
"recovery.detail": "{file} · operacija: {features} · automatski sačuvano {when}",
"recovery.discard_all": "Odbaci sve",
"recovery.discard_all_detail": "Ovo se ne može poništiti.",
"recovery.discard_all_title": "Odbaciti automatska čuvanja ({count})?",
"recovery.discard_all_title_one": "Odbaciti 1 automatsko čuvanje?",
"recovery.hours_ago": "prije {count} h",
"recovery.just_now": "upravo sada",
"recovery.min_ago": "prije {count} min",
@@ -2929,7 +3306,9 @@
"render.output_ready": "Spremno za renderovanje",
"render.output_run_check": "Pokreni provjeru",
"render.output_size_limit": "{method}: do {dimension} px po strani i {pixels} piksela. Najveća veličina koja staje: {width} × {height}.",
"render.output_size_method_profile": "Za renderovanje {width} × {height} izaberite {method} s CPU profilom resursa {profile} ili manju veličinu.",
"render.output_size_ok": "Veličina u redu · {width} × {height} px",
"render.output_size_profile": "Za renderovanje {width} × {height} izaberite CPU profil resursa {profile} ili manju veličinu.",
"render.output_swap_orientation": "Zamijeni širinu i visinu",
"render.output_tools_inspector": "Veličinu i format uredite u inspektoru Izlaz.",
"render.output_view_image": "Prikaži sliku",
@@ -3142,6 +3521,7 @@
"render.size_custom": "Prilagođeno…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · potrebno: {profile}",
"render.size_presets": "Predlošci veličine",
"render.size_presets_pending": "Stiže s uređivačem izlaza",
"render.size_preview": "Pregled",
@@ -3429,6 +3809,24 @@
"sheet.flange_type_contour": "Kontura",
"sheet.flange_type_edge": "Ivica",
"sheet.no_body": "nema limenog tijela: napravite ga pomoću Prirubnica > Osnova ili pretvorite ploču u lim pomoću Pretvori u lim (izaberite njeno veliko ravno lice)",
"sheet.personal_rules.confirm_delete": "Potvrdi brisanje",
"sheet.personal_rules.delete": "Izbriši",
"sheet.personal_rules.delete_prompt": "Izbrisati ovo lično pravilo? Pravila već u dizajnima ostaju nepromijenjena.",
"sheet.personal_rules.full": "Lična biblioteka već sadrži 64 pravila.",
"sheet.personal_rules.invalid_name": "Unesite kratak naziv bez prijeloma reda ili kontrolnih znakova.",
"sheet.personal_rules.invalid_values": "Dopunite vrijednosti pravila: svaka se mora izračunati u ovom dizajnu, s pozitivnom debljinom i K-faktorom od 0 do 1.",
"sheet.personal_rules.missing": "Izabrano lično pravilo više nije dostupno.",
"sheet.personal_rules.name": "Naziv pravila",
"sheet.personal_rules.name_exists": "Lično pravilo s ovim nazivom već postoji. Koristite drugi naziv.",
"sheet.personal_rules.name_hint": "Jedinstven naziv",
"sheet.personal_rules.overwrite": "Sačuvaj promjene u izabranom ličnom pravilu",
"sheet.personal_rules.rename": "Preimenuj",
"sheet.personal_rules.rule": "Lično pravilo",
"sheet.personal_rules.save": "Sačuvaj novo",
"sheet.personal_rules.scope": "Čuva se u vašem profilu za sve dizajne.",
"sheet.personal_rules.select": "Izaberite pravilo",
"sheet.personal_rules.title": "Lična pravila",
"sheet.personal_rules.unsupported": "Ovo pravilo ima vrijednosti koje ova verzija ne može koristiti.",
"shortcut.json_error": "parametri moraju biti JSON: {error}",
"shortcut.no_command": "nema komande `{id}`; tekst prije JSON-a mora biti id komande",
"shortcut.pin": "Prikači na vrh S okvira",
@@ -3465,8 +3863,10 @@
"sketch_text.align_right": "Desno",
"sketch_text.bold": "Podebljano",
"sketch_text.builtin_font": "{family} (ugrađeno)",
"sketch_text.formula": "Počnite znakom = da riječi složite iz parametara: ='W' + number(Width)",
"sketch_text.italic": "Kurziv",
"sketch_text.loading_fonts": "Traženje instaliranih fontova…",
"sketch_text.no_bold": "Ugrađeni font nema podebljanu varijantu: izaberite instalirani font",
"sketch_text.on_path": "Tekst prati svoju putanju: kriva ga postavlja",
"sketch_text.path_clear": "Skini tekst s njegove krive",
"sketch_text.path_hint": "Kriva skice koju tekst prati: kliknite ovdje, a zatim krivu",
@@ -3605,6 +4005,8 @@
"ui.3d_mouse_section": "3D miš",
"ui.3d_mouse_show_in_view": "Prikaži 3D miš u 3D prikazu",
"ui.3d_mouse_show_in_view_tip": "Slika vašeg 3D miša u donjem lijevom uglu, koja se kreće dok ga pomjerate.",
"ui.3d_mouse_sketch_orbit": "Okreći skice 3D mišem",
"ui.3d_mouse_sketch_orbit_tip": "3D miš okreće prikaz i dok je skica otvorena i gledana pod pravim uglom, kao što to može i miš. Isključeno: pomjera i zumira skicu dok ne nagnete prikaz.",
"ui.3d_mouse_skip": "Moj uređaj ovo ne može",
"ui.3d_mouse_skip_tip": "Preskače ovo kretanje u oba smjera, a kalibracija ga izostavlja.",
"ui.3d_mouse_speed": "Brzina 3D miša",
@@ -3633,6 +4035,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Zakrenite kapicu suprotno od kazaljke na satu, držite je, a zatim pustite.",
"ui.3d_mouse_switch_cap": "Kalibriši kao Povratak u centar",
"ui.3d_mouse_switch_trackball": "Kalibriši kao trackball",
"ui.3d_mouse_use_direct": "Koristi direktni drajver",
"ui.3d_mouse_use_direct_tip": "Čitaj 3D miš direktno (hidraw) umjesto preko spacenavd.",
"ui.3d_mouse_view_axis": "Osa prikaza",
"ui.3d_mouse_view_off": "Isključeno",
"ui.3d_mouse_view_off_tip": "Profil drajvera 3D miša isključuje ovu osu, pa njeno kretanje ne pomjera prikaz. Izbor kretanja druge ose ovdje ih zamjenjuje, a to kretanje prestaje pomjerati prikaz.",
@@ -3671,6 +4075,7 @@
"ui.agent_chat_auth_terminal": "Pokrenite ovo u terminalu, a zatim pritisnite Poveži ponovo:",
"ui.agent_chat_auth_title": "Prijavi se kod agenta",
"ui.agent_chat_cancel": "Otkaži",
"ui.agent_chat_cannot_start": "Instalirano, ali se ne pokreće: {error}",
"ui.agent_chat_changes": "Promjene: {n}, jedan korak poništavanja",
"ui.agent_chat_changes_n": "Promjene: {n}",
"ui.agent_chat_changes_of": "Promjene: {n}/{limit}",
@@ -3763,9 +4168,12 @@
"ui.agent_chat_queued_stopped": "U redu čekanja (zaustavljeno, nije poslano)",
"ui.agent_chat_refresh": "Osvježi",
"ui.agent_chat_refused": "Nije dozvoljeno agentu: {reason}",
"ui.agent_chat_reinstall": "{agent} je instaliran ({program}), ali se nije pokrenuo kada ga je SolidCraft provjerio: {error}\nNjegova instalacija možda nije potpuna ili je napravljena za drugu vrstu računara. Instalirajte ga ponovo naredbom `{install}`, provjerite da li u terminalu ispisuje svoju verziju, a zatim pritisnite Osvježi.",
"ui.agent_chat_remove": "Ukloni iz reda",
"ui.agent_chat_remove_file": "Skini s ove poruke",
"ui.agent_chat_remove_imported": "Skini s ove poruke; dio ostaje u dizajnu (Poništi ga uklanja)",
"ui.agent_chat_reply_cut_off": "Odgovor je dostigao ograničenje dužine prije nego što je završen, pa može biti nepotpun. Zamolite agenta da nastavi ili da da kraći odgovor.",
"ui.agent_chat_reply_declined": "Model je odbio odgovoriti na ovu poruku.",
"ui.agent_chat_retry": "Pokušaj ponovo",
"ui.agent_chat_right": "Desno",
"ui.agent_chat_save": "Sačuvaj",
@@ -3824,12 +4232,17 @@
"ui.ai_copy_suffix": "kopiraj",
"ui.ai_custom_url": "Prilagođeni URL",
"ui.ai_default": "Podrazumijevano",
"ui.ai_edit": "Uredi",
"ui.ai_edit_agent_title": "Uredi agenta",
"ui.ai_edit_gone": "Ovaj agent je uklonjen, pa nema šta da se sačuva.",
"ui.ai_edit_tip": "Promijenite naziv, komandu ili krajnju tačku ovog agenta. Sačuvani ključ ostaje skriven.",
"ui.ai_http_busy": "krajnja tačka je zauzeta ili je preko ograničenja; pokušajte ponovo kasnije",
"ui.ai_http_key": "krajnja tačka je odbila ključ: provjerite ga u Postavke > AI",
"ui.ai_http_not_found": "nema takve krajnje tačke ili modela: provjerite osnovni URL i naziv modela",
"ui.ai_http_too_large": "razgovor je prevelik za ovu krajnju tačku; započnite novi razgovor",
"ui.ai_key": "API ključ",
"ui.ai_key_hint": "neobavezno; čuva se u sistemskom privjesku ključeva",
"ui.ai_key_keep_hint": "ostavite prazno da zadržite sačuvani ključ",
"ui.ai_key_needs_https": "Ključ se šalje samo preko HTTPS-a ili na ovaj računar.",
"ui.ai_key_note": "Čuva se u sistemskom privjesku ključeva, nikad u postavkama SolidCrafta. Za lokalni server ostavite prazno.",
"ui.ai_keychain_error": "Sistemski privjesak ključeva je odbio: {error}",
@@ -3856,11 +4269,15 @@
"ui.ai_permissions": "Dozvole",
"ui.ai_preset": "Unaprijed postavljeno",
"ui.ai_protocol": "Protokol",
"ui.ai_reasoning_effort": "Nivo zaključivanja",
"ui.ai_reasoning_effort_field_hint": "prazno = podrazumijevana vrijednost pružaoca",
"ui.ai_reasoning_effort_hint": "Neobavezno. Šalje se krajnjoj tački kao `reasoning_effort`; prihvaćene vrijednosti razlikuju se od pružaoca do pružaoca (na primjer `low`, `high` ili `max`). SolidCraft nikad ne mijenja vrijednost koju ovdje postavite. Ostavite prazno da se ništa ne šalje i da se zadrži podrazumijevana vrijednost pružaoca.",
"ui.ai_remove": "Ukloni",
"ui.ai_request_timed_out": "Model je trajao duže od {seconds} s. Vremensko ograničenje zahtjeva za ovu krajnju tačku možete povećati u Postavke › Asistent.",
"ui.ai_request_timeout": "Vremensko ograničenje zahtjeva",
"ui.ai_request_timeout_hint": "Koliko dugo jedan zahtjev modelu na ovoj krajnjoj tački smije trajati, od 30 s do jednog sata. Povećajte za spori lokalni model.",
"ui.ai_reset": "Resetuj",
"ui.ai_save": "Sačuvaj",
"ui.ai_settings": "AI postavke…",
"ui.ai_show_features": "Prikaži AI funkcije",
"ui.ai_show_features_note": "Razgovor s agentom: panel u kojem agent za kodiranje koji već koristite ili krajnja tačka modela koju izaberete radi na otvorenom dizajnu. Isključeno: nema AI dugmeta, prečice, stavke menija ni panela, i ništa se ne traži niti pokreće.",
@@ -3949,6 +4366,7 @@
"ui.circle": "Krug",
"ui.circular_pattern": "Kružni niz",
"ui.clear": "Očisti",
"ui.clear_print_face": "Ukloni površinu za štampu",
"ui.clear_recent": "Očisti nedavno",
"ui.clear_the_selection": "Poništi izbor",
"ui.click_selects_whole_bodies": "Klik bira cijela tijela",
@@ -3983,6 +4401,14 @@
"ui.constant": "Konstantno",
"ui.constraints_conflict": "Ograničenja su u sukobu",
"ui.construction": "Pomoćna",
"ui.construction.angle_hint": "Pomoćna prava: kliknite tačku, pa upišite ugao ili izaberite smjer",
"ui.construction.bisector_hint": "Simetrala: izaberite dvije prave linije koje se sijeku, pa željeno rješenje",
"ui.construction.common_hint": "Zajednička tangenta: izaberite dva kruga ili luka, pa željeno rješenje",
"ui.construction.contact_hint": "Tangenta: izaberite krug/luk ili otvoreni splajn/koniku, pa tačku dodira (krive slobodnog oblika: krajnje tačke)",
"ui.construction.external_hint": "Tangenta: izaberite krug ili luk, vanjsku tačku, pa željeno rješenje",
"ui.construction.free_hint": "Pomoćna prava: kliknite dvije tačke; desnim klikom počnite ispočetka",
"ui.construction.point_hint": "Pomoćna prava: kliknite tačku kroz koju prolazi",
"ui.construction.reference_hint": "Pomoćna prava: izaberite pravu liniju, pa tačku; desnim klikom promijenite referencu",
"ui.construction.ribbon_label": "Pomoćne",
"ui.context_menu": "Kontekstni meni",
"ui.continuity_g0": "G0 pregib",
@@ -4011,6 +4437,7 @@
"ui.dark": "Tamna",
"ui.default_modelling_orientation": "Podrazumijevana orijentacija modeliranja",
"ui.delete": "Izbriši",
"ui.delete_select_first": "Prvo izaberite šta želite izbrisati: operacije, tijela, komponente ili geometriju skice",
"ui.depth": "Dubina",
"ui.design": "Dizajn",
"ui.design_properties.author": "Autor",
@@ -4025,6 +4452,40 @@
"ui.design_properties.project": "Projekat",
"ui.design_properties.revision": "Revizija",
"ui.design_saved": "Sačuvano: {path}",
"ui.details_access_caption": "Lica pokazuju može li alat doći do njih iz izabranog smjera.",
"ui.details_angle": "Ugao",
"ui.details_continuity_caption": "Uredite izabrani šav. Ostali šavovi zadržavaju vlastite postavke.",
"ui.details_continuity_stale": "Ranija geometrija: ponovo pokrenite analizu da uredite ili uokvirite ovaj šav.",
"ui.details_copy_list": "Kopiraj popis",
"ui.details_curvature": "Tolerancija zakrivljenosti",
"ui.details_curvature_caption": "Lica su obojena prema svom lokalnom poluprečniku zakrivljenosti.",
"ui.details_direction": "Smjer alata",
"ui.details_draft_caption": "Lica su obojena prema svom uglu u odnosu na smjer vađenja iz kalupa.",
"ui.details_edit_seam": "Uredi šav",
"ui.details_fixture": "Uzorak komponente — samo probni podaci",
"ui.details_fixture_caption": "Uzorak komponente s ilustrativnim vrijednostima; ovo nije rezultat modela.",
"ui.details_fixture_problem": "Primjer nalaza s problemom",
"ui.details_fixture_readout": "Primjer izmjerene vrijednosti",
"ui.details_flip": "Obrni",
"ui.details_gap": "Otvoreni kraj",
"ui.details_hatch": "Šrafiraj presječena lica",
"ui.details_jump": "Skok zakrivljenosti",
"ui.details_key": "Legenda",
"ui.details_offset": "Odmak",
"ui.details_plane": "Ravan",
"ui.details_pull": "Smjer vađenja iz kalupa",
"ui.details_radius": "Poluprečnik",
"ui.details_readout": "Očitanje",
"ui.details_samples": "Uzorci",
"ui.details_seam": "{body} · Ivica {edge}",
"ui.details_settings": "Postavke",
"ui.details_sharp_gap": "Oštro ili otvoreno",
"ui.details_show_all": "Prikaži svih {count}",
"ui.details_smooth": "Glatko",
"ui.details_stripes": "Pruge",
"ui.details_target": "Uokviri geometriju",
"ui.details_worst_seams": "Najgori šavovi",
"ui.details_zebra_caption": "Reflektovane pruge pokazuju koliko se glatko lica spajaju.",
"ui.diameter": "Prečnik",
"ui.dimension": "Kota",
"ui.direct_interaction": "Direktna interakcija",
@@ -4069,6 +4530,7 @@
"ui.edit_in_place_saved_elsewhere": "Dio je sačuvan u drugu datoteku; sklop i dalje povezuje: {name}",
"ui.edit_in_place_wait": "Pričekajte da se završi ono što se izvršava u dizajnu",
"ui.edit_section_analysis": "Uredi analizu presjeka",
"ui.edit_sketch_empty": "Nema skica za uređivanje",
"ui.ellipse": "Elipsa",
"ui.emboss_wrap_note": "Ortogonalni linijski profili koji tangiraju jedan podržani cilindar obavijaju se automatski. Izaberite zid da izaberete cilj.",
"ui.end_radius": "Krajnji poluprečnik",
@@ -4079,12 +4541,15 @@
"ui.equation_explicit": "Eksplicitno",
"ui.equation_form": "Oblik",
"ui.equation_note": "Varijabla je običan broj: sin(t) je uzima u radijanima. Rezultat bez jedinice je u jedinici dizajna.",
"ui.equation_note_range": "Varijabla je dužina, kao i njen raspon: kombinujte je s parametrima dužine, a za sin() je podijelite dužinom. Rezultat bez jedinice je u jedinici dizajna.",
"ui.equation_parametric": "Parametarski",
"ui.equation_variable_unit": "Varijabla ima jedinicu raspona",
"ui.equation_x_of_t": "x(t)",
"ui.equation_y_of_t": "y(t)",
"ui.equation_y_of_x": "y(x)",
"ui.equivalent_radius": "Ekvivalentni poluprečnik",
"ui.expand": "Proširi",
"ui.explode_needs_two_components": "Rastavljeni prikaz razdvaja komponente, pa dizajn treba imati dvije ili više njih (Nova komponenta, Umetni dizajn ili Napravi komponente iz tijela)",
"ui.exportDxf": "Izvezi DXF…",
"ui.export_ellipsis_keymap": "Izvezi…",
"ui.export_keymap_tip": "Tipke, favoriti okvira S i traka s alatima, kao datoteka",
@@ -4245,6 +4710,7 @@
"ui.insert_design.saved_file": "{name} ima nesačuvane promjene, pa je umetnuta njegova sačuvana datoteka. Sačuvajte ga, a zatim pokrenite Ažuriraj veze da unesete promjene.",
"ui.insert_design.unsaved": "Nesačuvane promjene",
"ui.inspect": "Pregledaj",
"ui.instances": "Instance",
"ui.intersect": "Presjek",
"ui.isolate": "Izoluj",
"ui.join": "Spoji",
@@ -4253,12 +4719,14 @@
"ui.joints": "Spojevi",
"ui.keyboard_shortcuts": "Prečice na tastaturi",
"ui.language": "Jezik",
"ui.lasso_select": "Laso (povucite petlju slobodnom rukom: uzima sve što je u cijelosti unutra)",
"ui.layout_compact": "Kompaktno",
"ui.layout_floating": "Plutajuće",
"ui.length": "Dužina",
"ui.length_decimals": "Decimale dužine",
"ui.light": "Svijetla",
"ui.line": "Linija",
"ui.line_arc_hint": "Tangentni luk: kliknite kraj luka. Ponovo se zadržite na posljednjoj tački za linije; desni klik ili Esc za završetak",
"ui.link_copies": "Poveži identične kopije",
"ui.link_copies_keep": "Zadrži",
"ui.link_copies_link": "Poveži",
@@ -4347,6 +4815,7 @@
"ui.part_size_shared": "Ponovo gradi dio, zamjenjujući sve dodane operacije. Svi primjerci ove komponente se ažuriraju.",
"ui.paste": "Zalijepi",
"ui.path": "Putanja",
"ui.pattern_object_type_tip": "Tijela: puni oblici, biraju se u prikazu ili u stablu modela; svako se kopira u cijelosti.\nOperacije: koraci vremenske trake, poput ekstruzije ili rupe, biraju se na vremenskoj traci ili na licu koje su napravili; korak se ponavlja.\nSkica nije ni jedno ni drugo: ponovite njene krive pomoću alata Niz ili Preslikaj u samoj skici ili je prvo ekstrudirajte.",
"ui.pattern_on_path": "Niz duž putanje",
"ui.perforate_margin_default": "iz prečnika",
"ui.perforate_pitch": "Korak",
@@ -4373,12 +4842,17 @@
"ui.plugins_skipped": "Foldere nije bilo moguće pročitati: {count}.",
"ui.plugins_version": "{version}",
"ui.point": "Tačka",
"ui.point_cloud_add_filter": "Dodaj filter…",
"ui.point_cloud_auto_range": "Automatski opseg",
"ui.point_cloud_color_by": "Boja prema",
"ui.point_cloud_file_colors": "Boje iz datoteke",
"ui.point_cloud_filter_by": "Filtriraj prema",
"ui.point_cloud_height_high": "Najviše",
"ui.point_cloud_height_low": "Najniže",
"ui.point_cloud_legend_filter": "Prikazano i skriveno u legendi iznad",
"ui.point_cloud_no_file_colors": "Datoteka nema boje",
"ui.point_cloud_pause": "Pauziraj",
"ui.point_cloud_play": "Reprodukuj",
"ui.point_cloud_single_color": "Jedna boja",
"ui.point_cloud_size": "Veličina tačke",
"ui.point_cloud_units": "Jedinice datoteke",
@@ -4387,6 +4861,11 @@
"ui.polygon": "Poligon",
"ui.preferences": "Postavke",
"ui.press_a_key": "pritisnite tipku…",
"ui.print_face_body_not_sent": "Tijelo te površine se ne šalje: prvo ga dodajte u Tijela",
"ui.print_face_cleared": "{body}: površina za štampu uklonjena",
"ui.print_face_input": "Površina za štampu",
"ui.print_face_pick": "Odaberite ravnu površinu punog tijela da postane površina za štampu",
"ui.print_face_set": "{body}: površina za štampu postavljena; Pošalji u slajser polaže tijelo na nju",
"ui.profile": "Profil",
"ui.profile_plane": "Ravan profila",
"ui.profiles": "Profili",
@@ -4402,6 +4881,7 @@
"ui.properties.inertia_y": "Iyx, Iyy, Iyz",
"ui.properties.inertia_z": "Izx, Izy, Izz",
"ui.properties.measuring": "Mjerenje…",
"ui.properties.measuring_bodies": "Mjerenje… {done} od {total} tijela",
"ui.properties.measuring_failed": "Mjerenje je zaustavljeno prije završetka.",
"ui.properties.principal_axis": "Glavna osa {number}",
"ui.properties.principal_moments": "Glavni momenti",
@@ -4450,6 +4930,7 @@
"ui.revolve": "Rotiraj",
"ui.ribbon_exit": "Izađi",
"ui.ribbon_labels": "Prikaži tekst ispod ikona na traci",
"ui.ribbon_menus_skip_pinned": "Izostavi prikačene komande iz padajućih menija",
"ui.ribbon_pin_footer": "Prikačene komande prikazuju se na traci. Zadržite pokazivač iznad reda da ga prikačite ili otkačite.",
"ui.ribbon_workspace": "Radni prostor",
"ui.right": "Desno",
@@ -4468,6 +4949,7 @@
"ui.selectOther": "Izaberi drugo…",
"ui.selectTangentChain": "Izaberi tangentni lanac",
"ui.select_all": "Izaberi sve",
"ui.select_components_by_face": "Izaberi komponente (lice bira svoju komponentu)",
"ui.selection": "Izbor",
"ui.selection_modifiers": "Modifikatori izbora",
"ui.selection_shift_add_only": "Shift+klik dodaje bez uklanjanja",
@@ -4509,6 +4991,11 @@
"ui.sketch_intersect_selection_filter": "Filter izbora",
"ui.sketch_modifiers": "Modifikatori skice",
"ui.sketch_project_geometry": "Geometrija",
"ui.sketch_report.all_constrained": "Sve je potpuno ograničeno.",
"ui.sketch_report.dof": "Stepeni slobode",
"ui.sketch_report.loose": "Nije potpuno ograničeno",
"ui.sketch_report.status": "Status",
"ui.sketch_report.title": "Izvještaj o skici",
"ui.sketch_scale": "Skaliranje skice",
"ui.sketches": "Skice",
"ui.slice": "Presijeci",
@@ -4604,19 +5091,31 @@
"ui.view_angles": "Uglovi prikaza",
"ui.view_angles_convention": "Stepeni oko ose prema gore. Skretanje 0° je pogled sprijeda, 90° slijeva, −90° zdesna, 180° straga; nagib 90° je pogled odozgo; pozitivno valjanje okreće model u smjeru kazaljke na satu.",
"ui.view_angles_help": "Prikazuje nagib, skretanje i valjanje prikaza u stepenima i njegovu projekciju ispod alata za orijentaciju. Kliknite očitanje da upišete tačne uglove.",
"ui.view_angles_lock": "Zaključaj {axis}",
"ui.view_angles_ortho": "Ortogr.",
"ui.view_angles_persp": "Persp.",
"ui.view_angles_pitch": "Nagib",
"ui.view_angles_readout": "Nagib {pitch} · Skretanje {yaw} · Valjanje {roll}",
"ui.view_angles_roll": "Valjanje",
"ui.view_angles_unlock": "Otključaj {axis}",
"ui.view_angles_yaw": "Skretanje",
"ui.view_angles_zero": "Postavi {axis} na 0°",
"ui.view_centre": "Centar prikaza",
"ui.viewport_status_clear": "Isključi: {name}",
"ui.viewport_status_clear_hint": "Isključi: {name} · Esc",
"ui.viewport_status_edges": "Ivice: {count}",
"ui.viewport_status_from": "iz smjera {direction}",
"ui.viewport_status_key": "Prikaži legendu: {name}",
"ui.viewport_status_minimum_radius": "Minimalni poluprečnik",
"ui.viewport_status_pairs": "Parovi: {count}",
"ui.viewport_status_section_hatch": "{setting} · šrafura",
"ui.viewport_status_section_plane": "Trenutna ravan",
"ui.viewport_status_section_tilted": "nagnuto · {offset}",
"ui.viewport_status_stripes": "Pruge: {count}",
"ui.viewport_status_summary_clear": "Isključi sve",
"ui.viewport_status_summary_heading": "U ovom prikazu",
"ui.viewport_status_summary_label": "{name} i još stavki: {count}",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Prati temu",
"ui.visual_shaded_hidden_edges": "Osjenčeno sa skrivenim ivicama",
"ui.visual_shaded_with_edges": "Osjenčeno s ivicama",
@@ -4653,7 +5152,9 @@
"update.button": "Ažuriraj",
"update.button_version": "Ažuriraj na {version}",
"update.check_automatically": "Automatski provjeravaj ažuriranja",
"update.check_for_updates": "Provjeri ažuriranja",
"update.check_now": "Provjeri sada",
"update.check_unavailable": "Provjera ažuriranja nije dostupna za ovu instalaciju.",
"update.checking": "Provjera…",
"update.copy": "Kopiraj",
"update.dmg_opened": "Slika diska je otvorena i provjerena: prevucite SolidCraft u Applications, zamijenite staru kopiju, a zatim ponovo pokrenite SolidCraft.",
File diff suppressed because it is too large Load Diff
+2735 -1
View File
File diff suppressed because it is too large Load Diff
+1275 -7
View File
File diff suppressed because it is too large Load Diff
+16 -2
View File
@@ -47,7 +47,13 @@
"color.edit": "Click to edit colour",
"color.gamma": "Showing colour values in 0-255 gamma space",
"color.linear": "Showing colour values in 0-1 linear space",
"color.recent": "Recent colours",
"color.recent_help": "Colours you pick in any colour field appear here",
"color.saturation": "Saturation (per cent)",
"color.selected": "Selected colour",
"color.step_down": "Step down",
"color.step_up": "Step up",
"color.value": "Value, the brightness (per cent)",
"dxf.cancelled": "DXF export cancelled",
"dxf.cancelling": "Cancelling DXF export",
"help.apache": "Apache licence 2.0",
@@ -70,12 +76,11 @@
"prefs.bottom_centre": "Bottom centre",
"prefs.logo_theme": "Theme's colours",
"prefs.max_file_size": "Largest file to open",
"prefs.max_file_size_design": "Design files",
"prefs.max_file_size_flag": "This run started with --max-file-size {size}, which applies until SolidCraft closes. These settings apply when it starts without it.",
"prefs.max_file_size_note": "Defaults: 512 MB and 256 MB; at most 16 GB. Opening a file takes several times its size in memory. A design that keeps a large import may need a higher design file limit to save and reopen.",
"prefs.max_file_size_step": "STEP and import files",
"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.sketch_conflict_colour": "Conflicting geometry in the conflict colour",
"prefs.sketch_conflict_colour_note": "While a sketch's constraints conflict, the curves and points a failing constraint holds are drawn in the conflict colour too, not only its glyph or dimension. Off: they keep their usual colours.",
"prefs.sketch_construction_lines": "Extended construction line tools",
@@ -86,12 +91,18 @@
"prefs.sketch_tracking_guides": "Centre and midpoint alignment guides",
"prefs.sketch_tracking_guides_note": "Hover over a circle centre or straight-edge midpoint, then move horizontally or vertically to show a temporary dotted guide.",
"prefs.status_bar_note": "A thin bar along the window's bottom edge shows what is being edited, the open sketch's state in its colour and the design's units; a click on the units changes them. The sketch's state then no longer shows in the view's corner.",
"prefs.structure.appearance_note": "Colours, text and accessibility.",
"prefs.structure.tools_note": "How modelling tools behave.",
"prefs.theme_follow": "Follow a theme file",
"prefs.theme_follow_error": "Not applied: {error}. The last good theme stays on.",
"prefs.theme_follow_launch": "Followed for this run only (--theme-file); reloaded whenever another tool rewrites it.",
"prefs.theme_follow_note": "Reloaded whenever another tool rewrites it. Choosing a theme stops following it.",
"prefs.theme_follow_off": "Off",
"prefs.theme_follow_stop": "Stop following",
"prefs.theme_option_opacity_help": "Adjust floating panel opacity. A readability floor protects text over viewport colours.",
"prefs.theme_option_tab_colours": "Tab colours",
"prefs.theme_option_tab_colours_help": "Use this theme’s panel and viewport colours for each ribbon tab.",
"prefs.theme_option_tab_fade_ms_help": "Fade panel and viewport colours when switching tabs. Zero switches instantly.",
"prefs.trackpad_zoom_modifier.help": "Hold only this key to zoom at the pointer with vertical scroll. Off keeps the existing scroll behaviour.",
"prefs.view": "View behaviour",
"prefs.viewport_background_colour": "Colour",
@@ -234,7 +245,10 @@
"ui.default_modelling_orientation": "Default modelling orientation",
"ui.design_properties.license": "Licence",
"ui.design_properties.organization": "Organisation",
"ui.details_curvature_caption": "Faces are coloured by their local radius of curvature.",
"ui.details_draft_caption": "Faces are coloured by their angle to the pull direction.",
"ui.export_keymap_tip": "Keys, S box favourites and the toolbar, as a file",
"ui.instances": "Instances",
"ui.layout_compact": "Compact",
"ui.layout_floating": "Floating",
"ui.material_review.claims": "Sources and licences are unverified claims. Missing values remain unknown.",
+567 -10
View File
File diff suppressed because it is too large Load Diff
+1195 -10
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+468
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "SURFACE DE RACCORD",
"BOUNDARY SURFACE": "SURFACE FRONTIÈRE",
"BROWSER": "NAVIGATEUR",
"Back to the original order": "Revenir à l'ordre d'origine",
"Ball": "Rotule",
"Bar": "Plat",
"Base Center": "Centre de la base",
@@ -181,6 +182,8 @@
"Designs you open or save appear here.": "Les conceptions que vous ouvrez ou enregistrez apparaissent ici.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Déterminé par Jusqu'à l'objet. Choisir Angle pour modifier l'étendue angulaire.",
"Diameter": "Diamètre",
"Did this assembly open the wrong way up?": "Cet assemblage s'est-il ouvert dans le mauvais sens ?",
"Did this part open the wrong way up?": "Cette pièce s'est-elle ouverte dans le mauvais sens ?",
"Dimension new shapes": "Coter les nouvelles formes",
"Direction": "Direction",
"Discard": "Abandonner",
@@ -593,6 +596,8 @@
"Snap to face and edge centres": "Accrocher aux centres des faces et des arêtes",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft ne peut pas soumettre directement les rapports. Soumettez-les sur GitHub ; aucun jeton n'est nécessaire dans l'application.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft s'est fermé avant l'enregistrement de ces conceptions. Leur dernier enregistrement automatique peut être ouvert.",
"Sort A to Z": "Trier de A à Z",
"Sort Z to A": "Trier de Z à A",
"Spacing 2 must be a non-zero number": "L'espacement 2 doit être un nombre non nul",
"Spacing must be a non-zero number": "L'espacement doit être un nombre non nul",
"Specialise": "Spécialiser",
@@ -601,6 +606,7 @@
"Square": "Carré",
"Stand it up": "Redresser",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Redresser : un quart de tour autour de X, sous forme d'une fonction Déplacer que vous pouvez annuler.",
"Stand this assembly up?": "Redresser cet assemblage ?",
"Stand this part up?": "Redresser cette pièce ?",
"Start angle": "Angle de départ",
"Start bulge": "Renflement de début",
@@ -772,6 +778,8 @@
"at the face": "sur la face",
"bom.group_children": "Sous-pièces dans la ligne de leur pièce",
"browser.body_name": "Corps{number}",
"browser.calibrate_click_a": "Cliquez sur l'image pour le point A, ou saisissez ses coordonnées.",
"browser.calibrate_click_b": "Cliquez sur l'image pour le point B, ou saisissez ses coordonnées.",
"browser.calibrate_title": "Calibrer : {name}",
"browser.capture_many": "{count} composants ont été déplacés, mais leurs positions ne sont pas capturées.",
"browser.capture_note": "Capturer la position conserve la nouvelle position ; Rétablir la position restaure la précédente.",
@@ -814,6 +822,8 @@
"cmd.appearance_pick_up": "Prélever l'apparence",
"cmd.appearance_replace": "Remplacer l'apparence",
"cmd.assembly_bom": "Nomenclature",
"cmd.assembly_mass_properties": "Propriétés de masse",
"cmd.body.print_face": "Face d’impression",
"cmd.body_rename": "Renommer le corps",
"cmd.browser_collapse_all": "Tout réduire",
"cmd.browser_expand_all": "Tout développer",
@@ -1185,6 +1195,7 @@
"cmd.sketch_spline_control_point_5": "Spline par points de contrôle (degré 5)",
"cmd.sketch_spline_fit_point": "Spline par points",
"cmd.sketch_spline_insert_point": "Ajouter un point de spline",
"cmd.sketch_spline_remove_point": "Supprimer un point de spline",
"cmd.sketch_spline_reverse": "Inverser le sens de la spline",
"cmd.sketch_spun_profile": "Profil de révolution",
"cmd.sketch_toggle_diameter": "Basculer rayon / diamètre",
@@ -1238,6 +1249,7 @@
"cmd.view.render_browser": "Navigateur de rendu",
"cmd.view.render_dock": "Déplacer le panneau de rendu",
"cmd.view.render_focus": "Vue de rendu ciblée",
"cmd.view.status_details": "Détails d'état de la vue 3D",
"cmd.view.status_items": "Éléments d'état de la vue 3D",
"cmd.view_ambient_occlusion": "Occlusion ambiante",
"cmd.view_angle_readout": "Angles de vue",
@@ -1276,8 +1288,14 @@
"color.hex_hint": "#rrggbb",
"color.hue": "Teinte",
"color.linear": "Valeurs de couleur affichées en espace linéaire 0-1",
"color.recent": "Couleurs récentes",
"color.recent_help": "Les couleurs choisies dans n'importe quel champ de couleur apparaissent ici",
"color.saturation": "Saturation (pour cent)",
"color.saturation_value": "Saturation et valeur",
"color.selected": "Couleur sélectionnée",
"color.step_down": "Diminuer d'un pas",
"color.step_up": "Augmenter d'un pas",
"color.value": "Valeur, la luminosité (pour cent)",
"config.column_filter_empty": "Aucune colonne correspondante",
"contact.components_count": "{count} composants",
"contact.enabled_label": "activé",
@@ -1323,6 +1341,8 @@
"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.select": "↖ Sélectionner",
"dialog.selected_count": "↖ {count} sélectionné(s)",
"dialog.selection_guidance": "Sélectionner une entrée pour la modifier. Cliquer sur un objet sélectionné pour le retirer ; Ctrl/Cmd-clic le bascule aussi. Vérifier l'aperçu, puis appuyer sur OK.",
@@ -1380,26 +1400,243 @@
"from the rule": "selon la règle",
"help.apache": "Licence Apache 2.0",
"help.appearance.assign": "Donne aux corps, faces ou composants sélectionnés une couleur ou l'apparence d'un matériau, sans changer leur poids. À utiliser pour distinguer des pièces ou prévisualiser une finition.",
"help.assembly.bom": "Liste les pièces de la conception ou d'un composant avec leurs quantités, par référence, et peut enregistrer la liste en CSV ou JSON. À utiliser comme nomenclature pour la commande et l'assemblage.",
"help.attributions": "Attributions (ATTRIBUTION.md)",
"help.build": "Version {version} · commit {commit} · compilée le {date}",
"help.canvas.decal": "Place une image sur une face plane du modèle, comme un autocollant. À utiliser pour afficher une étiquette, un logo ou un motif imprimé sur une pièce.",
"help.canvas.edit": "Modifie un canevas : sa position, sa taille, son angle, son opacité, sa symétrie, sa visibilité ou son nom. À utiliser pour aligner une image de référence sur l'esquisse que vous décalquez.",
"help.canvas.insert": "Place une image PNG ou JPEG sur un plan ou une face comme référence translucide. À utiliser pour décalquer une photo ou un dessin dans une esquisse, l'image étant mise à une échelle connue.",
"help.component.break_link": "Détache un composant lié de son fichier de conception en conservant ses corps tels quels. À utiliser lorsque la pièce ne doit plus suivre les modifications du fichier d'origine.",
"help.component.capture_position": "Conserve les positions des pièces que vous avez déplacées à la main, qui deviennent leur nouvelle position. À utiliser après avoir fait glisser en place des pièces qu'aucune liaison ne maintient.",
"help.component.create": "Ajoute un composant vide à la conception, dans le composant actif sauf si vous choisissez un autre parent. À utiliser pour donner à une nouvelle pièce d'un assemblage son propre espace pour les corps et les esquisses.",
"help.component.finish_edit": "Enregistre la pièce que vous modifiez sur place, revient à son assemblage et met à jour l'assemblage à partir d'elle. À utiliser lorsque vos modifications de la pièce liée sont terminées.",
"help.component.from_bodies": "Transforme chaque corps sélectionné en composant à part entière, portant le nom du corps. À utiliser lorsqu'une conception faite de corps isolés doit devenir un assemblage dont les pièces peuvent être reliées et déplacées.",
"help.component.insert_design": "Insère une autre conception enregistrée comme composant lié à son fichier, avec ses corps solides placés comme dans ce fichier. À utiliser pour réutiliser une pièce dans un assemblage et récupérer les modifications ultérieures avec Mettre à jour les liens.",
"help.component.properties": "Affiche le volume, la masse, le centre de gravité et les moments d'inertie d'un composant, d'après les matériaux de ses corps. À utiliser pour vérifier le poids et l'équilibre d'un assemblage.",
"help.component.revert_position": "Remet à leur place les pièces que vous avez déplacées à la main, en abandonnant les déplacements non capturés. À utiliser pour annuler des essais de déplacement dans un assemblage.",
"help.component.update_links": "Relit les fichiers de conception liés et remplace les corps qui ont changé. À utiliser après avoir modifié une pièce utilisée par cet assemblage.",
"help.config.configure": "Ajoute des paramètres, des fonctions, des matériaux ou des apparences comme colonnes de la table des configurations. À utiliser pour créer des variantes d'une conception, par exemple les tailles d'une pièce.",
"help.config.preview": "Vérifie qu'une ligne de la table des configurations se reconstruit, sans modifier la conception ni son historique. À utiliser avant de basculer vers une variante incertaine.",
"help.config.rules": "Affiche les règles de la table des configurations, ses lignes et la ligne active. À utiliser pour voir comment les variantes d'une conception sont définies.",
"help.config.table": "Affiche la table des configurations : une ligne par variante, une colonne par paramètre ou fonction, et si la conception correspond toujours à la ligne active. À utiliser pour passer en revue les variantes d'une conception.",
"help.connect_note": "Redémarrez chaque assistant pour charger le serveur SolidCraft. Chaque fichier a été sauvegardé en .bak avant d'être modifié.",
"help.connect_title": "Connecter les assistants IA",
"help.construct.axis.edge": "Crée un axe de construction le long d'une arête droite ou d'une ligne d'esquisse. À utiliser comme axe pour une révolution, une répétition circulaire ou une liaison.",
"help.construct.axis.normal_to_face": "Crée un axe de construction passant par un point, perpendiculaire à une face. À utiliser comme axe qui sort droit d'une surface.",
"help.construct.axis.through_cylinder": "Crée un axe de construction passant par le centre d'une face cylindrique, conique ou torique, ou d'une arête circulaire. À utiliser pour faire tourner ou répéter des fonctions autour d'une pièce ronde.",
"help.construct.axis.two_planes": "Crée un axe de construction là où deux plans ou faces planes se rencontrent. À utiliser lorsque l'axe voulu est la droite entre deux faces.",
"help.construct.axis.two_points": "Crée un axe de construction passant par deux points, sommets ou points d'esquisse. À utiliser pour un axe qui relie deux positions connues.",
"help.construct.plane.along_path": "Crée un plan de construction perpendiculaire à une arête ou une courbe, à une position choisie le long de celle-ci. À utiliser pour esquisser le profil d'un balayage ou une coupe d'une trajectoire.",
"help.construct.plane.at_angle": "Crée un plan de construction tourné d'un angle par rapport à un plan ou une face, autour d'un axe ou d'une arête. À utiliser pour des esquisses inclinées, par exemple une équerre à angle.",
"help.construct.plane.midplane": "Crée un plan de construction à mi-distance entre deux plans ou faces planes, ou bissecteur lorsqu'ils se rencontrent en formant un angle. À utiliser comme plan de symétrie.",
"help.construct.plane.offset": "Crée un plan de construction parallèle à un plan ou une face plane, à une distance donnée ou passant par un point. À utiliser pour esquisser à une hauteur précise au-dessus d'une face.",
"help.construct.plane.perpendicular": "Crée un plan de construction qui contient une droite ou une arête et qui est perpendiculaire à un plan ou une face. À utiliser pour esquisser à la verticale sur un modèle, le long d'une arête.",
"help.construct.plane.tangent": "Crée un plan de construction qui touche un cylindre, un cône, une sphère ou un tore en un point. À utiliser pour esquisser sur le flanc d'une pièce ronde.",
"help.construct.plane.three_points": "Crée un plan de construction passant par trois points. À utiliser pour un plan défini par des positions du modèle, comme trois coins.",
"help.construct.plane.two_edges": "Crée un plan de construction passant par deux arêtes droites ou lignes situées dans un même plan. À utiliser pour un plan à travers une pièce, par exemple par deux arêtes opposées.",
"help.construct.point.along_path": "Crée un point de construction à une position donnée le long d'une arête ou d'une courbe. À utiliser comme référence à mi-chemin sur une trajectoire.",
"help.construct.point.center": "Crée un point de construction au centre d'une arête circulaire, d'un cercle ou arc d'esquisse, d'une sphère ou d'un tore. À utiliser comme référence pour des perçages, des axes et des liaisons.",
"help.construct.point.edge_plane": "Crée un point de construction là où une arête ou une ligne traverse un plan ou une face plane. À utiliser pour repérer où une trajectoire coupe un plan.",
"help.construct.point.three_planes": "Crée un point de construction là où trois plans ou faces planes se rencontrent. À utiliser comme référence de coin, même lorsque le modèle n'a pas de sommet à cet endroit.",
"help.construct.point.two_edges": "Crée un point de construction là où deux arêtes ou lignes se rencontrent, ou se rapprochent le plus. À utiliser comme référence à un coin virtuel.",
"help.construct.point.vertex": "Crée un point de construction sur un sommet ou un point d'esquisse. À utiliser comme référence qui subsiste lorsque l'esquisse ou le corps qui l'entoure est masqué.",
"help.contact.check": "Liste les paires de pièces des jeux de contact qui se traversent dans leur position actuelle. À utiliser pour trouver les collisions avant de piloter les liaisons ou de lancer une étude de mouvement.",
"help.contact.create": "Regroupe deux pièces ou plus dans un jeu de contact, dont les corps ne peuvent pas se traverser pendant le mouvement des liaisons. À utiliser pour des pièces qui doivent s'arrêter au contact, comme un levier contre sa butée.",
"help.contact.disable_all": "Désactive le contact : les liaisons pilotées et les études de mouvement ne s'arrêtent plus là où les pièces se touchent. À utiliser pour déplacer à nouveau les pièces librement, ou pour garder un grand assemblage réactif.",
"help.contact.edit": "Modifie un jeu de contact : les pièces qu'il contient, son nom, ou s'il est désactivé ; peut aussi supprimer le jeu. À utiliser pour ajuster les pièces qui ne doivent pas se traverser.",
"help.contact.enable_all": "Place toutes les pièces de l'assemblage dans un seul jeu de contact, de sorte qu'aucune paire de pièces ne puisse se traverser pendant le mouvement des liaisons. À utiliser pour une vérification rapide d'un mécanisme entier.",
"help.contact.enable_sets": "Active le contact pour les jeux de contact que vous avez définis, de sorte que les liaisons pilotées et les études de mouvement s'arrêtent là où leurs pièces se traverseraient. À utiliser pour retrouver vos jeux après avoir désactivé le contact.",
"help.contact.list": "Liste les jeux de contact de la conception, les pièces de chacun et si le contact est activé. À utiliser pour vérifier la configuration du contact avant de piloter l'assemblage.",
"help.document.inspect": "Affiche une vue d'ensemble de la conception : ses composants, corps, esquisses, fonctions et paramètres. À utiliser pour se repérer dans une conception que vous n'avez pas créée vous-même.",
"help.document.units": "Change les unités dans lesquelles la conception affiche les longueurs, par exemple mm ou pouces. La géométrie garde sa taille ; seuls l'affichage et les nombres bruts suivent.",
"help.edit.checkpoint": "Mémorise la conception, l'esquisse active et l'historique d'annulation dans leur état actuel. À utiliser avant un essai risqué dont vous voudrez peut-être revenir.",
"help.edit.restore_checkpoint": "Revient à un point de restauration, historique d'annulation compris. À utiliser pour abandonner en une seule étape tout ce qui a été fait depuis ce point.",
"help.explode.create": "Crée une vue éclatée qui écarte les pièces du centre de l'assemblage, ou selon les déplacements que vous définissez. À utiliser pour montrer comment un assemblage se monte, pour des notices ou des présentations.",
"help.face.delete": "Supprime les faces sélectionnées et laisse les faces voisines refermer le vide, ou laisse le trou ouvert. À utiliser pour retirer un arrondi, un trou ou un bossage qui gêne.",
"help.file.insert_mesh": "Ajoute un fichier 3MF, STL, OBJ, PLY ou glTF à la conception sous forme de corps maillés dans un nouveau composant. À utiliser pour placer à côté de votre conception un modèle téléchargé ou numérisé comme référence.",
"help.file.insert_step": "Ajoute un fichier STEP, IGES, FreeCAD ou Rhino à la conception sous forme de corps solides dans un nouveau composant ; les pièces d'un assemblage deviennent des composants imbriqués. À utiliser pour importer des modèles de fournisseurs ou d'autres logiciels de CAO.",
"help.file.save_mesh": "Enregistre des corps en maillage STL, 3MF ou OBJ, avec la finesse des faces courbes que vous choisissez. À utiliser pour préparer des pièces à l'impression 3D ou pour des logiciels qui lisent des maillages.",
"help.file.send_to_slicer": "Enregistre les corps sélectionnés en fichier 3MF et l'ouvre dans le slicer de votre choix. À utiliser pour passer du modèle à l'impression en une seule étape.",
"help.font_details": "L’interface utilise les polices intégrées d’egui (Ubuntu Light et Hack, sous les licences Ubuntu Font Licence et MIT/Bitstream Vera ; Noto Emoji et la police d’icônes emoji, sous OFL et MIT). Le texte des esquisses utilise Liberation Sans (SIL OFL 1.1). Le pack de polices facultatif de The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic ; SIL OFL 1.1), n’est pas inclus dans les versions de SolidCraft.",
"help.fonts": "Polices",
"help.frame.member": "Place un profilé, tel qu'un tube ou un profilé d'aluminium, le long des lignes d'une esquisse de structure, un élément par ligne. À utiliser pour construire des châssis, des racks et des structures soudées.",
"help.inspect.accessibility": "Colore les faces qu'un outil peut atteindre depuis une direction, et celles qu'il ne peut pas atteindre. À utiliser pour vérifier l'usinabilité ou le moulage d'une pièce depuis ce côté.",
"help.inspect.center_of_mass": "Affiche le centre de gravité des corps, de chacun séparément et de l'ensemble, à densité uniforme. À utiliser pour vérifier l'équilibre et l'endroit où une pièce basculera.",
"help.inspect.colored_components": "Donne à chaque composant sa propre couleur temporaire, sans modifier les apparences. À utiliser pour distinguer d'un coup d'œil les pièces d'un assemblage.",
"help.inspect.compare": "Compare deux corps et montre où l'un a de la matière que l'autre n'a pas, avec le volume et l'écart. À utiliser pour comparer une pièce modifiée ou importée à l'original.",
"help.inspect.continuity": "Vérifie la douceur de raccord des faces le long de leurs arêtes : jeux, cassures ou sauts de courbure, et colore les raccords. À utiliser pour trouver où des surfaces ne se raccordent pas proprement.",
"help.inspect.curvature_comb": "Trace des peignes le long de courbes, d'arêtes ou de faces, dont les dents montrent l'intensité de la courbure. À utiliser pour repérer les méplats, les cassures et les courbures irrégulières.",
"help.inspect.curvature_map": "Colore les faces selon l'intensité de leur courbure, en rouge là où le rayon est petit. À utiliser pour trouver les zones serrées et juger la douceur d'une surface.",
"help.inspect.draft": "Colore les faces selon leur angle de dépouille par rapport à une direction de démoulage, et signale celles qui en manquent. À utiliser pour vérifier une pièce avant moulage ou coulée.",
"help.inspect.environment_map": "Affiche le modèle avec une finition réfléchissante, de type miroir. À utiliser pour juger la fluidité des surfaces d'après la façon dont les reflets les parcourent.",
"help.inspect.interference": "Trouve les corps qui se chevauchent et montre chaque chevauchement avec son volume. À utiliser pour vérifier qu'aucune pièce d'un assemblage n'entre en collision.",
"help.inspect.isocurve": "Trace les courbes isoparamétriques des faces, c'est-à-dire la grille sur laquelle la surface est construite. À utiliser pour voir comment une surface s'écoule et où elle se resserre.",
"help.inspect.measure": "Mesure des longueurs, distances, angles, aires et rayons entre les éléments choisis, ou le volume et la masse de corps. À utiliser pour vérifier des dimensions et des jeux.",
"help.inspect.minimum_radius": "Trouve le plus petit rayon de courbure de courbes, d'arêtes ou de faces, et son emplacement. À utiliser pour vérifier qu'un outil ou une buse peut suivre la forme.",
"help.inspect.section": "Ouvre la vue selon un plan de coupe et hachure les faces coupées, sans modifier le modèle. À utiliser pour regarder à l'intérieur des pièces et des assemblages.",
"help.inspect.zebra": "Affiche des bandes noires et blanches réfléchies sur les faces du modèle. À utiliser pour juger la douceur : des bandes qui se cassent ou se brisent sur une arête révèlent une mauvaise transition.",
"help.joint.as_built": "Relie deux pièces là où elles se trouvent déjà, sans déplacer aucune des deux, et définit comment elles peuvent encore bouger. À utiliser pour des pièces déjà placées dans leur position finale.",
"help.joint.cam": "Relie une came tournante à un suiveur, qui glisse ou pivote lorsque le contour de la came passe dessous. À utiliser pour des cames, des excentriques et des commandes de soupapes.",
"help.joint.create": "Relie deux pièces aux origines de liaison que vous choisissez et déplace la seconde pièce en place ; le type de liaison définit comment elles peuvent encore bouger. À utiliser pour construire un assemblage dont les pièces se déplacent comme le vrai mécanisme.",
"help.joint.drive": "Définit l'angle ou la distance d'une liaison et déplace les pièces en conséquence, dans ses limites ; les liaisons couplées et les boucles fermées suivent. À utiliser pour vérifier qu'un mécanisme se déplace comme prévu.",
"help.joint.gear": "Couple deux liaisons rotatives avec un rapport d'engrenage, de sorte que piloter l'une fait tourner l'autre. À utiliser pour des engrenages droits et des couronnes, avec le rapport tiré des rayons primitifs ou des nombres de dents.",
"help.joint.limits": "Définit jusqu'où une liaison peut tourner ou coulisser, par un minimum et un maximum, ou supprime ces limites. À utiliser pour garder une charnière, une glissière ou un levier dans sa course réelle.",
"help.joint.motion_link": "Couple deux liaisons avec un rapport et un décalage, de sorte que piloter l'une déplace l'autre. À utiliser pour une relation fixe entre liaisons que Engrenage, Pignon et crémaillère ou Vis ne couvrent pas.",
"help.joint.origin": "Place sur une pièce une origine de liaison nommée : au centre d'une face ou d'une extrémité ronde, au milieu d'une arête, à un coin ou en un point. À utiliser pour préparer l'emplacement des liaisons ; une origine choisie sur une face la suit lorsque la pièce change de taille.",
"help.joint.rack_pinion": "Relie un pignon tournant à une crémaillère coulissante, de sorte que la crémaillère avance de la longueur de cercle primitif que le pignon parcourt en roulant. À utiliser pour des commandes à crémaillère, des directions et des axes linéaires.",
"help.joint.reach": "Déplace un point d'une pièce vers une cible en modifiant les liaisons de sa chaîne, dans leurs limites. À utiliser pour positionner un mécanisme selon l'endroit où doit se trouver son extrémité, comme le bout d'un bras de robot.",
"help.joint.rigid_group": "Verrouille ensemble les pièces sélectionnées telles qu'elles sont, pour qu'elles se déplacent d'un seul bloc. À utiliser pour des pièces qui vont ensemble et qui n'ont aucun mouvement entre elles.",
"help.joint.screw": "Couple rotation et translation par un pas, de sorte que chaque tour avance de cette distance. À utiliser pour des vis mères, des boulons et des broches filetées.",
"help.library.apply": "Applique un matériau ou une apparence d'un fichier de bibliothèque local à des corps, des faces ou des composants. À utiliser pour réutiliser des matériaux enregistrés depuis une autre conception.",
"help.library.export": "Enregistre les matériaux et apparences de la conception dans un fichier de bibliothèque. À utiliser pour les partager ou les réutiliser dans d'autres conceptions.",
"help.library.inspect": "Vérifie un fichier de bibliothèque et liste ses matériaux et apparences, sans modifier la conception. À utiliser pour voir ce que contient une bibliothèque avant de l'appliquer.",
"help.licences": "Licences",
"help.material.assign": "Applique le matériau choisi aux corps sélectionnés, par exemple acier, aluminium ou ABS. À utiliser pour que la masse et le poids correspondent à la pièce réelle.",
"help.material.properties.validate": "Vérifie qu'une fiche de données de matériau a des valeurs cohérentes et qu'aucune n'est manquante. À utiliser avant de se fier aux données pour la masse ou la rigidité.",
"help.mesh.convert": "Reconstruit un corps maillé, tel qu'un STL, en solide modifiable avec des faces planes et cylindriques. À utiliser pour modifier une pièce mécanique importée comme un corps modélisé.",
"help.mit": "Licence MIT",
"help.motion.study": "Définit des valeurs de liaison à des étapes le long d'une chronologie et les rejoue sous forme d'animation. À utiliser pour voir un mécanisme se déplacer selon une séquence et vérifier les jeux en chemin.",
"help.motion.trace": "Trace le chemin que suit un point d'une pièce pendant la lecture d'une étude de mouvement ou le pilotage d'une liaison, et le conserve comme courbe d'esquisse 3D lorsque vous confirmez. À utiliser pour voir où arrive un mécanisme, ou pour construire à partir de sa trajectoire.",
"help.notice": "NOTICE",
"help.occurrence.ground": "Fixe une pièce en place, ou la libère, de sorte que les liaisons déplacent les autres pièces autour d'elle. À utiliser pour le châssis ou la base sur laquelle le reste de l'assemblage est construit.",
"help.occurrence.move": "Déplace une pièce d’assemblage selon les seules libertés permises par ses liaisons. Sélectionnez une face de la pièce, puis faites glisser une flèche ou un arc disponible.",
"help.parameters.change": "Ouvre le tableau des valeurs nommées et des expressions qui pilotent les cotes de la conception. À utiliser pour changer une dimension à un seul endroit et faire suivre toutes les fonctions qui l'utilisent.",
"help.parameters.export": "Enregistre les paramètres de la conception en CSV ou JSON. À utiliser pour les modifier dans un tableur ou les transférer vers une autre conception.",
"help.parameters.graph": "Montre quels paramètres et fonctions en utilisent quels autres, ainsi que les boucles ou erreurs entre eux. À utiliser pour voir ce que la modification d'un paramètre va affecter.",
"help.parameters.import": "Lit des paramètres depuis un CSV ou un JSON dans la conception, tous ou aucun. À utiliser pour importer des valeurs d'un tableur ou d'une autre conception.",
"help.parameters.move": "Déplace un paramètre utilisateur avant ou après un autre dans la liste. À utiliser pour garder ensemble les paramètres liés.",
"help.parts.fastener": "Ajoute une vis, un boulon, un écrou, une rondelle, une goupille ou un roulement standard à la taille choisie. Placé sur un perçage, il s'y loge avec une liaison, ce qui évite de modéliser soi-même les fixations.",
"help.parts.insert": "Insère une pièce standard de la famille, de la taille et de la longueur choisies. Placée sur le bord ou la paroi d'un perçage, elle s'y loge avec une liaison rigide.",
"help.parts.library": "Ouvre les pièces standard : vis, boulons, écrous, rondelles, vis sans tête, goupilles cylindriques et roulements en tailles normalisées. À utiliser pour ajouter une pièce standard plutôt que de la modéliser soi-même.",
"help.parts.resize": "Change la taille ou la longueur d'une pièce standard insérée et la reconstruit ; ses liaisons la maintiennent en place. À utiliser lorsqu'une vis se révèle trop longue ou qu'un perçage demande une autre taille.",
"help.plastic.assign_rule": "Affecte une règle plastique à des corps, de sorte que leurs fonctions plastique prennent sa dépouille et son jeu par défaut. À utiliser pour configurer une pièce moulée une fois pour toutes ses fonctions.",
"help.plastic.boss": "Ajoute un bossage de vis sur une face : un plot avec perçage, dépouille, congé de pied et nervures de soutien facultatifs. À utiliser pour les points de vissage des boîtiers moulés ou imprimés.",
"help.plastic.lip": "Ajoute une lèvre ou une rainure correspondante le long du bord d'un corps évidé. À utiliser pour que deux moitiés de boîtier se positionnent l'une sur l'autre et se ferment hermétiquement.",
"help.plastic.manage_rules": "Crée et modifie des règles plastique : matériau, épaisseur de paroi, rayons, jeu et dépouille. À utiliser pour garder cohérentes les fonctions d'une pièce moulée.",
"help.plastic.rest": "Ajoute un appui sur une face : un plot rond ou rectangulaire, plein ou creux, avec dépouille. À utiliser comme support ou entretoise dans un boîtier moulé.",
"help.plastic.snap_fit": "Ajoute un crochet de clip sur une face, avec sa longueur, son épaisseur et son ergot. À utiliser pour assembler des pièces par encliquetage sans vis.",
"help.pointcloud.insert": "Insère un scan PLY, XYZ ou PTS sous forme de nuage de points à côté du modèle. Utilisez-le comme référence pour construire un modèle à partir de données mesurées.",
"help.sheet.convert": "Transforme une plaque ou un corps évidé d'épaisseur uniforme en corps de tôlerie, avec des plis aux arêtes choisies. À utiliser pour rendre une pièce existante dépliable.",
"help.sheet.export_bend_table": "Enregistre la table des plis d'un corps de tôlerie en CSV ou JSON : angles, rayons et positions des plis à plat. À utiliser pour la plieuse ou l'atelier.",
"help.sheet.export_dxf": "Enregistre la mise à plat en DXF, avec contours, lignes de pli et annotations de pli facultatives. À utiliser pour la découpe laser, plasma ou jet d'eau.",
"help.sheet.flange": "Ajoute un bord tombé : une plaque de base à partir d'une esquisse, une paroi le long d'un contour d'esquisse ouvert, ou une paroi pliée sur des arêtes de tôle. À utiliser pour construire des pièces de tôlerie.",
"help.sheet.flat_pattern": "Crée la mise à plat d'un corps de tôlerie, avec sa taille, ses découpes et ses lignes de pli. À utiliser pour vérifier le flan avant de le découper.",
"help.sheet.fold": "Plie une tôle le long d'une ligne d'esquisse selon un angle et un rayon. À utiliser pour plier une tôle plate après y avoir découpé des fonctions.",
"help.sheet.hem": "Replie un bord de tôle sur lui-même. À utiliser pour obtenir des bords sûrs et rigides, sans bavures coupantes.",
"help.sheet.manage_rules": "Crée et modifie des règles de tôlerie : épaisseur, rayon de pli, facteur K, dégagements et jeux. À utiliser pour s'adapter au matériau et aux outils de votre atelier.",
"help.sheet.path_flange": "Ajoute une chaîne de bords tombés le long d'une arête de tôle, en suivant une chaîne ouverte de lignes d'esquisse. À utiliser pour des profils en U ou en Z en une seule étape.",
"help.sheet.perforate": "Ajoute un champ de trous sur toute la tôle à plat, en motif décalé ou carré. À utiliser pour des aérations, des grilles et des panneaux allégés.",
"help.sheet.refold": "Replie un corps de tôlerie déplié. À utiliser après avoir ajouté des fonctions à la tôle à plat.",
"help.sheet.unfold": "Déplie tous les plis d'un corps de tôlerie. À utiliser pour ajouter des trous ou des découpes qui doivent traverser un pli, puis replier.",
"help.sketch.arc.center_point": "Dessine un arc de cercle autour d'un centre, d'un point de départ à un point d'arrivée. À utiliser lorsque le centre de l'arc est connu, comme un rayon autour d'un perçage ou d'un pivot.",
"help.sketch.arc.tangent": "Dessine un arc qui part de l'extrémité d'une ligne ou d'un arc et continue en tangence avec lui. À utiliser pour poursuivre une chaîne avec une courbe douce plutôt qu'un angle.",
"help.sketch.arc.three_point": "Dessine un arc d'un point de départ à un point d'arrivée en passant par un troisième point. À utiliser lorsque l'arc doit passer par des points donnés plutôt qu'autour d'un centre connu.",
"help.sketch.arc3d": "Dessine un arc de cercle exact dans l'espace par trois points, avec Esquisse 3D activée. À utiliser pour des courbes de trajectoires et de rails guides qui sortent du plan d'esquisse.",
"help.sketch.auto_constrain": "Ajoute les contraintes que l'esquisse a presque déjà, comme horizontale, parallèle ou coïncidente, puis la cote jusqu'à ce qu'elle soit entièrement définie. À utiliser pour verrouiller en une étape une esquisse dessinée approximativement.",
"help.sketch.auto_constrain_from_datum": "Fonctionne comme Contraintes automatiques, mais mesure les positions depuis un point de référence que vous choisissez plutôt que depuis l'origine. À utiliser lorsque la pièce est tracée à partir d'un point de référence, comme un coin ou un perçage.",
"help.sketch.blend_curve": "Relie les extrémités de deux courbes par une spline douce, tangente aux deux, ou aussi continue en courbure si vous le choisissez. À utiliser pour combler un écart entre des courbes sans cassure.",
"help.sketch.break": "Scinde une courbe en morceaux là où d'autres courbes la croisent, ou en des points que vous placez dessus. À utiliser lorsqu'une partie d'une courbe a besoin de sa propre contrainte ou doit être supprimée seule.",
"help.sketch.cancel": "Quitte l'esquisse et abandonne tout ce qui a été modifié depuis sa création ou son ouverture pour modification. Annuler rétablit les modifications.",
"help.sketch.centerline": "Bascule des lignes entre lignes normales et lignes d'axe, des axes que les profils ignorent. Utilisez une ligne d'axe comme axe pour une révolution, une symétrie ou une contrainte de symétrie.",
"help.sketch.chamfer.distance_angle": "Biseaute le coin entre deux lignes, défini par une distance le long de la première ligne et un angle. À utiliser lorsqu'un dessin donne le chanfrein sous forme de longueur et d'angle, comme 2 mm × 30°.",
"help.sketch.chamfer.equal_distance": "Biseaute le coin entre deux lignes sur la même distance le long des deux. À utiliser pour le chanfrein habituel à 45° sur un contour.",
"help.sketch.chamfer.two_distance": "Biseaute le coin entre deux lignes, défini par une distance distincte le long de chaque ligne. À utiliser pour des chanfreins qui ne sont pas à 45°, donnés sous forme de deux longueurs.",
"help.sketch.circle.center": "Dessine un cercle à partir de son centre et de son diamètre. À utiliser pour des perçages, des bossages et d'autres formes rondes dont le centre est connu.",
"help.sketch.circle.three_point": "Dessine un cercle passant par trois points. À utiliser lorsque le cercle doit passer par des points donnés, par exemple de la géométrie existante.",
"help.sketch.circle.three_tangent": "Dessine un cercle qui touche trois lignes, cercles ou arcs. À utiliser pour inscrire un cercle dans l'espace entre trois courbes, comme le cercle inscrit d'un triangle.",
"help.sketch.circle.two_point": "Dessine un cercle à partir des deux extrémités de son diamètre. À utiliser lorsque les points du bord du cercle sont connus plutôt que son centre.",
"help.sketch.circle.two_tangent": "Dessine un cercle de taille donnée qui touche deux lignes, cercles ou arcs. À utiliser pour un arrondi de rayon fixe entre deux courbes.",
"help.sketch.conic": "Dessine une courbe conique entre deux extrémités, façonnée par un point sommet et une valeur rho. À utiliser pour des transitions douces dont vous voulez ajuster la tension, de presque plate à presque anguleuse.",
"help.sketch.constrainer": "Applique la contrainte qu'une ou deux courbes choisies ont presque déjà, comme horizontale, parallèle, tangente ou égale. À utiliser pour mettre au propre une esquisse à main levée, une relation à la fois.",
"help.sketch.constraint.coincident": "Place un point sur un autre point, une courbe ou un axe d'esquisse. À utiliser pour fermer les écarts d'un profil ou pour poser un point sur une ligne.",
"help.sketch.constraint.collinear": "Place deux lignes sur une même droite. À utiliser pour aligner des arêtes interrompues, comme les deux côtés d'une encoche.",
"help.sketch.constraint.concentric": "Donne le même centre à deux cercles ou arcs. À utiliser pour des bagues, des lamages et des arbres à épaulements.",
"help.sketch.constraint.curvature": "Fait que deux courbes qui se rejoignent en une extrémité continuent avec la même courbure (G2). À utiliser lorsqu'un raccord tangent n'est pas assez doux, par exemple sur des surfaces visibles.",
"help.sketch.constraint.equal": "Donne la même longueur à des lignes, ou le même rayon à des cercles et arcs. À utiliser pour qu'une seule cote pilote plusieurs formes identiques.",
"help.sketch.constraint.fix": "Fixe des points ou des courbes là où ils sont, ou les libère. À utiliser pour la géométrie qui ne doit pas bouger lorsque vous faites glisser ou résolvez l'esquisse.",
"help.sketch.constraint.horizontal_vertical": "Rend une ligne, ou la ligne entre deux points, horizontale ou verticale, selon la plus proche. À utiliser pour redresser un contour sur les axes de l'esquisse.",
"help.sketch.constraint.midpoint": "Place un point au milieu d'une ligne, ou fait coïncider les milieux de deux lignes. À utiliser pour centrer de la géométrie sur une arête.",
"help.sketch.constraint.parallel": "Rend deux lignes parallèles. À utiliser pour des côtés qui doivent rester parallèles lorsque d'autres cotes changent.",
"help.sketch.constraint.perpendicular": "Fait se rencontrer deux lignes à angle droit. À utiliser pour des coins d'équerre entre des lignes qui ne sont pas sur les axes de l'esquisse.",
"help.sketch.constraint.polygon": "Transforme une chaîne fermée de lignes en polygone régulier : côtés égaux, avec tous les coins sur un même cercle. À utiliser pour rendre régulier un polygone dessiné à main levée et le garder ainsi.",
"help.sketch.constraint.symmetry": "Fait que deux points ou courbes sont symétriques par rapport à une ligne ou à un point. À utiliser pour garder un profil symétrique lorsque vous modifiez un côté.",
"help.sketch.constraint.tangent": "Fait que deux courbes se touchent en tangence, comme une ligne et un arc. À utiliser pour des raccords doux sans cassure.",
"help.sketch.construction": "Bascule des courbes entre géométrie normale et géométrie de construction, que les profils ignorent. Utilisez les courbes de construction comme aides pour l'implantation, les références et les cotes.",
"help.sketch.construction_line": "Crée une droite de construction infinie passant par deux points. À utiliser pour structurer une esquisse sans diviser les profils.",
"help.sketch.construction_line.angle": "Crée une droite de construction infinie inclinée d'un angle par rapport à l'axe X de l'esquisse. Choisissez le point puis saisissez l'angle, ou choisissez sa direction.",
"help.sketch.construction_line.bisector": "Sélectionnez deux lignes droites sécantes de l'esquisse, puis choisissez l'une des deux bissectrices. Le résultat suit les lignes dont il provient.",
"help.sketch.construction_line.horizontal": "Crée une droite de construction horizontale infinie passant par un point. Sa contrainte horizontale est conservée lors des modifications ultérieures.",
"help.sketch.construction_line.parallel": "Sélectionnez une ligne droite de l'esquisse, puis placez une droite de construction infinie parallèle. Clic droit pour choisir une autre référence.",
"help.sketch.construction_line.perpendicular": "Sélectionnez une ligne droite de l'esquisse, puis placez une droite de construction infinie perpendiculaire. Clic droit pour choisir une autre référence.",
"help.sketch.construction_line.tangent_at": "Crée une tangente en un point d'un cercle ou d'un arc de cercle, ou à l'extrémité d'une spline ouverte ou d'une conique. Sélectionnez la courbe, puis son point de contact.",
"help.sketch.construction_line.tangent_external": "Sélectionnez un cercle ou un arc de cercle, puis un point extérieur et une solution de tangente. Les aperçus n'affichent que les points de contact situés sur l'arc dessiné.",
"help.sketch.construction_line.tangent_two": "Sélectionnez deux cercles ou arcs de cercle, puis choisissez une tangente commune dans l'aperçu. Les points de contact doivent se trouver sur les courbes dessinées.",
"help.sketch.construction_line.vertical": "Crée une droite de construction verticale infinie passant par un point. Sa contrainte verticale est conservée lors des modifications ultérieures.",
"help.sketch.copy": "Copie les courbes et points d'esquisse sélectionnés, avec les contraintes et cotes qui les relient. Collez-les dans cette esquisse ou dans une autre.",
"help.sketch.create": "Démarre une nouvelle esquisse 2D sur un plan ou sur une face plane du modèle. Toute extrusion, révolution et balayage commence par une esquisse.",
"help.sketch.cut": "Copie la géométrie d'esquisse sélectionnée et la supprime de l'esquisse, en une seule étape d'annulation. Collez-la ailleurs dans cette esquisse ou dans une autre.",
"help.sketch.dimension": "Ajoute une cote pilotante : une longueur, une distance, un angle, un rayon ou un diamètre, selon ce que vous choisissez. Saisissez une valeur ou un nom de paramètre, et l'esquisse la suit.",
"help.sketch.edit": "Ouvre une esquisse existante pour la modifier. Les fonctions construites dessus se mettent à jour lorsque vous terminez l'esquisse.",
"help.sketch.ellipse": "Dessine une ellipse à partir de son centre, de l'extrémité de son grand axe et d'un point côté petit axe. À utiliser pour des ouvertures ovales, des cames et d'autres formes rondes allongées.",
"help.sketch.equation_curve": "Dessine une courbe à partir de formules : x(t) et y(t), ou y = f(x), sur un intervalle. Les formules peuvent utiliser des paramètres et la courbe les suit : à utiliser pour une sinusoïde, une spirale ou la développante d'une dent d'engrenage.",
"help.sketch.extend": "Prolonge une ligne ou un arc jusqu'à la courbe la plus proche sur son chemin, ou fait croître deux courbes jusqu'à ce qu'elles se rejoignent. À utiliser pour fermer des jeux dans un profil après ajustement ou modification.",
"help.sketch.fillet": "Arrondit le coin où deux lignes ou arcs se rejoignent par un arc de rayon donné. Plusieurs coins arrondis ensemble partagent un même rayon.",
"help.sketch.finish": "Quitte l'esquisse et revient au modèle. Ses profils fermés sont alors prêts pour Extrusion, Révolution et d'autres fonctions.",
"help.sketch.finish_auto_constrain": "Lance Contraintes automatiques sur l'esquisse, puis la termine. À utiliser pour laisser une esquisse entièrement définie en une seule étape.",
"help.sketch.fit_curves_to_section": "Coupe un corps maillé ou solide avec le plan d'esquisse et ajuste des lignes et des arcs à la section. À utiliser pour reconstruire une pièce numérisée ou importée sous forme d'esquisse modifiable.",
"help.sketch.include_3d": "Amène des arêtes du modèle, des sommets et des courbes d'autres esquisses dans cette esquisse comme références liées, en aplatissant sur son plan celles qui en sont hors. À utiliser pour construire à partir d'une géométrie qui n'est pas dans le plan d'esquisse.",
"help.sketch.infinite": "Bascule des lignes entre lignes normales et lignes de construction infinies qui traversent toute la vue. À utiliser comme références d'implantation qui ne s'arrêtent jamais trop court.",
"help.sketch.insert_dxf": "Lit les courbes d'un dessin DXF dans une esquisse, mises à l'échelle, tournées et placées comme vous le définissez. À utiliser pour construire à partir d'un dessin 2D d'un autre logiciel, comme un contour de découpe laser.",
"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.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.",
"help.sketch.line3d": "Dessine des lignes droites connectées entre des points de l'espace, avec Esquisse 3D activée. À utiliser pour des trajectoires et des structures qui sortent du plan d'esquisse.",
"help.sketch.mirror": "Symétrise des courbes et points d'esquisse par rapport à une ligne ou un axe et ajoute des contraintes de symétrie. À utiliser pour dessiner la moitié d'un profil symétrique et le compléter.",
"help.sketch.move": "Déplace ou fait tourner des courbes et points d'esquisse, ou une copie. À utiliser pour repositionner de la géométrie en gardant sa forme et ses contraintes.",
"help.sketch.offset": "Crée une copie d'une chaîne de courbes à une distance donnée, d'un côté ou des deux. À utiliser pour des parois, des contours et des jeux ; la distance reste une cote modifiable.",
"help.sketch.paste": "Colle dans l'esquisse active la géométrie d'esquisse copiée ou coupée. Placez-la en un point ou décalez-la d'une valeur.",
"help.sketch.pattern.circular": "Copie la géométrie d'esquisse autour d'un centre, sur un cercle complet ou un angle donné. À utiliser pour des cercles de perçages, des rayons et des dents.",
"help.sketch.pattern.rectangular": "Copie la géométrie d'esquisse en lignes et colonnes à des espacements donnés. À utiliser pour des grilles de trous, de rainures ou d'aérations.",
"help.sketch.point": "Place un point d'esquisse. À utiliser comme référence pour des cotes, des contraintes, des centres de perçage ou des fonctions ultérieures.",
"help.sketch.point3d": "Place un point dans l'espace, avec Esquisse 3D activée. À utiliser comme référence pour des courbes et trajectoires 3D.",
"help.sketch.polygon.circumscribed": "Dessine un polygone régulier autour d'un cercle, dimensionné du centre au milieu d'un côté. À utiliser lorsque la largeur sur plats est connue, comme pour un écrou hexagonal.",
"help.sketch.polygon.edge": "Dessine un polygone régulier à partir des deux extrémités d'un côté. À utiliser lorsque la longueur et la position d'un côté sont connues.",
"help.sketch.polygon.inscribed": "Dessine un polygone régulier à l'intérieur d'un cercle, dimensionné du centre à un coin. À utiliser lorsque la dimension sur coins est connue.",
"help.sketch.project": "Amène des arêtes du modèle, des faces, des sommets et des courbes d'autres esquisses dans cette esquisse, projetés sur son plan. Ils restent liés et suivent le modèle lorsqu'il change.",
"help.sketch.project_to_surface": "Projette des courbes d'autres esquisses selon la normale de cette esquisse sur une face, sous forme de courbes 3D liées. À utiliser pour poser un logo, une ligne de partage ou une trajectoire sur une surface courbe.",
"help.sketch.rectangle.center": "Dessine un rectangle à partir de son centre et d'un coin. À utiliser pour des contours centrés sur un point, comme l'origine.",
"help.sketch.rectangle.rounded": "Dessine un rectangle dont les quatre coins sont arrondis au même rayon. À utiliser pour des plaques, des couvercles et des ouvertures à coins arrondis.",
"help.sketch.rectangle.three_point": "Dessine un rectangle à partir d'un côté et d'un point du côté opposé. À utiliser pour des rectangles inclinés par rapport aux axes de l'esquisse.",
"help.sketch.rectangle.two_point": "Dessine un rectangle à partir de deux coins opposés. À utiliser pour les contours de plaques, de blocs et de poches.",
"help.sketch.scale": "Met à l'échelle des courbes et points d'esquisse selon un facteur autour d'un point de base. À utiliser pour redimensionner une partie d'une esquisse sans la redessiner.",
"help.sketch.slot.arc_center": "Dessine une rainure courbe de largeur donnée le long d'un arc défini par son centre, son début et sa fin. À utiliser pour des rainures de réglage autour d'un pivot.",
"help.sketch.slot.arc_three_point": "Dessine une rainure courbe de largeur donnée le long d'un arc passant par trois points. À utiliser lorsque le tracé de la rainure doit passer par des points donnés.",
"help.sketch.slot.center_point": "Dessine une rainure droite de largeur donnée de son centre au centre d'un arc d'extrémité. À utiliser pour des rainures centrées sur un point.",
"help.sketch.slot.center_to_center": "Dessine une rainure droite de largeur donnée entre les centres de ses deux arcs d'extrémité. À utiliser pour des rainures cotées de centre à centre, comme les rainures de réglage de vis.",
"help.sketch.slot.overall": "Dessine une rainure droite de largeur donnée d'une extrémité extérieure à l'autre. À utiliser lorsque c'est la longueur hors tout de la rainure qui compte.",
"help.sketch.spline.control_point": "Dessine une spline cubique façonnée par des points de contrôle qu'elle ne traverse pas. À utiliser pour des courbes douces et fluides que vous façonnez en déplaçant les points.",
"help.sketch.spline.control_point_5": "Dessine une spline de degré 5 façonnée par des points de contrôle. À utiliser lorsque des courbes doivent se raccorder avec une courbure plus douce, par exemple sur des surfaces de style visibles.",
"help.sketch.spline.fit_point": "Dessine une spline douce qui passe par chaque point que vous placez, ouverte ou fermée. À utiliser pour décalquer une forme à travers des points connus.",
"help.sketch.spline3d": "Dessine une courbe douce dans l'espace par des points, avec Esquisse 3D activée. À utiliser pour des trajectoires de balayage et des cheminements de câbles qui sortent du plan d'esquisse.",
"help.sketch.spun_profile": "Crée dans le plan d'esquisse le contour qu'un corps balaie en tournant autour d'un axe. À utiliser pour obtenir la silhouette d'une pièce tournée pour une révolution ou un dessin.",
"help.sketch.text": "Écrit du texte dans l'esquisse avec une police, une hauteur et un angle au choix ; ses mots peuvent suivre des paramètres. À extruder ou embosser pour des étiquettes et des lettrages.",
"help.sketch.transform": "Met à l'échelle, fait tourner et déplace de la géométrie d'esquisse autour d'un centre en une seule étape. À utiliser pour repositionner et redimensionner une sélection d'un coup.",
"help.sketch.trim": "Supprime le morceau d'une courbe entre les croisements les plus proches autour de l'endroit où vous cliquez. À utiliser pour nettoyer des lignes qui se chevauchent et obtenir un profil fermé.",
"help.solid.align": "Déplace les corps sélectionnés pour qu'un de leurs points, faces ou plans rejoigne un point, une face ou un plan ailleurs. À utiliser pour poser une pièce contre une autre sans mesurer le décalage.",
"help.solid.boundary_fill": "Crée des solides à partir des régions délimitées par des corps outils qui se chevauchent, en ne remplissant que les régions que vous choisissez. À utiliser pour construire une forme à partir de l'espace que plusieurs corps délimitent ensemble.",
"help.solid.bounding_solid": "Crée un parallélépipède qui englobe les corps sélectionnés, avec une marge facultative. À utiliser pour voir l'encombrement d'une pièce, ou comme brut dans lequel elle est usinée.",
"help.solid.box": "Crée un parallélépipède à partir d'une longueur, d'une largeur et d'une hauteur, depuis un coin ou centré sur un point. À utiliser pour un bloc rapide, un volume provisoire ou un corps dans lequel tailler des fonctions.",
"help.solid.chamfer": "Coupe les arêtes sélectionnées par un biseau plat de la distance indiquée. À utiliser pour casser des arêtes vives ou faciliter l'entrée d'une goupille ou d'une vis.",
"help.solid.coil": "Crée un corps hélicoïdal, comme un ressort, à partir d'un diamètre, d'un nombre de tours, d'une hauteur ou d'un pas et d'une section ronde ou carrée. À utiliser pour des ressorts, des spires et des nervures hélicoïdales.",
"help.solid.combine": "Joint des corps, les retire d'un corps cible ou ne garde que le volume qu'ils ont en commun, et peut conserver les corps outils. À utiliser pour fusionner des pièces, tailler une forme avec une autre ou repérer où deux corps se recouvrent.",
"help.solid.cone": "Crée un cône à partir d'un cercle de base et d'une hauteur, ou un tronc de cône lorsque le sommet a aussi un rayon. À utiliser pour des conicités, des entonnoirs, des fraisages et des pointes.",
"help.solid.cylinder": "Crée un cylindre à partir d'un rayon ou d'un diamètre et d'une hauteur, posé sur un point selon un axe. À utiliser pour des arbres, des goupilles, des bossages et des bruts ronds.",
"help.solid.draft": "Incline les faces sélectionnées de l'angle indiqué, mesuré depuis un plan neutre. À utiliser sur les pièces moulées ou coulées pour qu'elles sortent du moule.",
"help.solid.emboss": "Fait saillir des profils d'esquisse, comme du texte ou un logo, hors d'une face, ou les enfonce dedans. À utiliser pour des lettrages, des étiquettes et des marquages sur des faces planes ou rondes.",
"help.solid.extrude": "Pousse le profil d'une esquisse en 3D sur une distance, en ajoutant ou en retirant de la matière. À utiliser pour transformer un contour plat en bloc, en bossage ou en poche.",
"help.solid.fillet": "Arrondit les arêtes sélectionnées par un raccord circulaire lisse du rayon indiqué. À utiliser pour adoucir des arêtes vives par souci de solidité, de confort ou d'aspect.",
"help.solid.hole": "Perce un trou rond du diamètre indiqué en un point choisi d'une face. À utiliser pour les passages de vis, les goupilles et autres ouvertures rondes simples.",
@@ -1408,20 +1645,62 @@
"help.solid.move": "Déplace ou fait tourner les corps sélectionnés, ou une copie, selon la distance et l'angle indiqués. À utiliser pour repositionner un corps ou en créer un second ailleurs.",
"help.solid.offset_face": "Pousse les faces sélectionnées vers l'intérieur ou l'extérieur de la distance indiquée, et les faces voisines suivent. À utiliser pour épaissir une paroi, creuser une poche ou rehausser un bossage sans modifier l'esquisse.",
"help.solid.pattern.circular": "Répète les objets sélectionnés autour d'un axe, selon le nombre et l'angle total indiqués. À utiliser pour les cercles de perçages, rayons, ailettes et dents.",
"help.solid.pattern.path": "Copie des fonctions ou des corps le long d'une trajectoire d'esquisse, selon un nombre et un espacement. À utiliser pour des trous le long d'une arête courbe ou des pièces le long d'un rail.",
"help.solid.pattern.rectangular": "Répète les objets sélectionnés dans une ou deux directions, selon le nombre et l'espacement indiqués. À utiliser pour les rangées et grilles de trous, nervures, goupilles ou pieds.",
"help.solid.pipe": "Crée un tube rond le long d'une trajectoire de courbes d'esquisse, plein ou creux avec une épaisseur de paroi. À utiliser pour des tubes, des fils, des câbles et des rails.",
"help.solid.press_pull": "Ajoute ou retire de la matière quand on fait glisser une face, ou arrondit des arêtes quand on les fait glisser. À utiliser pour des modifications rapides directement sur le modèle.",
"help.solid.remove": "Retire les corps sélectionnés de la conception à partir de ce point de la chronologie ; les étapes précédentes les gardent. À utiliser pour écarter un corps d'aide, comme un outil de découpe devenu inutile.",
"help.solid.replace_face": "Amène les faces planes sélectionnées sur un plan ou une face parallèle, et le corps s'allonge ou se raccourcit pour la rejoindre. À utiliser pour mettre une face au ras d'une autre pièce ou d'un plan de construction.",
"help.solid.revolve": "Fait tourner le profil d'une esquisse autour d'un axe pour obtenir un solide de révolution. À utiliser pour les arbres, boutons, bols et toute pièce ronde autour d'une ligne centrale.",
"help.solid.rib": "Ajoute une paroi mince le long d'une courbe d'esquisse ouverte, prolongée jusqu'aux faces suivantes du corps. À utiliser pour rigidifier des parois et des coins, par exemple dans des pièces moulées ou imprimées.",
"help.solid.scale": "Agrandit ou réduit les corps sélectionnés d'un facteur, identique dans toutes les directions ou propre à chaque axe. À utiliser pour redimensionner une forme importée ou compenser un retrait.",
"help.solid.shell": "Évide un solide en laissant des parois de l'épaisseur indiquée, et peut ouvrir les faces choisies. À utiliser pour les boîtes, couvercles et autres pièces à parois minces.",
"help.solid.sphere": "Crée une sphère à partir d'un rayon ou d'un diamètre autour d'un centre. À utiliser pour des billes, des boutons et des extrémités arrondies.",
"help.solid.split_body": "Scinde un corps en corps séparés avec un plan, la face d'un autre corps ou une ligne d'esquisse. À utiliser pour obtenir des pièces imprimées, usinées ou colorées séparément.",
"help.solid.split_face": "Scinde les faces sélectionnées le long d'un plan, de la face d'un autre corps ou de courbes d'esquisse, sans scinder le corps. À utiliser pour donner à une partie de face sa propre dépouille, couleur ou décalage.",
"help.solid.sweep": "Entraîne le profil d'une esquisse le long d'une trajectoire pour obtenir un solide. À utiliser pour les tuyaux, rails, poignées et toute pièce de section constante qui suit une courbe.",
"help.solid.thread": "Ajoute un filetage métrique ISO sur une face cylindrique, soit sous forme de filetage cosmétique, soit taillé dans le corps. À utiliser pour des boulons, des trous taraudés et des pièces vissées imprimées.",
"help.solid.torus": "Crée un anneau à partir d'un grand rayon autour de son axe et d'un petit rayon pour sa section ronde. À utiliser pour des joints toriques, des poignées et des perles rondes.",
"help.solid.web": "Ajoute une paroi mince le long de chaque courbe d'esquisse ouverte, chacune prolongée jusqu'aux faces suivantes du corps. À utiliser pour une grille de parois de soutien à l'intérieur de boîtiers et de plateaux.",
"help.surface.blend": "Crée une surface qui relie les arêtes de deux corps, en tangence ou en continuité de courbure avec les deux. À utiliser pour joindre en douceur deux surfaces à travers un écart.",
"help.surface.boundary": "Crée une surface remplissant une boucle de deux à quatre courbes d'esquisse, ou un réseau de courbes. À utiliser pour des panneaux de forme libre définis par leurs bords.",
"help.surface.extend": "Prolonge une surface au-delà de ses bords, d'une distance ou jusqu'à un plan ou une face. À utiliser pour que des surfaces se chevauchent avant de les ajuster ou de les coudre.",
"help.surface.extrude": "Pousse des courbes ou profils d'esquisse sur une distance pour former une surface, parois seulement, sans fonds. À utiliser pour des parois minces et pour des outils de coupe ou de partage.",
"help.surface.fair": "Lisse l'intérieur de faces de forme libre tandis que leurs bords restent en place. À utiliser pour atténuer les bosses et les reflets ondulés d'une surface.",
"help.surface.loft": "Crée une surface qui passe par deux sections ou plus dans l'ordre, sans fonds. À utiliser pour des coques et des transitions entre des formes différentes.",
"help.surface.match": "Reconstruit le bord d'une surface pour qu'il rejoigne le bord d'un autre corps, avec une tangence ou une courbure concordante. À utiliser pour fermer en douceur un raccord entre deux surfaces.",
"help.surface.offset": "Crée une copie de faces à une distance le long de leurs normales, sous forme de nouveau corps surfacique. À utiliser pour des peaux, des jeux ou l'autre côté d'une paroi.",
"help.surface.patch": "Remplit une région d'esquisse ou une boucle fermée d'arêtes par une face, plane ou courbe et douce. À utiliser pour boucher des trous dans un corps surfacique.",
"help.surface.reverse_normal": "Retourne des faces d'un corps surfacique, de sorte que leur côté extérieur soit orienté dans l'autre sens. À utiliser avant d'épaissir ou de coudre lorsque les faces pointent vers l'intérieur.",
"help.surface.revolve": "Fait tourner des courbes ou profils d'esquisse autour d'un axe pour former une surface, sans fonds. À utiliser pour des coques rondes comme des abat-jour, des bols et des buses.",
"help.surface.ruled": "Crée une bande de droites à partir d'une chaîne d'arêtes, selon la normale de la face, la tangente ou un angle. À utiliser pour des parois de dépouille, des dégagements et des surfaces de joint.",
"help.surface.stitch": "Coud des corps surfaciques le long de leurs arêtes communes, en un solide lorsqu'ils se referment. À utiliser pour transformer un ensemble de surfaces en un seul corps.",
"help.surface.sweep": "Déplace des courbes ou profils d'esquisse le long d'une trajectoire pour former une surface, sans fonds. À utiliser pour des gouttières, des moulures et d'autres formes minces qui suivent une trajectoire.",
"help.surface.thicken": "Donne une épaisseur à un corps surfacique et le transforme en solide. À utiliser pour fabriquer des pièces imprimables ou usinables à partir de surfaces.",
"help.surface.trim": "Coupe une surface avec un plan, une autre surface ou des courbes d'esquisse, et garde le côté que vous choisissez. À utiliser pour ajuster des surfaces à leur forme avant de les coudre.",
"help.surface.unstitch": "Sépare des faces d'un corps en corps surfaciques distincts ; un solide se décompose en surfaces. À utiliser pour retravailler des faces isolées d'un corps.",
"help.surface.untrim": "Supprime des trous d'une face, ou rétablit son contour extérieur aux limites naturelles de la surface. À utiliser pour retrouver la surface complète après un ajustement.",
"help.third_party": "Licences tierces",
"help.timeline.compute_all": "Recalcule toutes les fonctions de la chronologie depuis le début. À utiliser pour rafraîchir le modèle et voir quelles fonctions échouent.",
"help.timeline.delete": "Supprime les fonctions sélectionnées de la chronologie ; supprimer une esquisse supprime aussi les fonctions construites à partir d'elle. À utiliser pour retirer une étape devenue inutile.",
"help.timeline.dependents": "Liste les fonctions qui seraient supprimées avec une fonction, et celles qui échoueraient sans elle. À utiliser avant de supprimer ou de modifier une fonction.",
"help.timeline.play": "Rejoue les fonctions restantes une par une, avec une pause d'une demi-seconde après chaque reconstruction. Relancez cette commande ou appuyez sur Échap pour arrêter ; Aller à la fin saute immédiatement.",
"help.view.apply": "Oriente la vue selon une direction standard comme face, dessus ou iso, ou selon une vue nommée enregistrée. À utiliser pour regarder le modèle sous un angle fixe.",
"help.view.delete": "Supprime une vue nommée enregistrée. Le modèle n'est pas modifié.",
"help.view.fit": "Zoome et déplace la vue pour que tout le modèle, ou la sélection, remplisse la vue. À utiliser lorsque le modèle est sorti du champ.",
"help.view.grid": "Affiche ou masque la grille dans la vue 3D. À utiliser comme repère de taille et d'orientation pendant la modélisation.",
"help.view.ground_shadow": "Affiche ou masque l'ombre du modèle sur le sol. À utiliser pour juger plus facilement la hauteur et la position.",
"help.view.home": "Remet la vue dans la direction d'accueil et ajuste le modèle. À utiliser pour revenir rapidement à une vue connue.",
"help.view.list": "Liste les vues nommées enregistrées dans la conception. À utiliser pour retrouver une vue enregistrée plus tôt.",
"help.view.navigate": "Fait tourner, déplace ou zoome la vue par étapes, comme un quart de tour à gauche. À utiliser pour déplacer la caméra avec précision plutôt qu'en faisant glisser.",
"help.view.navigation_bar": "Affiche ou masque la barre de navigation en bas de la vue 3D. Masquez-la pour gagner de la place, affichez-la pour accéder aux outils de vue à la souris.",
"help.view.rename": "Donne un nouveau nom à une vue nommée enregistrée. La vue elle-même reste inchangée.",
"help.view.save": "Enregistre la position actuelle de la caméra comme vue nommée dans la conception. À utiliser pour revenir plus tard au même angle, par exemple pour des captures d'écran.",
"help.view.spin": "Fait orbiter la vue autour du modèle en continu jusqu'au prochain clic ou à la prochaine touche. À utiliser pour présenter un modèle sous tous les angles.",
"help.view.tool": "Verrouille un outil de caméra, orbite, déplacement ou zoom, de sorte que faire glisser avec le bouton gauche de la souris déplace la vue jusqu'à ce que vous le désactiviez. À utiliser sur un trackpad ou sans bouton central de souris.",
"help.view.visual_style": "Change le mode d'affichage du modèle : ombré avec ou sans arêtes, filaire, ou avec arêtes cachées. Utilisez le filaire pour voir à l'intérieur, l'ombré pour un rendu net.",
"history.sketch_under_constrained": "Sous-contraint ({count} degrés de liberté)",
"history.sketch_under_constrained_one": "Sous-contraint ({count} degré de liberté)",
"hole_presets.confirm_delete": "Confirmer la suppression",
"hole_presets.delete": "Supprimer",
"hole_presets.delete_prompt": "Supprimer ce préréglage personnel ? Les perçages existants ne changent pas.",
@@ -1596,6 +1875,10 @@
"material.selection.mixed": "Matériaux différents",
"measure.at": "À",
"measure.item": "Élément {number}",
"measure.quick_copied": "Mesure copiée",
"measure.quick_copy": "Cliquer pour copier la mesure",
"measure.quick_edges": "{count} arêtes",
"measure.quick_faces": "{count} faces",
"messages.ago_h": "il y a {n} h",
"messages.ago_min": "il y a {n} min",
"messages.ago_s": "il y a {n} s",
@@ -1748,6 +2031,11 @@
"not a move": "pas un déplacement",
"not a number": "pas un nombre",
"open a sketch first": "ouvrir d'abord une esquisse",
"parameters.apply_live": "Appliquer chaque modification en quittant son champ",
"parameters.apply_live_tip": "Désactivé : les modifications attendent Appliquer ou OK, puis le modèle est reconstruit une seule fois pour toutes. Annuler ou Échap les abandonne.",
"parameters.held": "{count} modifications en attente d’Appliquer",
"parameters.held_hint": "Modifiez les expressions et les unités. Les modifications attendent Appliquer ou OK.",
"parameters.held_one": "1 modification en attente d’Appliquer",
"parts.automatic_name": "automatique",
"parts.dimension.across_flats": "Sur plats",
"parts.dimension.bore": "Alésage",
@@ -1766,10 +2054,19 @@
"parts.family.button_head_cap_screw": "Vis à tête bombée à six pans creux",
"parts.family.dowel_pin": "Goupille cylindrique",
"parts.family.hex_bolt": "Vis à tête hexagonale",
"parts.family.hex_cap_screw_inch": "Vis à tête hexagonale (pouces)",
"parts.family.hex_nut": "Écrou hexagonal",
"parts.family.hex_nut_inch": "Écrou hexagonal (pouces)",
"parts.family.set_screw": "Vis sans tête à six pans creux, bout plat",
"parts.family.socket_head_cap_screw": "Vis à tête cylindrique à six pans creux",
"parts.family.washer": "Rondelle plate",
"parts.family.washer_sae": "Rondelle plate, SAE (étroite)",
"parts.family.washer_uss": "Rondelle plate, USS (large)",
"parts.holes": "Trous (facultatif)",
"parts.placement_hint": "choisissez un ou plusieurs trous pour y loger une pièce chacun, ou OK la place à l'origine",
"parts.system": "Système d'unités",
"parts.system.inch": "Pouces",
"parts.system.metric": "Métrique",
"pattern.alignment_2": "Alignement 2",
"pattern.alignment_hint": "La quantité inclut l’original. Le centrage répartit les copies de chaque côté ; si la quantité est paire, l’inversion choisit le côté qui reçoit la copie supplémentaire.",
"pattern.direction_1": "Direction 1",
@@ -1810,6 +2107,8 @@
"prefs.anti_aliasing_note": "Adoucit les bords crénelés.",
"prefs.applies_on_restart": "Appliqué au redémarrage",
"prefs.assistant": "Assistant",
"prefs.auto_operation": "Choisir l'opération automatiquement",
"prefs.auto_operation_note": "Les boîtes de dialogue des fonctions choisissent Joindre, Nouveau corps ou Couper d'après ce que touche la nouvelle matière, tant que vous n'en choisissez pas une vous-même. Désactivé : elles gardent l'opération avec laquelle elles s'ouvrent.",
"prefs.axes": "Axes",
"prefs.axis": "Axe",
"prefs.bodies_folders": "Dossiers Corps",
@@ -1820,6 +2119,8 @@
"prefs.bottom_centre": "Bas centre",
"prefs.bottom_right": "Bas droite",
"prefs.browser": "Navigateur",
"prefs.browser_eye_left": "Œil à côté de la flèche de dépliage",
"prefs.browser_eye_left_note": "Les lignes du navigateur affichent leur œil d'affichage et de masquage à côté de la flèche de dépliage, comme avant, au lieu d'une colonne à l'extrémité droite des lignes.",
"prefs.browser_reveal_on_hover": "Révéler au survol",
"prefs.browser_show_note": "Affiché garde le navigateur à l'écran. Révéler au survol le cache jusqu'à ce que le pointeur passe sur sa place ou sur le fin onglet à son bord gauche, et il reste ouvert tant que le pointeur est dedans ou qu'un de ses menus est ouvert.",
"prefs.browser_shown": "Affiché",
@@ -1870,6 +2171,8 @@
"prefs.graphics_card_restart": "Prend effet après le redémarrage de SolidCraft. WGPU_ADAPTER_NAME prévaut sur ce choix.",
"prefs.graphics_note": "Compromis entre vitesse et fluidité. Chaque choix montre son effet sur un exemple.",
"prefs.graphics_presets_note": "Les préréglages définissent ensemble les choix ci-dessous.",
"prefs.hide_constraint_glyphs_zoomed_out": "Masquer les symboles de contraintes en cas de zoom arrière",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Les symboles de contraintes quittent la vue lorsque le zoom arrière sur l'esquisse est assez important pour qu'ils l'encombrent. Désactivé : ils gardent une taille fixe à l'écran quel que soit le zoom.",
"prefs.history": "Historique",
"prefs.history_origin_bodies": "Afficher l'origine et les corps en haut",
"prefs.history_origin_bodies_note": "L'historique à gauche ou sous le navigateur commence par le dossier Corps et l'Origine du composant actif, avec leurs yeux et leurs menus comme dans le navigateur. L'historique en bas ne change pas.",
@@ -1888,6 +2191,8 @@
"prefs.interface_language": "Langue de l'interface",
"prefs.interface_note": "L'apparence de SolidCraft et la place de ses éléments.",
"prefs.interval_minutes": "{n} min",
"prefs.keep_work_on_tab_switch": "Garder dialogues et outils ouverts en changeant de conception",
"prefs.keep_work_on_tab_switch_note": "Chaque onglet de conception garde son dialogue ou son outil ouvert, avec ce que vous y avez sélectionné et saisi, ainsi que son onglet de barre d'outils. Vous les retrouvez en revenant à la conception. Désactivé : changer de conception les annule et affiche l'onglet Modèle.",
"prefs.labels": "Libellés",
"prefs.layout": "Disposition",
"prefs.layout_note": "Compacte : ancre les panneaux aux bords de la fenêtre. La transparence du navigateur est disponible en mode Flottante. Changez le côté de l'assistant dans la page Assistant.",
@@ -1908,6 +2213,9 @@
"prefs.max_file_size_step": "Fichiers STEP et d’importation",
"prefs.max_file_size_web": "L’application web ouvre les fichiers STEP et d’importation jusqu’à 512 Mo et les conceptions jusqu’à 256 Mo : un onglet du navigateur ne peut pas contenir de fichiers plus volumineux. L’application de bureau permet de relever ces limites.",
"prefs.megabytes": "Mo",
"prefs.menu_grid": "Grandes icônes",
"prefs.menu_list": "Liste",
"prefs.menus": "Menus",
"prefs.minutes": "minutes",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Souris et pavé tactile",
@@ -1927,10 +2235,16 @@
"prefs.origin_plane_names": "Noms des plans d'origine",
"prefs.origin_plane_names_note": "Noms affichés dans le navigateur, les boîtes de dialogue et les listes. {top} est XY, {front} est XZ et {right} est YZ, avec Z vers le haut. Les commandes, les scripts et les conceptions enregistrées gardent XY, XZ et YZ.",
"prefs.overall_speed": "Vitesse globale",
"prefs.parameter_list_rows": "Paramètres listés",
"prefs.parameter_list_rows_tip": "La flèche bas dans un champ de valeur vide liste les paramètres, favoris en premier, jusqu'à ce nombre. Moins allège la liste dans un gros design.",
"prefs.persistent_ribbon_pressed": "Mettre en surbrillance les commandes actives du ruban",
"prefs.persistent_ribbon_pressed_help": "Met en surbrillance les commandes du ruban tant que leurs effets sont actifs. Désactivez cette option pour conserver l'apparence neutre précédente et l'aide des commandes.",
"prefs.perspective_fov": "Champ de vision",
"prefs.placement": "Placement",
"prefs.prefer_free_sketch_points": "Privilégier les points d'esquisse déplaçables quand des points se superposent",
"prefs.preview_choices": "Aperçu de vos choix",
"prefs.print_face_highlight": "Mise en évidence de la face d’impression",
"prefs.print_face_highlight_tip": "Intensité de la teinte et du contour de la face d’impression d’un corps dans la vue. 0 % n’affiche aucune mise en évidence.",
"prefs.profile_active": "Profil actif",
"prefs.profile_applied": "Profil appliqué.",
"prefs.profile_changed": "Les réglages ont changé depuis l'enregistrement du profil. Enregistrer les y conserve.",
@@ -1952,6 +2266,8 @@
"prefs.quality_fastest": "le plus rapide",
"prefs.quality_slowest": "le plus lent",
"prefs.quality_smoother": "plus fluide",
"prefs.quick_measure": "Mesure rapide dans le coin de la vue",
"prefs.quick_measure_note": "La taille de la sélection, dans le coin inférieur droit de la vue : la longueur d'une arête, ou Ø et R pour une arête ronde, l'aire d'une face, la position d'un point, la distance et l'angle entre deux éléments, le total de plusieurs arêtes ou faces. Un clic la copie.",
"prefs.render_previous_geometry_budget": "Utiliser l'ancien budget de géométrie de l'aperçu Rendu",
"prefs.render_previous_geometry_budget_help": "Rétablir l'ancienne limite fixe de géométrie (512 Mio sur un ordinateur 64 bits, 128 Mio ailleurs) au lieu du budget basé sur l'appareil.",
"prefs.reports_saved": "Enregistrés : {n}",
@@ -1965,6 +2281,8 @@
"prefs.search_empty": "Aucun paramètre correspondant",
"prefs.select": "Sélectionner",
"prefs.select_one_item": "Sélectionner un élément",
"prefs.select_other_hidden": "Sélectionner la géométrie masquée avec Sélectionner un autre",
"prefs.select_other_hidden_help": "Activé : inclure les corps et les esquisses masqués ainsi que les arêtes occultées près du pointeur. Désactivé : utiliser la liste d'origine de la géométrie visible sous le pointeur.",
"prefs.selecting": "Sélection",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Maj ajoute seulement ; Ctrl / Cmd bascule",
@@ -1976,31 +2294,44 @@
"prefs.sketch_align_points_note": "Pendant le dessin, un point s'aligne à la fois horizontalement sur un point d'esquisse et verticalement sur un autre, avec un guide en pointillés, et le clic conserve les deux alignements. Désactivé : uniquement sur le point précédent, un axe à la fois.",
"prefs.sketch_conflict_colour": "Géométrie en conflit dans la couleur de conflit",
"prefs.sketch_conflict_colour_note": "Tant que les contraintes d'une esquisse sont en conflit, les courbes et les points que retient une contrainte en échec sont aussi dessinés dans la couleur de conflit, et pas seulement son symbole ou sa cote. Désactivé : ils gardent leurs couleurs habituelles.",
"prefs.sketch_construction_lines": "Outils étendus de droites de construction",
"prefs.sketch_exit_view": "À la fin d'une esquisse",
"prefs.sketch_exit_view.keep": "Garder la vue actuelle",
"prefs.sketch_exit_view.previous": "Revenir à la vue précédente",
"prefs.sketch_exit_view.unless_rotated": "Revenir, sauf si la vue a été tournée",
"prefs.sketch_exit_view_note": "Où va la vue après Terminer l'esquisse, Annuler ou un double Échap.",
"prefs.sketch_open_look_at": "Se placer face à l'esquisse à son ouverture",
"prefs.sketch_open_look_at_note": "Désactivé : créer ou modifier une esquisse laisse la vue telle quelle. Vue normale place la vue face à l'esquisse quand vous le souhaitez.",
"prefs.sketch_recent_snap": "Accrocher automatiquement aux centres et points milieux récents",
"prefs.sketch_recent_snap_note": "Pendant le dessin, le curseur s'accroche là où se croisent les guides des deux derniers centres de cercle ou points milieux de segment droit. Désactivé : ces guides restent purement visuels et le curseur ne bouge pas.",
"prefs.sketch_selection": "Esquisse et sélection",
"prefs.sketch_selection_heading": "Esquisse et sélection",
"prefs.sketch_selection_note": "Comment les esquisses s'accrochent et comment les clics sélectionnent.",
"prefs.sketch_state_icons": "Icônes d'esquisse dans la couleur de leur état",
"prefs.sketch_state_icons_note": "Dans l'historique et le navigateur, l'icône d'une esquisse prend la couleur de son état (sous-contraint, entièrement contrainte ou en conflit) et son infobulle nomme cet état. Désactivé : toutes les esquisses ont la même icône.",
"prefs.sketch_tab_always": "Toujours afficher l'onglet Esquisse",
"prefs.sketch_tab_always_note": "L'onglet Esquisse reste dans le ruban, juste après Modèle, tant qu'aucune esquisse n'est ouverte. Ses outils de dessin démarrent alors une esquisse sur le plan ou la face sélectionnés, ou sur le plan que vous choisissez ensuite, avec l'outil prêt à l'emploi.",
"prefs.sketch_tracking_guides": "Guides d'alignement sur le centre et le point milieu",
"prefs.sketch_tracking_guides_note": "Survolez le centre d'un cercle ou le point milieu d'un segment droit, puis déplacez le pointeur horizontalement ou verticalement pour afficher un guide pointillé temporaire.",
"prefs.sketch_tracking_points": "Guides d'alignement depuis les extrémités et les coins pendant le dessin",
"prefs.sketch_tracking_points_note": "Quand un outil de dessin attend un point, survoler l'extrémité d'une ligne ou d'un arc, le coin d'un rectangle ou un point libre affiche le même guide pointillé temporaire qu'un centre ou un point milieu. Désactivé : seulement les centres et les points milieux.",
"prefs.sketching": "Esquisse",
"prefs.snap_drawing_note": "Pendant le dessin ou le déplacement d'un point d'esquisse.",
"prefs.source": "Source",
"prefs.speed": "Vitesse",
"prefs.stand_up_note": "Après l'ouverture d'un fichier STEP ou IGES en Z vers le haut, proposer un quart de tour autour de X.",
"prefs.stand_up_unknown": "Pour les fichiers de programmes inconnus",
"prefs.stand_up_unknown.ask": "Demander dans une boîte de dialogue",
"prefs.stand_up_unknown.note": "Afficher une note",
"prefs.stand_up_unknown.off": "Ne pas proposer",
"prefs.stand_up_unknown_note": "Comment la proposition est faite quand l'en-tête du fichier ne nomme aucun programme connu pour enregistrer en Y vers le haut. Une note ne s'affiche que lorsque le fichier semble couché sur le dos ; Demander dans une boîte de dialogue pose la question pour chaque fichier de ce genre, comme avant.",
"prefs.status_bar": "Barre d'état",
"prefs.status_bar_note": "Une barre fine en bas de la fenêtre indique ce qui est en cours de modification, l'état de l'esquisse ouverte par sa couleur et les unités de la conception ; un clic sur les unités permet de les changer. L'état de l'esquisse n'apparaît alors plus dans le coin de la vue.",
"prefs.status_done": "Terminé",
"prefs.status_error": "Erreur",
"prefs.status_selected": "Sélectionné",
"prefs.status_warning": "Avertissement",
"prefs.structure.scale_percent": "{percent} %",
"prefs.supersampling_brief": "Dessine la vue en plus grand puis la réduit par filtrage : très net, mais plus lourd.",
"prefs.tab_accent_note": "La couleur d'accent suit l'onglet actif",
"prefs.theme_file": "fichier de thème",
@@ -2012,6 +2343,16 @@
"prefs.theme_follow_off": "Désactivé",
"prefs.theme_follow_stop": "Arrêter le suivi",
"prefs.theme_import_hint": "Cliquez pour choisir un fichier de thème, déposez-en un ici, ou appuyez sur Ctrl+V (Cmd+V sur Mac) avec un thème copié",
"prefs.theme_option_opacity": "Opacité",
"prefs.theme_option_opacity_help": "Règle l'opacité des panneaux flottants. Un seuil de lisibilité protège le texte placé sur les couleurs de la vue 3D.",
"prefs.theme_option_tab_colours": "Couleurs des onglets",
"prefs.theme_option_tab_colours_help": "Utilise les couleurs de panneau et de vue 3D de ce thème pour chaque onglet du ruban.",
"prefs.theme_option_tab_fade_ms": "Transition des onglets",
"prefs.theme_option_tab_fade_ms_help": "Fait passer progressivement les couleurs du panneau et de la vue 3D au changement d'onglet. Zéro bascule instantanément.",
"prefs.theme_options_badge": "Options",
"prefs.theme_options_more": "Plus · {count}",
"prefs.theme_options_reset": "Réinitialiser",
"prefs.theme_options_unavailable": "Indisponible sur cet appareil",
"prefs.theme_paste": "Coller le thème",
"prefs.theme_paste_none": "Le presse-papiers ne contient aucun thème SolidCraft",
"prefs.timeline_half_hour": "−30 min",
@@ -2033,13 +2374,23 @@
"prefs.under_cursor": "Sous le curseur",
"prefs.units_precision": "Unités et précision",
"prefs.updates": "Mises à jour",
"prefs.value_boxes_in_view": "Afficher les valeurs à côté de leurs poignées dans la vue",
"prefs.value_boxes_in_view_tip": "La valeur d'une commande (distance, rayon…) dans une case à côté de sa poignée, par-dessus le modèle. Désactivé : vous la saisissez dans la fenêtre de la commande, qui prend le clavier après une sélection, et rien ne masque le modèle.",
"prefs.view": "Comportement de la vue",
"prefs.view_confirm_corner": "OK et Annuler dans le coin de la vue",
"prefs.view_confirm_corner_note": "Lorsqu'une boîte de dialogue ou une esquisse est ouverte, une coche et une croix en haut à droite de la vue font la même chose que OK et Annuler, ou terminent et annulent l'esquisse. Le repère de navigation se place en dessous.",
"prefs.viewport": "Vue 3D",
"prefs.viewport_analysis_status": "Afficher l'état d'analyse de la vue 3D",
"prefs.viewport_analysis_status_help": "Afficher les analyses actives au-dessus de la vue 3D, ouvrir leur légende depuis chaque élément et utiliser Échap pour désactiver l'analyse la plus récente. Désactiver pour rétablir les légendes dans les coins et l'ancien comportement d'Échap.",
"prefs.viewport_background": "Fond",
"prefs.viewport_background_colour": "Couleur",
"prefs.viewport_background_theme": "Dégradé du thème",
"prefs.viewport_section_status": "Afficher l'état de la coupe dans la vue 3D",
"prefs.viewport_section_status_help": "Affiche le réglage de coupe dans la vue 3D. Échap et le bouton actif du ruban le désactivent. Désactivez cette option pour conserver les contrôles de coupe précédents.",
"prefs.viewport_status_details": "Détails de la vue 3D modifiables",
"prefs.viewport_status_details_help": "Modifie les réglages de l'analyse et de la coupe actives dans des fiches de détails standard. Désactivez cette option pour retrouver les légendes d'analyse en lecture seule et la boîte de dialogue Coupe.",
"prefs.viewport_status_summary": "Regrouper les éléments d'état de la vue 3D",
"prefs.viewport_status_summary_help": "Regroupe trois éléments d'état de la vue 3D ou plus dans un seul badge de synthèse. Désactivez cette option pour garder tous les éléments sur la ligne complète.",
"prefs.wheel": "Molette",
"prefs.wheel_trackpad": "Molette et pavé tactile",
"prefs.workspace": "Espace de travail",
@@ -2918,7 +3269,9 @@
"render.output_ready": "Prêt à lancer le rendu",
"render.output_run_check": "Lancer la vérification",
"render.output_size_limit": "{method} : jusqu'à {dimension} px par côté et {pixels} pixels. Plus grande taille possible : {width} × {height}.",
"render.output_size_method_profile": "Pour un rendu en {width} × {height}, choisissez {method} avec le profil de ressources CPU {profile}, ou une taille plus petite.",
"render.output_size_ok": "Taille correcte · {width} × {height} px",
"render.output_size_profile": "Pour un rendu en {width} × {height}, choisissez le profil de ressources CPU {profile}, ou une taille plus petite.",
"render.output_swap_orientation": "Permuter largeur et hauteur",
"render.output_tools_inspector": "Modifier la taille et le format dans l'inspecteur Sortie.",
"render.output_view_image": "Afficher l'image",
@@ -3131,6 +3484,7 @@
"render.size_custom": "Personnalisé…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · nécessite {profile}",
"render.size_presets": "Préréglages de taille",
"render.size_presets_pending": "Arrive avec l'éditeur Sortie",
"render.size_preview": "Aperçu",
@@ -3418,6 +3772,24 @@
"sheet.flange_type_contour": "Contour",
"sheet.flange_type_edge": "Arête",
"sheet.no_body": "il n'y a pas de corps de tôlerie : commencez-en un avec Bord tombé > Base, ou transformez une plaque en tôlerie avec Convertir en tôlerie (choisissez sa grande face plane)",
"sheet.personal_rules.confirm_delete": "Confirmer la suppression",
"sheet.personal_rules.delete": "Supprimer",
"sheet.personal_rules.delete_prompt": "Supprimer cette règle personnelle ? Les règles déjà présentes dans les conceptions ne changent pas.",
"sheet.personal_rules.full": "La bibliothèque personnelle contient déjà 64 règles.",
"sheet.personal_rules.invalid_name": "Saisissez un nom court, sans saut de ligne ni caractère de contrôle.",
"sheet.personal_rules.invalid_values": "Complétez les valeurs de la règle : chacune doit pouvoir être calculée dans cette conception, avec une épaisseur positive et un facteur K compris entre 0 et 1.",
"sheet.personal_rules.missing": "La règle personnelle sélectionnée n'est plus disponible.",
"sheet.personal_rules.name": "Nom de la règle",
"sheet.personal_rules.name_exists": "Une règle personnelle porte déjà ce nom. Choisissez-en un autre.",
"sheet.personal_rules.name_hint": "Un nom unique",
"sheet.personal_rules.overwrite": "Enregistrer les modifications de la règle personnelle sélectionnée",
"sheet.personal_rules.rename": "Renommer",
"sheet.personal_rules.rule": "Règle personnelle",
"sheet.personal_rules.save": "Enregistrer comme nouvelle",
"sheet.personal_rules.scope": "Enregistrée dans votre profil, pour toutes les conceptions.",
"sheet.personal_rules.select": "Choisir une règle",
"sheet.personal_rules.title": "Règles personnelles",
"sheet.personal_rules.unsupported": "Cette règle contient des valeurs que cette version ne peut pas utiliser.",
"shortcut.json_error": "les paramètres doivent être au format JSON : {error}",
"shortcut.no_command": "la commande `{id}` n’existe pas ; le texte avant le JSON doit être un ID de commande",
"shortcut.pin": "Épingler en haut de la boîte S",
@@ -3454,6 +3826,7 @@
"sketch_text.align_right": "Droite",
"sketch_text.bold": "Gras",
"sketch_text.builtin_font": "{family} (intégrée)",
"sketch_text.formula": "Commencez par = pour composer le texte avec des paramètres : ='L' + number(Largeur)",
"sketch_text.italic": "Italique",
"sketch_text.loading_fonts": "Recherche des polices installées…",
"sketch_text.on_path": "Le texte suit sa trajectoire : la courbe le positionne",
@@ -3507,8 +3880,10 @@
"timeline.move_to_group": "Déplacer les fonctions vers le dossier",
"timeline.rename_group": "Renommer le dossier de fonctions",
"timeline.reorder_many": "Réordonner les fonctions",
"timeline.suppress_features": "Désactiver les fonctions",
"timeline.suppress_folders_tip": "Désactiver ou réactiver toutes les fonctions des dossiers et de leurs dossiers imbriqués",
"timeline.ungroup": "Supprimer les dossiers de fonctions",
"timeline.unsuppress_features": "Réactiver les fonctions",
"to the face": "jusqu'à la face",
"to the next face": "jusqu'à la face suivante",
"toolbar.customize_note": "Onglet {tab}. Cochez une commande pour lui attribuer un bouton dans la barre d’outils. Vous pouvez aussi faire glisser une ligne du menu déroulant d’un panneau vers la barre d’outils, retirer un bouton en le faisant glisser hors de la barre ou utiliser l’épingle d’une ligne du menu déroulant.",
@@ -3592,6 +3967,8 @@
"ui.3d_mouse_section": "Souris 3D",
"ui.3d_mouse_show_in_view": "Afficher la souris 3D dans la vue 3D",
"ui.3d_mouse_show_in_view_tip": "Une image de votre souris 3D dans le coin inférieur gauche, qui bouge quand vous la bougez.",
"ui.3d_mouse_sketch_orbit": "Faire pivoter les esquisses avec la souris 3D",
"ui.3d_mouse_sketch_orbit_tip": "La souris 3D fait aussi pivoter la vue quand une esquisse est ouverte et vue de face, comme la souris. Désactivé : elle déplace et zoome l'esquisse jusqu'à ce que vous incliniez la vue.",
"ui.3d_mouse_skip": "Mon appareil ne peut pas le faire",
"ui.3d_mouse_skip_tip": "Ignore ce mouvement dans les deux sens ; la calibration le laisse de côté.",
"ui.3d_mouse_speed": "Vitesse de la souris 3D",
@@ -3620,6 +3997,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
"ui.3d_mouse_switch_cap": "Calibrer comme retour au centre",
"ui.3d_mouse_switch_trackball": "Calibrer comme un trackball",
"ui.3d_mouse_use_direct": "Utiliser le pilote direct",
"ui.3d_mouse_use_direct_tip": "Lire la souris 3D directement (hidraw) au lieu de passer par spacenavd.",
"ui.3d_mouse_view_axis": "Axe de la vue",
"ui.3d_mouse_view_off": "Désactivé",
"ui.3d_mouse_view_off_tip": "Le profil du pilote de la souris 3D désactive cet axe : son mouvement ne déplace pas la vue. Choisir ici le mouvement d'un autre axe les échange, et ce mouvement cesse de déplacer la vue.",
@@ -3658,6 +4037,7 @@
"ui.agent_chat_auth_terminal": "Exécuter ceci dans un terminal, puis appuyer de nouveau sur Connecter :",
"ui.agent_chat_auth_title": "Authentification auprès de l'agent",
"ui.agent_chat_cancel": "Annuler",
"ui.agent_chat_cannot_start": "Installé, mais ne démarre pas : {error}",
"ui.agent_chat_changes": "Modifications : {n}, en une seule étape d'annulation",
"ui.agent_chat_changes_n": "Modifications : {n}",
"ui.agent_chat_changes_of": "{n}/{limit} modifications",
@@ -3750,9 +4130,12 @@
"ui.agent_chat_queued_stopped": "En file d'attente (arrêté, non envoyé)",
"ui.agent_chat_refresh": "Actualiser",
"ui.agent_chat_refused": "Interdit à l'agent : {reason}",
"ui.agent_chat_reinstall": "{agent} est installé ({program}), mais n'a pas démarré quand SolidCraft l'a vérifié : {error}\nSon installation est peut-être incomplète, ou prévue pour un autre type d'ordinateur. Réinstallez-le avec `{install}`, vérifiez qu'il affiche sa version dans un terminal, puis cliquez sur Actualiser.",
"ui.agent_chat_remove": "Retirer de la file d'attente",
"ui.agent_chat_remove_file": "Retirer de ce message",
"ui.agent_chat_remove_imported": "Retirer de ce message ; la pièce reste dans la conception (Annuler la retire)",
"ui.agent_chat_reply_cut_off": "La réponse a atteint sa longueur maximale avant la fin : elle est peut-être incomplète. Demandez à l'agent de continuer, ou une réponse plus courte.",
"ui.agent_chat_reply_declined": "Le modèle a refusé de répondre à ce message.",
"ui.agent_chat_retry": "Réessayer",
"ui.agent_chat_right": "Droite",
"ui.agent_chat_save": "Enregistrer",
@@ -3811,12 +4194,17 @@
"ui.ai_copy_suffix": "copie",
"ui.ai_custom_url": "URL personnalisée",
"ui.ai_default": "Par défaut",
"ui.ai_edit": "Modifier",
"ui.ai_edit_agent_title": "Modifier l'agent",
"ui.ai_edit_gone": "Cet agent a été retiré : il n'y a rien à enregistrer.",
"ui.ai_edit_tip": "Modifier le nom, la commande ou le point d'accès de cet agent. La clé enregistrée reste masquée.",
"ui.ai_http_busy": "le point d'accès est occupé ou a dépassé sa limite ; réessayez plus tard",
"ui.ai_http_key": "le point d'accès a refusé la clé : vérifiez-la dans Préférences › IA",
"ui.ai_http_not_found": "point d'accès ou modèle introuvable : vérifiez l'URL de base et le nom du modèle",
"ui.ai_http_too_large": "la conversation est trop volumineuse pour ce point d'accès ; commencez une nouvelle discussion",
"ui.ai_key": "Clé d'API",
"ui.ai_key_hint": "facultative ; conservée dans le trousseau de clés du système",
"ui.ai_key_keep_hint": "laisser vide pour garder la clé enregistrée",
"ui.ai_key_needs_https": "Une clé n'est envoyée qu'en HTTPS, ou vers cet ordinateur.",
"ui.ai_key_note": "Stockée dans le trousseau de clés du système, jamais dans les réglages de SolidCraft. Laisser vide pour un serveur local.",
"ui.ai_keychain_error": "Le trousseau de clés du système a refusé : {error}",
@@ -3848,6 +4236,7 @@
"ui.ai_request_timeout": "Délai d'expiration des requêtes",
"ui.ai_request_timeout_hint": "Durée maximale d'une requête au modèle de ce point d'accès, de 30 s à une heure. Augmentez-la pour un modèle local lent.",
"ui.ai_reset": "Réinitialiser",
"ui.ai_save": "Enregistrer",
"ui.ai_settings": "Réglages IA…",
"ui.ai_show_features": "Afficher les fonctionnalités IA",
"ui.ai_show_features_note": "Le chat de l'agent : un panneau où un agent de programmation que vous utilisez déjà, ou un point d'accès de modèle de votre choix, travaille sur la conception ouverte. Désactivé : ni bouton IA, ni raccourci, ni entrée de menu, ni panneau, et rien n'est recherché ni lancé.",
@@ -3926,6 +4315,8 @@
"ui.cancel": "Annuler",
"ui.canvases": "Canevas",
"ui.center": "Centre",
"ui.center_arc_follows_cursor": "L'arc par centre suit le sens du curseur",
"ui.center_arc_follows_cursor_tip": "Un arc par centre tourne dans le sens horaire ou antihoraire selon la façon dont le curseur tourne autour de son centre. Désactivé : toujours dans le sens antihoraire depuis le début.",
"ui.centerline": "Ligne de construction",
"ui.chain": "Chaîne",
"ui.chamfer": "Chanfrein",
@@ -3934,6 +4325,7 @@
"ui.circle": "Cercle",
"ui.circular_pattern": "Répétition circulaire",
"ui.clear": "Effacer",
"ui.clear_print_face": "Effacer la face d’impression",
"ui.clear_recent": "Effacer les récents",
"ui.clear_the_selection": "Effacer la sélection",
"ui.click_selects_whole_bodies": "Un clic sélectionne des corps entiers",
@@ -3968,6 +4360,14 @@
"ui.constant": "Constant",
"ui.constraints_conflict": "Contraintes en conflit",
"ui.construction": "Construction",
"ui.construction.angle_hint": "Droite de construction : cliquez sur un point, puis saisissez un angle ou choisissez une direction",
"ui.construction.bisector_hint": "Bissectrice : sélectionnez deux lignes droites sécantes, puis la solution souhaitée",
"ui.construction.common_hint": "Tangente commune : sélectionnez deux cercles/arcs, puis la solution souhaitée",
"ui.construction.contact_hint": "Tangente : sélectionnez un cercle/arc ou une spline/conique ouverte, puis le point de contact (courbes libres : extrémités)",
"ui.construction.external_hint": "Tangente : sélectionnez un cercle ou un arc, un point extérieur, puis la solution souhaitée",
"ui.construction.free_hint": "Droite de construction : cliquez sur deux points ; clic droit pour recommencer",
"ui.construction.point_hint": "Droite de construction : cliquez sur le point de passage",
"ui.construction.reference_hint": "Droite de construction : sélectionnez une ligne droite, puis un point ; clic droit pour changer la référence",
"ui.construction.ribbon_label": "Lignes aux.",
"ui.context_menu": "Menu contextuel",
"ui.continuity_g0": "G0 angle vif",
@@ -4010,6 +4410,40 @@
"ui.design_properties.project": "Projet",
"ui.design_properties.revision": "Indice",
"ui.design_saved": "Enregistré dans {path}",
"ui.details_access_caption": "Les faces indiquent si un outil peut les atteindre depuis la direction choisie.",
"ui.details_angle": "Angle",
"ui.details_continuity_caption": "Modifie la jonction sélectionnée. Les autres jonctions gardent leurs propres réglages.",
"ui.details_continuity_stale": "Géométrie antérieure : relancez l'analyse pour modifier ou cadrer cette jonction.",
"ui.details_copy_list": "Copier la liste",
"ui.details_curvature": "Tolérance de courbure",
"ui.details_curvature_caption": "Les faces sont colorées selon leur rayon de courbure local.",
"ui.details_direction": "Direction de l'outil",
"ui.details_draft_caption": "Les faces sont colorées selon leur angle avec la direction de démoulage.",
"ui.details_edit_seam": "Modifier la jonction",
"ui.details_fixture": "Spécimen de composant — données d'exemple uniquement",
"ui.details_fixture_caption": "Spécimen de composant avec des valeurs d'illustration ; ce n'est pas un résultat du modèle.",
"ui.details_fixture_problem": "Exemple de constat signalant un problème",
"ui.details_fixture_readout": "Exemple de valeur mesurée",
"ui.details_flip": "Inverser",
"ui.details_gap": "Écart",
"ui.details_hatch": "Hachurer les faces coupées",
"ui.details_jump": "Saut de courbure",
"ui.details_key": "Légende",
"ui.details_offset": "Décalage",
"ui.details_plane": "Plan",
"ui.details_pull": "Direction de démoulage",
"ui.details_radius": "Rayon",
"ui.details_readout": "Valeur affichée",
"ui.details_samples": "Échantillons",
"ui.details_seam": "{body} · Arête {edge}",
"ui.details_settings": "Réglages",
"ui.details_sharp_gap": "Vive ou ouverte",
"ui.details_show_all": "Tout afficher ({count})",
"ui.details_smooth": "Lisse",
"ui.details_stripes": "Bandes",
"ui.details_target": "Cadrer la géométrie",
"ui.details_worst_seams": "Pires jonctions",
"ui.details_zebra_caption": "Les bandes réfléchies montrent la régularité du raccord entre les faces.",
"ui.diameter": "Diamètre",
"ui.dimension": "Cote",
"ui.direct_interaction": "Interaction directe",
@@ -4070,6 +4504,7 @@
"ui.equation_y_of_x": "y(x)",
"ui.equivalent_radius": "Rayon équivalent",
"ui.expand": "Développer",
"ui.explode_needs_two_components": "La vue éclatée écarte les composants les uns des autres : la conception doit en contenir au moins deux (Nouveau composant, Insérer une conception ou Créer des composants à partir des corps)",
"ui.exportDxf": "Exporter en DXF…",
"ui.export_ellipsis_keymap": "Exporter…",
"ui.export_keymap_tip": "Raccourcis, favoris de la boîte S et barre d'outils, dans un fichier",
@@ -4230,6 +4665,7 @@
"ui.insert_design.saved_file": "{name} contient des modifications non enregistrées ; c'est donc son fichier enregistré qui a été inséré. Enregistrez-le, puis utilisez Mettre à jour les liens pour intégrer les modifications.",
"ui.insert_design.unsaved": "Modifications non enregistrées",
"ui.inspect": "Inspecter",
"ui.instances": "Instances",
"ui.intersect": "Intersection",
"ui.isolate": "Isoler",
"ui.join": "Joindre",
@@ -4332,6 +4768,7 @@
"ui.part_size_shared": "Reconstruit la pièce en remplaçant les entités ajoutées. Toutes les occurrences de ce composant sont mises à jour.",
"ui.paste": "Coller",
"ui.path": "Trajectoire",
"ui.pattern_object_type_tip": "Corps : formes pleines, choisies dans la vue ou le Navigateur ; chacun est copié en entier.\nFonctions : étapes de l'Historique, comme une extrusion ou un perçage, choisies dans l'Historique ou sur une face qu'elles ont créée ; l'étape est répétée.\nUne 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.",
"ui.pattern_on_path": "Répétition sur trajectoire",
"ui.perforate_margin_default": "depuis le diamètre",
"ui.perforate_pitch": "Pas",
@@ -4358,12 +4795,17 @@
"ui.plugins_skipped": "{count} dossier(s) illisible(s).",
"ui.plugins_version": "{version}",
"ui.point": "Point",
"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_file_colors": "Couleurs du fichier",
"ui.point_cloud_filter_by": "Filtrer par",
"ui.point_cloud_height_high": "Plus haut",
"ui.point_cloud_height_low": "Plus bas",
"ui.point_cloud_legend_filter": "Affichées et masquées dans la légende ci-dessus",
"ui.point_cloud_no_file_colors": "Le fichier n'a pas de couleurs",
"ui.point_cloud_pause": "Pause",
"ui.point_cloud_play": "Lire",
"ui.point_cloud_single_color": "Couleur unique",
"ui.point_cloud_size": "Taille des points",
"ui.point_cloud_units": "Unités du fichier",
@@ -4372,6 +4814,11 @@
"ui.polygon": "Polygone",
"ui.preferences": "Préférences",
"ui.press_a_key": "appuyez sur une touche…",
"ui.print_face_body_not_sent": "Le corps de cette face n’est pas envoyé : ajoutez-le d’abord à Corps",
"ui.print_face_cleared": "{body} : face d’impression effacée",
"ui.print_face_input": "Face d’impression",
"ui.print_face_pick": "Sélectionnez une face plane d’un corps solide pour en faire la face d’impression",
"ui.print_face_set": "{body} : face d’impression définie ; Envoyer au slicer pose le corps sur cette face",
"ui.profile": "Profil",
"ui.profile_plane": "Plan du profil",
"ui.profiles": "Profils",
@@ -4387,6 +4834,7 @@
"ui.properties.inertia_y": "Iyx, Iyy, Iyz",
"ui.properties.inertia_z": "Izx, Izy, Izz",
"ui.properties.measuring": "Mesure en cours…",
"ui.properties.measuring_bodies": "Mesure en cours… {done} corps sur {total}",
"ui.properties.measuring_failed": "La mesure s'est arrêtée avant la fin.",
"ui.properties.principal_axis": "Axe principal {number}",
"ui.properties.principal_moments": "Moments principaux",
@@ -4435,6 +4883,7 @@
"ui.revolve": "Révolution",
"ui.ribbon_exit": "Quitter",
"ui.ribbon_labels": "Afficher le texte sous les icônes du ruban",
"ui.ribbon_menus_skip_pinned": "Laisser les commandes épinglées hors des menus déroulants",
"ui.ribbon_pin_footer": "Les commandes épinglées s’affichent dans le ruban. Survolez une ligne pour l’épingler ou la détacher.",
"ui.ribbon_workspace": "Espace de travail",
"ui.right": "Droite",
@@ -4453,6 +4902,7 @@
"ui.selectOther": "Sélectionner un autre…",
"ui.selectTangentChain": "Sélectionner la chaîne tangente",
"ui.select_all": "Tout sélectionner",
"ui.select_components_by_face": "Sélectionner des composants (une face sélectionne son composant)",
"ui.selection": "Sélection",
"ui.selection_modifiers": "Modificateurs de sélection",
"ui.selection_shift_add_only": "Maj+clic ajoute sans retirer",
@@ -4494,6 +4944,11 @@
"ui.sketch_intersect_selection_filter": "Filtre de sélection",
"ui.sketch_modifiers": "Modificateurs d’esquisse",
"ui.sketch_project_geometry": "Géométrie",
"ui.sketch_report.all_constrained": "Tout est entièrement contraint.",
"ui.sketch_report.dof": "Degrés de liberté",
"ui.sketch_report.loose": "Pas entièrement contrainte",
"ui.sketch_report.status": "État",
"ui.sketch_report.title": "Rapport d'esquisse",
"ui.sketch_scale": "Échelle d'esquisse",
"ui.sketches": "Esquisses",
"ui.slice": "Trancher",
@@ -4598,10 +5053,19 @@
"ui.view_centre": "Centre de la vue",
"ui.viewport_status_clear": "Désactiver {name}",
"ui.viewport_status_clear_hint": "Désactiver {name} · Échap",
"ui.viewport_status_edges": "{count} arêtes",
"ui.viewport_status_from": "depuis {direction}",
"ui.viewport_status_key": "Afficher la légende de {name}",
"ui.viewport_status_minimum_radius": "Rayon minimum",
"ui.viewport_status_pairs": "{count} paires",
"ui.viewport_status_section_hatch": "{setting} · hachures",
"ui.viewport_status_section_plane": "Plan actuel",
"ui.viewport_status_section_tilted": "inclinée · {offset}",
"ui.viewport_status_stripes": "{count} bandes",
"ui.viewport_status_summary_clear": "Tout désactiver",
"ui.viewport_status_summary_heading": "Dans cette vue",
"ui.viewport_status_summary_label": "{name} et {count} autres éléments",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Suivre le thème",
"ui.visual_shaded_hidden_edges": "Ombré avec arêtes cachées",
"ui.visual_shaded_with_edges": "Ombré avec arêtes",
@@ -4638,7 +5102,9 @@
"update.button": "Mettre à jour",
"update.button_version": "Mettre à jour vers {version}",
"update.check_automatically": "Rechercher automatiquement les mises à jour",
"update.check_for_updates": "Rechercher des mises à jour",
"update.check_now": "Vérifier maintenant",
"update.check_unavailable": "La recherche de mises à jour n’est pas disponible pour cette installation.",
"update.checking": "Vérification…",
"update.copy": "Copier",
"update.dmg_opened": "L'image disque est ouverte et vérifiée : faites glisser SolidCraft dans Applications en remplaçant l'ancienne copie, puis redémarrez SolidCraft.",
@@ -4652,11 +5118,13 @@
"update.installed": "SolidCraft {version} est installé. Redémarrez pour l'utiliser.",
"update.installer_started": "L'installateur démarre à la fermeture de SolidCraft.",
"update.later": "Me le rappeler plus tard",
"update.managed": "Mise à jour disponible : mettez à jour SolidCraft dans {manager}",
"update.no_notes": "Cette version n'a pas de notes.",
"update.package_hint": "Cette copie provient d'un {kind}. Téléchargez le nouveau paquet, puis installez-le avec :",
"update.prefs_dev": "Ceci est une version de développement ({version}) : elle ne vérifie pas les mises à jour.",
"update.prefs_flatpak": "Flatpak met à jour cette copie ({version}) : exécutez {command} ou utilisez votre logithèque. SolidCraft ne vérifie pas.",
"update.prefs_label": "Mises à jour",
"update.prefs_managed": "Ceci est SolidCraft {version}, géré par {manager}. Une vérification interroge GitHub sur la dernière version et n'envoie rien d'autre ; mettez à jour SolidCraft dans {manager}.",
"update.prefs_note": "Ceci est SolidCraft {version} ({kind}). Une vérification interroge GitHub sur la dernière version et n'envoie rien d'autre.",
"update.prefs_web": "Rechargez la page pour obtenir la dernière version de l'application web. Si le réseau est indisponible ou si la mise à jour ne peut pas finir de se télécharger à temps, la version enregistrée s'ouvre à la place ; un téléchargement en cours se poursuit pour le prochain rechargement.",
"update.refresh_checking": "Recherche de la dernière version…",
File diff suppressed because it is too large Load Diff
+567 -10
View File
File diff suppressed because it is too large Load Diff
+472
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "PRIJELAZNA PLOHA",
"BOUNDARY SURFACE": "GRANIČNA PLOHA",
"BROWSER": "STABLO MODELA",
"Back to the original order": "Natrag na izvorni redoslijed",
"Ball": "Kuglasti",
"Bar": "Šipka",
"Base Center": "Središte osnove",
@@ -595,6 +596,8 @@
"Snap to face and edge centres": "Privlači na središta lica i bridova",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft ne može izravno slati izvješća. Pošaljite ih na GitHubu; u aplikaciji nije potreban token.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft se zatvorio prije nego što su ovi dizajni spremljeni. Može se otvoriti njihovo posljednje automatsko spremanje.",
"Sort A to Z": "Poredaj od A do Ž",
"Sort Z to A": "Poredaj od Ž do A",
"Spacing 2 must be a non-zero number": "Razmak 2 mora biti broj različit od nule",
"Spacing must be a non-zero number": "Razmak mora biti broj različit od nule",
"Specialise": "Specijaliziraj",
@@ -790,6 +793,13 @@
"browser.sketch_attachment_lost": "{name} (pričvršćenje izgubljeno)",
"browser.sketch_editing": "{name} (uređuje se)",
"browser.sketch_name": "Skica{number}",
"browser.sketches_to_parts_already_in_part": "već u svojem dijelu",
"browser.sketches_to_parts_design_body": "na tijelu samog dizajna",
"browser.sketches_to_parts_moved": "Skice premještene u dio na kojem se nalaze: {moved}",
"browser.sketches_to_parts_no_exact_attachment": "na licu bez točnog pričvršćenja",
"browser.sketches_to_parts_not_on_face": "ne na licu dijela",
"browser.sketches_to_parts_part_missing": "njihov dio više ne postoji",
"browser.sketches_to_parts_result": "Skice premještene u dio na kojem se nalaze: {moved}; ostale na mjestu: {stayed} ({why})",
"browser.units": "Jedinice: {units}",
"cam.flat": "Ravno lice",
"cam.follower": "Pratitelj",
@@ -819,6 +829,8 @@
"cmd.appearance_pick_up": "Preuzmi izgled",
"cmd.appearance_replace": "Zamijeni izgled",
"cmd.assembly_bom": "Sastavnica",
"cmd.assembly_mass_properties": "Svojstva mase",
"cmd.body.print_face": "Ploha ispisa",
"cmd.body_rename": "Preimenuj tijelo",
"cmd.browser_collapse_all": "Sažmi sve",
"cmd.browser_expand_all": "Proširi sve",
@@ -855,6 +867,7 @@
"cmd.component_move_bodies": "Premjesti tijela u komponentu",
"cmd.component_move_features": "Premjesti značajke u komponentu",
"cmd.component_move_sketches": "Premjesti skice u komponentu",
"cmd.component_move_sketches_to_parts": "Premjesti skice u njihove dijelove",
"cmd.component_paste_from_design": "Zalijepi iz drugog dizajna",
"cmd.component_paste_new": "Zalijepi kao novo",
"cmd.component_properties": "Fizička svojstva komponente",
@@ -1190,6 +1203,7 @@
"cmd.sketch_spline_control_point_5": "Spline kontrolnih točaka (stupanj 5)",
"cmd.sketch_spline_fit_point": "Spline kroz točke",
"cmd.sketch_spline_insert_point": "Dodaj točku splinea",
"cmd.sketch_spline_remove_point": "Ukloni točku splinea",
"cmd.sketch_spline_reverse": "Obrni smjer splinea",
"cmd.sketch_spun_profile": "Rotirani profil",
"cmd.sketch_toggle_diameter": "Prebaci polumjer / promjer",
@@ -1243,6 +1257,7 @@
"cmd.view.render_browser": "Stablo renderiranja",
"cmd.view.render_dock": "Premjesti panel renderiranja",
"cmd.view.render_focus": "Fokusni prikaz renderiranja",
"cmd.view.status_details": "Detalji statusa prozora prikaza",
"cmd.view.status_items": "Statusne stavke prozora prikaza",
"cmd.view_ambient_occlusion": "Ambijentalna okluzija",
"cmd.view_angle_readout": "Kutovi prikaza",
@@ -1281,8 +1296,14 @@
"color.hex_hint": "#rrggbb",
"color.hue": "Nijansa",
"color.linear": "Prikaz vrijednosti boje u linearnom prostoru 0-1",
"color.recent": "Nedavne boje",
"color.recent_help": "Ovdje se pojavljuju boje koje odaberete u bilo kojem polju za boju",
"color.saturation": "Zasićenost (postotak)",
"color.saturation_value": "Zasićenost i vrijednost",
"color.selected": "Odabrana boja",
"color.step_down": "Smanji za korak",
"color.step_up": "Povećaj za korak",
"color.value": "Vrijednost, svjetlina (postotak)",
"config.column_filter_empty": "Nema odgovarajućih stupaca",
"contact.components_count": "Komponente: {count}",
"contact.enabled_label": "omogućeno",
@@ -1329,6 +1350,7 @@
"dialog.dimension_name": "Kota: {name}",
"dialog.edit_title": "UREDI {title}",
"dialog.no_shown_body": "Nema prikazanog tijela za promjenu: prikažite jedno ili odaberite Novo tijelo",
"dialog.pattern_sketch_not_object": "Odaberite tijela ili značajke: skica nije ni jedno ni drugo. Ponovite njezine krivulje pomoću alata Niz ili Zrcali u samoj skici ili je najprije ekstrudirajte.",
"dialog.select": "↖ Odaberi",
"dialog.selected_count": "↖ Odabrano: {count}",
"dialog.selection_guidance": "Odaberite ulaz kako biste ga promijenili. Kliknite odabrane objekte kako biste ih uklonili; Ctrl/Cmd-klik ih također uključuje i isključuje. Pogledajte pretpregled, a zatim pritisnite U redu.",
@@ -1386,21 +1408,168 @@
"from the rule": "iz pravila",
"help.apache": "Apache licenca 2.0",
"help.appearance.assign": "Daje odabranim tijelima, licima ili komponentama boju ili izgled materijala, bez promjene njihove težine. Upotrijebite ga kako biste razlikovali dijelove ili pregledali završnu obradu.",
"help.assembly.bom": "Navodi dijelove dizajna ili komponente s količinama, po broju dijela, a popis može spremiti kao CSV ili JSON. Upotrijebite ga kao popis dijelova za narudžbu i montažu.",
"help.attributions": "Atribucije (ATTRIBUTION.md)",
"help.build": "Verzija {version} · commit {commit} · izgrađeno {date}",
"help.canvas.decal": "Postavlja sliku na ravno lice modela, poput naljepnice. Upotrijebite ga kako biste na dijelu prikazali oznaku, logotip ili otisnuti uzorak.",
"help.canvas.edit": "Mijenja platno: njegov položaj, veličinu, kut, neprozirnost, zrcaljenje, vidljivost ili naziv. Upotrijebite ga kako biste referentnu sliku poravnali sa skicom koju po njoj ocrtavate.",
"help.canvas.insert": "Postavlja PNG ili JPEG sliku na ravninu ili lice kao prozirnu referencu. Upotrijebite ga kako biste u skici ocrtali fotografiju ili crtež, sa slikom skaliranom na poznatu veličinu.",
"help.component.break_link": "Odvaja povezanu komponentu od njezine datoteke dizajna, a njezina tijela ostaju onakva kakva jesu. Upotrijebite ga kad dio više ne treba pratiti promjene u izvornoj datoteci.",
"help.component.capture_position": "Zadržava položaje dijelova koje ste ručno pomaknuli, tako da ti dijelovi ostaju na tim mjestima. Upotrijebite ga nakon povlačenja dijelova na mjesto koje nijedan spoj ne drži.",
"help.component.create": "Dodaje novu, praznu komponentu u dizajn, unutar aktivne komponente osim ako odaberete drugu nadređenu komponentu. Upotrijebite ga kako biste novom dijelu sklopa dali vlastito mjesto za tijela i skice.",
"help.component.finish_edit": "Sprema dio koji uređujete na mjestu, vraća se u njegov sklop i ažurira sklop prema njemu. Upotrijebite je kad završite izmjene povezanog dijela.",
"help.component.from_bodies": "Pretvara svako odabrano tijelo u zasebnu komponentu, nazvanu po tijelu. Upotrijebite ga kad dizajn izgrađen od samostalnih tijela treba postati sklop čije se dijelove može spajati i pomicati.",
"help.component.insert_design": "Umeće drugi spremljeni dizajn kao komponentu povezanu s njegovom datotekom, a njegova puna tijela postavlja onako kako su u njemu. Upotrijebite ga kako biste dio ponovno upotrijebili u sklopu i preuzeli kasnije promjene naredbom Ažuriraj veze.",
"help.component.properties": "Prikazuje volumen, masu, središte mase i momente tromosti komponente, prema materijalima njezinih tijela. Upotrijebite ga kako biste provjerili težinu i ravnotežu sklopa.",
"help.component.revert_position": "Vraća ručno pomaknute dijelove na mjesto na kojem su bili, odbacujući pomake koji nisu zabilježeni. Upotrijebite ga kako biste poništili probne pomake u sklopu.",
"help.component.update_links": "Ponovno čita povezane datoteke dizajna i zamjenjuje tijela koja su se promijenila. Upotrijebite ga nakon uređivanja dijela koji ovaj sklop koristi.",
"help.config.configure": "Dodaje parametre, značajke, materijale ili izglede kao stupce tablice konfiguracija. Upotrijebite ga kako biste izradili inačice jednog dizajna, primjerice veličine nekog dijela.",
"help.config.preview": "Provjerava može li se redak tablice konfiguracija ponovno izgraditi, bez promjene dizajna ili njegove vremenske trake. Upotrijebite ga prije prebacivanja na inačicu u čiju ispravnost niste sigurni.",
"help.config.rules": "Prikazuje pravila tablice konfiguracija, njezine retke i koji je redak aktivan. Upotrijebite ga kako biste vidjeli kako su postavljene inačice dizajna.",
"help.config.table": "Prikazuje tablicu konfiguracija: jedan redak po inačici, jedan stupac po parametru ili značajki, te podudara li se dizajn još s aktivnim retkom. Upotrijebite ga za pregled inačica dizajna.",
"help.connect_note": "Ponovno pokrenite svakog asistenta kako bi učitao SolidCraft poslužitelj. Svaka datoteka sigurnosno je kopirana kao .bak prije promjene.",
"help.connect_title": "Poveži AI asistente",
"help.construct.axis.edge": "Izrađuje pomoćnu os duž ravnog brida ili linije skice. Upotrijebite je kao os za rotaciju, kružni niz ili spoj.",
"help.construct.axis.normal_to_face": "Izrađuje pomoćnu os kroz točku, okomito na lice. Upotrijebite je kao os koja stoji ravno iz površine.",
"help.construct.axis.through_cylinder": "Izrađuje pomoćnu os kroz središte lica valjka, stošca ili torusa ili kružnog brida. Upotrijebite je za okretanje ili umnožavanje značajki oko okruglog dijela.",
"help.construct.axis.two_planes": "Izrađuje pomoćnu os na mjestu gdje se dvije ravnine ili ravna lica sijeku. Upotrijebite je kad je potrebna os pravac između dvaju lica.",
"help.construct.axis.two_points": "Izrađuje pomoćnu os kroz dvije točke, vrhove ili točke skice. Upotrijebite je za os koja prolazi između dvaju poznatih položaja.",
"help.construct.plane.along_path": "Izrađuje pomoćnu ravninu okomitu na brid ili krivulju, na položaju duž njih. Upotrijebite je za skiciranje profila za povlačenje ili presjeka putanje.",
"help.construct.plane.at_angle": "Izrađuje pomoćnu ravninu zakrenutu za kut u odnosu na ravninu ili lice, oko osi ili brida. Upotrijebite je za nagnute skice, primjerice kutnog nosača.",
"help.construct.plane.midplane": "Izrađuje pomoćnu ravninu na pola puta između dviju ravnina ili ravnih lica, ili ravninu koja raspolavlja kut kad se one sijeku pod kutom. Upotrijebite je kao ravninu simetrije za zrcaljenje.",
"help.construct.plane.offset": "Izrađuje pomoćnu ravninu paralelnu s ravninom ili ravnim licem, na određenoj udaljenosti ili kroz točku. Upotrijebite je za skiciranje na zadanoj visini iznad lica.",
"help.construct.plane.perpendicular": "Izrađuje pomoćnu ravninu koja sadrži liniju ili brid i okomita je na ravninu ili lice. Upotrijebite je za skiciranje uspravno na modelu duž brida.",
"help.construct.plane.tangent": "Izrađuje pomoćnu ravninu koja u jednoj točki dodiruje valjak, stožac, kuglu ili torus. Upotrijebite je za skiciranje na boku okruglog dijela.",
"help.construct.plane.three_points": "Izrađuje pomoćnu ravninu kroz tri točke. Upotrijebite je za ravninu određenu položajima na modelu, primjerice trima kutovima.",
"help.construct.plane.two_edges": "Izrađuje pomoćnu ravninu kroz dva ravna brida ili linije koji leže u istoj ravnini. Upotrijebite je za ravninu kroz dio, primjerice kroz dva nasuprotna brida.",
"help.construct.point.along_path": "Izrađuje pomoćnu točku na položaju duž brida ili krivulje. Upotrijebite je kao referencu negdje na putanji.",
"help.construct.point.center": "Izrađuje pomoćnu točku u središtu kružnog brida, kružnice ili luka skice, kugle ili torusa. Upotrijebite je kao referencu za provrte, osi i spojeve.",
"help.construct.point.edge_plane": "Izrađuje pomoćnu točku na mjestu gdje brid ili linija prolazi kroz ravninu ili ravno lice. Upotrijebite je za označavanje mjesta gdje putanja siječe ravninu.",
"help.construct.point.three_planes": "Izrađuje pomoćnu točku na mjestu gdje se sijeku tri ravnine ili ravna lica. Upotrijebite je kao referencu kuta, čak i tamo gdje model nema vrh.",
"help.construct.point.two_edges": "Izrađuje pomoćnu točku na mjestu gdje se dva brida ili linije sijeku ili si najviše približe. Upotrijebite je kao referencu u virtualnom kutu.",
"help.construct.point.vertex": "Izrađuje pomoćnu točku na vrhu ili točki skice. Upotrijebite je kao referencu koja ostaje kad se skica ili tijelo oko nje sakriju.",
"help.contact.check": "Navodi parove dijelova u skupovima kontakata koji u svojem trenutnom položaju prolaze jedan kroz drugi. Upotrijebite ga za pronalaženje sudara prije pokretanja spojeva ili studije kretanja.",
"help.contact.create": "Grupira dva ili više dijelova u skup kontakata čija se tijela tijekom pomicanja spojeva sprječavaju da prolaze jedno kroz drugo. Upotrijebite ga za dijelove koji se trebaju zaustaviti kad se dodirnu, primjerice polugu uz njezin graničnik.",
"help.contact.disable_all": "Isključuje kontakt, pa se pokretani spojevi i studije kretanja više ne zaustavljaju tamo gdje se dijelovi dodiruju. Upotrijebite ga za ponovno slobodno pomicanje dijelova ili za bolji odziv velikog sklopa.",
"help.contact.edit": "Mijenja skup kontakata: dijelove koje sadrži, njegov naziv ili je li potisnut; može i izbrisati skup. Upotrijebite ga kako biste prilagodili koji se dijelovi sprječavaju da prolaze jedan kroz drugi.",
"help.contact.enable_all": "Stavlja svaki dio sklopa u jedan skup kontakata, pa nijedna dva dijela ne mogu proći jedan kroz drugi tijekom pomicanja spojeva. Upotrijebite ga za brzu provjeru cijelog mehanizma.",
"help.contact.enable_sets": "Uključuje kontakt za skupove kontakata koje ste definirali, pa se pokretani spojevi i studije kretanja zaustavljaju tamo gdje bi njihovi dijelovi prošli jedan kroz drugi. Upotrijebite ga kako biste vratili svoje skupove nakon isključivanja kontakta.",
"help.contact.list": "Navodi skupove kontakata dizajna, dijelove u svakome od njih i je li kontakt uključen. Upotrijebite ga za provjeru kako je kontakt postavljen prije pokretanja sklopa.",
"help.document.inspect": "Prikazuje pregled dizajna: njegove komponente, tijela, skice, značajke i parametre. Upotrijebite ga kako biste se snašli u dizajnu koji niste sami izradili.",
"help.document.units": "Mijenja jedinice u kojima dizajn prikazuje duljine, primjerice mm ili inče. Geometrija zadržava svoju veličinu; mijenjaju se samo prikaz i obični brojevi.",
"help.edit.checkpoint": "Pamti dizajn, aktivnu skicu i povijest poništavanja onakve kakvi su sada. Upotrijebite ga prije isprobavanja nečeg rizičnog od čega ćete se možda htjeti vratiti.",
"help.edit.restore_checkpoint": "Vraća se na kontrolnu točku, uključujući povijest poništavanja. Upotrijebite ga kako biste jednim korakom odbacili sve učinjeno od kontrolne točke.",
"help.explode.create": "Izrađuje rastavljeni prikaz koji dijelove odmiče od središta sklopa ili prema pomacima koje zadate. Upotrijebite ga za prikaz sastavljanja sklopa, za upute ili prezentacije.",
"help.face.delete": "Uklanja odabrana lica i pušta da susjedna lica zatvore otvor, ili ostavlja otvor otvorenim kao plohu. Upotrijebite ga kako biste uklonili zaobljeni brid, provrt ili ispupčenje koje smeta.",
"help.file.insert_mesh": "Dodaje 3MF, STL, OBJ, PLY ili glTF datoteku u dizajn kao mrežna tijela u novoj komponenti. Upotrijebite ga kako biste preuzeti ili skenirani model postavili uz svoj dizajn kao referencu.",
"help.file.insert_step": "Dodaje STEP, IGES, FreeCAD ili Rhino datoteku u dizajn kao puna tijela u novoj komponenti; dijelovi sklopa postaju ugniježđene komponente. Upotrijebite ga za unos modela od dobavljača ili iz drugih CAD programa.",
"help.file.save_mesh": "Sprema tijela kao STL, 3MF ili OBJ mrežu, s glatkoćom zakrivljenih lica po vašem izboru. Upotrijebite ga za pripremu dijelova za 3D ispis ili za programe koji čitaju mreže.",
"help.file.send_to_slicer": "Sprema odabrana tijela kao 3MF datoteku i otvara je u slicer programu koji odaberete. Upotrijebite ga za prijelaz s modela na ispis u jednom koraku.",
"help.font_details": "Sučelje koristi ugrađene fontove biblioteke egui (Ubuntu Light i Hack, pod licencama Ubuntu Font Licence i MIT/Bitstream Vera; Noto Emoji i font emoji ikona, pod licencama OFL i MIT). Tekst skice koristi Liberation Sans (SIL OFL 1.1). Neobavezni paket fontova Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), nije uključen u izdanja SolidCrafta.",
"help.fonts": "Fontovi",
"help.frame.member": "Postavlja profil, primjerice cijev ili aluminijski ekstrudirani profil, duž linija skice okvira, jedan element po liniji. Upotrijebite ga za izradu okvira, regala i zavarenih konstrukcija.",
"help.inspect.accessibility": "Boji lica do kojih alat može doći iz jednog smjera i ona do kojih ne može. Upotrijebite ga za provjeru dijela za strojnu obradu ili kalupljenje s te strane.",
"help.inspect.center_of_mass": "Prikazuje središte mase tijela, svakog zasebno i svih zajedno, pri jednolikoj gustoći. Upotrijebite ga za provjeru ravnoteže i mjesta na kojem će se dio prevrnuti.",
"help.inspect.colored_components": "Daje svakoj komponenti vlastitu privremenu boju, bez promjene izgleda. Upotrijebite ga kako biste na prvi pogled razlikovali dijelove sklopa.",
"help.inspect.compare": "Uspoređuje dva tijela i prikazuje gdje jedno ima materijala kojeg drugo nema, s volumenom i odstupanjem. Upotrijebite ga za provjeru izmijenjenog ili uvezenog dijela u odnosu na izvornik.",
"help.inspect.continuity": "Provjerava koliko glatko se lica spajaju duž svojih bridova (razmake, lomove ili skokove zakrivljenosti) i sjenčanjem označava šavove. Upotrijebite ga za pronalaženje mjesta na kojima se plohe ne spajaju čisto.",
"help.inspect.curvature_comb": "Crta češljeve duž krivulja, bridova ili lica čiji zupci pokazuju koliko se snažno savijaju. Upotrijebite ga za uočavanje ravnih mjesta, lomova i neujednačene zakrivljenosti.",
"help.inspect.curvature_map": "Boji lica prema jakosti zakrivljenosti, crveno tamo gdje je polumjer malen. Upotrijebite ga za pronalaženje uskih mjesta i procjenu glatkoće plohe.",
"help.inspect.draft": "Boji lica prema kutu nagiba u odnosu na smjer vađenja iz kalupa i označava ona s premalo nagiba. Upotrijebite ga za provjeru dijela prije kalupljenja ili lijevanja.",
"help.inspect.environment_map": "Prikazuje model s reflektirajućom završnom obradom nalik zrcalu. Upotrijebite ga za procjenu toka ploha prema načinu na koji se odrazi pružaju preko njih.",
"help.inspect.interference": "Pronalazi tijela koja se preklapaju i prikazuje svako preklapanje s njegovim volumenom. Upotrijebite ga za provjeru sudaraju li se dijelovi u sklopu.",
"help.inspect.isocurve": "Crta izoparametarske krivulje lica, mrežu na kojoj je ploha izgrađena. Upotrijebite ga kako biste vidjeli kako ploha teče i gdje se zgušnjava.",
"help.inspect.measure": "Mjeri duljine, udaljenosti, kutove, površine i polumjere među elementima koje odaberete, ili volumen i masu tijela. Upotrijebite ga za provjeru mjera i zazora.",
"help.inspect.minimum_radius": "Pronalazi najmanji polumjer zakrivljenosti krivulja, bridova ili lica i mjesto na kojem se nalazi. Upotrijebite ga kako biste provjerili može li alat ili mlaznica pratiti oblik.",
"help.inspect.section": "Presijeca prikaz duž ravnine i šrafira presječena lica, bez promjene modela. Upotrijebite ga za pogled u unutrašnjost dijelova i sklopova.",
"help.inspect.zebra": "Prikazuje crne i bijele pruge odražene na licima modela. Upotrijebite ga za procjenu glatkoće: pruge koje se prekidaju ili lome na bridu upućuju na loš prijelaz.",
"help.joint.as_built": "Spaja dva dijela tamo gdje već jesu, bez pomicanja ijednog od njih, i određuje kako se još smiju kretati. Upotrijebite ga za dijelove koje ste već postavili u konačni položaj.",
"help.joint.cam": "Povezuje rotirajući brijeg sa sljedbenikom, pa sljedbenik klizi ili se njiše dok ispod njega prolazi obris brijega. Upotrijebite ga za brijegove, ekscentre i pogone ventila.",
"help.joint.create": "Spaja dva dijela na ishodištima spojeva koja odaberete i pomiče drugi dio na mjesto; vrsta spoja određuje kako se još smiju kretati. Upotrijebite ga za izradu sklopa čiji se dijelovi kreću poput stvarnog mehanizma.",
"help.joint.drive": "Postavlja kut ili udaljenost spoja i s njim pomiče dijelove, unutar njegovih granica; povezani spojevi i zatvorene petlje slijede ga. Upotrijebite ga za provjeru kreće li se mehanizam kako je zamišljeno.",
"help.joint.gear": "Povezuje dva rotirajuća spoja prijenosnim omjerom, pa pokretanje jednog okreće drugi. Upotrijebite ga za čelne zupčanike i zupčanike s unutarnjim ozubljenjem, s omjerom iz polumjera diobenih kružnica ili broja zubaca.",
"help.joint.limits": "Određuje koliko se spoj smije zakrenuti ili pomaknuti, kao najmanju i najveću vrijednost, ili briše te granice. Upotrijebite ga kako bi šarka, klizač ili poluga ostali unutar svojeg stvarnog raspona kretanja.",
"help.joint.motion_link": "Povezuje dva spoja omjerom i odmakom, pa pokretanje jednog pomiče drugi. Upotrijebite ga za čvrst odnos među spojevima koji nije pokriven naredbama Zupčanik, Zupčasta letva i zupčanik ili Vijak.",
"help.joint.origin": "Postavlja imenovano ishodište spoja na dio: u središte lica ili okruglog kraja, sredinu brida, kut ili točku. Upotrijebite ga za pripremu mjesta na kojima se spojevi vežu; ishodište odabrano na licu prati ga kad dio promijeni veličinu.",
"help.joint.rack_pinion": "Povezuje rotirajući mali zupčanik s kliznom zupčastom letvom, pa se letva pomiče za duljinu diobene kružnice koju mali zupčanik prevali. Upotrijebite ga za pogone s letvom, upravljanje i linearne stolove.",
"help.joint.reach": "Pomiče točku na dijelu prema cilju mijenjajući spojeve na njezinu lancu, unutar njihovih granica. Upotrijebite ga za namještanje mehanizma prema tome gdje mu treba biti kraj, primjerice vrh ruke robota.",
"help.joint.rigid_group": "Zaključava odabrane dijelove zajedno onakve kakvi jesu, pa se pomiču kao jedan. Upotrijebite ga za dijelove koji pripadaju zajedno i među kojima nema kretanja.",
"help.joint.screw": "Povezuje okretanje i klizanje korakom, pa svaki okretaj pomiče za tu udaljenost. Upotrijebite ga za vodeće vijke, vijke i navojna vretena.",
"help.library.apply": "Primjenjuje materijal ili izgled iz lokalne datoteke biblioteke na tijela, lica ili komponente. Upotrijebite ga za ponovnu upotrebu materijala koje ste spremili iz drugog dizajna.",
"help.library.export": "Sprema materijale i izglede dizajna u datoteku biblioteke. Upotrijebite ga za njihovo dijeljenje ili ponovnu upotrebu u drugim dizajnima.",
"help.library.inspect": "Provjerava datoteku biblioteke i navodi njezine materijale i izglede, bez promjene dizajna. Upotrijebite ga kako biste prije primjene vidjeli što biblioteka sadrži.",
"help.licences": "Licence",
"help.material.assign": "Određuje od čega su odabrana tijela izrađena, npr. čelik, aluminij ili ABS. Upotrijebite ga kako bi masa i težina odgovarale stvarnom dijelu.",
"help.material.properties.validate": "Provjerava karticu podataka o materijalu radi dosljednosti brojeva i vrijednosti koje nedostaju. Upotrijebite ga prije nego se na podatke oslonite za masu ili krutost.",
"help.mesh.convert": "Ponovno izrađuje mrežno tijelo, primjerice STL, kao uredivo puno tijelo s ravnim i cilindričnim licima. Upotrijebite ga za izmjenu uvezenih strojnih dijelova kao da su modelirano tijelo.",
"help.mit": "MIT licenca",
"help.motion.study": "Postavlja vrijednosti spojeva u koracima duž vremenske trake i reproducira ih kao animaciju. Upotrijebite ga za praćenje kretanja mehanizma kroz slijed i provjeru zazora usput.",
"help.motion.trace": "Prati putanju kojom ide točka na dijelu dok se reproducira studija kretanja ili pokreće spoj i, nakon potvrde, zadržava je kao krivulju 3D skice. Upotrijebite ga kako biste vidjeli dokle mehanizam doseže ili kako biste gradili na njegovoj putanji.",
"help.notice": "NOTICE",
"help.occurrence.ground": "Učvršćuje dio na mjestu ili ga ponovno oslobađa, pa spojevi pomiču ostale dijelove oko njega. Upotrijebite ga za okvir ili podnožje na kojem je izgrađen ostatak sklopa.",
"help.occurrence.move": "Pomiče dio sklopa samo u stupnjevima slobode koje dopuštaju njegovi spojevi. Odaberite lice dijela, a zatim povucite dostupnu strelicu ili luk.",
"help.parameters.change": "Otvara tablicu imenovanih vrijednosti i izraza koji upravljaju kotama dizajna. Upotrijebite je kako biste promijenili mjeru na jednom mjestu, a sve značajke koje je koriste je slijede.",
"help.parameters.export": "Sprema parametre dizajna kao CSV ili JSON. Upotrijebite ga za uređivanje u proračunskoj tablici ili prijenos u drugi dizajn.",
"help.parameters.graph": "Prikazuje koji parametri i značajke koriste koje druge, te petlje ili pogreške među njima. Upotrijebite ga kako biste vidjeli na što će utjecati promjena parametra.",
"help.parameters.import": "Čita parametre iz CSV ili JSON datoteke u dizajn, i to sve ili nijedan. Upotrijebite ga za unos vrijednosti iz proračunske tablice ili drugog dizajna.",
"help.parameters.move": "Premješta korisnički parametar ispred ili iza drugog na popisu. Upotrijebite ga za držanje povezanih parametara zajedno.",
"help.parts.fastener": "Dodaje standardni vijak, svornjak, maticu, podlošku, zatik ili ležaj u odabranoj veličini. Postavljen na provrt, smješta se u njega spojem, pa spojne elemente ne morate modelirati sami.",
"help.parts.insert": "Umeće standardni dio u odabranoj obitelji, veličini i duljini. Postavljen na rub ili stijenku provrta, smješta se na provrt krutim spojem.",
"help.parts.library": "Otvara standardne dijelove: vijke, svornjake, matice, podloške, bezglave vijke, cilindrične zatike i ležajeve u standardnim veličinama. Upotrijebite ga za dodavanje standardnog dijela umjesto da ga sami modelirate.",
"help.parts.resize": "Mijenja veličinu ili duljinu umetnutog standardnog dijela i ponovno ga izgrađuje; njegovi spojevi ga drže na mjestu. Upotrijebite ga kad se vijak pokaže predugim ili provrtu treba druga veličina.",
"help.plastic.assign_rule": "Dodjeljuje pravilo za plastiku tijelima, pa njihove značajke za plastiku preuzimaju njegov nagib i zazor kao zadane vrijednosti. Upotrijebite ga za jednokratno postavljanje oblikovanog dijela za sve njegove značajke.",
"help.plastic.boss": "Dodaje ispust za vijak na licu: stup s neobaveznim provrtom, nagibom, zaobljenjem korijena i potpornim rebrima. Upotrijebite ga za točke vijaka u kalupljenim ili ispisanim kućištima.",
"help.plastic.lip": "Dodaje usnu ili odgovarajući utor uz rub tijela s ljuskom. Upotrijebite ga kako bi se dvije polovice kućišta naslanjale jedna na drugu i čvrsto zatvarale.",
"help.plastic.manage_rules": "Izrađuje i uređuje pravila za plastiku: materijal, debljinu stijenke, polumjere, zazor i nagib. Upotrijebite ih kako bi značajke oblikovanog dijela ostale dosljedne.",
"help.plastic.rest": "Dodaje oslonac na licu: okruglu ili pravokutnu podlogu, punu ili šuplju, s nagibom. Upotrijebite ga kao potporu ili distancu u oblikovanom kućištu.",
"help.plastic.snap_fit": "Dodaje kuku za škljocajući spoj na licu, s njezinom duljinom, debljinom i zapinjačem. Upotrijebite ga za spajanje dijelova kopčanjem, bez vijaka.",
"help.pointcloud.insert": "Umetnite PLY, XYZ ili PTS skeniranje kao oblak točaka uz model. Koristite ga kao referencu pri izradi modela iz izmjerenih podataka.",
"help.sheet.convert": "Pretvara ploču ili tijelo s ljuskom ujednačene debljine u tijelo od lima, sa savijanjima na bridovima koje odaberete. Upotrijebite ga kako biste postojeći dio učinili razvijivim.",
"help.sheet.export_bend_table": "Sprema tablicu savijanja tijela od lima kao CSV ili JSON: kutove, polumjere i položaje savijanja u razvijenom obliku. Upotrijebite ga za stroj za savijanje ili radionicu.",
"help.sheet.export_dxf": "Sprema razvijeni oblik kao DXF, s obrisima, linijama savijanja i neobaveznim bilješkama o savijanju. Upotrijebite ga za lasersko, plazma ili vodenim mlazom rezanje.",
"help.sheet.flange": "Dodaje prirubnicu: osnovnu ploču iz skice, stijenku duž otvorene konture skice ili savijenu stijenku na bridovima lima. Upotrijebite je za izgradnju dijelova od lima.",
"help.sheet.flat_pattern": "Izrađuje razvijeni oblik tijela od lima, s njegovom veličinom, izrezima i linijama savijanja. Upotrijebite ga za provjeru pripremka prije rezanja.",
"help.sheet.fold": "Presavija lim duž linije skice za zadani kut i polumjer. Upotrijebite ga za savijanje ravnog lima nakon što ste u njega urezali značajke.",
"help.sheet.hem": "Preklapa rub lima sam na sebe. Upotrijebite ga za sigurne, krute rubove bez oštrih srha.",
"help.sheet.manage_rules": "Izrađuje i uređuje pravila za lim: debljinu, polumjer savijanja, K-faktor, rasterećenja i razmake. Upotrijebite ih za usklađivanje s materijalom i alatima vaše radionice.",
"help.sheet.path_flange": "Dodaje lanac prirubnica duž ruba lima, prateći otvoreni lanac linija skice. Upotrijebite je za profile poput U ili Z oblika u jednom koraku.",
"help.sheet.perforate": "Dodaje polje provrta po cijelom ravnom limu, u pomaknutom ili kvadratnom rasporedu. Upotrijebite ga za ventilacijske otvore, rešetke i lakše ploče.",
"help.sheet.refold": "Ponovno savija razvijeno tijelo od lima. Upotrijebite ga nakon dodavanja značajki ravnom limu.",
"help.sheet.unfold": "Razvija sva savijanja tijela od lima. Upotrijebite ga za dodavanje provrta ili izreza koji moraju prelaziti preko savijanja, a zatim ponovno presavijte.",
"help.sketch.arc.center_point": "Crta kružni luk oko središnje točke, od početne do završne točke. Upotrijebite ga kad je središte luka poznato, primjerice za polumjer oko provrta ili okretišta.",
"help.sketch.arc.tangent": "Crta luk koji počinje na kraju linije ili luka i nastavlja se tangentno na njega. Upotrijebite ga za nastavak lanca glatkim zavojem umjesto kutom.",
"help.sketch.arc.three_point": "Crta luk od početne do završne točke kroz treću točku. Upotrijebite ga kad luk mora prolaziti kroz zadane točke, a ne oko poznatog središta.",
"help.sketch.arc3d": "Crta točan kružni luk u prostoru kroz tri točke, uz uključenu 3D skicu. Upotrijebite ga za zavoje u putanjama i vodilicama koji izlaze iz ravnine skice.",
"help.sketch.auto_constrain": "Dodaje ograničenja koja skica već gotovo ima, primjerice vodoravnost, paralelnost ili podudarnost, a zatim je kotira dok nije potpuno ograničena. Upotrijebite ga za učvršćivanje grubo nacrtane skice u jednom koraku.",
"help.sketch.auto_constrain_from_datum": "Radi kao Automatska ograničenja, ali položaje mjeri od referentne točke koju odaberete umjesto od ishodišta. Upotrijebite ga kad je dio raspoređen od jedne referentne točke, primjerice kuta ili provrta.",
"help.sketch.blend_curve": "Spaja krajeve dviju krivulja glatkim splineom koji je tangentan na obje, ili i kontinuiran po zakrivljenosti ako tako odaberete. Upotrijebite ga za premošćivanje razmaka među krivuljama bez loma.",
"help.sketch.break": "Dijeli krivulju na dijelove tamo gdje je sijeku druge krivulje ili u točkama koje na nju postavite. Upotrijebite ga kad dio krivulje treba vlastito ograničenje ili ga treba zasebno izbrisati.",
"help.sketch.cancel": "Napušta skicu i odbacuje sve promjene učinjene od njezina stvaranja ili otvaranja za uređivanje. Naredba Poništi vraća promjene.",
"help.sketch.centerline": "Prebacuje linije između običnih linija i središnjih linija, osi koje profili izostavljaju. Središnju liniju upotrijebite kao os za rotaciju, zrcaljenje ili ograničenje simetrije.",
"help.sketch.chamfer.distance_angle": "Skošuje kut između dviju linija, zadan udaljenošću duž prve linije i kutom. Upotrijebite ga kad crtež daje skošenje kao duljinu i kut, primjerice 2 mm × 30°.",
"help.sketch.chamfer.equal_distance": "Skošuje kut između dviju linija jednakom udaljenošću unatrag duž obiju. Upotrijebite ga za uobičajeno skošenje od 45° na obrisu.",
"help.sketch.chamfer.two_distance": "Skošuje kut između dviju linija, zadan zasebnom udaljenošću duž svake linije. Upotrijebite ga za skošenja koja nisu 45°, zadana kao dvije duljine.",
"help.sketch.circle.center": "Crta kružnicu iz njezina središta i promjera. Upotrijebite ga za provrte, ispuste i druge okrugle značajke čije je središte poznato.",
"help.sketch.circle.three_point": "Crta kružnicu kroz tri točke. Upotrijebite ga kad kružnica mora prolaziti kroz zadane točke, primjerice postojeće geometrije.",
"help.sketch.circle.three_tangent": "Crta kružnicu koja dodiruje tri elementa (linije, kružnice ili lukove). Upotrijebite ga za uklapanje kružnice u prostor među tri krivulje, primjerice upisane kružnice trokuta.",
"help.sketch.circle.two_point": "Crta kružnicu iz dvaju krajeva njezina promjera. Upotrijebite ga kad su poznate rubne točke kružnice, a ne njezino središte.",
"help.sketch.circle.two_tangent": "Crta kružnicu zadane veličine koja dodiruje dva elementa (linije, kružnice ili lukove). Upotrijebite ga za zaobljenje stalnog polumjera između dviju krivulja.",
"help.sketch.conic": "Crta koniku između dviju krajnjih točaka, oblikovanu vršnom točkom i vrijednošću rho. Upotrijebite ga za glatke prijelaze čiju punoću želite podesiti, od ravnog do gotovo oštrog kuta.",
"help.sketch.constrainer": "Primjenjuje ograničenje koje jedna ili dvije odabrane krivulje već gotovo imaju, primjerice vodoravnost, paralelnost, tangentnost ili jednakost. Upotrijebite ga za sređivanje ručno nacrtane skice jedan po jedan odnos.",
"help.sketch.constraint.coincident": "Čini da točka leži na drugoj točki, krivulji ili osi skice. Upotrijebite ga za zatvaranje razmaka u profilu ili za postavljanje točke na liniju.",
"help.sketch.constraint.collinear": "Čini da dvije linije leže na istom pravcu. Upotrijebite ga za poravnanje prekinutih rubova, primjerice obiju strana zareza.",
"help.sketch.constraint.concentric": "Daje dvjema kružnicama ili lukovima isto središte. Upotrijebite ga za prstenove, cilindrična upuštenja i stepenaste osovine.",
"help.sketch.constraint.curvature": "Čini da dvije krivulje koje se susreću u krajnjoj točki nastave s istom zakrivljenošću (G2). Upotrijebite ga tamo gdje tangentni prijelaz nije dovoljno gladak, primjerice na vidljivim plohama.",
"help.sketch.constraint.equal": "Izjednačuje duljine linija ili polumjere kružnica i lukova. Upotrijebite ga kako bi jedna kota upravljala s više istovjetnih elemenata.",
"help.sketch.constraint.fix": "Fiksira točke ili krivulje tamo gdje jesu ili ih ponovno oslobađa. Upotrijebite ga za geometriju koja se ne smije pomaknuti kad povlačite ili rješavate skicu.",
"help.sketch.constraint.horizontal_vertical": "Čini liniju, ili liniju između dviju točaka, vodoravnom ili okomitom, što god je bliže. Upotrijebite ga za poravnanje obrisa s osima skice.",
"help.sketch.constraint.midpoint": "Postavlja točku na sredinu linije ili spaja središnje točke dviju linija. Upotrijebite ga za centriranje geometrije na bridu.",
"help.sketch.constraint.parallel": "Čini dvije linije paralelnima. Upotrijebite ga za stranice koje moraju ostati paralelne kad se druge kote promijene.",
"help.sketch.constraint.perpendicular": "Čini da se dvije linije susreću pod pravim kutom. Upotrijebite ga za pravokutne kutove između linija koje nisu na osima skice.",
"help.sketch.constraint.polygon": "Pretvara zatvoreni lanac linija u pravilni mnogokut: jednake stranice, a svaki vrh na istoj kružnici. Upotrijebite ga kako biste ručno nacrtani mnogokut učinili pravilnim i takvim ga zadržali.",
"help.sketch.constraint.symmetry": "Čini da dvije točke ili krivulje budu zrcalne jedna drugoj preko linije ili oko točke. Upotrijebite ga za održavanje simetričnosti profila kad mijenjate jednu stranu.",
"help.sketch.constraint.tangent": "Čini da se dvije krivulje, primjerice linija i luk, dodiruju tangentno. Upotrijebite ga za glatke prijelaze bez loma.",
"help.sketch.construction": "Prebacuje krivulje između obične i pomoćne geometrije, koju profili izostavljaju. Pomoćne krivulje upotrijebite kao pomagala za raspored, reference i kote.",
"help.sketch.construction_line": "Izradite beskonačni pomoćni pravac kroz dvije točke. Upotrijebite ga kako biste rasporedili skicu bez razdvajanja profila.",
"help.sketch.construction_line.angle": "Izradite beskonačni pomoćni pravac pod kutom od osi X skice. Odaberite točku i upišite kut ili odaberite smjer.",
"help.sketch.construction_line.bisector": "Odaberite dvije ravne linije skice koje se sijeku, pa jednu od dviju simetrala kuta. Rezultat prati izvorne linije.",
@@ -1411,11 +1580,71 @@
"help.sketch.construction_line.tangent_external": "Odaberite kružnicu ili kružni luk, pa vanjsku točku i rješenje tangente. Pregledi prikazuju samo dodirne točke na nacrtanom luku.",
"help.sketch.construction_line.tangent_two": "Odaberite dvije kružnice ili kružna luka, pa u pregledu odaberite zajedničku tangentu. Dodirne točke moraju ležati na nacrtanim krivuljama.",
"help.sketch.construction_line.vertical": "Izradite beskonačni okomiti pomoćni pravac kroz točku. Njegovo ograničenje okomitosti ostaje na snazi pri kasnijim promjenama.",
"help.sketch.copy": "Kopira odabrane krivulje i točke skice zajedno s ograničenjima i kotama među njima. Zalijepite ih u ovu ili u drugu skicu.",
"help.sketch.create": "Započinje novu 2D skicu na ravnini ili ravnom licu modela. Svaka naredba Ekstrudiraj, Rotiraj i Povlačenje počinje skicom.",
"help.sketch.cut": "Kopira odabranu geometriju skice i briše je iz skice, u jednom koraku poništavanja. Zalijepite je drugdje u ovoj skici ili u drugu skicu.",
"help.sketch.dimension": "Dodaje upravljačku kotu: duljinu, udaljenost, kut, polumjer ili promjer, ovisno o tome što odaberete. Upišite vrijednost ili naziv parametra, a skica će je slijediti.",
"help.sketch.edit": "Otvara postojeću skicu za uređivanje. Značajke izgrađene na njoj ažuriraju se kad dovršite skicu.",
"help.sketch.ellipse": "Crta elipsu iz njezina središta, kraja velike osi i točke na maloj strani. Upotrijebite ga za ovalne otvore, brijegove i druge izdužene okrugle oblike.",
"help.sketch.equation_curve": "Crta krivulju prema formulama: x(t) i y(t) ili y = f(x), u zadanom rasponu. Formule mogu koristiti parametre i krivulja ih prati: upotrijebite je za sinusoidu, spiralu ili evolventu zuba zupčanika.",
"help.sketch.extend": "Produljuje liniju ili luk do najbliže krivulje na njegovu putu ili produljuje dvije krivulje dok se ne susretnu. Upotrijebite ga za zatvaranje razmaka u profilu nakon odsijecanja ili uređivanja.",
"help.sketch.fillet": "Zaobljuje kut na kojem se susreću dvije linije ili dva luka lukom zadanog polumjera. Više kutova zaobljenih zajedno dijeli jedan polumjer.",
"help.sketch.finish": "Napušta skicu i vraća se na model. Njezini zatvoreni profili tada su spremni za Ekstrudiraj, Rotiraj i druge značajke.",
"help.sketch.finish_auto_constrain": "Pokreće Automatska ograničenja na skici, a zatim je dovršava. Upotrijebite ga kako biste skicu ostavili potpuno ograničenom u jednom koraku.",
"help.sketch.fit_curves_to_section": "Presijeca mrežno ili puno tijelo ravninom skice i uklapa linije i lukove u presjek. Upotrijebite ga za ponovnu izradu skeniranog ili uvezenog dijela kao uredive skice.",
"help.sketch.include_3d": "Unosi bridove modela, vrhove i krivulje drugih skica u ovu skicu kao povezane reference, spljoštavajući one izvan njezine ravnine na nju. Upotrijebite ga za gradnju na geometriji koja ne leži u ravnini skice.",
"help.sketch.infinite": "Prebacuje linije između običnih linija i beskonačnih pomoćnih linija koje prolaze cijelim prikazom. Upotrijebite ih kao reference rasporeda koje nikad nisu prekratke.",
"help.sketch.insert_dxf": "Čita krivulje DXF crteža u skicu, skalirane, zakrenute i postavljene kako zadate. Upotrijebite ga za gradnju na 2D crtežu iz drugog programa, primjerice na obrisu za lasersko rezanje.",
"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.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.",
"help.sketch.line3d": "Crta povezane ravne linije između točaka u prostoru, uz uključenu 3D skicu. Upotrijebite ga za putanje i okvire koji izlaze iz ravnine skice.",
"help.sketch.mirror": "Zrcali krivulje i točke skice preko linije ili osi i dodaje ograničenja simetrije. Upotrijebite ga za crtanje polovice simetričnog profila i njegovo dovršavanje.",
"help.sketch.move": "Pomiče ili zakreće krivulje i točke skice, ili njihovu kopiju. Upotrijebite ga za premještanje geometrije uz zadržavanje njezina oblika i ograničenja.",
"help.sketch.offset": "Izrađuje kopiju lanca krivulja na zadanoj udaljenosti, s jedne ili obje strane. Upotrijebite ga za stijenke, obrise i zazore; udaljenost ostaje uređiva kota.",
"help.sketch.paste": "Lijepi geometriju skice koju ste kopirali ili izrezali u aktivnu skicu. Postavite je u točku ili je pomaknite za odmak.",
"help.sketch.pattern.circular": "Kopira geometriju skice oko središnje točke, po punom krugu ili zadanom kutu. Upotrijebite ga za kružne rasporede vijaka, žbice i zube.",
"help.sketch.pattern.rectangular": "Kopira geometriju skice u retke i stupce na zadanim razmacima. Upotrijebite ga za mreže provrta, utora ili otvora za ventilaciju.",
"help.sketch.point": "Postavlja točku skice. Upotrijebite je kao referencu za kote, ograničenja, središta provrta ili kasnije značajke.",
"help.sketch.point3d": "Postavlja točku u prostoru, uz uključenu 3D skicu. Upotrijebite je kao referencu za 3D krivulje i putanje.",
"help.sketch.polygon.circumscribed": "Crta pravilni mnogokut oko kružnice, veličine određene od središta do sredine stranice. Upotrijebite ga kad je poznata širina ključa, kao kod šesterokutne matice.",
"help.sketch.polygon.edge": "Crta pravilni mnogokut iz dvaju krajeva jedne stranice. Upotrijebite ga kad su poznate duljina i položaj stranice.",
"help.sketch.polygon.inscribed": "Crta pravilni mnogokut unutar kružnice, veličine određene od središta do vrha. Upotrijebite ga kad je poznata mjera preko vrhova.",
"help.sketch.project": "Unosi bridove modela, lica, vrhove i krivulje drugih skica u ovu skicu, projicirane na njezinu ravninu. Ostaju povezani i prate model kad se promijeni.",
"help.sketch.project_to_surface": "Projicira krivulje drugih skica duž normale ove skice na lice, kao povezane 3D krivulje. Upotrijebite ga za nanošenje logotipa, linije razdvajanja ili putanje na zakrivljenu plohu.",
"help.sketch.rectangle.center": "Crta pravokutnik iz njegova središta i jednog kuta. Upotrijebite ga za obrise centrirane na točku, primjerice ishodište.",
"help.sketch.rectangle.rounded": "Crta pravokutnik sa sva četiri kuta zaobljena istim polumjerom. Upotrijebite ga za ploče, poklopce i otvore sa zaobljenim kutovima.",
"help.sketch.rectangle.three_point": "Crta pravokutnik iz jednog brida i točke na suprotnoj strani. Upotrijebite ga za pravokutnike pod kutom u odnosu na osi skice.",
"help.sketch.rectangle.two_point": "Crta pravokutnik iz dvaju nasuprotnih kutova. Upotrijebite ga za obrise ploča, blokova i džepova.",
"help.sketch.scale": "Skalira krivulje i točke skice faktorom oko osnovne točke. Upotrijebite ga za promjenu veličine dijela skice bez ponovnog crtanja.",
"help.sketch.slot.arc_center": "Crta zakrivljeni utor zadane širine duž luka određenog njegovim središtem, početkom i krajem. Upotrijebite ga za utore za podešavanje oko okretišta.",
"help.sketch.slot.arc_three_point": "Crta zakrivljeni utor zadane širine duž luka kroz tri točke. Upotrijebite ga kad putanja utora mora prolaziti kroz zadane točke.",
"help.sketch.slot.center_point": "Crta ravni utor zadane širine od njegova središta do središta jednog završnog luka. Upotrijebite ga za utore centrirane na točku.",
"help.sketch.slot.center_to_center": "Crta ravni utor zadane širine između središta njegovih dvaju završnih lukova. Upotrijebite ga za utore kotirane od središta do središta, primjerice za utore za podešavanje vijaka.",
"help.sketch.slot.overall": "Crta ravni utor zadane širine od jednog vanjskog kraja do drugog. Upotrijebite ga kad je važna ukupna duljina utora.",
"help.sketch.spline.control_point": "Crta kubični spline oblikovan kontrolnim točkama kroz koje ne prolazi. Upotrijebite ga za glatke, tekuće krivulje koje oblikujete pomicanjem točaka.",
"help.sketch.spline.control_point_5": "Crta spline stupnja 5 oblikovan kontrolnim točkama. Upotrijebite ga tamo gdje se krivulje moraju spajati s glađom zakrivljenošću, primjerice na vidljivim oblikovanim plohama.",
"help.sketch.spline.fit_point": "Crta glatki spline koji prolazi kroz svaku točku koju postavite, otvoren ili zatvoren. Upotrijebite ga za ocrtavanje oblika kroz poznate točke.",
"help.sketch.spline3d": "Crta glatku krivulju u prostoru kroz točke, uz uključenu 3D skicu. Upotrijebite ga za putanje povlačenja i trase kabela koje izlaze iz ravnine skice.",
"help.sketch.spun_profile": "Izrađuje u ravnini skice obris koji tijelo opiše okretanjem oko osi. Upotrijebite ga za dobivanje siluete tokarenog dijela za Rotiraj ili za crtež.",
"help.sketch.text": "Piše tekst u skici odabranim fontom, visinom i kutom; njegove riječi mogu pratiti parametre. Ekstrudirajte ga ili primijenite Reljef za oznake i natpise.",
"help.sketch.transform": "Skalira, zakreće i pomiče geometriju skice oko središnje točke u jednom koraku. Upotrijebite ga za istodobno premještanje i promjenu veličine odabira.",
"help.sketch.trim": "Uklanja dio krivulje između najbližih sjecišta oko mjesta na koje kliknete. Upotrijebite ga za čišćenje linija koje se preklapaju u zatvoreni profil.",
"help.solid.align": "Pomiče odabrana tijela tako da se točka, lice ili ravnina na njima poklopi s točkom, licem ili ravninom negdje drugdje. Upotrijebite ga kako biste postavili dio uz drugi bez mjerenja odmaka.",
"help.solid.boundary_fill": "Izrađuje puna tijela iz područja zatvorenih preklapajućim alatnim tijelima, ispunjavajući samo područja koja odaberete. Upotrijebite ga za izradu oblika iz prostora koji zajedno omeđuje više tijela.",
"help.solid.bounding_solid": "Izrađuje kutiju koja obuhvaća odabrana tijela, s neobaveznom marginom. Upotrijebite ga za uvid u ukupnu veličinu dijela ili kao sirovac iz kojeg se obrađuje.",
"help.solid.box": "Izrađuje kutiju iz duljine, širine i visine, iz kutne točke ili centriranu na točku. Upotrijebite ga za brzi blok, privremeni oblik ili tijelo u koje urezujete značajke.",
"help.solid.chamfer": "Odsijeca odabrane bridove ravnim skošenjem zadane udaljenosti. Koristite ga kako biste ublažili oštre bridove ili olakšali uvođenje zatika ili vijka.",
"help.solid.coil": "Izrađuje zavojito tijelo, primjerice oprugu, iz promjera, broja zavoja, visine ili koraka te okruglog ili kvadratnog presjeka. Upotrijebite ga za opruge, zavojnice i zavojne grebene.",
"help.solid.combine": "Spaja tijela, oduzima ih od ciljnog tijela ili zadržava samo zajednički volumen, a može zadržati i alatna tijela. Koristite ga kako biste spojili dijelove, jednim oblikom odrezali drugi ili pronašli gdje se dva tijela preklapaju.",
"help.solid.cone": "Izrađuje stožac iz osnovne kružnice i visine, ili krnji stožac kad i vrh ima polumjer. Upotrijebite ga za suženja, lijevke, upuštenja i šiljke.",
"help.solid.cylinder": "Izrađuje valjak iz polumjera ili promjera i visine, koji stoji na točki duž osi. Upotrijebite ga za osovine, zatike, ispuste i okrugle pripremke.",
"help.solid.draft": "Naginje odabrana lica za kut koji zadate, mjereno od neutralne ravnine. Upotrijebite ga na lijevanim ili brizganim dijelovima kako bi izašli iz kalupa.",
"help.solid.emboss": "Izdiže profile skice, primjerice tekst ili logotip, iz lica ili ih utiskuje u njega. Upotrijebite ga za natpise, oznake i markiranje na ravnim ili okruglim licima.",
"help.solid.extrude": "Izvlači profil skice u 3D duž zadane udaljenosti, dodajući ili uklanjajući materijal. Koristite ga kako biste ravni obris pretvorili u blok, ispust ili džep.",
"help.solid.fillet": "Zaobljuje odabrane bridove glatkim kružnim prijelazom zadanog polumjera. Koristite ga kako biste omekšali oštre bridove radi čvrstoće, udobnosti ili izgleda.",
"help.solid.hole": "Buši okrugli provrt zadanog promjera na odabranoj točki lica. Koristite ga za prolaze vijaka, zatike i druge jednostavne okrugle otvore.",
@@ -1424,20 +1653,60 @@
"help.solid.move": "Pomiče ili rotira odabrana tijela, ili njihovu kopiju, za udaljenost i kut koje zadate. Upotrijebite ga kako biste premjestili tijelo ili izradili drugo na drugom mjestu.",
"help.solid.offset_face": "Gura odabrana lica unutra ili van za udaljenost koju zadate, a okolna lica ih prate. Upotrijebite ga kako biste podebljali stijenku, produbili džep ili povisili ispupčenje bez uređivanja skice.",
"help.solid.pattern.circular": "Ponavlja odabrane objekte oko osi, za zadani broj i ukupni kut. Koristite ga za kružne rasporede vijaka, žbice, rebra hladnjaka i zupce.",
"help.solid.pattern.path": "Kopira značajke ili tijela duž putanje skice, prema broju i razmaku. Upotrijebite ga za provrte duž zakrivljenog brida ili dijelove duž vodilice.",
"help.solid.pattern.rectangular": "Ponavlja odabrane objekte u jednom ili dva smjera sa zadanim brojem i razmakom. Koristite ga za redove i mreže provrta, rebara, zatika ili nožica.",
"help.solid.pipe": "Izrađuje okruglu cijev duž putanje krivulja skice, punu ili šuplju s debljinom stijenke. Upotrijebite ga za cijevi, žice, kabele i vodilice.",
"help.solid.press_pull": "Dodaje ili uklanja materijal dok povlačite lice, ili zaobljuje bridove dok ih povlačite. Upotrijebite ga za brze izmjene izravno na modelu.",
"help.solid.remove": "Uklanja odabrana tijela iz dizajna od ove točke vremenske trake nadalje; raniji koraci ih zadržavaju. Upotrijebite ga kako biste odbacili pomoćno tijelo, npr. alat za rezanje koji vam više ne treba.",
"help.solid.replace_face": "Premješta odabrana ravna lica na ravninu ili na paralelno lice, a tijelo raste ili se smanjuje da ga dosegne. Upotrijebite ga kako biste poravnali lice s drugim dijelom ili pomoćnom ravninom.",
"help.solid.revolve": "Okreće profil skice oko osi i izrađuje okruglo tijelo. Koristite ga za vratila, gumbe, posude i svaki dio koji je okrugao oko središnje linije.",
"help.solid.rib": "Dodaje tanku stijenku duž otvorene krivulje skice, produljenu do sljedećih lica tijela. Upotrijebite ga za ukrućivanje stijenki i kutova, primjerice u kalupljenim ili ispisanim dijelovima.",
"help.solid.scale": "Povećava ili smanjuje odabrana tijela za faktor, jednako u svim smjerovima ili posebno po osi. Upotrijebite ga kako biste promijenili veličinu uvezenog oblika ili uračunali skupljanje.",
"help.solid.shell": "Izdubljuje tijelo i ostavlja stijenke zadane debljine, a može otvoriti lica koja odaberete. Koristite ga za kutije, poklopce i druge tankostijene dijelove.",
"help.solid.sphere": "Izrađuje kuglu iz polumjera ili promjera oko središnje točke. Upotrijebite ga za kugle, gumbe i zaobljene krajeve.",
"help.solid.split_body": "Reže tijelo na zasebna tijela ravninom, licem drugog tijela ili linijom skice. Upotrijebite ga za dijelove koji se zasebno ispisuju, obrađuju ili boje.",
"help.solid.split_face": "Dijeli odabrana lica po ravnini, licu drugog tijela ili krivuljama skice, bez rezanja tijela. Upotrijebite ga kako biste dijelu lica dali vlastiti nagib, boju ili odmak.",
"help.solid.sweep": "Povlači profil skice duž putanje i izrađuje tijelo. Koristite ga za cijevi, tračnice, ručke i svaki dio konstantnog presjeka koji prati krivulju.",
"help.solid.thread": "Dodaje ISO metrički navoj na cilindrično lice, kao kozmetički navoj ili urezan u tijelo. Upotrijebite ga za vijke, navojne provrte i ispisane dijelove s vijkom.",
"help.solid.torus": "Izrađuje prsten iz velikog polumjera oko njegove osi i malog polumjera za njegov okrugli presjek. Upotrijebite ga za O-prstenove, ručke i okrugle perle.",
"help.solid.web": "Dodaje tanku stijenku duž svake otvorene krivulje skice, svaku produljenu do sljedećih lica tijela. Upotrijebite ga za mrežu potpornih stijenki unutar kućišta i posuda.",
"help.surface.blend": "Izrađuje plohu koja premošćuje bridove dvaju tijela, tangentnu ili kontinuiranu po zakrivljenosti s oba. Upotrijebite ga za glatko spajanje dviju ploha preko razmaka.",
"help.surface.boundary": "Izrađuje plohu koja ispunjava petlju od dvije do četiri krivulje skice ili mrežu krivulja. Upotrijebite ga za ploče slobodnog oblika određene svojim bridovima.",
"help.surface.extend": "Produljuje plohu preko njezinih bridova, za zadanu udaljenost ili do ravnine ili lica. Upotrijebite ga kako bi se plohe preklapale prije odsijecanja ili sašivanja.",
"help.surface.extrude": "Potiskuje krivulje ili profile skice na zadanu udaljenost u plohu, samo stijenke, bez zatvaranja krajeva. Upotrijebite ga za tanke stijenke te alate za rezanje ili razdvajanje.",
"help.surface.fair": "Zaglađuje unutrašnjost ploha slobodnog oblika dok njihovi bridovi ostaju na mjestu. Upotrijebite ga za izravnavanje izbočina i valovitih odraza na plohi.",
"help.surface.loft": "Izrađuje plohu koja redom prolazi kroz dva ili više presjeka, bez zatvaranja krajeva. Upotrijebite ga za ljuske i prijelaze između različitih oblika.",
"help.surface.match": "Ponovno izrađuje brid plohe tako da se susreće s bridom drugog tijela, uz usklađenu tangentnost ili zakrivljenost. Upotrijebite ga za glatko zatvaranje šava između dviju ploha.",
"help.surface.offset": "Izrađuje kopiju lica na udaljenosti duž njihovih normala, kao novo plošno tijelo. Upotrijebite ga za oplate, zazore ili drugu stranu stijenke.",
"help.surface.patch": "Ispunjava područje skice ili zatvorenu petlju bridova licem, ravnim ili glatko zakrivljenim. Upotrijebite ga za zatvaranje otvora u plošnom tijelu.",
"help.surface.reverse_normal": "Okreće lica plošnog tijela, pa im se vanjska strana okrene na drugu stranu. Upotrijebite ga prije podebljavanja ili sašivanja kad lica gledaju prema unutra.",
"help.surface.revolve": "Okreće krivulje ili profile skice oko osi u plohu, bez zatvaranja krajeva. Upotrijebite ga za okrugle ljuske poput sjenila svjetiljki, zdjela i mlaznica.",
"help.surface.ruled": "Izrađuje traku ravnih linija koje izlaze iz lanca bridova, duž normale lica, tangente ili pod kutom. Upotrijebite ga za stijenke s nagibom, izlazne plohe i plohe razdvajanja.",
"help.surface.stitch": "Sašiva plošna tijela duž njihovih zajedničkih bridova, u puno tijelo kad se zatvore. Upotrijebite ga za pretvaranje skupa ploha u jedno tijelo.",
"help.surface.sweep": "Pomiče krivulje ili profile skice duž putanje u plohu, bez zatvaranja krajeva. Upotrijebite ga za oluke, obrube i druge tanke oblike koji prate putanju.",
"help.surface.thicken": "Daje plošnom tijelu debljinu i pretvara ga u puno tijelo. Upotrijebite ga za izradu dijelova za ispis ili obradu iz ploha.",
"help.surface.trim": "Odsijeca plohu ravninom, drugom plohom ili krivuljama skice i zadržava stranu koju odaberete. Upotrijebite ga za rezanje ploha na oblik prije sašivanja.",
"help.surface.unstitch": "Odvaja lica tijela u zasebna plošna tijela; puno tijelo se raspada na plohe. Upotrijebite ga za preradu pojedinačnih lica tijela.",
"help.surface.untrim": "Uklanja otvore iz lica ili vraća njegovu vanjsku granicu na prirodne granice plohe. Upotrijebite ga za povrat cijele plohe nakon odsijecanja.",
"help.third_party": "Licence trećih strana",
"help.timeline.compute_all": "Ponovno gradi svaku značajku na vremenskoj traci od početka. Upotrijebite ga kako biste osvježili model i vidjeli koje značajke ne uspijevaju.",
"help.timeline.delete": "Briše odabrane značajke s vremenske trake; brisanje skice briše i značajke izrađene iz nje. Upotrijebite ga kako biste uklonili korak koji više ne želite.",
"help.timeline.dependents": "Navodi značajke koje bi se izbrisale zajedno s nekom značajkom i one koje bi bez nje zakazale. Upotrijebite ga prije brisanja ili promjene značajke.",
"help.timeline.play": "Ponavlja preostale značajke jednu po jednu, s pauzom od pola sekunde nakon svake ponovne izgradnje. Ponovno pokrenite ovu naredbu ili pritisnite Escape za zaustavljanje; Idi na kraj skače odmah.",
"help.view.apply": "Okreće prikaz u standardni smjer, primjerice pogled sprijeda, odozgo ili izometrijski, ili u spremljeni imenovani prikaz. Upotrijebite ga za gledanje modela iz stalnog kuta.",
"help.view.delete": "Briše spremljeni imenovani prikaz. Model se ne mijenja.",
"help.view.fit": "Zumira i pomiče prikaz tako da cijeli model, ili odabir, ispuni prikaz. Upotrijebite ga kad je model izašao iz vidnog polja.",
"help.view.grid": "Prikazuje ili skriva mrežu u prozoru prikaza. Upotrijebite je kao vodič za veličinu i orijentaciju pri modeliranju.",
"help.view.ground_shadow": "Prikazuje ili skriva sjenu modela na podlozi. Upotrijebite je kako biste lakše procijenili visinu i položaj.",
"help.view.home": "Vraća prikaz u početni smjer i uklapa model. Upotrijebite ga za brz povratak na poznat prikaz.",
"help.view.list": "Navodi imenovane prikaze spremljene u dizajnu. Upotrijebite ga za pronalaženje prikaza koji ste ranije spremili.",
"help.view.navigate": "Okreće, pomiče ili zumira prikaz u koracima, primjerice za četvrtinu okreta ulijevo. Upotrijebite ga za precizno pomicanje kamere umjesto povlačenja.",
"help.view.navigation_bar": "Prikazuje ili skriva navigacijsku traku na dnu prozora prikaza. Sakrijte je za više prostora, prikažite je za pristup alatima prikaza mišem.",
"help.view.rename": "Daje spremljenom imenovanom prikazu novi naziv. Sam se prikaz ne mijenja.",
"help.view.save": "Sprema trenutni položaj kamere kao imenovani prikaz u dizajnu. Upotrijebite ga za kasniji povratak na isti kut, primjerice za snimke zaslona.",
"help.view.spin": "Neprekidno orbitira prikaz oko modela do sljedećeg klika ili tipke. Upotrijebite ga za predstavljanje modela sa svih strana.",
"help.view.tool": "Uključuje alat kamere, orbitiranje, pomicanje ili zumiranje, pa povlačenje lijevom tipkom miša pomiče prikaz dok ga ne isključite. Upotrijebite ga na dodirnoj pločici ili bez srednje tipke miša.",
"help.view.visual_style": "Mijenja način crtanja modela: osjenčano sa ili bez bridova, žičani model ili sa skrivenim bridovima. Žičani model upotrijebite za pogled unutra, osjenčano za čist izgled.",
"history.sketch_under_constrained": "Nedovoljno ograničeno (stupnjevi slobode: {count})",
"history.sketch_under_constrained_one": "Nedovoljno ograničeno (stupanj slobode: {count})",
"hole_presets.confirm_delete": "Potvrdi brisanje",
@@ -1614,6 +1883,10 @@
"material.selection.mixed": "Različiti materijali",
"measure.at": "Na",
"measure.item": "Stavka {number}",
"measure.quick_copied": "Mjerenje kopirano",
"measure.quick_copy": "Kliknite za kopiranje mjerenja",
"measure.quick_edges": "Bridovi: {count}",
"measure.quick_faces": "Lica: {count}",
"messages.ago_h": "prije {n} h",
"messages.ago_min": "prije {n} min",
"messages.ago_s": "prije {n} s",
@@ -1647,6 +1920,7 @@
"nav.hfss.help": "Alt+povlačenje lijevom tipkom pomiče, Shift+Alt+lijeva tipka zumira, Ctrl+povlačenje lijevom tipkom na praznom prostoru orbitira",
"nav.inventor.help": "Povlačenje srednjom tipkom pomiče, Shift+srednja orbitira, Ctrl+srednja zumira, povlačenje desnom orbitira",
"nav.maya.help": "Alt+povlačenje lijevom tipkom orbitira, Alt+srednja tipka pomiče, Alt+desna tipka zumira; povlačenje srednjom tipkom također pomiče",
"nav.nx.help": "Povlačenje srednjom tipkom orbitira, Shift+srednja pomiče (u NX-u srednja + desna), Ctrl+srednja zumira (u NX-u srednja + lijeva); povlačenje desnom tipkom ne dira kameru",
"nav.openscad.help": "Povlačenje lijevom tipkom po praznom prostoru orbitira, Shift+povlačenje lijevom odabire okvirom, povlačenje geometrije skice uređuje je; povlačenje desnom ili srednjom tipkom pomiče, Shift+desna ili Shift+srednja zumira",
"nav.perspective.sketch": "Uređivanje skice koristi ortografski prikaz. Završite skicu kako biste prešli na perspektivu.",
"nav.right_drag_orbits.help": "Isključeno: povlačenje desnom tipkom ne dira kameru, a desna tipka samo otvara kontekstni izbornik. Odabir mapiranja miša SolidWorks isključuje ovu opciju.",
@@ -1766,6 +2040,11 @@
"not a move": "nije pomak",
"not a number": "nije broj",
"open a sketch first": "najprije otvorite skicu",
"parameters.apply_live": "Primijeni svaku promjenu čim napustite njezino polje",
"parameters.apply_live_tip": "Isključeno: promjene čekaju Primijeni ili U redu, a zatim se dizajn jednom ponovno izgradi za sve njih. Odustani ili Escape ih odbacuje.",
"parameters.held": "Promjene koje čekaju Primijeni: {count}",
"parameters.held_hint": "Uređujte izraze i jedinice. Promjene čekaju Primijeni ili U redu.",
"parameters.held_one": "1 promjena čeka Primijeni",
"parts.automatic_name": "automatski",
"parts.dimension.across_flats": "Mjera ključa",
"parts.dimension.bore": "Provrt",
@@ -1788,6 +2067,8 @@
"parts.family.set_screw": "Bezglavi vijak s unutarnjim šesterokutom, ravni vrh",
"parts.family.socket_head_cap_screw": "Vijak s cilindričnom glavom i unutarnjim šesterokutom",
"parts.family.washer": "Obična podloška",
"parts.holes": "Provrti (neobavezno)",
"parts.placement_hint": "odaberite jedan ili više provrta kako biste u svaki smjestili dio, ili U redu postavlja dio u ishodište",
"pattern.alignment_2": "Poravnanje 2",
"pattern.alignment_hint": "Broj uključuje izvornik. Središte ravnomjerno raspoređuje kopije; Obrni bira dodatnu stranu za parne ukupne brojeve.",
"pattern.direction_1": "Smjer 1",
@@ -1840,6 +2121,8 @@
"prefs.bottom_centre": "Dolje u sredini",
"prefs.bottom_right": "Dolje desno",
"prefs.browser": "Stablo modela",
"prefs.browser_eye_left": "Oko pored strelice za sažimanje",
"prefs.browser_eye_left_note": "Redci stabla modela prikazuju oko za prikazivanje/skrivanje pored strelice za sažimanje, kao prije, umjesto u stupcu na desnom kraju redaka.",
"prefs.browser_reveal_on_hover": "Prikaži pri prelasku mišem",
"prefs.browser_show_note": "Prikazano drži stablo modela na zaslonu. Prikaži pri prelasku mišem skriva ga dok pokazivač ne dođe na njegovo mjesto ili na tanku karticu uz njegov lijevi rub, a ostaje otvoreno dok je pokazivač unutar njega ili dok je otvoren izbornik iz njega.",
"prefs.browser_shown": "Prikazano",
@@ -1859,6 +2142,8 @@
"prefs.ctrl_cmd": "Ctrl / Cmd",
"prefs.curved_surfaces": "Zakrivljene plohe",
"prefs.curves": "Zakrivljene plohe",
"prefs.dark_parts_shading": "Zadrži oblik tamnih dijelova",
"prefs.dark_parts_shading_help": "Sjenča prikaz u linearnom svjetlu uz meko dopunsko svjetlo neba, tako da tamna lica zadrže gradijent umjesto da pocrne",
"prefs.dead_zone_note": "Šrafirana traka je mrtva zona: kretanje unutar nje se zanemaruje.",
"prefs.default": "zadano",
"prefs.design": "Zadane postavke dizajna",
@@ -1873,6 +2158,8 @@
"prefs.edges": "Bridovi i sjenčanje",
"prefs.extend": "Produži",
"prefs.extra_navigation": "Ctrl + povlačenje lijevom tipkom po praznom prostoru orbitira; držite razmaknicu za pomicanje.",
"prefs.extrude_view_isometric": "Okreni prikaz u izometrijski kad se otvori Ekstrudiraj",
"prefs.extrude_view_return": "Vrati se na prethodni prikaz kad se Ekstrudiraj zatvori",
"prefs.follows_os": "Prati operacijski sustav",
"prefs.font": "Font",
"prefs.font_fallback": "Znakovi koji nedostaju odabranom fontu prikazuju se ugrađenim fontom, tako da nijedna oznaka ne prikazuje prazan kvadrat.",
@@ -1888,8 +2175,11 @@
"prefs.graphics_card_auto": "Automatski",
"prefs.graphics_card_missing": "Spremljena grafička kartica nije dostupna; koristi se Automatski.",
"prefs.graphics_card_restart": "Stupa na snagu nakon ponovnog pokretanja SolidCrafta. WGPU_ADAPTER_NAME ima prednost nad ovim odabirom.",
"prefs.graphics_card_unused": "Spremljena grafička kartica nije korištena za ovaj prozor; kartica koja se koristi prikazana je u Pomoć › O programu i u statusnoj traci. Pokušajte s opcijom Automatski ili postavite WGPU_ADAPTER_NAME na jedinstveni dio naziva kartice.",
"prefs.graphics_note": "Žrtvujte brzinu za glatkoću. Svaki odabir pokazuje svoj učinak na primjeru.",
"prefs.graphics_presets_note": "Unaprijed zadane vrijednosti postavljaju sve odabire ispod odjednom.",
"prefs.hide_constraint_glyphs_zoomed_out": "Sakrij simbole ograničenja kada je prikaz umanjen",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Simboli ograničenja nestaju iz prikaza kada je skica toliko umanjena da bi je zatrpali. Isključeno: ostaju fiksne veličine na zaslonu pri svakom zumiranju.",
"prefs.history": "Vremenska traka",
"prefs.history_origin_bodies": "Prikaži ishodište i tijela na vrhu",
"prefs.history_origin_bodies_note": "Vremenska traka lijevo ili ispod stabla modela počinje mapom Tijela aktivne komponente i Ishodištem, s njihovim okom i izbornicima kao u stablu modela. Vremenska traka na dnu ostaje nepromijenjena.",
@@ -1900,6 +2190,8 @@
"prefs.history_sketches_note": "Kad je Skice postavljeno na Ispod svoje značajke, vremenska traka lijevo prikazuje svaku skicu jednu razinu uvučeno, ispod prve značajke izrađene iz nje, kad god je dovoljno široka za nazive. Vremenska traka na dnu uvijek zadržava redoslijed nastanka.",
"prefs.history_sketches_under_feature": "Ispod svoje značajke",
"prefs.hold_stop_snapping": "Držite kako biste isključili privlačenje",
"prefs.home_fit_size": "Veličina za Početni prikaz i Uklopi",
"prefs.home_fit_size_note": "Koliki dio prikaza model ispunjava kad ga uokviri Početni prikaz, Uklopi, otvaranje dizajna ili promjena prikaza u navigatoru, bez obzira na to je li postavka ispod uključena. 100 %: ispunjava prikaz između panela, kompasa i navigacijske trake.",
"prefs.icons_only": "Samo ikone",
"prefs.imported_file": "Uvezena datoteka",
"prefs.in_browser": "U stablu modela",
@@ -1908,13 +2200,20 @@
"prefs.interface_language": "Jezik sučelja",
"prefs.interface_note": "Kako SolidCraft izgleda i gdje su njegovi dijelovi.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_navigator_arrows": "Obrni strelice za rotaciju kocke prikaza",
"prefs.invert_navigator_arrows_note": "Strelice oko kocke orbitiraju kameru u smjeru u kojem pokazuju, kao u programima Fusion i Inventor: strelica s lijeve strane pokazuje udesno i zakreće kameru udesno. Isključeno: strelica okreće kocku prema strani na kojoj se nalazi, pa lijeva strelica zakreće kameru ulijevo. Postavka ne utječe na zakrivljene strelice za valjanje.",
"prefs.keep_work_on_tab_switch": "Zadrži dijaloge i alate otvorenima pri prebacivanju između dizajna",
"prefs.keep_work_on_tab_switch_note": "Svaka kartica dizajna zadržava svoj otvoreni dijalog ili alat, s onim što ste u njemu odabrali i upisali, te svoju karticu alatne trake. Kad se vratite na dizajn, ponovno su tu. Isključeno: prebacivanje između dizajna poništava ih i prikazuje karticu Model.",
"prefs.labels": "Oznake",
"prefs.language_system": "Sustav",
"prefs.layout": "Raspored",
"prefs.layout_note": "Kompaktno pričvršćuje panele uz rubove prozora. Prozirnost stabla modela dostupna je u opciji Plutajuće. Stranu Asistenta promijenite na stranici Asistent.",
"prefs.left": "Lijevo",
"prefs.left_click": "Lijevi klik",
"prefs.line_drag_draws_one": "Pritisak i povlačenje crta jednu liniju",
"prefs.line_drag_draws_one_note": "U alatu Linija pritisak, povlačenje i otpuštanje crta jednu liniju od mjesta pritiska do mjesta otpuštanja i ondje završava. Klikovi i dalje crtaju lanac. Isključeno: povlačenje postavlja točku ondje gdje se otpusti, kao i klik.",
"prefs.line_hover_tangent_arc": "Zadržite pokazivač na posljednjoj točki linije kako biste prešli na tangentni luk",
"prefs.line_hover_tangent_arc_note": "U alatu Linija, nakon što je nacrtan segment, odmaknite pokazivač od posljednje točke lanca, vratite ga na nju i držite ga mirno: sljedeći klik završava tangentni luk, a isti pokret vraća na linije. Isključeno: zadržavanje na toj točki ne radi ništa.",
"prefs.linked_designs": "Povezani dizajni",
"prefs.live_input": "Ulaz uživo",
"prefs.live_preview": "Pregled uživo",
@@ -1928,6 +2227,9 @@
"prefs.max_file_size_step": "STEP i uvezene datoteke",
"prefs.max_file_size_web": "Web aplikacija otvara STEP i uvezene datoteke do 512 MB te dizajne do 256 MB: kartica preglednika ne može primiti veće datoteke. Desktop aplikacija može podići ove granice.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Velike ikone",
"prefs.menu_list": "Popis",
"prefs.menus": "Izbornici",
"prefs.minutes": "minuta",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Miš i dodirna pločica",
@@ -1935,6 +2237,8 @@
"prefs.mouse_mapping": "Mapiranje miša",
"prefs.navigation": "Navigacija",
"prefs.navigation_note": "Kako miš, dodirna pločica i 3D miš pomiču prikaz.",
"prefs.navigator_fit": "Uklopi model nakon promjene prikaza u navigatoru",
"prefs.navigator_fit_note": "Lice, kut ili strelica kocke prikaza, odnosno os orijentacijskog gizma, također usmjerava pogled na sredinu modela s jedne udaljenosti na kojoj se uklapa iz svakog smjera (njegova granična sfera). Početni prikaz koristi isti zum i središte dok je ova opcija uključena, pa se prikazi koje odaberete poslije njega samo okreću. Isključeno: prikaz se okreće na mjestu i zadržava svoj cilj i zum.",
"prefs.network_host_remove": "Ukloni",
"prefs.network_locations": "Dopusti mrežne lokacije u dizajnima",
"prefs.network_locations_note": "Isključeno: povezane datoteke dizajna na drugom računalu (\\\\server\\share) otvaraju se samo s poslužitelja navedenih ovdje ili nakon što ga dopustite kad se to zatraži.",
@@ -1947,12 +2251,16 @@
"prefs.origin_plane_names": "Nazivi ravnina ishodišta",
"prefs.origin_plane_names_note": "Nazivi prikazani u stablu modela, dijalozima i popisima. {top} je XY, {front} je XZ, a {right} je YZ, sa Z prema gore. Naredbe, skripte i spremljeni dizajni zadržavaju XY, XZ i YZ.",
"prefs.overall_speed": "Ukupna brzina",
"prefs.parameter_list_rows": "Retci popisa parametara",
"prefs.parameter_list_rows_tip": "Strelica prema dolje u praznom polju vrijednosti prikazuje popis parametara, prvo favorite, do ovoliko njih. Manje održava popis laganim u velikom dizajnu.",
"prefs.persistent_ribbon_pressed": "Istakni aktivne naredbe na vrpci",
"prefs.persistent_ribbon_pressed_help": "Ističe naredbe na vrpci dok su njihovi efekti uključeni. Isključite kako biste zadržali prethodni neutralni izgled i pomoć za naredbe.",
"prefs.perspective_fov": "Vidno polje",
"prefs.placement": "Postavljanje",
"prefs.prefer_free_sketch_points": "Daj prednost pomičnim točkama skice kad se točke preklapaju",
"prefs.preview_choices": "Pregled vaših odabira",
"prefs.print_face_highlight": "Isticanje plohe ispisa",
"prefs.print_face_highlight_tip": "Koliko se jako ploha ispisa tijela boji i obrubljuje u prikazu. 0 % ne prikazuje isticanje.",
"prefs.profile_active": "Aktivni profil",
"prefs.profile_applied": "Profil je primijenjen.",
"prefs.profile_changed": "Postavke su se promijenile otkako je profil spremljen. Spremi ih zadržava u njemu.",
@@ -1974,9 +2282,13 @@
"prefs.quality_fastest": "najbrže",
"prefs.quality_slowest": "najsporije",
"prefs.quality_smoother": "glađe",
"prefs.quick_measure": "Brzo mjerenje u kutu prikaza",
"prefs.quick_measure_note": "Veličina odabira u donjem desnom kutu prikaza: duljina brida, ili Ø i R za okrugli, površina lica, položaj točke, udaljenost i kut dviju stavki, zbroj više bridova ili lica. Klik ga kopira.",
"prefs.render_previous_geometry_budget": "Koristi prethodni proračun geometrije za pregled renderiranja",
"prefs.render_previous_geometry_budget_help": "Vrati prethodnu fiksnu granicu geometrije (512 MiB na 64-bitnom desktopu, 128 MiB drugdje) umjesto proračuna prema uređaju.",
"prefs.reports_saved": "Spremljeno: {n}",
"prefs.ribbon_click_ends_tool": "Klik na gumb aktivnog alata završava ga",
"prefs.ribbon_click_ends_tool_note": "Klik na istaknuti gumb na vrpci alata za skiciranje koji je u uporabi završava taj alat, pa klikovi u prikazu ponovno služe za odabir. Isključeno: klik ponovno pokreće alat.",
"prefs.ribbon_reveal": "Prikaži rezultate pretraživanja na vrpci",
"prefs.ribbon_reveal_note": "Zadržavanje pokazivača iznad rezultata u Pretraži naredbe pokazuje gdje se naredba nalazi na vrpci: njezinu karticu, gumb ili redak u padajućem izborniku panela. Naredba pokrenuta iz pretraživanja ondje zatreperi, a pokrenuta naredba iz padajućeg izbornika označava svoj panel.",
"prefs.right_click": "Desni klik",
@@ -1987,6 +2299,8 @@
"prefs.search_empty": "Nema odgovarajućih postavki",
"prefs.select": "Odabir",
"prefs.select_one_item": "Odaberi jednu stavku",
"prefs.select_other_hidden": "Odaberi skrivenu geometriju pomoću Odaberi drugo",
"prefs.select_other_hidden_help": "Uključeno: obuhvaća skrivena tijela, skice i zaklonjene bridove u blizini pokazivača. Isključeno: koristi se izvorni popis vidljive geometrije ispod pokazivača.",
"prefs.selecting": "Odabiranje",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift samo dodaje; Ctrl / Cmd uključuje i isključuje",
@@ -2004,6 +2318,8 @@
"prefs.sketch_exit_view.previous": "Vrati se na prethodni prikaz",
"prefs.sketch_exit_view.unless_rotated": "Vrati se osim ako je rotirano",
"prefs.sketch_exit_view_note": "Kamo prikaz ide nakon Završi skicu, Odustani ili dvostrukog Esc.",
"prefs.sketch_face_activates_part": "Skiciranje na licu dijela čini taj dio aktivnim",
"prefs.sketch_face_activates_part_note": "Skica započeta na licu tijela druge komponente najprije aktivira tu komponentu, pa skica pripada njoj i u stablu modela ostaje uz taj dio. Taj dio nakon toga ostaje aktivan. Isključeno: skica pripada aktivnoj komponenti.",
"prefs.sketch_open_look_at": "Okreni kameru prema skici pri otvaranju",
"prefs.sketch_open_look_at_note": "Isključeno: kad izradite ili uredite skicu, prikaz ostaje onakav kakav jest. Pogledaj okreće prikaz prema skici kad to želite.",
"prefs.sketch_recent_snap": "Automatski privlači na nedavna središta i središnje točke",
@@ -2017,6 +2333,8 @@
"prefs.sketch_tab_always_note": "Kartica Skica ostaje na vrpci, odmah iza kartice Model, dok nijedna skica nije otvorena. Njezini alati za crtanje tada pokreću skicu na odabranoj ravnini ili licu, ili na ravnini koju sljedeću odaberete, s alatom spremnim u njoj.",
"prefs.sketch_tracking_guides": "Vodilice poravnanja središta i središnje točke",
"prefs.sketch_tracking_guides_note": "Zadržite pokazivač iznad središta kružnice ili središnje točke ravnog brida, zatim se pomaknite vodoravno ili okomito da se prikaže privremena točkasta vodilica.",
"prefs.sketch_tracking_points": "Vodilice poravnanja iz krajnjih točaka i kutova tijekom crtanja",
"prefs.sketch_tracking_points_note": "Dok alat za crtanje čeka točku, zadržavanje pokazivača iznad kraja linije ili luka, kuta pravokutnika ili slobodne točke prikazuje istu privremenu točkastu vodilicu kao i za središte ili središnju točku. Isključeno: samo središta i središnje točke.",
"prefs.sketching": "Skiciranje",
"prefs.snap_drawing_note": "Tijekom crtanja ili povlačenja točke skice.",
"prefs.source": "Izvor",
@@ -2033,6 +2351,24 @@
"prefs.status_error": "Pogreška",
"prefs.status_selected": "Odabrano",
"prefs.status_warning": "Upozorenje",
"prefs.structure.appearance": "Izgled",
"prefs.structure.appearance_note": "Boje, tekst i pristupačnost.",
"prefs.structure.in_profile": "U profilu",
"prefs.structure.layout": "Raspored",
"prefs.structure.layout_note": "Gdje se nalaze dijelovi prozora.",
"prefs.structure.look": "Vizualno",
"prefs.structure.measure_pointer": "Mjerenje i pokazivač",
"prefs.structure.mouse3d": "3D miš",
"prefs.structure.mouse3d_note": "SpaceMouse i SpaceNavigator.",
"prefs.structure.navigation_note": "Miš i dodirna pločica. 3D miš ima vlastitu stranicu.",
"prefs.structure.profile": "Profil",
"prefs.structure.profile_edited": "Promijenjeno otkako je spremljeno",
"prefs.structure.profile_hover": "Spremljeno u profilima postavki",
"prefs.structure.scale_percent": "{percent}%",
"prefs.structure.sketch": "Skica",
"prefs.structure.tools": "Alati",
"prefs.structure.tools_note": "Kako se ponašaju alati za modeliranje.",
"prefs.structure.work": "Rad",
"prefs.supersampling_brief": "Iscrtava prikaz većim i smanjuje ga filtriranjem: vrlo oštro i zahtjevnije.",
"prefs.tab_accent_note": "Naglasna boja prati aktivnu karticu",
"prefs.theme_file": "datoteka teme",
@@ -2044,6 +2380,16 @@
"prefs.theme_follow_off": "Isključeno",
"prefs.theme_follow_stop": "Prestani pratiti",
"prefs.theme_import_hint": "Kliknite za odabir datoteke teme, ispustite je ovdje ili pritisnite Ctrl+V (Cmd+V na Macu) s kopiranom temom",
"prefs.theme_option_opacity": "Neprozirnost",
"prefs.theme_option_opacity_help": "Podešava neprozirnost plutajućih panela. Donja granica čitljivosti štiti tekst preko boja prozora prikaza.",
"prefs.theme_option_tab_colours": "Boje kartica",
"prefs.theme_option_tab_colours_help": "Koristi boje panela i prozora prikaza ove teme za svaku karticu vrpce.",
"prefs.theme_option_tab_fade_ms": "Prijelaz kartica",
"prefs.theme_option_tab_fade_ms_help": "Postupno prelijeva boje panela i prozora prikaza pri promjeni kartice. Nula mijenja trenutno.",
"prefs.theme_options_badge": "Opcije",
"prefs.theme_options_more": "Više · {count}",
"prefs.theme_options_reset": "Vrati zadano",
"prefs.theme_options_unavailable": "Nedostupno na ovom uređaju",
"prefs.theme_paste": "Zalijepi temu",
"prefs.theme_paste_none": "Međuspremnik ne sadrži SolidCraft temu",
"prefs.timeline_half_hour": "−30 min",
@@ -2065,13 +2411,23 @@
"prefs.under_cursor": "Ispod pokazivača",
"prefs.units_precision": "Jedinice i preciznost",
"prefs.updates": "Ažuriranja",
"prefs.value_boxes_in_view": "Prikaži vrijednosti pokraj njihovih ručica u prikazu",
"prefs.value_boxes_in_view_tip": "Vrijednost naredbe (Udaljenost, Polumjer…) u okviru pokraj njezine ručice, preko modela. Isključeno: upisujete je u prozor naredbe, koji preuzima tipkovnicu nakon odabira, i ništa ne prekriva model.",
"prefs.view": "Ponašanje prikaza",
"prefs.view_confirm_corner": "U redu i Odustani u kutu prikaza",
"prefs.view_confirm_corner_note": "Dok je otvoren dijalog ili skica, kvačica i križić gore desno u prikazu rade isto što i gumbi U redu i Odustani u dijalogu, odnosno završavaju i poništavaju skicu. Navigator prikaza pomiče se ispod njih.",
"prefs.viewport": "Prozor prikaza",
"prefs.viewport_analysis_status": "Prikaži status analize u prozoru prikaza",
"prefs.viewport_analysis_status_help": "Prikazuje aktivne analize iznad prozora prikaza, otvara njihove legende iz svake stavke, a tipka Esc isključuje najnoviju analizu. Isključite kako biste vratili legende u kutovima i prethodno ponašanje tipke Esc.",
"prefs.viewport_background": "Pozadina",
"prefs.viewport_background_colour": "Boja",
"prefs.viewport_background_theme": "Gradijent teme",
"prefs.viewport_section_status": "Prikaži status presjeka u prozoru prikaza",
"prefs.viewport_section_status_help": "Prikazuje postavku presjeka u prozoru prikaza. Escape i aktivni gumb na vrpci isključuju je. Isključite kako biste zadržali prethodne kontrole presjeka.",
"prefs.viewport_status_details": "Detalji prozora prikaza uživo",
"prefs.viewport_status_details_help": "Uređuje postavke aktivne analize i presjeka u standardnim karticama detalja. Isključite kako biste vratili legende analize samo za čitanje i dijaloški prozor Presjek.",
"prefs.viewport_status_summary": "Sažmi statusne stavke prozora prikaza",
"prefs.viewport_status_summary_help": "Sažima tri ili više statusnih stavki prozora prikaza u jednu zbirnu oznaku. Isključite kako bi svaka stavka ostala u punom retku.",
"prefs.wheel": "Kotačić",
"prefs.wheel_trackpad": "Kotačić i dodirna pločica",
"prefs.workspace": "Radni prostor",
@@ -2950,7 +3306,9 @@
"render.output_ready": "Spremno za renderiranje",
"render.output_run_check": "Pokreni provjeru",
"render.output_size_limit": "{method}: do {dimension} px po strani i {pixels} piksela. Najveća veličina koja stane: {width} × {height}.",
"render.output_size_method_profile": "Za renderiranje {width} × {height} odaberite {method} s CPU profilom resursa {profile} ili manju veličinu.",
"render.output_size_ok": "Veličina u redu · {width} × {height} px",
"render.output_size_profile": "Za renderiranje {width} × {height} odaberite CPU profil resursa {profile} ili manju veličinu.",
"render.output_swap_orientation": "Zamijeni širinu i visinu",
"render.output_tools_inspector": "Veličinu i format uredite u inspektoru Izlaz.",
"render.output_view_image": "Prikaži sliku",
@@ -3163,6 +3521,7 @@
"render.size_custom": "Prilagođeno…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · treba {profile}",
"render.size_presets": "Unaprijed zadane veličine",
"render.size_presets_pending": "Stiže s uređivačem Izlaz",
"render.size_preview": "Pregled",
@@ -3450,6 +3809,24 @@
"sheet.flange_type_contour": "Kontura",
"sheet.flange_type_edge": "Brid",
"sheet.no_body": "nema limenog tijela: izradite ga pomoću Prirubnica > Osnova ili pretvorite ploču u lim pomoću Pretvori u lim (odaberite njezino veliko ravno lice)",
"sheet.personal_rules.confirm_delete": "Potvrdi brisanje",
"sheet.personal_rules.delete": "Izbriši",
"sheet.personal_rules.delete_prompt": "Izbrisati ovo osobno pravilo? Pravila koja su već u dizajnima ostaju nepromijenjena.",
"sheet.personal_rules.full": "Osobna biblioteka već sadrži 64 pravila.",
"sheet.personal_rules.invalid_name": "Unesite kratak naziv bez prijeloma redaka ili kontrolnih znakova.",
"sheet.personal_rules.invalid_values": "Dovršite vrijednosti pravila: svaka se mora moći izračunati u ovom dizajnu, uz pozitivnu debljinu i K-faktor od 0 do 1.",
"sheet.personal_rules.missing": "Odabrano osobno pravilo više nije dostupno.",
"sheet.personal_rules.name": "Naziv pravila",
"sheet.personal_rules.name_exists": "Osobno pravilo s ovim nazivom već postoji. Upotrijebite drugi naziv.",
"sheet.personal_rules.name_hint": "Jedinstven naziv",
"sheet.personal_rules.overwrite": "Spremi promjene u odabrano osobno pravilo",
"sheet.personal_rules.rename": "Preimenuj",
"sheet.personal_rules.rule": "Osobno pravilo",
"sheet.personal_rules.save": "Spremi novo",
"sheet.personal_rules.scope": "Spremljeno u vašem profilu za sve dizajne.",
"sheet.personal_rules.select": "Odaberite pravilo",
"sheet.personal_rules.title": "Osobna pravila",
"sheet.personal_rules.unsupported": "Ovo pravilo ima vrijednosti koje ova verzija ne može upotrijebiti.",
"shortcut.json_error": "parametri moraju biti JSON: {error}",
"shortcut.no_command": "nema naredbe `{id}`; tekst prije JSON-a mora biti id naredbe",
"shortcut.pin": "Prikvači na vrh okvira S",
@@ -3486,8 +3863,10 @@
"sketch_text.align_right": "Desno",
"sketch_text.bold": "Podebljano",
"sketch_text.builtin_font": "{family} (ugrađeno)",
"sketch_text.formula": "Počnite znakom = kako biste riječi izgradili iz parametara: ='W' + number(Width)",
"sketch_text.italic": "Kurziv",
"sketch_text.loading_fonts": "Traženje instaliranih fontova…",
"sketch_text.no_bold": "Ugrađeni font nema podebljanu varijantu: odaberite instalirani font",
"sketch_text.on_path": "Tekst prati svoju putanju: krivulja ga postavlja",
"sketch_text.path_clear": "Skini tekst s njegove krivulje",
"sketch_text.path_hint": "Krivulja skice koju tekst prati: kliknite ovdje, a zatim krivulju",
@@ -3626,6 +4005,8 @@
"ui.3d_mouse_section": "3D miš",
"ui.3d_mouse_show_in_view": "Prikaži 3D miš u 3D prikazu",
"ui.3d_mouse_show_in_view_tip": "Slika vašeg 3D miša u donjem lijevom kutu, koja se kreće dok ga pomičete.",
"ui.3d_mouse_sketch_orbit": "Okreći skice 3D mišem",
"ui.3d_mouse_sketch_orbit_tip": "3D miš okreće prikaz i dok je skica otvorena i gleda se okomito, kao što to može i miš. Isključeno: pomiče i zumira skicu dok ne nagnete prikaz.",
"ui.3d_mouse_skip": "Moj uređaj ovo ne može",
"ui.3d_mouse_skip_tip": "Preskače ovo kretanje u oba smjera, a kalibracija ga izostavlja.",
"ui.3d_mouse_speed": "Brzina 3D miša",
@@ -3654,6 +4035,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Zakrenite kapicu suprotno od kazaljke na satu, držite je, a zatim pustite.",
"ui.3d_mouse_switch_cap": "Kalibriraj kao Povratak u središte",
"ui.3d_mouse_switch_trackball": "Kalibriraj kao kuglični miš",
"ui.3d_mouse_use_direct": "Koristi izravni upravljački program",
"ui.3d_mouse_use_direct_tip": "Čitaj 3D miš izravno (hidraw) umjesto preko spacenavd.",
"ui.3d_mouse_view_axis": "Os prikaza",
"ui.3d_mouse_view_off": "Isključeno",
"ui.3d_mouse_view_off_tip": "Profil upravljačkog programa 3D miša isključuje ovu os, pa njezino kretanje ne pomiče prikaz. Odabir kretanja druge osi ovdje ih zamjenjuje, a to kretanje prestaje pomicati prikaz.",
@@ -3692,6 +4075,7 @@
"ui.agent_chat_auth_terminal": "Pokrenite ovo u terminalu, a zatim pritisnite Poveži ponovno:",
"ui.agent_chat_auth_title": "Prijavi se kod agenta",
"ui.agent_chat_cancel": "Odustani",
"ui.agent_chat_cannot_start": "Instalirano, ali se ne pokreće: {error}",
"ui.agent_chat_changes": "Promjene: {n}, jedan korak poništavanja",
"ui.agent_chat_changes_n": "Promjene: {n}",
"ui.agent_chat_changes_of": "Promjene: {n}/{limit}",
@@ -3784,9 +4168,12 @@
"ui.agent_chat_queued_stopped": "U redu čekanja (zaustavljeno, nije poslano)",
"ui.agent_chat_refresh": "Osvježi",
"ui.agent_chat_refused": "Nije dopušteno agentu: {reason}",
"ui.agent_chat_reinstall": "{agent} je instaliran ({program}), ali se nije pokrenuo kad ga je SolidCraft provjerio: {error}\nInstalacija je možda nepotpuna ili izrađena za drugu vrstu računala. Ponovno ga instalirajte naredbom `{install}`, provjerite ispisuje li u terminalu svoju verziju, a zatim pritisnite Osvježi.",
"ui.agent_chat_remove": "Ukloni iz reda",
"ui.agent_chat_remove_file": "Skini s ove poruke",
"ui.agent_chat_remove_imported": "Skini s ove poruke; dio ostaje u dizajnu (Poništi ga uklanja)",
"ui.agent_chat_reply_cut_off": "Odgovor je dosegnuo ograničenje duljine prije nego što je završio pa je možda nepotpun. Zamolite agenta da nastavi ili da odgovori kraće.",
"ui.agent_chat_reply_declined": "Model je odbio odgovoriti na ovu poruku.",
"ui.agent_chat_retry": "Pokušaj ponovno",
"ui.agent_chat_right": "Desno",
"ui.agent_chat_save": "Spremi",
@@ -3845,12 +4232,17 @@
"ui.ai_copy_suffix": "kopiraj",
"ui.ai_custom_url": "Prilagođeni URL",
"ui.ai_default": "Zadano",
"ui.ai_edit": "Uredi",
"ui.ai_edit_agent_title": "Uredi agenta",
"ui.ai_edit_gone": "Ovaj agent je uklonjen pa nema što spremiti.",
"ui.ai_edit_tip": "Promijenite naziv, naredbu ili krajnju točku ovog agenta. Spremljeni ključ ostaje skriven.",
"ui.ai_http_busy": "krajnja točka je zauzeta ili je preko ograničenja; pokušajte ponovno kasnije",
"ui.ai_http_key": "krajnja točka odbila je ključ: provjerite ga u Postavke > AI",
"ui.ai_http_not_found": "nema takve krajnje točke ili modela: provjerite osnovni URL i naziv modela",
"ui.ai_http_too_large": "razgovor je prevelik za ovu krajnju točku; započnite novi razgovor",
"ui.ai_key": "API ključ",
"ui.ai_key_hint": "neobavezno; čuva se u sustavskom privjesku ključeva",
"ui.ai_key_keep_hint": "ostavite prazno kako biste zadržali spremljeni ključ",
"ui.ai_key_needs_https": "Ključ se šalje samo preko HTTPS-a ili na ovo računalo.",
"ui.ai_key_note": "Čuva se u sustavskom privjesku ključeva, nikad u postavkama SolidCrafta. Za lokalni poslužitelj ostavite prazno.",
"ui.ai_keychain_error": "Sustavski privjesak ključeva odbio je: {error}",
@@ -3877,11 +4269,15 @@
"ui.ai_permissions": "Dopuštenja",
"ui.ai_preset": "Unaprijed zadano",
"ui.ai_protocol": "Protokol",
"ui.ai_reasoning_effort": "Razina zaključivanja",
"ui.ai_reasoning_effort_field_hint": "prazno = zadana vrijednost pružatelja",
"ui.ai_reasoning_effort_hint": "Neobavezno. Šalje se krajnjoj točki kao `reasoning_effort`; prihvaćene vrijednosti razlikuju se od pružatelja do pružatelja (na primjer `low`, `high` ili `max`). SolidCraft nikad ne mijenja vrijednost koju ovdje postavite. Ostavite prazno da se ništa ne šalje i da se zadrži zadana vrijednost pružatelja.",
"ui.ai_remove": "Ukloni",
"ui.ai_request_timed_out": "Model je trajao dulje od {seconds} s. Vremensko ograničenje zahtjeva za ovu krajnju točku možete povećati u Postavke › Asistent.",
"ui.ai_request_timeout": "Vremensko ograničenje zahtjeva",
"ui.ai_request_timeout_hint": "Koliko dugo jedan zahtjev modelu na ovoj krajnjoj točki smije trajati, od 30 s do jednog sata. Povećajte za spori lokalni model.",
"ui.ai_reset": "Vrati zadano",
"ui.ai_save": "Spremi",
"ui.ai_settings": "AI postavke…",
"ui.ai_show_features": "Prikaži AI funkcije",
"ui.ai_show_features_note": "Razgovor s agentom: ploča u kojoj agent za kodiranje koji već koristite ili krajnja točka modela koju odaberete radi na otvorenom dizajnu. Isključeno: nema AI gumba, prečaca, stavke izbornika ni ploče, i ništa se ne traži niti pokreće.",
@@ -3970,6 +4366,7 @@
"ui.circle": "Kružnica",
"ui.circular_pattern": "Kružni niz",
"ui.clear": "Očisti",
"ui.clear_print_face": "Ukloni plohu ispisa",
"ui.clear_recent": "Očisti nedavno",
"ui.clear_the_selection": "Poništi odabir",
"ui.click_selects_whole_bodies": "Klik odabire cijela tijela",
@@ -4040,6 +4437,7 @@
"ui.dark": "Tamna",
"ui.default_modelling_orientation": "Zadana orijentacija modeliranja",
"ui.delete": "Izbriši",
"ui.delete_select_first": "Najprije odaberite što želite izbrisati: značajke, tijela, komponente ili geometriju skice",
"ui.depth": "Dubina",
"ui.design": "Dizajn",
"ui.design_properties.author": "Autor",
@@ -4054,6 +4452,40 @@
"ui.design_properties.project": "Projekt",
"ui.design_properties.revision": "Revizija",
"ui.design_saved": "Spremljeno: {path}",
"ui.details_access_caption": "Lica pokazuju može li alat doći do njih iz odabranog smjera.",
"ui.details_angle": "Kut",
"ui.details_continuity_caption": "Uredite odabrani šav. Ostali šavovi zadržavaju vlastite postavke.",
"ui.details_continuity_stale": "Ranija geometrija: ponovno pokrenite analizu kako biste uredili ili uokvirili ovaj šav.",
"ui.details_copy_list": "Kopiraj popis",
"ui.details_curvature": "Tolerancija zakrivljenosti",
"ui.details_curvature_caption": "Lica su obojena prema svojem lokalnom polumjeru zakrivljenosti.",
"ui.details_direction": "Smjer alata",
"ui.details_draft_caption": "Lica su obojena prema kutu u odnosu na smjer vađenja iz kalupa.",
"ui.details_edit_seam": "Uredi šav",
"ui.details_fixture": "Uzorak komponente — samo primjer podataka",
"ui.details_fixture_caption": "Uzorak komponente s ilustrativnim vrijednostima; ovo nije rezultat modela.",
"ui.details_fixture_problem": "Primjer nalaza s problemom",
"ui.details_fixture_readout": "Primjer izmjerene vrijednosti",
"ui.details_flip": "Obrni",
"ui.details_gap": "Otvoreni kraj",
"ui.details_hatch": "Šrafiraj presječena lica",
"ui.details_jump": "Skok zakrivljenosti",
"ui.details_key": "Legenda",
"ui.details_offset": "Odmak",
"ui.details_plane": "Ravnina",
"ui.details_pull": "Smjer vađenja iz kalupa",
"ui.details_radius": "Polumjer",
"ui.details_readout": "Očitanje",
"ui.details_samples": "Uzorci",
"ui.details_seam": "{body} · Brid {edge}",
"ui.details_settings": "Postavke",
"ui.details_sharp_gap": "Oštro ili otvoreno",
"ui.details_show_all": "Prikaži sve ({count})",
"ui.details_smooth": "Glatko",
"ui.details_stripes": "Pruge",
"ui.details_target": "Uokviri geometriju",
"ui.details_worst_seams": "Najgori šavovi",
"ui.details_zebra_caption": "Odražene pruge pokazuju koliko se glatko lica spajaju.",
"ui.diameter": "Promjer",
"ui.dimension": "Kota",
"ui.direct_interaction": "Izravna interakcija",
@@ -4098,6 +4530,7 @@
"ui.edit_in_place_saved_elsewhere": "Dio je spremljen u drugu datoteku; sklop i dalje povezuje {name}",
"ui.edit_in_place_wait": "Pričekajte da završi ono što se izvodi u dizajnu",
"ui.edit_section_analysis": "Uredi analizu presjeka",
"ui.edit_sketch_empty": "Nema skica za uređivanje",
"ui.ellipse": "Elipsa",
"ui.emboss_wrap_note": "Ortogonalni linijski profili tangentni na jedan podržani valjak automatski se omataju. Odaberite stijenku kako biste odabrali cilj.",
"ui.end_radius": "Krajnji polumjer",
@@ -4108,12 +4541,15 @@
"ui.equation_explicit": "Eksplicitni",
"ui.equation_form": "Oblik",
"ui.equation_note": "Varijabla je običan broj: sin(t) je uzima u radijanima. Rezultat bez jedinice izražen je u jedinici dizajna.",
"ui.equation_note_range": "Varijabla je duljina, kao i njezin raspon: kombinirajte je s parametrima duljine, a za sin() je podijelite duljinom. Rezultat bez jedinice izražen je u jedinici dizajna.",
"ui.equation_parametric": "Parametarski",
"ui.equation_variable_unit": "Varijabla ima jedinicu raspona",
"ui.equation_x_of_t": "x(t)",
"ui.equation_y_of_t": "y(t)",
"ui.equation_y_of_x": "y(x)",
"ui.equivalent_radius": "Ekvivalentni polumjer",
"ui.expand": "Proširi",
"ui.explode_needs_two_components": "Rastavljeni prikaz razmiče komponente, pa dizajn treba imati dvije ili više njih (Nova komponenta, Umetni dizajn ili Izradi komponente iz tijela)",
"ui.exportDxf": "Izvezi DXF…",
"ui.export_ellipsis_keymap": "Izvezi…",
"ui.export_keymap_tip": "Tipke, favoriti okvira S i alatna traka, kao datoteka",
@@ -4274,6 +4710,7 @@
"ui.insert_design.saved_file": "{name} ima nespremljene promjene, pa je umetnuta njegova spremljena datoteka. Spremite ga, a zatim pokrenite Ažuriraj veze kako biste unijeli promjene.",
"ui.insert_design.unsaved": "Nespremljene promjene",
"ui.inspect": "Pregledaj",
"ui.instances": "Instance",
"ui.intersect": "Presjek",
"ui.isolate": "Izoliraj",
"ui.join": "Spoji",
@@ -4282,12 +4719,14 @@
"ui.joints": "Spojevi",
"ui.keyboard_shortcuts": "Tipkovnički prečaci",
"ui.language": "Jezik",
"ui.lasso_select": "Laso (povucite petlju slobodnom rukom: obuhvaća sve što je u cijelosti unutra)",
"ui.layout_compact": "Kompaktno",
"ui.layout_floating": "Plutajuće",
"ui.length": "Duljina",
"ui.length_decimals": "Decimale duljine",
"ui.light": "Svijetla",
"ui.line": "Linija",
"ui.line_arc_hint": "Tangentni luk: kliknite kraj luka. Ponovno se zadržite na posljednjoj točki za linije; desni klik ili Esc za završetak",
"ui.link_copies": "Poveži identične kopije",
"ui.link_copies_keep": "Zadrži",
"ui.link_copies_link": "Poveži",
@@ -4376,6 +4815,7 @@
"ui.part_size_shared": "Ponovno gradi dio, zamjenjujući sve dodane značajke. Svi primjerci ove komponente ažuriraju se.",
"ui.paste": "Zalijepi",
"ui.path": "Putanja",
"ui.pattern_object_type_tip": "Tijela: puni oblici, odabiru se u prikazu ili u stablu modela; svako se kopira u cijelosti.\nZnačajke: koraci vremenske trake, poput ekstruzije ili provrta, odabiru se na vremenskoj traci ili na licu koje su izradili; korak se ponavlja.\nSkica nije ni jedno ni drugo: ponovite njezine krivulje pomoću alata Niz ili Zrcali u samoj skici ili je najprije ekstrudirajte.",
"ui.pattern_on_path": "Niz duž putanje",
"ui.perforate_margin_default": "iz promjera",
"ui.perforate_pitch": "Korak",
@@ -4402,12 +4842,17 @@
"ui.plugins_skipped": "Mape nije bilo moguće pročitati: {count}.",
"ui.plugins_version": "{version}",
"ui.point": "Točka",
"ui.point_cloud_add_filter": "Dodaj filter…",
"ui.point_cloud_auto_range": "Automatski raspon",
"ui.point_cloud_color_by": "Boja prema",
"ui.point_cloud_file_colors": "Boje iz datoteke",
"ui.point_cloud_filter_by": "Filtriraj prema",
"ui.point_cloud_height_high": "Najviše",
"ui.point_cloud_height_low": "Najniže",
"ui.point_cloud_legend_filter": "Prikazano i skriveno u legendi iznad",
"ui.point_cloud_no_file_colors": "Datoteka nema boja",
"ui.point_cloud_pause": "Pauziraj",
"ui.point_cloud_play": "Reproduciraj",
"ui.point_cloud_single_color": "Jedna boja",
"ui.point_cloud_size": "Veličina točke",
"ui.point_cloud_units": "Jedinice datoteke",
@@ -4416,6 +4861,11 @@
"ui.polygon": "Mnogokut",
"ui.preferences": "Postavke",
"ui.press_a_key": "pritisnite tipku…",
"ui.print_face_body_not_sent": "Tijelo te plohe ne šalje se: najprije ga dodajte u Tijela",
"ui.print_face_cleared": "{body}: ploha ispisa uklonjena",
"ui.print_face_input": "Ploha ispisa",
"ui.print_face_pick": "Odaberite ravnu plohu punog tijela da postane ploha ispisa",
"ui.print_face_set": "{body}: ploha ispisa postavljena; Pošalji u slicer polaže tijelo na nju",
"ui.profile": "Profil",
"ui.profile_plane": "Ravnina profila",
"ui.profiles": "Profili",
@@ -4431,6 +4881,7 @@
"ui.properties.inertia_y": "Iyx, Iyy, Iyz",
"ui.properties.inertia_z": "Izx, Izy, Izz",
"ui.properties.measuring": "Mjerenje…",
"ui.properties.measuring_bodies": "Mjerenje… {done} od {total} tijela",
"ui.properties.measuring_failed": "Mjerenje je zaustavljeno prije završetka.",
"ui.properties.principal_axis": "Glavna os {number}",
"ui.properties.principal_moments": "Glavni momenti tromosti",
@@ -4479,6 +4930,7 @@
"ui.revolve": "Rotiraj",
"ui.ribbon_exit": "Izlaz",
"ui.ribbon_labels": "Prikaži tekst ispod ikona na traci",
"ui.ribbon_menus_skip_pinned": "Izostavi prikvačene naredbe iz padajućih izbornika",
"ui.ribbon_pin_footer": "Prikvačene naredbe prikazuju se na traci. Zadržite pokazivač iznad retka kako biste ga prikvačili ili otkvačili.",
"ui.ribbon_workspace": "Radni prostor",
"ui.right": "Desno",
@@ -4497,6 +4949,7 @@
"ui.selectOther": "Odaberi drugo…",
"ui.selectTangentChain": "Odaberi tangentni lanac",
"ui.select_all": "Odaberi sve",
"ui.select_components_by_face": "Odaberi komponente (lice bira svoju komponentu)",
"ui.selection": "Odabir",
"ui.selection_modifiers": "Modifikatori odabira",
"ui.selection_shift_add_only": "Shift+klik dodaje bez uklanjanja",
@@ -4538,6 +4991,11 @@
"ui.sketch_intersect_selection_filter": "Filtar odabira",
"ui.sketch_modifiers": "Modifikatori skice",
"ui.sketch_project_geometry": "Geometrija",
"ui.sketch_report.all_constrained": "Sve je potpuno ograničeno.",
"ui.sketch_report.dof": "Stupnjevi slobode",
"ui.sketch_report.loose": "Nije potpuno ograničeno",
"ui.sketch_report.status": "Status",
"ui.sketch_report.title": "Izvješće o skici",
"ui.sketch_scale": "Skaliranje skice",
"ui.sketches": "Skice",
"ui.slice": "Presijeci",
@@ -4633,19 +5091,31 @@
"ui.view_angles": "Kutovi prikaza",
"ui.view_angles_convention": "Stupnjevi oko osi prema gore. Skretanje 0° je Pogled sprijeda, 90° Pogled slijeva, −90° Pogled zdesna, 180° Pogled straga; nagib 90° je Pogled odozgo; pozitivno valjanje okreće model u smjeru kazaljke na satu.",
"ui.view_angles_help": "Prikazuje nagib, skretanje i valjanje prikaza u stupnjevima te njegovu projekciju ispod orijentacijskog gizma. Kliknite očitanje kako biste upisali točne kutove.",
"ui.view_angles_lock": "Zaključaj {axis}",
"ui.view_angles_ortho": "Ortogr.",
"ui.view_angles_persp": "Persp.",
"ui.view_angles_pitch": "Nagib",
"ui.view_angles_readout": "Nagib {pitch} · Skretanje {yaw} · Valjanje {roll}",
"ui.view_angles_roll": "Valjanje",
"ui.view_angles_unlock": "Otključaj {axis}",
"ui.view_angles_yaw": "Skretanje",
"ui.view_angles_zero": "Postavi {axis} na 0°",
"ui.view_centre": "Središte prikaza",
"ui.viewport_status_clear": "Isključi {name}",
"ui.viewport_status_clear_hint": "Isključi {name} · Esc",
"ui.viewport_status_edges": "Bridovi: {count}",
"ui.viewport_status_from": "iz smjera {direction}",
"ui.viewport_status_key": "Prikaži legendu za {name}",
"ui.viewport_status_minimum_radius": "Minimalni polumjer",
"ui.viewport_status_pairs": "Parovi: {count}",
"ui.viewport_status_section_hatch": "{setting} · šrafura",
"ui.viewport_status_section_plane": "Trenutna ravnina",
"ui.viewport_status_section_tilted": "nagnuto · {offset}",
"ui.viewport_status_stripes": "Pruge: {count}",
"ui.viewport_status_summary_clear": "Isključi sve",
"ui.viewport_status_summary_heading": "U ovom prikazu",
"ui.viewport_status_summary_label": "{name} i još stavki: {count}",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Prati temu",
"ui.visual_shaded_hidden_edges": "Osjenčano sa skrivenim bridovima",
"ui.visual_shaded_with_edges": "Osjenčano s bridovima",
@@ -4682,7 +5152,9 @@
"update.button": "Ažuriraj",
"update.button_version": "Ažuriraj na {version}",
"update.check_automatically": "Automatski provjeravaj ažuriranja",
"update.check_for_updates": "Provjeri ažuriranja",
"update.check_now": "Provjeri sada",
"update.check_unavailable": "Provjera ažuriranja nije dostupna za ovu instalaciju.",
"update.checking": "Provjera…",
"update.copy": "Kopiraj",
"update.dmg_opened": "Slika diska je otvorena i provjerena: povucite SolidCraft u Programe, zamijenite staru kopiju, a zatim ponovno pokrenite SolidCraft.",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+567 -10
View File
File diff suppressed because it is too large Load Diff
+231 -5
View File
@@ -407,6 +407,7 @@
"Recover unsaved design?": "저장되지 않은 디자인을 복구할까요?",
"Rectangular": "직사각형",
"Reference face": "참조 면",
"Reference plane": "참조 평면",
"Relief Depth": "릴리프 깊이",
"Relief Width": "릴리프 너비",
"Remove Tools": "공구 제거",
@@ -422,6 +423,7 @@
"Rename Feature": "피처 이름 바꾸기",
"Rename Folder": "폴더 이름 바꾸기",
"Render Stage": "렌더링 단계",
"Reopen the designs that were open when SolidCraft was last closed": "지난번에 열려 있던 디자인 다시 열기",
"Replace existing library file": "기존 라이브러리 파일 바꾸기",
"Report a bug — review a screenshot and open a GitHub draft": "버그 신고 — 스크린샷을 검토하고 GitHub 초안 열기",
"Report text copied. Paste it into GitHub and submit your report there.": "보고서 텍스트를 복사했습니다. GitHub에 붙여넣고 그곳에서 보고서를 제출하세요.",
@@ -476,6 +478,7 @@
"Second point": "두 번째 점",
"Section": "단면",
"Section position": "단면 위치",
"Sections": "단면",
"Sections (one sketch each)": "단면 (각각 스케치 하나)",
"Select Other": "다른 항목 선택",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "다른 항목 선택: 모델 위에서 왼쪽 버튼을 가만히 누르고 있거나(또는 오른쪽 클릭 › 다른 항목 선택…) 커서 아래의 모든 항목을 나열합니다. 같은 위치를 다시 클릭하면 다음 항목이 선택됩니다. 가운데 버튼을 더블 클릭하면 홈으로 이동합니다(환경설정 › 탐색).",
@@ -711,6 +714,8 @@
"cmd.appearance_pick_up": "모양 가져오기",
"cmd.appearance_replace": "모양 바꾸기",
"cmd.assembly_bom": "자재 명세서",
"cmd.assembly_mass_properties": "질량 특성",
"cmd.body.print_face": "출력 바닥면",
"cmd.body_rename": "바디 이름 변경",
"cmd.browser_group": "새 그룹",
"cmd.browser_move": "브라우저 항목 이동",
@@ -756,6 +761,7 @@
"cmd.config_rule": "구성 규칙",
"cmd.config_rules": "구성 규칙",
"cmd.config_table": "구성 표시",
"cmd.construct.plane.axis_point": "축과 점을 지나는 평면",
"cmd.construct_axis_edge": "모서리를 통과하는 축",
"cmd.construct_axis_normal_to_face": "면에 수직인 축",
"cmd.construct_axis_through_cylinder": "원기둥/원뿔/토러스를 통과하는 축",
@@ -902,6 +908,9 @@
"cmd.pointcloud_edit": "포인트 클라우드 편집",
"cmd.pointcloud_insert": "포인트 클라우드 삽입",
"cmd.pointcloud_list": "포인트 클라우드",
"cmd.render.compare.divider": "렌더 비교 구분선 이동",
"cmd.render.result.discard": "렌더 결과 버리기",
"cmd.render.result.select": "렌더 결과 선택",
"cmd.render_backends": "렌더링 백엔드",
"cmd.render_batch": "모든 샷 렌더링",
"cmd.render_camera_fit": "렌더 대상 화면 맞춤",
@@ -1090,9 +1099,11 @@
"cmd.timeline_rollback": "타임라인 마커 이동",
"cmd.timeline_stop": "기록 재생 중지",
"cmd.timeline_suppress": "특성 비활성화",
"cmd.ui.assistant": "도우미 표시 전환",
"cmd.view.render_browser": "렌더 브라우저",
"cmd.view.render_dock": "렌더 도크 이동",
"cmd.view.render_focus": "렌더 집중 보기",
"cmd.view.status_details": "뷰포트 상태 상세 정보",
"cmd.view.status_items": "뷰포트 상태 항목",
"cmd.view_ambient_occlusion": "앰비언트 오클루전",
"cmd.view_angle_readout": "뷰 각도",
@@ -1106,14 +1117,17 @@
"cmd.view_render_frame": "렌더 프레임 가이드",
"cmd.view_save": "새 이름 있는 보기",
"cmd.view_shots_strip": "샷 스트립",
"color.blue": "파랑",
"color.copy": "클릭하여 색상 값 복사",
"color.edit": "클릭하여 색상 편집",
"color.gamma": "색상 값을 0-255 감마 공간으로 표시 중",
"color.green": "초록",
"color.hex": "Hex",
"color.hex_help": "#rrggbb 또는 #rrggbbaa 입력. 6자리 값은 불투명도 유지",
"color.hex_hint": "#rrggbb",
"color.hue": "색조",
"color.linear": "색상 값을 0-1 선형 공간으로 표시 중",
"color.red": "빨강",
"color.saturation_value": "채도 및 명도",
"color.selected": "선택한 색상",
"config.column_filter_empty": "일치하는 열이 없습니다",
@@ -1160,9 +1174,14 @@
"dialog.dimension_insert_hint": "다른 구동 치수를 두 번 클릭하여 이름 삽입",
"dialog.dimension_name": "치수: {name}",
"dialog.edit_title": "{title} 편집",
"dialog.pattern_sketch_not_object": "바디나 피처를 선택하세요. 스케치는 둘 다 아닙니다. 곡선은 스케치 안의 패턴이나 대칭 복사로 반복하거나, 먼저 돌출하세요.",
"dialog.select": "↖ 선택",
"dialog.selected_count": "↖ {count}개 선택됨",
"dialog.selection_constraint": "구속 조건 {id}",
"dialog.selection_external_curve": "{sketch} · 곡선 {id}",
"dialog.selection_guidance": "변경할 입력을 선택하세요. 선택한 객체를 클릭하면 선택이 해제되며, Ctrl/Cmd를 누른 채 클릭해도 전환됩니다. 미리 보기를 확인한 다음 확인을 누르세요.",
"dialog.selection_midpoint": "중점 · {id}",
"dialog.selection_profile": "{sketch} · 프로파일 {number}",
"dialog.selection_step": "{step}/{total} 단계: {input} 선택",
"dialog.step": "단계",
"document.save_changes": "{name}의 변경 사항을 저장하시겠습니까?",
@@ -1203,6 +1222,7 @@
"help.attributions": "저작자 표시 (ATTRIBUTION.md)",
"help.build": "버전 {version} · 커밋 {commit} · 빌드 날짜 {date}",
"help.component.finish_edit": "제자리에서 편집 중인 부품을 저장하고 어셈블리로 돌아가 그 부품으로 어셈블리를 업데이트합니다. 연결된 부품의 변경을 마쳤을 때 사용하세요.",
"help.construct.plane.axis_point": "축과 축 위에 있지 않은 점을 포함하는 구성 평면을 만듭니다. 원점 축, 구성 축, 직선 모서리 또는 스케치 직선을 선택한 다음 꼭짓점, 스케치 점 또는 구성점을 선택합니다. 축 위의 점으로는 평면을 하나로 결정할 수 없습니다.",
"help.font_details": "인터페이스는 egui의 내장 글꼴을 사용합니다(Ubuntu Font Licence 및 MIT/Bitstream Vera 라이선스의 Ubuntu Light와 Hack, OFL 및 MIT의 Noto Emoji와 이모지 아이콘 글꼴). 스케치 텍스트는 Liberation Sans(SIL OFL 1.1)를 사용합니다. The Crafting Apps의 선택적 글꼴 팩 craft-fonts(BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1)는 SolidCraft 빌드에 포함되지 않습니다.",
"help.fonts": "글꼴",
"help.licences": "라이선스",
@@ -1305,6 +1325,8 @@
"joint.from_bodies_hint": "선택한 각 바디가 제자리에 그대로 있으면서 자체의 새 구성 요소로 옮겨집니다.",
"joint.one_body": "두 선택이 모두 {body}에 있습니다. 두 번째는 다른 부품에서 선택하세요.",
"joint.one_component": "이 부품들은 한 구성 요소의 바디입니다: {bodies}. 관절은 한 구성 요소를 다른 구성 요소에 대해 움직이므로 부품마다 자체 구성 요소가 필요합니다.",
"joint_move.conflict": "이 부품의 조인트가 다른 조인트와 충돌합니다: {joint}.",
"joint_move.conflict_hint": "충돌하는 조인트를 편집, 비활성화 또는 삭제하여 다른 조인트가 부품을 배치할 수 있게 하세요.",
"keymap.command_id": "명령 ID",
"keymap.empty": "일치하는 명령이 없습니다",
"keymap.use_anyway": "그래도 여기서 {key} 사용",
@@ -1345,6 +1367,8 @@
"motion.track_name": "트랙 {number}",
"nav.alias.help": "Shift+Alt+왼쪽 드래그는 회전, Shift+Alt+가운데 드래그는 이동, Shift+Alt+오른쪽 드래그는 확대/축소입니다. 가운데 드래그로도 이동하고 오른쪽 드래그로도 회전합니다",
"nav.blender.help": "가운데 드래그는 회전, Shift+가운데 드래그는 이동, Ctrl+가운데 드래그는 확대/축소입니다. 오른쪽 드래그는 카메라에 영향을 주지 않습니다",
"nav.creo.help": "가운데 버튼을 끌면 회전, Shift+가운데 버튼은 이동, Ctrl+가운데 버튼을 위아래로 끌면 확대/축소합니다(아래로 끌면 확대). 휠을 굴려도 확대/축소합니다",
"nav.creo.label": "Creo",
"nav.free_orbit.help": "마우스와 3D 마우스로 롤을 유지하며 양 극점을 넘어 회전합니다. Shift 축 스냅은 비활성화됩니다.",
"nav.free_orbit.label": "자유 회전",
"nav.free_orbit.snap_help": "마우스와 3D 마우스로 양쪽 극점을 지나 회전할 수 있으며 롤 각도가 유지됩니다. 궤도 회전 드래그 중 Shift를 누르면 보기 방향과 롤 각도가 스냅됩니다.",
@@ -1525,10 +1549,14 @@
"prefs.bottom_centre": "하단 가운데",
"prefs.bottom_right": "오른쪽 아래",
"prefs.browser": "브라우저",
"prefs.browser_eye_left": "표시 아이콘을 접기 화살표 옆에 표시",
"prefs.browser_eye_left_note": "브라우저 행의 표시 아이콘을 행 오른쪽 끝의 열이 아니라 이전처럼 접기 화살표 옆에 표시합니다.",
"prefs.built_in": "기본 제공",
"prefs.button_left": "왼쪽 드래그",
"prefs.button_middle": "가운데 드래그",
"prefs.button_right": "오른쪽 드래그",
"prefs.carousel_next": "다음",
"prefs.carousel_previous": "이전",
"prefs.choose_font": "글꼴 파일 선택…",
"prefs.click": "클릭",
"prefs.contact_shading": "부드러운 접촉 음영",
@@ -1541,6 +1569,10 @@
"prefs.ctrl_cmd": "Ctrl / Cmd",
"prefs.curved_surfaces": "곡면",
"prefs.curves": "곡면",
"prefs.cut_preview_tint": "제거되는 재료를 빨간색으로 표시",
"prefs.cut_preview_tint_help": "미리 보기는 피처가 제거하는 재료와 대체되는 형상을 빨간색 반투명 고스트로 채웁니다. 끄면 이 채움이 완전히 투명해지고 윤곽선만 그려집니다.",
"prefs.dark_parts_shading": "어두운 부품의 형태 유지",
"prefs.dark_parts_shading_help": "선형 조명과 부드러운 하늘광으로 뷰를 음영 처리하여 어두운 면이 검게 뭉개지지 않고 그라데이션을 유지합니다",
"prefs.dead_zone_note": "빗금 친 영역이 데드존입니다. 그 안의 움직임은 무시됩니다.",
"prefs.default": "기본값",
"prefs.design": "설계 기본값",
@@ -1561,19 +1593,35 @@
"prefs.font_sample": "The quick brown fox · Ø12.50 mm · 45.0° · 0123456789",
"prefs.font_unreadable": "선택한 글꼴을 읽을 수 없습니다.",
"prefs.general": "일반",
"prefs.general_note": "언어, 저장 및 업데이트.",
"prefs.general.active": "사용 중",
"prefs.general.crash_reports": "충돌 보고서",
"prefs.general.edited": "수정됨",
"prefs.general.held": "포함 항목",
"prefs.general.import": "가져오기...",
"prefs.general.last_check": "마지막 확인: {time}",
"prefs.general.minutes": "분",
"prefs.general.name_setup": "이 설정의 이름",
"prefs.general.profile_name": "프로필 이름",
"prefs.general.save_new": "새로 저장",
"prefs.general.step_import": "STEP 및 가져오기",
"prefs.general.this_computer": "이 컴퓨터",
"prefs.general_note": "프로필, 언어, 파일 및 업데이트.",
"prefs.gesture_orbit": "회전",
"prefs.gesture_pan": "이동",
"prefs.gesture_zoom": "확대/축소",
"prefs.graphics": "그래픽",
"prefs.graphics_note": "속도와 부드러움 중에서 선택합니다. 각 선택의 효과가 샘플에 표시됩니다.",
"prefs.graphics_presets_note": "프리셋은 아래의 선택 항목을 한 번에 설정합니다.",
"prefs.hide_constraint_glyphs_zoomed_out": "축소 시 구속 조건 기호 숨기기",
"prefs.hide_constraint_glyphs_zoomed_out_note": "스케치를 충분히 축소하면 구속 조건 기호가 가려지지 않도록 숨깁니다. 끄기: 확대/축소와 관계없이 고정된 화면 크기를 유지합니다.",
"prefs.history": "이력",
"prefs.history_origin_bodies": "원점과 바디를 맨 위에 표시",
"prefs.history_origin_bodies_note": "왼쪽 또는 브라우저 아래의 히스토리가 활성 구성 요소의 바디 폴더와 원점으로 시작하며, 브라우저처럼 표시 아이콘과 메뉴가 있습니다. 아래쪽 히스토리는 바뀌지 않습니다.",
"prefs.history_play_button": "기록 컨트롤에 기록 재생 버튼 표시",
"prefs.history_play_button_note": "기록 컨트롤의 한 단계 뒤로와 한 단계 앞으로 사이에 기록 재생을 추가합니다. 컨트롤을 마우스 오른쪽 버튼으로 클릭해 표시하거나 숨길 수도 있습니다.",
"prefs.hold_stop_snapping": "누르고 있으면 스냅 중지",
"prefs.home_fit_size": "홈·맞춤 크기",
"prefs.home_fit_size_note": "홈, 맞춤, 연 디자인 또는 뷰 탐색기의 뷰 변경으로 모델을 화면에 맞출 때 모델이 뷰에서 차지하는 비율. 아래 설정과 관계없이 적용됩니다. 100 %: 패널, 나침반, 내비게이션 바 사이의 뷰를 가득 채웁니다.",
"prefs.icons_only": "아이콘만",
"prefs.imported_file": "가져온 파일",
"prefs.input": "입력",
@@ -1582,19 +1630,22 @@
"prefs.interface_note": "SolidCraft의 모양과 각 부분의 위치입니다.",
"prefs.interval_minutes": "{n}분",
"prefs.labels": "레이블",
"prefs.language_system": "시스템",
"prefs.layout": "레이아웃",
"prefs.layout_note": "컴팩트는 패널을 창 가장자리에 고정합니다. 브라우저 반투명은 플로팅에서 사용할 수 있습니다. 어시스턴트 위치는 어시스턴트 페이지에서 바꿀 수 있습니다.",
"prefs.left": "왼쪽",
"prefs.left_click": "왼쪽 클릭",
"prefs.line_drag_draws_one": "누른 채 드래그하면 선 하나 그리기",
"prefs.line_drag_draws_one_note": "선 도구에서 누르고 드래그한 뒤 놓으면 누른 곳에서 놓은 곳까지 선 하나를 그리고 거기서 끝납니다. 클릭하면 이전처럼 이어지는 선을 그립니다. 끄면: 드래그는 클릭처럼 놓은 곳에 점을 둡니다.",
"prefs.line_hover_tangent_arc": "선의 마지막 점에 머무르면 접선 호로 전환",
"prefs.line_hover_tangent_arc_note": "선 도구에서 선분을 그린 뒤 체인의 마지막 점에서 벗어났다가 돌아와 포인터를 그 위에 멈추면, 다음 클릭으로 접선 호의 끝점을 지정합니다. 같은 동작으로 선으로 돌아갑니다. 끔: 머물러도 아무 일도 일어나지 않습니다.",
"prefs.live_input": "실시간 입력",
"prefs.live_preview": "실시간 미리보기",
"prefs.max_file_size": "열 수 있는 최대 파일 크기",
"prefs.max_file_size_design": "디자인 파일",
"prefs.max_file_size_design": "열 파일의 최대 크기: 디자인",
"prefs.max_file_size_flag": "이번 실행은 --max-file-size {size}로 시작했으며 SolidCraft를 닫을 때까지 적용됩니다. 이 설정은 해당 옵션 없이 시작할 때 적용됩니다.",
"prefs.max_file_size_note": "기본값: 512 MB 및 256 MB, 최대 16 GB. 파일을 열려면 파일 크기의 몇 배에 달하는 메모리가 필요합니다. 큰 가져오기 데이터를 보관하는 디자인은 저장하고 다시 열기 위해 디자인 파일 한도를 높여야 할 수 있습니다.",
"prefs.max_file_size_step": "STEP 및 가져오기 파일",
"prefs.max_file_size_step": "열 파일의 최대 크기: STEP 및 가져오기",
"prefs.max_file_size_web": "웹 앱은 STEP 및 가져오기 파일을 512 MB까지, 디자인을 256 MB까지 엽니다. 브라우저 탭은 더 큰 파일을 담을 수 없습니다. 데스크톱 앱에서는 이 한도를 높일 수 있습니다.",
"prefs.megabytes": "MB",
"prefs.minutes": "분",
@@ -1604,6 +1655,9 @@
"prefs.mouse_mapping": "마우스 매핑",
"prefs.navigation": "탐색",
"prefs.navigation_note": "마우스, 트랙패드, 3D 마우스로 뷰를 움직이는 방식입니다.",
"prefs.navigator_fit": "뷰 탐색기로 뷰를 바꾼 뒤 모델 맞춤",
"prefs.navigator_fit_note": "큐브의 면, 모서리, 화살표나 기즈모의 축을 클릭하면 어느 방향에서든 전체가 들어가는 한 거리(경계 구)에서 모델의 중심을 바라봅니다. 켜져 있는 동안 홈 뷰도 같은 확대/축소와 중심을 쓰므로 그 뒤에 고른 뷰는 방향만 바뀝니다. 끄면 뷰가 제자리에서 회전하며 대상과 확대/축소를 유지합니다.",
"prefs.network_locations_note": "파일 서버에 디자인을 보관하는 조직용입니다. 끄면 허용한 서버만 사용합니다.",
"prefs.not_on_gpu": "이 GPU에서는 사용할 수 없음",
"prefs.on": "켜짐",
"prefs.opaque": "불투명",
@@ -1616,6 +1670,12 @@
"prefs.persistent_ribbon_pressed_help": "효과가 켜져 있는 동안 리본 명령을 강조 표시합니다. 끄면 이전의 중립적인 모양과 명령 도움말을 유지합니다.",
"prefs.perspective_fov": "시야각",
"prefs.preview_choices": "선택 항목 미리보기",
"prefs.preview_tint_new_faces": "미리 보기에서 새 면에 색 입히기",
"prefs.preview_tint_new_faces_help": "바디를 변경하는 피처의 미리 보기에서 추가되는 면을 미리 보기 색으로 그릴 수 있습니다. 끄면 바디가 원래 색으로 그려집니다.",
"prefs.print_face_highlight": "출력 바닥면 강조",
"prefs.print_face_highlight_tip": "뷰에서 바디의 출력 바닥면을 색칠하고 윤곽을 그리는 강도입니다. 0 %이면 강조하지 않습니다.",
"prefs.profile_export": "내보내기",
"prefs.profile_imported": "프로필을 가져왔습니다.",
"prefs.project_options_dialog": "Project 옵션 대화상자",
"prefs.project_options_dialog_note": "켜기: 투영 전에 지오메트리 또는 바디와 투영 링크를 선택합니다. 끄기: 기존 직접 선택 작업 흐름을 유지합니다.",
"prefs.quality_default": "기본값",
@@ -1623,14 +1683,19 @@
"prefs.quality_slowest": "가장 느림",
"prefs.quality_smoother": "더 부드럽게",
"prefs.reports_saved": "{n}개 저장됨",
"prefs.restore_page_defaults": "페이지 기본값 복원",
"prefs.ribbon_click_ends_tool": "실행 중인 도구의 버튼을 클릭하면 도구 종료",
"prefs.ribbon_click_ends_tool_note": "사용 중인 스케치 도구의 강조된 리본 버튼을 클릭하면 도구가 종료되어 뷰에서 클릭하면 다시 선택됩니다. 끄기: 클릭하면 도구가 다시 시작됩니다.",
"prefs.right_click": "오른쪽 클릭",
"prefs.saving": "자동 저장 및 복구",
"prefs.saving": "시작 및 저장",
"prefs.scale_note": "창에서 Ctrl + / Ctrl −로도 조절합니다. 버튼은 100%에서 28 px입니다.",
"prefs.scale_text": "배율 및 텍스트",
"prefs.search": "설정 검색",
"prefs.search_empty": "일치하는 설정이 없습니다",
"prefs.select": "선택",
"prefs.select_one_item": "항목 하나 선택",
"prefs.select_other_hidden": "다른 항목 선택으로 숨겨진 형상 선택",
"prefs.select_other_hidden_help": "켜짐: 포인터 근처의 숨겨진 바디, 스케치 및 가려진 모서리를 포함합니다. 꺼짐: 포인터 바로 아래에 보이는 형상만 포함하는 기존 목록을 사용합니다.",
"prefs.selecting": "선택",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift는 추가만, Ctrl / Cmd는 토글",
@@ -1656,6 +1721,8 @@
"prefs.sketch_tab_always_note": "스케치가 열려 있지 않을 때도 스케치 탭이 리본에서 모델 바로 다음에 남아 있습니다. 그 그리기 도구는 선택한 평면이나 면, 또는 다음에 고르는 평면에서 스케치를 시작하고 그 안에서 도구를 바로 쓸 수 있게 합니다.",
"prefs.sketch_tracking_guides": "중심 및 중점 정렬 안내선",
"prefs.sketch_tracking_guides_note": "원 중심이나 직선 모서리의 중점에 마우스를 올린 다음 수평 또는 수직으로 이동하면 임시 점선 안내선이 표시됩니다.",
"prefs.sketch_tracking_points": "그리는 동안 끝점과 모서리에서도 정렬 안내선",
"prefs.sketch_tracking_points_note": "그리기 도구가 점을 기다리는 동안 선이나 호의 끝점, 직사각형 모서리, 자유 점에 마우스를 올리면 중심이나 중점과 같은 임시 점선 안내선이 표시됩니다. 끄면: 중심과 중점만.",
"prefs.sketching": "스케치",
"prefs.snap_drawing_note": "스케치 점을 그리거나 드래그하는 동안.",
"prefs.source": "소스",
@@ -1667,6 +1734,24 @@
"prefs.status_error": "오류",
"prefs.status_selected": "선택됨",
"prefs.status_warning": "경고",
"prefs.structure.appearance": "모양",
"prefs.structure.appearance_note": "색상, 텍스트 및 접근성.",
"prefs.structure.in_profile": "프로필에 포함",
"prefs.structure.layout": "레이아웃",
"prefs.structure.layout_note": "창 구성 요소의 위치.",
"prefs.structure.look": "보기",
"prefs.structure.measure_pointer": "측정 및 포인터",
"prefs.structure.mouse3d": "3D 마우스",
"prefs.structure.mouse3d_note": "SpaceMouse 및 SpaceNavigator.",
"prefs.structure.navigation_note": "마우스와 트랙패드. 3D 마우스는 별도 페이지에 있습니다.",
"prefs.structure.profile": "프로필",
"prefs.structure.profile_edited": "저장 후 변경됨",
"prefs.structure.profile_hover": "설정 프로필에 저장됨",
"prefs.structure.scale_percent": "{percent}%",
"prefs.structure.sketch": "스케치",
"prefs.structure.tools": "도구",
"prefs.structure.tools_note": "모델링 도구의 동작.",
"prefs.structure.work": "작업",
"prefs.supersampling_brief": "뷰를 더 크게 그린 뒤 줄여서 필터링합니다. 매우 선명하지만 더 무겁습니다.",
"prefs.tab_accent_note": "강조 색상이 활성 탭을 따라갑니다",
"prefs.theme_file": "테마 파일",
@@ -1676,6 +1761,16 @@
"prefs.theme_follow_note": "다른 도구가 파일을 다시 쓸 때마다 다시 불러옵니다. 테마를 선택하면 따르기를 중지합니다.",
"prefs.theme_follow_off": "끄기",
"prefs.theme_follow_stop": "따르기 중지",
"prefs.theme_option_opacity": "불투명도",
"prefs.theme_option_opacity_help": "플로팅 패널의 불투명도를 조정합니다. 뷰포트 색상 위에서도 텍스트를 읽을 수 있도록 최솟값이 적용됩니다.",
"prefs.theme_option_tab_colours": "탭 색상",
"prefs.theme_option_tab_colours_help": "각 리본 탭에 이 테마의 패널 및 뷰포트 색상을 사용합니다.",
"prefs.theme_option_tab_fade_ms": "탭 전환",
"prefs.theme_option_tab_fade_ms_help": "탭을 전환할 때 패널 및 뷰포트 색상을 서서히 변경합니다. 0이면 즉시 전환합니다.",
"prefs.theme_options_badge": "옵션",
"prefs.theme_options_more": "더 보기 · {count}",
"prefs.theme_options_reset": "초기화",
"prefs.theme_options_unavailable": "이 기기에서는 사용할 수 없음",
"prefs.timeline_half_hour": "−30분",
"prefs.timeline_now": "지금",
"prefs.timeline_quarter_hour": "−15",
@@ -1695,12 +1790,20 @@
"prefs.under_cursor": "커서 아래",
"prefs.units_precision": "단위 및 정밀도",
"prefs.updates": "업데이트",
"prefs.value_boxes_in_view": "뷰에서 핸들 옆에 값 표시",
"prefs.value_boxes_in_view_tip": "명령의 값(거리, 반경 등)을 모델 위 핸들 옆 상자에 표시합니다. 끄면 값은 명령 창에 입력하며, 선택 후 키보드 입력이 그 창으로 가고 모델을 가리는 것이 없습니다.",
"prefs.view": "뷰 동작",
"prefs.view_confirm_corner": "뷰 모서리에 확인 및 취소",
"prefs.view_confirm_corner_note": "대화 상자나 스케치가 열려 있는 동안 뷰 오른쪽 위의 체크와 X 표시가 대화 상자의 확인과 취소와 같은 동작을 하거나 스케치를 마치고 취소합니다. 뷰 탐색기는 그 아래로 내려갑니다.",
"prefs.viewport": "뷰포트",
"prefs.viewport_analysis_status": "뷰포트 해석 상태 표시",
"prefs.viewport_analysis_status_help": "활성 해석을 뷰포트 위에 표시하고 각 항목에서 범례를 엽니다. Esc로 최신 해석을 끕니다. 끄면 모서리 범례와 이전 Esc 동작으로 돌아갑니다.",
"prefs.viewport_section_status": "뷰포트 단면 상태 표시",
"prefs.viewport_section_status_help": "뷰포트에 단면 설정을 표시합니다. Escape 키와 활성 리본 버튼으로 단면을 끕니다. 비활성화하면 이전 단면 조작을 유지합니다.",
"prefs.viewport_status_details": "뷰포트 실시간 상세 정보",
"prefs.viewport_status_details_help": "표준 상세 카드에서 활성 분석 및 단면 설정을 편집합니다. 끄면 읽기 전용 분석 범례와 단면 대화상자로 돌아갑니다.",
"prefs.viewport_status_summary": "뷰포트 상태 항목 요약",
"prefs.viewport_status_summary_help": "상태 항목이 3개 이상이면 하나의 요약 배지로 표시합니다. 비활성화하면 모든 항목을 기존 행에 표시합니다.",
"prefs.wheel": "휠",
"prefs.wheel_trackpad": "휠 및 트랙패드",
"prefs.workspace": "작업 공간",
@@ -1762,6 +1865,10 @@
"render.appearances": "외관",
"render.assign_help": "클릭하여 선택 항목에 적용하거나 드래그하여 미리 보고 놓으세요. 선택이 없으면 칠하기를 시작합니다.",
"render.background": "장면 배경",
"render.batch_all_shots": "모든 샷 ({count})",
"render.batch_empty": "모든 샷을 렌더링하기 전에 샷을 저장하세요.",
"render.batch_limit": "한 번에 최대 32개의 샷을 렌더링할 수 있습니다.",
"render.batch_status": "{running}개 실행 중 · {waiting}개 대기 중",
"render.bloom_enable": "표시 블룸",
"render.bloom_help": "양의 선형 하이라이트를 세 번의 정규화 필터로 확산하는 출력 전용 효과입니다. 반경은 출력 픽셀 단위이며 sRGB 전에 더합니다. 렌즈·볼륨·수렴 개선이 아니며 원본 EXR은 변하지 않습니다.",
"render.bloom_opaque": "블룸에는 불투명 출력이 필요합니다. 투명 PNG를 몰래 합성하지 않고 거부합니다.",
@@ -1853,6 +1960,13 @@
"render.children.workstation": "워크스테이션",
"render.choose_look": "먼저 외관 라이브러리에서 외관을 선택하세요",
"render.clear_stage": "Remove saved stage",
"render.compare_divider": "비교 구분선",
"render.compare_live": "라이브",
"render.compare_live_preview": "라이브 미리보기",
"render.compare_render": "렌더 {number}",
"render.compare_side": "렌더 {number} · {shot}",
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds}초 · {renderer} · 리비전 {revision}에서 캡처",
"render.compare_swap": "맞바꾸기",
"render.cpu_budget": "CPU 리소스 프로필",
"render.cpu_budget_bodies": "바디 수 상한",
"render.cpu_budget_compact": "컴팩트 (기존 제한)",
@@ -2265,7 +2379,9 @@
"render.output_ready": "렌더링 준비 완료",
"render.output_run_check": "검사 실행",
"render.output_size_limit": "{method}: 한 변 최대 {dimension} px, 총 {pixels} 픽셀. 가능한 최대 크기: {width} × {height}.",
"render.output_size_method_profile": "{width} × {height}을(를) 렌더링하려면 {method}와(과) {profile} CPU 리소스 프로필을 선택하거나 더 작은 크기를 선택하세요.",
"render.output_size_ok": "크기 정상 · {width} × {height} px",
"render.output_size_profile": "{width} × {height}을(를) 렌더링하려면 {profile} CPU 리소스 프로필이나 더 작은 크기를 선택하세요.",
"render.output_tools_inspector": "출력 검사기에서 크기와 형식을 편집하세요.",
"render.output_view_image": "이미지 보기",
"render.paint_label": "칠하기",
@@ -2319,6 +2435,10 @@
"render.result_actual": "100%",
"render.result_actual_size": "100%",
"render.result_reduced_display": "표시 미리보기를 뷰포트 장치 또는 캐시 제한에 맞춰 축소했습니다. 보관된 PNG와 내보내기는 전체 해상도를 유지합니다. 이 축소 보기에서는 원본 100% 검사가 불가능합니다.",
"render.results_choose": "보관된 결과",
"render.results_label": "렌더 {number} · {shot} · {seconds}초",
"render.results_stale_label": "{result} [이전 버전]",
"render.results_unnamed": "이미지",
"render.return_design": "설계로 돌아가기",
"render.ribbon_backgrounds": "배경",
"render.ribbon_check": "검사",
@@ -2397,6 +2517,7 @@
"render.size_custom": "사용자 지정…",
"render.size_full_hd": "풀 HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · {profile} 필요",
"render.size_presets": "크기 프리셋",
"render.size_presets_pending": "출력 편집기와 함께 제공 예정",
"render.size_preview": "미리보기",
@@ -2618,6 +2739,24 @@
"sheet.flange_type_base": "베이스",
"sheet.flange_type_contour": "윤곽",
"sheet.flange_type_edge": "모서리",
"sheet.personal_rules.confirm_delete": "삭제 확인",
"sheet.personal_rules.delete": "삭제",
"sheet.personal_rules.delete_prompt": "이 개인 규칙을 삭제할까요? 설계에 이미 있는 규칙은 바뀌지 않습니다.",
"sheet.personal_rules.full": "개인 라이브러리에 이미 규칙이 64개 있습니다.",
"sheet.personal_rules.invalid_name": "줄 바꿈이나 제어 문자가 없는 짧은 이름을 입력하세요.",
"sheet.personal_rules.invalid_values": "규칙 값을 모두 입력하세요. 각 값은 이 설계에서 계산할 수 있어야 하며, 두께는 양수, K 계수는 0에서 1 사이여야 합니다.",
"sheet.personal_rules.missing": "선택한 개인 규칙을 더 이상 사용할 수 없습니다.",
"sheet.personal_rules.name": "규칙 이름",
"sheet.personal_rules.name_exists": "같은 이름의 개인 규칙이 이미 있습니다. 다른 이름을 사용하세요.",
"sheet.personal_rules.name_hint": "고유한 이름",
"sheet.personal_rules.overwrite": "선택한 개인 규칙에 변경 사항 저장",
"sheet.personal_rules.rename": "이름 바꾸기",
"sheet.personal_rules.rule": "개인 규칙",
"sheet.personal_rules.save": "새로 저장",
"sheet.personal_rules.scope": "모든 설계에서 쓸 수 있도록 프로필에 저장됩니다.",
"sheet.personal_rules.select": "규칙 선택",
"sheet.personal_rules.title": "개인 규칙",
"sheet.personal_rules.unsupported": "이 규칙에는 이 버전에서 사용할 수 없는 값이 있습니다.",
"shortcut.json_error": "매개변수는 JSON 형식이어야 합니다: {error}",
"shortcut.no_command": "`{id}` 명령이 없습니다. JSON 앞의 텍스트는 명령 ID여야 합니다",
"shortcut.pin": "S 상자 맨 위에 고정",
@@ -2802,6 +2941,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "캡을 시계 반대 방향으로 돌려 유지한 다음 놓으세요.",
"ui.3d_mouse_switch_cap": "중앙 복귀형으로 보정",
"ui.3d_mouse_switch_trackball": "트랙볼로 보정",
"ui.3d_mouse_use_direct": "직접 드라이버 사용",
"ui.3d_mouse_use_direct_tip": "spacenavd를 거치지 않고 3D 마우스를 직접(hidraw) 읽습니다.",
"ui.3d_mouse_view_axis": "뷰 축",
"ui.3d_mouse_view_off": "끔",
"ui.3d_mouse_view_off_tip": "3D 마우스 드라이버 프로필이 이 축을 끄므로, 이 축의 동작으로는 뷰가 움직이지 않습니다. 여기서 다른 축의 동작을 고르면 둘이 바뀌고, 그 동작은 더 이상 뷰를 움직이지 않습니다.",
@@ -2838,12 +2979,15 @@
"ui.agent_chat_auth_terminal": "터미널에서 이것을 실행한 뒤 다시 연결을 누르세요:",
"ui.agent_chat_auth_title": "에이전트에 로그인",
"ui.agent_chat_cancel": "취소",
"ui.agent_chat_cancel_timeout": "에이전트가 중지 요청에 응답하지 않아 연결이 끊어졌습니다. 계속하려면 다시 연결하세요.",
"ui.agent_chat_cannot_start": "설치되어 있지만 시작되지 않습니다: {error}",
"ui.agent_chat_changes": "변경 {n}개, 실행 취소 한 번",
"ui.agent_chat_changes_of": "변경 {n}/{limit}",
"ui.agent_chat_close": "닫기",
"ui.agent_chat_command": "에이전트 명령",
"ui.agent_chat_connect": "연결",
"ui.agent_chat_copy": "복사",
"ui.agent_chat_custom_heading": "사용자 지정 모델",
"ui.agent_chat_custom_hint": "표준 입출력으로 Agent Client Protocol을 사용하는 모든 프로그램. 셸 없이 실행됩니다.",
"ui.agent_chat_denied": "거부함",
"ui.agent_chat_deny": "거부",
@@ -2885,7 +3029,15 @@
"ui.agent_chat_mode_all": "모두",
"ui.agent_chat_mode_ask": "확인",
"ui.agent_chat_mode_edits": "편집",
"ui.agent_chat_model_custom": "직접 입력 사용: {model}",
"ui.agent_chat_model_rejected": "에이전트가 {model}을(를) 받아들이지 않았습니다: {error}",
"ui.agent_chat_model_search": "모델 검색…",
"ui.agent_chat_model_tip": "에이전트가 사용하는 모델입니다. 선택은 이 에이전트에 기억됩니다.",
"ui.agent_chat_models_failed": "모델 목록을 가져올 수 없습니다: {error}",
"ui.agent_chat_models_heading": "사용 가능한 모델",
"ui.agent_chat_models_listing": "모델 목록을 가져오는 중…",
"ui.agent_chat_models_none": "엔드포인트에 나열된 모델이 없습니다",
"ui.agent_chat_models_not_a_list": "응답이 모델 목록이 아닙니다",
"ui.agent_chat_new": "새 채팅",
"ui.agent_chat_new_confirm": "새 채팅을 시작할까요? 현재 채팅은 지워집니다.",
"ui.agent_chat_next_picture": "다음 이미지",
@@ -2914,9 +3066,12 @@
"ui.agent_chat_queued_stopped": "대기 중 (중지됨, 보내지 않음)",
"ui.agent_chat_refresh": "새로 고침",
"ui.agent_chat_refused": "에이전트에게 허용되지 않음: {reason}",
"ui.agent_chat_reinstall": "{agent}이(가) 설치되어 있지만({program}) SolidCraft가 확인할 때 시작되지 않았습니다: {error}\n설치가 완료되지 않았거나 다른 종류의 컴퓨터용일 수 있습니다. `{install}`(으)로 다시 설치하고, 터미널에서 버전이 출력되는지 확인한 뒤 새로 고침을 누르세요.",
"ui.agent_chat_remove": "대기열에서 제거",
"ui.agent_chat_remove_file": "이 메시지에서 빼기",
"ui.agent_chat_remove_imported": "이 메시지에서 빼기. 부품은 디자인에 남습니다 (실행 취소하면 제거됨)",
"ui.agent_chat_reply_cut_off": "응답이 끝나기 전에 길이 한도에 도달해 불완전할 수 있습니다. 에이전트에게 계속하거나 더 짧게 답하라고 요청하세요.",
"ui.agent_chat_reply_declined": "모델이 이 메시지에 대한 답변을 거절했습니다.",
"ui.agent_chat_right": "오른쪽",
"ui.agent_chat_save": "저장",
"ui.agent_chat_send": "보내기",
@@ -2970,12 +3125,17 @@
"ui.ai_copy_suffix": "복사본",
"ui.ai_custom_url": "사용자 지정 URL",
"ui.ai_default": "기본값",
"ui.ai_edit": "편집",
"ui.ai_edit_agent_title": "에이전트 편집",
"ui.ai_edit_gone": "이 에이전트는 제거되었으므로 저장할 내용이 없습니다.",
"ui.ai_edit_tip": "이 에이전트의 이름, 명령 또는 엔드포인트를 변경합니다. 저장된 키는 표시되지 않습니다.",
"ui.ai_http_busy": "엔드포인트가 바쁘거나 한도에 도달했습니다. 나중에 다시 시도하세요",
"ui.ai_http_key": "엔드포인트가 키를 거부했습니다. 환경 설정 > AI에서 확인하세요",
"ui.ai_http_not_found": "엔드포인트나 모델을 찾을 수 없습니다. 기본 URL과 모델 이름을 확인하세요",
"ui.ai_http_too_large": "대화가 이 엔드포인트에 비해 너무 큽니다. 새 채팅을 시작하세요",
"ui.ai_key": "API 키",
"ui.ai_key_hint": "선택 사항, 시스템 키체인에 저장",
"ui.ai_key_keep_hint": "저장된 키를 유지하려면 비워 두세요",
"ui.ai_key_needs_https": "키는 HTTPS를 통해서만, 또는 이 컴퓨터로만 전송됩니다.",
"ui.ai_key_note": "SolidCraft 설정이 아닌 시스템 키체인에 저장됩니다. 로컬 서버라면 비워 두세요.",
"ui.ai_keychain_error": "시스템 키체인이 거부했습니다: {error}",
@@ -3002,6 +3162,7 @@
"ui.ai_preset": "프리셋",
"ui.ai_remove": "제거",
"ui.ai_reset": "재설정",
"ui.ai_save": "저장",
"ui.ai_settings": "AI 설정…",
"ui.ai_show_features": "AI 기능 표시",
"ui.ai_show_features_note": "에이전트 채팅: 이미 사용 중인 코딩 에이전트나 선택한 모델 엔드포인트가 열린 디자인을 다루는 패널입니다. 끄면 AI 버튼, 단축키, 메뉴 항목, 패널이 없고 아무것도 찾거나 시작하지 않습니다.",
@@ -3065,6 +3226,7 @@
"ui.circle": "원",
"ui.circular_pattern": "원형 패턴",
"ui.clear": "지우기",
"ui.clear_print_face": "출력 바닥면 해제",
"ui.clear_recent": "최근 항목 지우기",
"ui.clear_the_selection": "선택 해제",
"ui.click_selects_whole_bodies": "클릭하면 바디 전체 선택",
@@ -3093,6 +3255,8 @@
"ui.coming_later": "추후 지원 예정",
"ui.command_done": "{command} 완료",
"ui.command_palette_hint": "명령 입력(예: {example}) — 또는 S 키를 눌러 검색",
"ui.components_from_bodies_hint": "활성 구성요소 아래에 선택한 각 바디와 같은 이름의 구성요소를 만듭니다.",
"ui.components_from_bodies_required": "바디를 하나 이상 선택하세요.",
"ui.concentric": "동심",
"ui.cone": "원뿔",
"ui.conic_curve": "원뿔 곡선",
@@ -3105,9 +3269,11 @@
"ui.construction.contact_hint": "접선: 원/원호 또는 열린 스플라인/원뿔 곡선 선택 후 접점 선택 (자유 곡선은 끝점만)",
"ui.construction.external_hint": "접선: 원 또는 원호, 외부 점, 원하는 해를 차례로 선택",
"ui.construction.free_hint": "보조 직선: 두 점 클릭; 마우스 오른쪽 버튼으로 다시 시작",
"ui.construction.normal_needs_length": "무한 직선은 항상 보조선입니다. 일반 선을 그리려면 길이를 입력하세요",
"ui.construction.point_hint": "보조 직선: 통과할 점 클릭",
"ui.construction.reference_hint": "보조 직선: 직선 선택 후 점 선택; 마우스 오른쪽 버튼으로 참조 변경",
"ui.construction.ribbon_label": "보조선",
"ui.construction.side_hint": "보조 직선: 선이 뻗어 나갈 쪽 클릭; 마우스 오른쪽 버튼으로 다시 시작",
"ui.context_menu": "컨텍스트 메뉴",
"ui.continuity_g0": "G0 꺾임",
"ui.continuity_g1": "G1 접선",
@@ -3133,6 +3299,7 @@
"ui.dark": "어둡게",
"ui.default_modelling_orientation": "기본 모델링 방향",
"ui.delete": "삭제",
"ui.delete_select_first": "먼저 삭제할 항목을 선택하세요: 피처, 바디, 컴포넌트 또는 스케치 형상",
"ui.depth": "깊이",
"ui.design": "디자인",
"ui.design_properties.author": "작성자",
@@ -3146,6 +3313,40 @@
"ui.design_properties.part_number": "부품 번호",
"ui.design_properties.project": "프로젝트",
"ui.design_properties.revision": "리비전",
"ui.details_access_caption": "선택한 방향에서 공구가 각 면에 도달할 수 있는지 표시합니다.",
"ui.details_angle": "각도",
"ui.details_continuity_caption": "선택한 이음부를 편집합니다. 다른 이음부는 각자의 설정을 유지합니다.",
"ui.details_continuity_stale": "이전 형상입니다. 이 이음부를 편집하거나 화면에 맞추려면 분석을 다시 실행하세요.",
"ui.details_copy_list": "목록 복사",
"ui.details_curvature": "곡률 공차",
"ui.details_curvature_caption": "각 면의 국소 곡률 반지름에 따라 색상이 표시됩니다.",
"ui.details_direction": "공구 방향",
"ui.details_draft_caption": "금형 분리 방향에 대한 각 면의 각도에 따라 색상이 표시됩니다.",
"ui.details_edit_seam": "이음부 편집",
"ui.details_fixture": "UI 구성 요소 예시 — 샘플 데이터만 표시",
"ui.details_fixture_caption": "설명용 값을 사용하는 UI 구성 요소 예시입니다. 모델의 분석 결과가 아닙니다.",
"ui.details_fixture_problem": "문제가 있는 결과 예시",
"ui.details_fixture_readout": "측정값 예시",
"ui.details_flip": "뒤집기",
"ui.details_gap": "간격",
"ui.details_hatch": "절단면 해칭",
"ui.details_jump": "곡률 불연속",
"ui.details_key": "범례",
"ui.details_offset": "오프셋",
"ui.details_plane": "평면",
"ui.details_pull": "금형 분리 방향",
"ui.details_radius": "반지름",
"ui.details_readout": "측정값",
"ui.details_samples": "샘플 수",
"ui.details_seam": "{body} · 모서리 {edge}",
"ui.details_settings": "설정",
"ui.details_sharp_gap": "날카로운 이음 또는 간격",
"ui.details_show_all": "전체 {count}개 표시",
"ui.details_smooth": "매끄러움",
"ui.details_stripes": "줄무늬",
"ui.details_target": "형상에 맞게 보기 조정",
"ui.details_worst_seams": "연속성이 가장 나쁜 이음부",
"ui.details_zebra_caption": "반사된 줄무늬는 면이 얼마나 매끄럽게 이어지는지 보여 줍니다.",
"ui.diameter": "지름",
"ui.dimension": "치수",
"ui.direct_interaction": "직접 조작",
@@ -3190,6 +3391,7 @@
"ui.edit_in_place_saved_elsewhere": "부품이 다른 파일에 저장되었습니다. 어셈블리는 여전히 {name}에 연결되어 있습니다",
"ui.edit_in_place_wait": "디자인에서 실행 중인 작업이 끝날 때까지 기다리세요",
"ui.edit_section_analysis": "단면 분석 편집",
"ui.edit_sketch_empty": "편집할 스케치가 없습니다",
"ui.ellipse": "타원",
"ui.emboss_wrap_note": "지원되는 원통에 접하는 직교 선 프로파일은 자동으로 감깁니다. 대상을 고르려면 벽을 선택하세요.",
"ui.end_radius": "끝 반지름",
@@ -3200,12 +3402,15 @@
"ui.equation_explicit": "명시적",
"ui.equation_form": "형식",
"ui.equation_note": "변수는 단위가 없는 수입니다. sin(t)는 라디안으로 읽습니다. 단위가 없는 결과는 디자인 단위입니다.",
"ui.equation_note_range": "변수는 범위와 같이 길이입니다. 길이 매개변수와 함께 쓸 수 있으며, sin()에는 길이로 나누어 넣습니다. 단위가 없는 결과는 디자인 단위로 읽습니다.",
"ui.equation_parametric": "매개변수식",
"ui.equation_variable_unit": "변수에 범위의 단위 사용",
"ui.equation_x_of_t": "x(t)",
"ui.equation_y_of_t": "y(t)",
"ui.equation_y_of_x": "y(x)",
"ui.equivalent_radius": "등가 반지름",
"ui.expand": "펼치기",
"ui.explode_needs_two_components": "분해 보기는 구성 요소를 서로 떼어 놓으므로 설계에 구성 요소가 두 개 이상 있어야 합니다(새 구성 요소, 설계 삽입 또는 바디로 구성 요소 만들기)",
"ui.exportDxf": "DXF 내보내기…",
"ui.export_ellipsis_keymap": "내보내기…",
"ui.export_keymap_tip": "키, S 상자 즐겨찾기, 도구 모음을 파일로 내보내기",
@@ -3355,6 +3560,7 @@
"ui.insert_design.saved_file": "{name}에 저장되지 않은 변경 사항이 있어 저장된 파일을 삽입했습니다. 저장한 다음 링크 업데이트로 변경 사항을 가져오세요.",
"ui.insert_design.unsaved": "저장되지 않은 변경 사항",
"ui.inspect": "검사",
"ui.instances": "인스턴스",
"ui.intersect": "교차",
"ui.isolate": "분리 표시",
"ui.join": "접합",
@@ -3363,12 +3569,14 @@
"ui.joints": "조인트",
"ui.keyboard_shortcuts": "키보드 단축키",
"ui.language": "언어",
"ui.lasso_select": "올가미(자유롭게 둘러 그리기: 완전히 안쪽에 있는 것을 선택)",
"ui.layout_compact": "컴팩트",
"ui.layout_floating": "플로팅",
"ui.length": "길이",
"ui.length_decimals": "길이 소수점 자릿수",
"ui.light": "밝게",
"ui.line": "선",
"ui.line_arc_hint": "접선 호: 호의 끝점을 클릭합니다. 선으로 돌아가려면 마지막 점에 다시 머무르고, 끝내려면 오른쪽 클릭 또는 Esc를 누릅니다",
"ui.lock": "잠금",
"ui.loft": "로프트",
"ui.look_at": "바라보기",
@@ -3448,6 +3656,7 @@
"ui.part_size_shared": "부품을 다시 빌드하며 추가된 피처는 모두 대체됩니다. 이 구성요소의 모든 인스턴스가 업데이트됩니다.",
"ui.paste": "붙여넣기",
"ui.path": "경로",
"ui.pattern_object_type_tip": "바디: 뷰나 브라우저에서 선택하는 솔리드 형상이며, 각각 통째로 복사됩니다.\n피처: 돌출이나 구멍 같은 기록의 단계이며, 기록에서 또는 그 단계가 만든 면에서 선택합니다. 그 단계가 반복됩니다.\n스케치는 둘 다 아닙니다. 곡선은 스케치 안의 패턴이나 대칭 복사로 반복하거나, 먼저 돌출하세요.",
"ui.pattern_on_path": "경로 패턴",
"ui.perpendicular": "수직",
"ui.perspective_view": "원근 투영 뷰",
@@ -3479,6 +3688,11 @@
"ui.polygon": "다각형",
"ui.preferences": "환경 설정",
"ui.press_a_key": "키를 누르세요…",
"ui.print_face_body_not_sent": "이 면의 바디는 보내지지 않습니다. 먼저 바디에 추가하십시오",
"ui.print_face_cleared": "{body}: 출력 바닥면을 해제했습니다",
"ui.print_face_input": "출력 바닥면",
"ui.print_face_pick": "솔리드 바디의 평면 하나를 선택하면 출력 바닥면이 됩니다",
"ui.print_face_set": "{body}: 출력 바닥면을 설정했습니다. 슬라이서로 보내면 이 면이 아래로 놓입니다",
"ui.profile": "프로파일",
"ui.profile_plane": "프로파일 평면",
"ui.profiles": "프로파일",
@@ -3494,6 +3708,7 @@
"ui.properties.inertia_y": "Iyx, Iyy, Iyz",
"ui.properties.inertia_z": "Izx, Izy, Izz",
"ui.properties.measuring": "측정 중…",
"ui.properties.measuring_bodies": "측정 중… 바디 {total}개 중 {done}개",
"ui.properties.measuring_failed": "측정이 끝나기 전에 중단되었습니다.",
"ui.properties.principal_axis": "주축 {number}",
"ui.properties.principal_moments": "주관성 모멘트",
@@ -3555,6 +3770,7 @@
"ui.selectOther": "기타 선택…",
"ui.selectTangentChain": "접선 체인 선택",
"ui.select_all": "모두 선택",
"ui.select_components_by_face": "구성요소 선택(면을 클릭하면 해당 구성요소 선택)",
"ui.selection": "선택",
"ui.selection_modifiers": "선택 보조 키",
"ui.selection_shift_add_only": "Shift+클릭은 추가만 함",
@@ -3597,6 +3813,7 @@
"ui.slice": "슬라이스",
"ui.slicer": "슬라이서",
"ui.slicer_delete_tip": "{program}\nDelete 키로 목록에서 제거",
"ui.slicer_single_instance_tip": "실행 중인 창에 파일을 불러오는 슬라이서용입니다.",
"ui.slot": "슬롯",
"ui.smooth": "부드럽게",
"ui.smooth_wheel_zoom": "부드러운 휠 확대/축소",
@@ -3688,7 +3905,14 @@
"ui.viewport_status_key": "{name} 범례 표시",
"ui.viewport_status_minimum_radius": "최소 반지름",
"ui.viewport_status_pairs": "{count}쌍",
"ui.viewport_status_section_hatch": "{setting} · 해칭",
"ui.viewport_status_section_plane": "현재 평면",
"ui.viewport_status_section_tilted": "기울어짐 · {offset}",
"ui.viewport_status_stripes": "줄무늬 {count}개",
"ui.viewport_status_summary_clear": "모두 끄기",
"ui.viewport_status_summary_heading": "이 뷰에서",
"ui.viewport_status_summary_label": "{name} 외 {count}개 항목",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "테마 따르기",
"ui.visual_shaded_hidden_edges": "음영 처리 및 숨겨진 모서리",
"ui.visual_shaded_with_edges": "음영 처리 및 모서리",
@@ -3719,8 +3943,10 @@
"update.available": "SolidCraft {version} 사용 가능",
"update.button": "업데이트",
"update.button_version": "{version}(으)로 업데이트",
"update.check_automatically": "업데이트 자동 확인",
"update.check_automatically": "시작 시 및 하루에 한 번 확인",
"update.check_for_updates": "업데이트 확인",
"update.check_now": "지금 확인",
"update.check_unavailable": "이 설치에서는 업데이트를 확인할 수 없습니다.",
"update.checking": "확인 중…",
"update.copy": "복사",
"update.dmg_opened": "디스크 이미지를 확인하고 열었습니다: SolidCraft를 응용 프로그램 폴더로 끌어 기존 버전을 바꾼 뒤 SolidCraft를 다시 시작하세요.",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,33 +0,0 @@
{
"bs": {
"browser.calibrate_click_a": "Kliknite sliku da zadate tačku A ili upišite njene koordinate.",
"browser.calibrate_click_b": "Kliknite sliku da zadate tačku B ili upišite njene koordinate.",
"help.sketch.construction_line": "Napravite beskonačnu pomoćnu pravu kroz dvije tačke. Koristite je da rasporedite skicu bez razdvajanja profila.",
"help.sketch.construction_line.angle": "Napravite beskonačnu pomoćnu pravu pod uglom od ose X skice. Izaberite tačku i upišite ugao ili izaberite smjer.",
"help.sketch.construction_line.bisector": "Izaberite dvije prave linije skice koje se sijeku, pa jednu od dvije simetrale ugla. Rezultat prati izvorne linije.",
"help.sketch.construction_line.horizontal": "Napravite beskonačnu horizontalnu pomoćnu pravu kroz tačku. Njeno ograničenje horizontalnosti ostaje na snazi pri kasnijim izmjenama.",
"help.sketch.construction_line.parallel": "Izaberite pravu liniju skice, pa postavite paralelnu beskonačnu pomoćnu pravu. Desnim klikom izaberite drugu referencu.",
"help.sketch.construction_line.perpendicular": "Izaberite pravu liniju skice, pa postavite okomitu beskonačnu pomoćnu pravu. Desnim klikom izaberite drugu referencu.",
"help.sketch.construction_line.tangent_at": "Napravite tangentu u tački na krugu ili kružnom luku, ili u krajnjoj tački otvorenog splajna ili konike. Izaberite krivu, pa tačku dodira.",
"help.sketch.construction_line.tangent_external": "Izaberite krug ili kružni luk, pa vanjsku tačku i rješenje tangente. Pregledi prikazuju samo dodirne tačke na nacrtanom luku.",
"help.sketch.construction_line.tangent_two": "Izaberite dva kruga ili kružna luka, pa u pregledu izaberite zajedničku tangentu. Dodirne tačke moraju ležati na nacrtanim krivama.",
"help.sketch.construction_line.vertical": "Napravite beskonačnu vertikalnu pomoćnu pravu kroz tačku. Njeno ograničenje vertikalnosti ostaje na snazi pri kasnijim izmjenama.",
"prefs.persistent_ribbon_pressed": "Istakni aktivne komande na traci",
"prefs.persistent_ribbon_pressed_help": "Ističe komande na traci dok su njihovi efekti uključeni. Isključite da biste zadržali prethodni neutralni izgled i pomoć za komande.",
"prefs.sketch_construction_lines": "Prošireni alati za pomoćne prave",
"prefs.sketch_open_look_at": "Okreni kameru prema skici kad se otvori",
"prefs.sketch_open_look_at_note": "Isključeno: kad napravite ili uredite skicu, prikaz ostaje onakav kakav jest. Pogledaj okreće prikaz prema skici kad to želite.",
"recovery.discard_all": "Odbaci sve",
"recovery.discard_all_detail": "Ovo se ne može poništiti.",
"recovery.discard_all_title": "Odbaciti automatska čuvanja ({count})?",
"recovery.discard_all_title_one": "Odbaciti 1 automatsko čuvanje?",
"ui.construction.angle_hint": "Pomoćna prava: kliknite tačku, pa upišite ugao ili izaberite smjer",
"ui.construction.bisector_hint": "Simetrala: izaberite dvije prave linije koje se sijeku, pa željeno rješenje",
"ui.construction.common_hint": "Zajednička tangenta: izaberite dva kruga ili luka, pa željeno rješenje",
"ui.construction.contact_hint": "Tangenta: izaberite krug/luk ili otvoreni splajn/koniku, pa tačku dodira (krive slobodnog oblika: krajnje tačke)",
"ui.construction.external_hint": "Tangenta: izaberite krug ili luk, vanjsku tačku, pa željeno rješenje",
"ui.construction.free_hint": "Pomoćna prava: kliknite dvije tačke; desnim klikom počnite ispočetka",
"ui.construction.point_hint": "Pomoćna prava: kliknite tačku kroz koju prolazi",
"ui.construction.reference_hint": "Pomoćna prava: izaberite pravu liniju, pa tačku; desnim klikom promijenite referencu"
}
}
@@ -1,33 +0,0 @@
{
"gl": {
"browser.calibrate_click_a": "Fai clic na imaxe para o punto A, ou escribe as súas coordenadas.",
"browser.calibrate_click_b": "Fai clic na imaxe para o punto B, ou escribe as súas coordenadas.",
"help.sketch.construction_line": "Crea unha recta de construción infinita por dous puntos. Úsaa para organizar un bosquexo sen dividir os perfís.",
"help.sketch.construction_line.angle": "Crea unha recta de construción infinita cun ángulo respecto do eixe X do bosquexo. Escolle o punto e escribe o ángulo, ou escolle a súa dirección.",
"help.sketch.construction_line.bisector": "Escolle dúas rectas do bosquexo que se crucen e despois unha das dúas bisectrices. O resultado segue as súas rectas de orixe.",
"help.sketch.construction_line.horizontal": "Crea unha recta de construción horizontal infinita por un punto. A súa restrición horizontal mantense nas edicións posteriores.",
"help.sketch.construction_line.parallel": "Escolle unha recta do bosquexo e despois coloca unha recta de construción infinita paralela. Fai clic co botón dereito para escoller outra referencia.",
"help.sketch.construction_line.perpendicular": "Escolle unha recta do bosquexo e despois coloca unha recta de construción infinita perpendicular. Fai clic co botón dereito para escoller outra referencia.",
"help.sketch.construction_line.tangent_at": "Crea unha tanxente nun punto dun círculo ou dun arco circular, ou no extremo dunha spline ou dunha cónica abertas. Escolle a curva e despois o seu punto de tanxencia.",
"help.sketch.construction_line.tangent_external": "Escolle un círculo ou un arco circular e despois un punto exterior e unha solución de tanxente. As vistas previas só mostran os puntos de tanxencia sobre o arco debuxado.",
"help.sketch.construction_line.tangent_two": "Escolle dous círculos ou arcos circulares e despois unha tanxente común na vista previa. Os puntos de tanxencia deben estar sobre as curvas debuxadas.",
"help.sketch.construction_line.vertical": "Crea unha recta de construción vertical infinita por un punto. A súa restrición vertical mantense nas edicións posteriores.",
"prefs.persistent_ribbon_pressed": "Resaltar as ordes activas da cinta",
"prefs.persistent_ribbon_pressed_help": "Resalta as ordes da cinta mentres os seus efectos están activados. Desactívao para manter o aspecto neutro e a axuda das ordes anteriores.",
"prefs.sketch_construction_lines": "Ferramentas ampliadas de rectas de construción",
"prefs.sketch_open_look_at": "Xirar para mirar cara ao bosquexo cando se abre",
"prefs.sketch_open_look_at_note": "Desactivado: crear ou editar un bosquexo deixa a vista tal e como está. Mirar cara a orienta a vista cara ao bosquexo cando o queres.",
"recovery.discard_all": "Descartar todo",
"recovery.discard_all_detail": "Esta acción non se pode desfacer.",
"recovery.discard_all_title": "Descartar {count} gardados automáticos?",
"recovery.discard_all_title_one": "Descartar 1 gardado automático?",
"ui.construction.angle_hint": "Recta de construción: fai clic nun punto e despois escribe un ángulo ou escolle unha dirección",
"ui.construction.bisector_hint": "Bisectriz: escolle dúas rectas que se crucen e despois a solución desexada",
"ui.construction.common_hint": "Tanxente común: escolle dous círculos/arcos e despois a solución desexada",
"ui.construction.contact_hint": "Tanxente: escolle un círculo/arco ou unha spline/cónica aberta e despois o punto de tanxencia (curvas de forma libre: extremos)",
"ui.construction.external_hint": "Tanxente: escolle un círculo ou arco, un punto exterior e despois a solución desexada",
"ui.construction.free_hint": "Recta de construción: fai clic en dous puntos; clic dereito para reiniciar",
"ui.construction.point_hint": "Recta de construción: fai clic no punto polo que debe pasar",
"ui.construction.reference_hint": "Recta de construción: escolle unha recta e despois un punto; clic dereito para cambiar a referencia"
}
}
@@ -1,33 +0,0 @@
{
"id": {
"browser.calibrate_click_a": "Klik gambar untuk Titik A, atau ketik koordinatnya.",
"browser.calibrate_click_b": "Klik gambar untuk Titik B, atau ketik koordinatnya.",
"help.sketch.construction_line": "Buat garis bantu tak hingga melalui dua titik. Gunakan untuk menata sketsa tanpa memecah profil.",
"help.sketch.construction_line.angle": "Buat garis bantu tak hingga dengan sudut terhadap sumbu X sketsa. Pilih titiknya dan ketik sudutnya, atau pilih arahnya.",
"help.sketch.construction_line.bisector": "Pilih dua garis sketsa lurus yang berpotongan, lalu pilih salah satu garis bagi sudut. Hasilnya mengikuti garis sumbernya.",
"help.sketch.construction_line.horizontal": "Buat garis bantu horizontal tak hingga melalui sebuah titik. Batasan horizontalnya tetap berlaku pada pengeditan berikutnya.",
"help.sketch.construction_line.parallel": "Pilih sebuah garis sketsa lurus, lalu tempatkan garis bantu tak hingga yang sejajar. Klik kanan untuk memilih referensi lain.",
"help.sketch.construction_line.perpendicular": "Pilih sebuah garis sketsa lurus, lalu tempatkan garis bantu tak hingga yang tegak lurus. Klik kanan untuk memilih referensi lain.",
"help.sketch.construction_line.tangent_at": "Buat garis singgung pada sebuah titik di lingkaran atau busur lingkaran, atau pada titik ujung spline atau kurva konik yang terbuka. Pilih kurvanya, lalu titik singgungnya.",
"help.sketch.construction_line.tangent_external": "Pilih lingkaran atau busur lingkaran, lalu sebuah titik luar dan satu solusi garis singgung. Pratinjau hanya menampilkan titik singgung pada busur yang digambar.",
"help.sketch.construction_line.tangent_two": "Pilih dua lingkaran atau busur lingkaran, lalu pilih garis singgung persekutuan di pratinjau. Titik singgung harus berada pada kurva yang digambar.",
"help.sketch.construction_line.vertical": "Buat garis bantu vertikal tak hingga melalui sebuah titik. Batasan vertikalnya tetap berlaku pada pengeditan berikutnya.",
"prefs.persistent_ribbon_pressed": "Sorot perintah ribbon yang aktif",
"prefs.persistent_ribbon_pressed_help": "Sorot perintah ribbon selama efeknya aktif. Matikan untuk mempertahankan tampilan netral dan bantuan perintah sebelumnya.",
"prefs.sketch_construction_lines": "Alat garis bantu lanjutan",
"prefs.sketch_open_look_at": "Putar untuk menghadap sketsa saat dibuka",
"prefs.sketch_open_look_at_note": "Mati: membuat atau mengedit sketsa membiarkan tampilan apa adanya. Look At menghadapkan tampilan ke sketsa saat Anda menginginkannya.",
"recovery.discard_all": "Buang semua",
"recovery.discard_all_detail": "Aksi ini tidak dapat diurungkan.",
"recovery.discard_all_title": "Buang {count} simpan otomatis?",
"recovery.discard_all_title_one": "Buang 1 simpan otomatis?",
"ui.construction.angle_hint": "Garis bantu: klik sebuah titik, lalu ketik sudut atau pilih arah",
"ui.construction.bisector_hint": "Garis bagi sudut: pilih dua garis lurus yang berpotongan, lalu solusi yang diinginkan",
"ui.construction.common_hint": "Garis singgung persekutuan: pilih dua lingkaran/busur, lalu solusi yang diinginkan",
"ui.construction.contact_hint": "Garis singgung: pilih lingkaran/busur atau spline/kurva konik terbuka, lalu titik singgung (kurva bentuk bebas: titik ujung)",
"ui.construction.external_hint": "Garis singgung: pilih lingkaran atau busur, sebuah titik luar, lalu solusi yang diinginkan",
"ui.construction.free_hint": "Garis bantu: klik dua titik; klik kanan untuk mengulang",
"ui.construction.point_hint": "Garis bantu: klik titik yang akan dilalui",
"ui.construction.reference_hint": "Garis bantu: pilih garis lurus, lalu sebuah titik; klik kanan untuk mengganti referensi"
}
}
-33
View File
@@ -1,33 +0,0 @@
{
"ms": {
"browser.calibrate_click_a": "Klik imej untuk Titik A, atau taip koordinatnya.",
"browser.calibrate_click_b": "Klik imej untuk Titik B, atau taip koordinatnya.",
"help.sketch.construction_line": "Cipta garis binaan tak terhingga melalui dua titik. Gunakannya untuk menyusun lakaran tanpa memisahkan profil.",
"help.sketch.construction_line.angle": "Cipta garis binaan tak terhingga pada sudut daripada paksi X lakaran. Pilih titik dan taip sudutnya, atau pilih arahnya.",
"help.sketch.construction_line.bisector": "Pilih dua garis lakaran lurus yang bersilang, kemudian pilih salah satu pembahagi dua sudut. Hasilnya mengikut garis sumbernya.",
"help.sketch.construction_line.horizontal": "Cipta garis binaan mendatar tak terhingga melalui satu titik. Kekangan mendatarnya kekal apabila disunting kemudian.",
"help.sketch.construction_line.parallel": "Pilih garis lakaran lurus, kemudian letakkan garis binaan tak terhingga yang selari. Klik kanan untuk memilih rujukan lain.",
"help.sketch.construction_line.perpendicular": "Pilih garis lakaran lurus, kemudian letakkan garis binaan tak terhingga yang serenjang. Klik kanan untuk memilih rujukan lain.",
"help.sketch.construction_line.tangent_at": "Cipta tangen pada satu titik pada bulatan atau lengkok bulat, atau pada titik hujung splin atau lengkung kon yang terbuka. Pilih lengkung itu, kemudian titik sentuhnya.",
"help.sketch.construction_line.tangent_external": "Pilih bulatan atau lengkok bulat, kemudian satu titik luar dan satu penyelesaian tangen. Pratonton hanya menunjukkan titik sentuh pada lengkok yang dilukis.",
"help.sketch.construction_line.tangent_two": "Pilih dua bulatan atau lengkok bulat, kemudian pilih tangen sepunya dalam pratonton. Titik sentuh mesti terletak pada lengkung yang dilukis.",
"help.sketch.construction_line.vertical": "Cipta garis binaan menegak tak terhingga melalui satu titik. Kekangan menegaknya kekal apabila disunting kemudian.",
"prefs.persistent_ribbon_pressed": "Serlahkan arahan reben yang aktif",
"prefs.persistent_ribbon_pressed_help": "Serlahkan arahan reben selagi kesannya dihidupkan. Matikan untuk mengekalkan rupa neutral dan bantuan arahan yang terdahulu.",
"prefs.sketch_construction_lines": "Alat garis binaan lanjutan",
"prefs.sketch_open_look_at": "Putar menghadap lakaran apabila dibuka",
"prefs.sketch_open_look_at_note": "Mati: mencipta atau menyunting lakaran membiarkan pandangan seperti sedia ada. Pandang Tepat menghadapkan pandangan ke lakaran apabila anda mahu.",
"recovery.discard_all": "Buang semua",
"recovery.discard_all_detail": "Tindakan ini tidak boleh dibuat asal.",
"recovery.discard_all_title": "Buang {count} simpanan automatik?",
"recovery.discard_all_title_one": "Buang 1 simpanan automatik?",
"ui.construction.angle_hint": "Garis binaan: klik satu titik, kemudian taip sudut atau pilih arah",
"ui.construction.bisector_hint": "Pembahagi dua sudut: pilih dua garis lurus yang bersilang, kemudian penyelesaian yang dikehendaki",
"ui.construction.common_hint": "Tangen sepunya: pilih dua bulatan/lengkok, kemudian penyelesaian yang dikehendaki",
"ui.construction.contact_hint": "Tangen: pilih bulatan/lengkok atau splin/lengkung kon terbuka, kemudian titik sentuh (lengkung bentuk bebas: titik hujung)",
"ui.construction.external_hint": "Tangen: pilih bulatan atau lengkok, satu titik luar, kemudian penyelesaian yang dikehendaki",
"ui.construction.free_hint": "Garis binaan: klik dua titik; klik kanan untuk mula semula",
"ui.construction.point_hint": "Garis binaan: klik titik yang akan dilalui",
"ui.construction.reference_hint": "Garis binaan: pilih garis lurus, kemudian satu titik; klik kanan untuk menukar rujukan"
}
}
@@ -1,33 +0,0 @@
{
"nb": {
"browser.calibrate_click_a": "Klikk på bildet for punkt A, eller skriv inn koordinatene.",
"browser.calibrate_click_b": "Klikk på bildet for punkt B, eller skriv inn koordinatene.",
"help.sketch.construction_line": "Opprett en uendelig hjelpelinje gjennom to punkter. Bruk den til å legge opp en skisse uten å dele opp profilene.",
"help.sketch.construction_line.angle": "Opprett en uendelig hjelpelinje i en vinkel fra skissens X-akse. Velg punktet og skriv inn vinkelen, eller velg retningen.",
"help.sketch.construction_line.bisector": "Velg to rette skisselinjer som krysser hverandre, og velg deretter en av vinkelhalveringslinjene. Resultatet følger kildelinjene sine.",
"help.sketch.construction_line.horizontal": "Opprett en uendelig vannrett hjelpelinje gjennom et punkt. Den vannrette bindingen følger med ved senere endringer.",
"help.sketch.construction_line.parallel": "Velg en rett skisselinje, og plasser deretter en parallell uendelig hjelpelinje. Høyreklikk for å velge en annen referanse.",
"help.sketch.construction_line.perpendicular": "Velg en rett skisselinje, og plasser deretter en vinkelrett uendelig hjelpelinje. Høyreklikk for å velge en annen referanse.",
"help.sketch.construction_line.tangent_at": "Opprett en tangent i et punkt på en sirkel eller sirkelbue, eller i endepunktet på en åpen spline eller kjeglesnittkurve. Velg kurven, og deretter berøringspunktet.",
"help.sketch.construction_line.tangent_external": "Velg en sirkel eller sirkelbue, deretter et eksternt punkt og en tangentløsning. Forhåndsvisningene viser bare berøringspunkter på den tegnede buen.",
"help.sketch.construction_line.tangent_two": "Velg to sirkler eller sirkelbuer, og velg deretter en felles tangent i forhåndsvisningen. Berøringspunktene må ligge på de tegnede kurvene.",
"help.sketch.construction_line.vertical": "Opprett en uendelig loddrett hjelpelinje gjennom et punkt. Den loddrette bindingen følger med ved senere endringer.",
"prefs.persistent_ribbon_pressed": "Fremhev aktive kommandoer på båndet",
"prefs.persistent_ribbon_pressed_help": "Fremhev kommandoer på båndet mens effektene deres er slått på. Slå av for å beholde det forrige nøytrale utseendet og den forrige kommandohjelpen.",
"prefs.sketch_construction_lines": "Utvidede hjelpelinjeverktøy",
"prefs.sketch_open_look_at": "Orienter mot skissen når den åpnes",
"prefs.sketch_open_look_at_note": "Av: å opprette eller redigere en skisse lar visningen stå som den er. Orienter mot vender visningen mot skissen når du vil.",
"recovery.discard_all": "Forkast alle",
"recovery.discard_all_detail": "Dette kan ikke angres.",
"recovery.discard_all_title": "Forkast {count} automatiske lagringer?",
"recovery.discard_all_title_one": "Forkast 1 automatisk lagring?",
"ui.construction.angle_hint": "Hjelpelinje: klikk på et punkt, og skriv deretter inn en vinkel eller velg en retning",
"ui.construction.bisector_hint": "Vinkelhalveringslinje: velg to rette linjer som krysser hverandre, og deretter ønsket løsning",
"ui.construction.common_hint": "Felles tangent: velg to sirkler/buer, og deretter ønsket løsning",
"ui.construction.contact_hint": "Tangent: velg en sirkel/bue eller åpen spline/kjeglesnittkurve, og deretter berøringspunktet (frie kurver: endepunkter)",
"ui.construction.external_hint": "Tangent: velg en sirkel eller bue, et eksternt punkt, og deretter ønsket løsning",
"ui.construction.free_hint": "Hjelpelinje: klikk på to punkter; høyreklikk for å starte på nytt",
"ui.construction.point_hint": "Hjelpelinje: klikk på punktet linjen skal gå gjennom",
"ui.construction.reference_hint": "Hjelpelinje: velg en rett linje, og deretter et punkt; høyreklikk for å bytte referanse"
}
}
@@ -1,33 +0,0 @@
{
"zh-TW": {
"browser.calibrate_click_a": "按一下影像以指定點 A,或輸入其座標。",
"browser.calibrate_click_b": "按一下影像以指定點 B,或輸入其座標。",
"help.sketch.construction_line": "建立通過兩點的無限長建構直線。可用來規劃草圖佈局,而不會分割輪廓。",
"help.sketch.construction_line.angle": "建立與草圖 X 軸夾指定角度的無限長建構直線。請選取點並輸入角度,或選取其方向。",
"help.sketch.construction_line.bisector": "選取兩條相交的草圖直線,再選擇其中一條角平分線。結果會跟隨其來源直線。",
"help.sketch.construction_line.horizontal": "建立通過某一點的水平無限長建構直線。其水平拘束會在後續編輯中持續維持。",
"help.sketch.construction_line.parallel": "選取一條草圖直線,再放置與其平行的無限長建構直線。按右鍵可選擇其他參考。",
"help.sketch.construction_line.perpendicular": "選取一條草圖直線,再放置與其垂直的無限長建構直線。按右鍵可選擇其他參考。",
"help.sketch.construction_line.tangent_at": "在圓或圓弧上的某一點,或在開放雲形線或圓錐曲線的端點建立切線。請先選取曲線,再選取其切點。",
"help.sketch.construction_line.tangent_external": "選取圓或圓弧,再選取外部點與一個切線解。預覽只會顯示位於所繪圓弧上的切點。",
"help.sketch.construction_line.tangent_two": "選取兩個圓或圓弧,再於預覽中選擇一條公切線。切點必須位於所繪曲線上。",
"help.sketch.construction_line.vertical": "建立通過某一點的鉛直無限長建構直線。其鉛直拘束會在後續編輯中持續維持。",
"prefs.persistent_ribbon_pressed": "醒目顯示作用中的功能區命令",
"prefs.persistent_ribbon_pressed_help": "在功能區命令的效果開啟期間醒目顯示這些命令。關閉可維持先前的中性外觀與命令說明。",
"prefs.sketch_construction_lines": "延伸建構直線工具",
"prefs.sketch_open_look_at": "開啟草圖時轉向正對草圖",
"prefs.sketch_open_look_at_note": "關閉:建立或編輯草圖時維持目前的檢視。需要時可用「注視」正對草圖。",
"recovery.discard_all": "全部捨棄",
"recovery.discard_all_detail": "此操作無法復原。",
"recovery.discard_all_title": "捨棄 {count} 個自動儲存檔?",
"recovery.discard_all_title_one": "捨棄 1 個自動儲存檔?",
"ui.construction.angle_hint": "建構直線:按一下一點,再輸入角度或選取方向",
"ui.construction.bisector_hint": "角平分線:選取兩條相交的直線,再選取所需的解",
"ui.construction.common_hint": "公切線:選取兩個圓/圓弧,再選取所需的解",
"ui.construction.contact_hint": "切線:選取圓/圓弧或開放雲形線/圓錐曲線,再選取切點(自由曲線:端點)",
"ui.construction.external_hint": "切線:選取圓或圓弧、一個外部點,再選取所需的解",
"ui.construction.free_hint": "建構直線:按一下兩點;按右鍵可重新開始",
"ui.construction.point_hint": "建構直線:按一下要通過的點",
"ui.construction.reference_hint": "建構直線:選取一條直線,再選取一點;按右鍵可變更參考"
}
}
@@ -1,33 +0,0 @@
{
"tr": {
"browser.calibrate_click_a": "A Noktası için görüntüye tıklayın veya koordinatlarını yazın.",
"browser.calibrate_click_b": "B Noktası için görüntüye tıklayın veya koordinatlarını yazın.",
"help.sketch.construction_line": "İki noktadan geçen sonsuz bir yardımcı doğru oluşturun. Profilleri bölmeden bir eskizi kurgulamak için kullanın.",
"help.sketch.construction_line.angle": "Eskizin X ekseninden belirli bir açıyla sonsuz bir yardımcı doğru oluşturun. Noktayı seçip açıyı yazın veya yönünü seçin.",
"help.sketch.construction_line.bisector": "Kesişen iki düz eskiz çizgisini seçin, ardından iki açıortaydan birini seçin. Sonuç, kaynak çizgilerini izler.",
"help.sketch.construction_line.horizontal": "Bir noktadan geçen sonsuz yatay bir yardımcı doğru oluşturun. Yatay kısıtlaması sonraki düzenlemelerde de korunur.",
"help.sketch.construction_line.parallel": "Düz bir eskiz çizgisi seçin, ardından paralel bir sonsuz yardımcı doğru yerleştirin. Başka bir referans seçmek için sağ tıklayın.",
"help.sketch.construction_line.perpendicular": "Düz bir eskiz çizgisi seçin, ardından dik bir sonsuz yardımcı doğru yerleştirin. Başka bir referans seçmek için sağ tıklayın.",
"help.sketch.construction_line.tangent_at": "Bir daire veya dairesel yay üzerindeki bir noktada ya da açık bir spline veya konik eğrinin uç noktasında teğet oluşturun. Önce eğriyi, sonra değme noktasını seçin.",
"help.sketch.construction_line.tangent_external": "Bir daire veya dairesel yay seçin, ardından bir dış nokta ve bir teğet çözümü seçin. Önizlemeler yalnızca çizilen yay üzerindeki değme noktalarını gösterir.",
"help.sketch.construction_line.tangent_two": "İki daire veya dairesel yay seçin, ardından önizlemede ortak bir teğet seçin. Değme noktaları çizilen eğriler üzerinde olmalıdır.",
"help.sketch.construction_line.vertical": "Bir noktadan geçen sonsuz dikey bir yardımcı doğru oluşturun. Dikey kısıtlaması sonraki düzenlemelerde de korunur.",
"prefs.persistent_ribbon_pressed": "Etkin şerit komutlarını vurgula",
"prefs.persistent_ribbon_pressed_help": "Şerit komutlarını, etkileri açık olduğu sürece vurgula. Önceki nötr görünümü ve komut yardımını korumak için kapatın.",
"prefs.sketch_construction_lines": "Genişletilmiş yardımcı doğru araçları",
"prefs.sketch_open_look_at": "Eskiz açıldığında görünümü ona doğru döndür",
"prefs.sketch_open_look_at_note": "Kapalı: eskiz oluşturmak veya düzenlemek görünümü olduğu gibi bırakır. Bak komutu, istediğinizde görünümü eskize çevirir.",
"recovery.discard_all": "Tümünü at",
"recovery.discard_all_detail": "Bu işlem geri alınamaz.",
"recovery.discard_all_title": "{count} otomatik kayıt atılsın mı?",
"recovery.discard_all_title_one": "1 otomatik kayıt atılsın mı?",
"ui.construction.angle_hint": "Yardımcı doğru: bir noktaya tıklayın, ardından bir açı yazın veya bir yön seçin",
"ui.construction.bisector_hint": "Açıortay: kesişen iki düz çizgiyi, ardından istenen çözümü seçin",
"ui.construction.common_hint": "Ortak teğet: iki daire/yay, ardından istenen çözümü seçin",
"ui.construction.contact_hint": "Teğet: bir daire/yay veya açık spline/konik eğri, ardından değme noktasını seçin (serbest biçimli eğriler: uç noktalar)",
"ui.construction.external_hint": "Teğet: bir daire veya yay, bir dış nokta, ardından istenen çözümü seçin",
"ui.construction.free_hint": "Yardımcı doğru: iki noktaya tıklayın; yeniden başlamak için sağ tıklayın",
"ui.construction.point_hint": "Yardımcı doğru: geçeceği noktaya tıklayın",
"ui.construction.reference_hint": "Yardımcı doğru: düz bir çizgi, ardından bir nokta seçin; referansı değiştirmek için sağ tıklayın"
}
}
+1172 -1
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -606,6 +606,8 @@
"cmd.appearance_library": "Biblioteca de aparências",
"cmd.appearance_list": "Listar aparências",
"cmd.assembly_bom": "Lista de materiais",
"cmd.assembly_mass_properties": "Propriedades de massa",
"cmd.body.print_face": "Face de impressão",
"cmd.body_rename": "Renomear corpo",
"cmd.browser_group": "Novo grupo",
"cmd.browser_move": "Mover itens do navegador",
@@ -1292,6 +1294,8 @@
"prefs.orientation": "Orientação",
"prefs.overall_speed": "Velocidade geral",
"prefs.preview_choices": "Prévia das suas escolhas",
"prefs.print_face_highlight": "Destaque da face de impressão",
"prefs.print_face_highlight_tip": "Intensidade da cor e do contorno da face de impressão de um corpo na vista. 0 % não mostra destaque.",
"prefs.quality_default": "padrão",
"prefs.quality_fastest": "mais rápido",
"prefs.quality_slowest": "mais lento",
@@ -1571,6 +1575,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
"ui.3d_mouse_use_direct": "Usar driver direto",
"ui.3d_mouse_use_direct_tip": "Ler o mouse 3D diretamente (hidraw) em vez de pelo spacenavd.",
"ui.3d_mouse_view_axis": "Eixo da vista",
"ui.3d_mouse_view_off": "Desligado",
"ui.3d_mouse_view_off_tip": "O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
@@ -1840,6 +1846,7 @@
"ui.circle": "Círculo",
"ui.circular_pattern": "Padrão circular",
"ui.clear": "Limpar",
"ui.clear_print_face": "Limpar face de impressão",
"ui.clear_recent": "Limpar recentes",
"ui.clear_the_selection": "Limpar a seleção",
"ui.click_selects_whole_bodies": "O clique seleciona corpos inteiros",
@@ -2085,6 +2092,7 @@
"ui.infinite": "Infinita",
"ui.insert": "Inserir",
"ui.inspect": "Inspecionar",
"ui.instances": "Instâncias",
"ui.intersect": "Interseção",
"ui.isolate": "Isolar",
"ui.join": "Unir",
@@ -2198,6 +2206,11 @@
"ui.polygon": "Polígono",
"ui.preferences": "Preferências",
"ui.press_a_key": "pressione uma tecla…",
"ui.print_face_body_not_sent": "O corpo dessa face não está sendo enviado: adicione-o primeiro a Corpos",
"ui.print_face_cleared": "{body}: face de impressão removida",
"ui.print_face_input": "Face de impressão",
"ui.print_face_pick": "Selecione uma face plana de um corpo sólido para torná-la a face de impressão",
"ui.print_face_set": "{body}: face de impressão definida; Enviar ao fatiador apoia o corpo sobre ela",
"ui.profile": "Perfil",
"ui.profile_plane": "Plano do perfil",
"ui.profiles": "Perfis",
@@ -2388,7 +2401,9 @@
"update.button": "Atualizar",
"update.button_version": "Atualizar para {version}",
"update.check_automatically": "Verificar atualizações automaticamente",
"update.check_for_updates": "Verificar atualizações",
"update.check_now": "Verificar agora",
"update.check_unavailable": "A verificação de atualizações não está disponível para esta instalação.",
"update.checking": "Verificando…",
"update.copy": "Copiar",
"update.dmg_opened": "A imagem de disco está aberta e verificada: arraste o SolidCraft para Aplicativos, substituindo a cópia antiga, e reinicie o SolidCraft.",
File diff suppressed because it is too large Load Diff
+492 -1
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "СОПРЯГАЮЩАЯ ПОВЕРХНОСТЬ",
"BOUNDARY SURFACE": "ГРАНИЧНАЯ ПОВЕРХНОСТЬ",
"BROWSER": "БРАУЗЕР",
"Back to the original order": "Вернуть исходный порядок",
"Ball": "Шаровое",
"Bar": "Полоса",
"Base Center": "Центр основания",
@@ -114,7 +115,7 @@
"Circular": "Круговой",
"Circumference": "Окружность",
"Clockwise": "По часовой стрелке",
"Close draft": "Закрыть анализ уклонов",
"Close draft": "Закрыть черновик",
"Collapse": "Свернуть",
"Collisions": "Столкновения",
"Colour": "Цвет",
@@ -593,6 +594,8 @@
"Snap to face and edge centres": "Привязка к центрам граней и рёбер",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft не может отправлять отчёты напрямую. Отправьте отчёт на GitHub; токен в приложении не нужен.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft закрылся до сохранения этих дизайнов. Можно открыть последнюю автосохранённую версию.",
"Sort A to Z": "Сортировать от А до Я",
"Sort Z to A": "Сортировать от Я до А",
"Spacing 2 must be a non-zero number": "Интервал 2 должен быть ненулевым числом",
"Spacing must be a non-zero number": "Интервал должен быть ненулевым числом",
"Specialise": "Специализировать",
@@ -772,6 +775,8 @@
"at the face": "у грани",
"bom.group_children": "Вложенные детали в строке своей детали",
"browser.body_name": "Тело{number}",
"browser.calibrate_click_a": "Щёлкните по изображению, чтобы задать точку A, или введите её координаты.",
"browser.calibrate_click_b": "Щёлкните по изображению, чтобы задать точку B, или введите её координаты.",
"browser.calibrate_title": "Калибровка: {name}",
"browser.capture_many": "Компоненты ({count}) перемещены, но их положения не зафиксированы.",
"browser.capture_note": "«Зафиксировать положение» сохраняет новое положение; «Вернуть положение» восстанавливает прежнее.",
@@ -784,6 +789,13 @@
"browser.sketch_attachment_lost": "{name} (привязка потеряна)",
"browser.sketch_editing": "{name} (редактируется)",
"browser.sketch_name": "Эскиз{number}",
"browser.sketches_to_parts_already_in_part": "уже в своей детали",
"browser.sketches_to_parts_design_body": "на теле самого дизайна",
"browser.sketches_to_parts_moved": "Перемещено эскизов в деталь, на которой они лежат: {moved}",
"browser.sketches_to_parts_no_exact_attachment": "на грани без точной привязки",
"browser.sketches_to_parts_not_on_face": "не на грани детали",
"browser.sketches_to_parts_part_missing": "их детали больше нет",
"browser.sketches_to_parts_result": "Перемещено эскизов в деталь, на которой они лежат: {moved}; остались на месте: {stayed} — {why}",
"browser.units": "Единицы: {units}",
"cam.flat": "Плоский",
"cam.follower": "Толкатель",
@@ -813,6 +825,8 @@
"cmd.appearance_pick_up": "Взять внешний вид",
"cmd.appearance_replace": "Заменить внешний вид",
"cmd.assembly_bom": "Спецификация",
"cmd.assembly_mass_properties": "Массовые характеристики",
"cmd.body.print_face": "Грань печати",
"cmd.body_rename": "Переименовать тело",
"cmd.browser_collapse_all": "Свернуть всё",
"cmd.browser_expand_all": "Развернуть всё",
@@ -849,6 +863,7 @@
"cmd.component_move_bodies": "Переместить тела в компонент",
"cmd.component_move_features": "Переместить операции в компонент",
"cmd.component_move_sketches": "Переместить эскизы в компонент",
"cmd.component_move_sketches_to_parts": "Переместить эскизы в их детали",
"cmd.component_paste_from_design": "Вставить из другого дизайна",
"cmd.component_paste_new": "Вставить как новый",
"cmd.component_properties": "Физические свойства компонента",
@@ -1175,6 +1190,7 @@
"cmd.sketch_spline_control_point_5": "Сплайн по контрольным точкам (степень 5)",
"cmd.sketch_spline_fit_point": "Сплайн по точкам сглаживания",
"cmd.sketch_spline_insert_point": "Добавить точку сплайна",
"cmd.sketch_spline_remove_point": "Удалить точку сплайна",
"cmd.sketch_spline_reverse": "Обратить направление сплайна",
"cmd.sketch_spun_profile": "Профиль вращения",
"cmd.sketch_toggle_diameter": "Переключить радиус / диаметр",
@@ -1228,6 +1244,7 @@
"cmd.view.render_browser": "Браузер рендера",
"cmd.view.render_dock": "Переместить панель рендера",
"cmd.view.render_focus": "Режим фокуса рендера",
"cmd.view.status_details": "Подробности состояния видового экрана",
"cmd.view.status_items": "Элементы состояния видового экрана",
"cmd.view_ambient_occlusion": "Окружающее затенение",
"cmd.view_angle_readout": "Углы вида",
@@ -1266,8 +1283,14 @@
"color.hex_hint": "#rrggbb",
"color.hue": "Оттенок",
"color.linear": "Значения цвета показаны в линейном пространстве 0–1",
"color.recent": "Недавние цвета",
"color.recent_help": "Цвета, выбранные в любом поле цвета, появляются здесь",
"color.saturation": "Насыщенность (в процентах)",
"color.saturation_value": "Насыщенность и яркость",
"color.selected": "Выбранный цвет",
"color.step_down": "Уменьшить на шаг",
"color.step_up": "Увеличить на шаг",
"color.value": "Значение, то есть яркость (в процентах)",
"config.column_filter_empty": "Нет подходящих столбцов",
"contact.components_count": "Компонентов: {count}",
"contact.enabled_label": "включено",
@@ -1313,6 +1336,7 @@
"dialog.dimension_insert_hint": "Дважды щёлкните другой управляющий размер, чтобы вставить его имя",
"dialog.dimension_name": "Размер: {name}",
"dialog.edit_title": "ИЗМЕНИТЬ: {title}",
"dialog.pattern_sketch_not_object": "Выберите тела или операции: эскиз — ни то, ни другое. Повторите его кривые собственным инструментом «Массив» или «Зеркало» в эскизе либо сначала выдавите его.",
"dialog.select": "↖ Выбрать",
"dialog.selected_count": "↖ Выбрано: {count}",
"dialog.selection_guidance": "Щёлкните поле ввода, чтобы изменить его. Щелчок по выделенному объекту убирает его; Ctrl/Cmd+щелчок тоже переключает. Проверьте предпросмотр и нажмите OK.",
@@ -1370,25 +1394,242 @@
"from the rule": "из правила",
"help.apache": "Лицензия Apache 2.0",
"help.appearance.assign": "Задаёт выбранным телам, граням или компонентам цвет или вид материала, не меняя их вес. Используйте, чтобы различать детали или заранее увидеть отделку.",
"help.assembly.bom": "Перечисляет детали дизайна или компонента с их количеством по номерам деталей и может сохранить список в CSV или JSON. Используйте как список деталей для заказа и сборки.",
"help.attributions": "Сведения об авторстве (ATTRIBUTION.md)",
"help.build": "Версия {version} · коммит {commit} · сборка от {date}",
"help.canvas.decal": "Помещает изображение на плоскую грань модели, как наклейку. Используйте, чтобы показать на детали надпись, логотип или печатный рисунок.",
"help.canvas.edit": "Меняет подложку: её положение, размер, угол, непрозрачность, отражение, видимость или имя. Используйте, чтобы совместить образец с эскизом, который вы по нему обводите.",
"help.canvas.insert": "Помещает изображение PNG или JPEG на плоскость или грань как полупрозрачный образец. Используйте, чтобы обвести в эскизе фотографию или чертёж, масштабировав изображение до известного размера.",
"help.component.break_link": "Отсоединяет связанный компонент от файла дизайна, оставляя его тела как есть. Используйте, когда деталь больше не должна следовать изменениям исходного файла.",
"help.component.capture_position": "Сохраняет положения деталей, которые вы передвинули вручную, как их новые места. Используйте после перетаскивания деталей на место, если их не удерживает ни одно сопряжение.",
"help.component.create": "Добавляет в дизайн новый пустой компонент: внутрь активного, если вы не выберете другого родителя. Используйте, чтобы дать новой детали сборки собственное место для тел и эскизов.",
"help.component.finish_edit": "Сохраняет деталь, которую вы изменяете на месте, возвращается в её сборку и обновляет сборку по ней. Используйте, когда изменения связанной детали готовы.",
"help.component.from_bodies": "Превращает каждое выбранное тело в отдельный компонент с именем этого тела. Используйте, когда дизайн из разрозненных тел должен стать сборкой, детали которой можно соединять и перемещать.",
"help.component.insert_design": "Вставляет другой сохранённый дизайн как компонент, связанный со своим файлом; его твёрдые тела размещаются так же, как в оригинале. Используйте, чтобы повторно применить деталь в сборке и подхватывать последующие изменения командой «Обновить связи».",
"help.component.properties": "Показывает объём, массу, центр масс и моменты инерции компонента по материалам его тел. Используйте, чтобы проверить вес и баланс сборки.",
"help.component.revert_position": "Возвращает вручную перемещённые детали на прежние места, отбрасывая незафиксированные перемещения. Используйте, чтобы отменить пробные перемещения в сборке.",
"help.component.update_links": "Заново читает связанные файлы дизайнов и заменяет изменившиеся тела. Используйте после правки детали, которую применяет эта сборка.",
"help.config.configure": "Добавляет параметры, операции, материалы или внешние виды как столбцы таблицы конфигурации. Используйте, чтобы создавать варианты одного дизайна, например размеры детали.",
"help.config.preview": "Проверяет, что строка таблицы конфигурации пересобирается, не меняя дизайн и его историю. Используйте перед переключением на вариант, в котором вы не уверены.",
"help.config.rules": "Показывает правила таблицы конфигурации, её строки и активную строку. Используйте, чтобы увидеть, как настроены варианты дизайна.",
"help.config.table": "Показывает таблицу конфигурации: строка на вариант, столбец на параметр или операцию, и совпадает ли дизайн с активной строкой. Используйте, чтобы просмотреть варианты дизайна.",
"help.connect_note": "Перезапустите каждого ассистента, чтобы загрузить сервер SolidCraft. Перед изменением каждого файла создана резервная копия с расширением .bak.",
"help.connect_title": "Подключение ИИ-ассистентов",
"help.construct.axis.edge": "Создаёт вспомогательную ось вдоль прямого ребра или линии эскиза. Используйте как ось для вращения, кругового массива или сопряжения.",
"help.construct.axis.normal_to_face": "Создаёт вспомогательную ось через точку перпендикулярно грани. Используйте как ось, прямо выходящую из поверхности.",
"help.construct.axis.through_cylinder": "Создаёт вспомогательную ось через центр цилиндрической, конической или тороидальной грани либо круглого ребра. Используйте, чтобы вращать элементы или делать массив вокруг круглой детали.",
"help.construct.axis.two_planes": "Создаёт вспомогательную ось в месте пересечения двух плоскостей или плоских граней. Используйте, когда нужная ось — линия между двумя гранями.",
"help.construct.axis.two_points": "Создаёт вспомогательную ось через две точки, вершины или точки эскиза. Используйте для оси, идущей между двумя известными положениями.",
"help.construct.plane.along_path": "Создаёт вспомогательную плоскость, перпендикулярную ребру или кривой, в выбранном месте вдоль неё. Используйте, чтобы нарисовать профиль для операции «Сдвиг» или сечение траектории.",
"help.construct.plane.at_angle": "Создаёт вспомогательную плоскость, повёрнутую на угол от плоскости или грани вокруг оси или ребра. Используйте для наклонных эскизов, например для кронштейна под углом.",
"help.construct.plane.midplane": "Создаёт вспомогательную плоскость посередине между двумя плоскостями или плоскими гранями либо делящую пополам угол между ними. Используйте как плоскость симметрии для зеркального отражения.",
"help.construct.plane.offset": "Создаёт вспомогательную плоскость, параллельную плоскости или плоской грани, на заданном расстоянии или через точку. Используйте, чтобы рисовать на заданной высоте над гранью.",
"help.construct.plane.perpendicular": "Создаёт вспомогательную плоскость, содержащую линию или ребро и перпендикулярную плоскости или грани. Используйте, чтобы рисовать на модели вертикально вдоль ребра.",
"help.construct.plane.tangent": "Создаёт вспомогательную плоскость, касающуюся цилиндра, конуса, сферы или тора в точке. Используйте, чтобы рисовать на боковой стороне круглой детали.",
"help.construct.plane.three_points": "Создаёт вспомогательную плоскость через три точки. Используйте для плоскости, заданной положениями на модели, например тремя углами.",
"help.construct.plane.two_edges": "Создаёт вспомогательную плоскость через два прямых ребра или линии, лежащие в одной плоскости. Используйте для плоскости сквозь деталь, например через два противоположных ребра.",
"help.construct.point.along_path": "Создаёт вспомогательную точку в выбранном месте вдоль ребра или кривой. Используйте как ориентир на полпути вдоль траектории.",
"help.construct.point.center": "Создаёт вспомогательную точку в центре круглого ребра, окружности или дуги эскиза, сферы или тора. Используйте как ориентир для отверстий, осей и сопряжений.",
"help.construct.point.edge_plane": "Создаёт вспомогательную точку там, где ребро или линия пересекает плоскость или плоскую грань. Используйте, чтобы отметить, где траектория пересекает плоскость.",
"help.construct.point.three_planes": "Создаёт вспомогательную точку там, где сходятся три плоскости или плоские грани. Используйте как угловой ориентир, даже если у модели нет вершины.",
"help.construct.point.two_edges": "Создаёт вспомогательную точку там, где два ребра или линии пересекаются или подходят ближе всего друг к другу. Используйте как ориентир в воображаемом углу.",
"help.construct.point.vertex": "Создаёт вспомогательную точку в вершине или точке эскиза. Используйте как ориентир, который остаётся, когда окружающий эскиз или тело скрыты.",
"help.contact.check": "Перечисляет пары деталей в наборах контактов, которые в текущем положении проходят друг сквозь друга. Используйте, чтобы найти столкновения до привода сопряжений или исследования движения.",
"help.contact.create": "Объединяет две или более детали в набор контактов: их тела не проходят друг сквозь друга, пока двигаются сопряжения. Используйте для деталей, которые должны останавливаться при касании, например рычага у упора.",
"help.contact.disable_all": "Отключает контакт, так что приводимые сопряжения и исследования движения больше не останавливаются там, где детали соприкасаются. Используйте, чтобы снова свободно двигать детали или чтобы большая сборка отзывалась быстрее.",
"help.contact.edit": "Меняет набор контактов: входящие в него детали, имя или подавление; набор можно и удалить. Используйте, чтобы изменить, какие детали не должны проходить друг сквозь друга.",
"help.contact.enable_all": "Помещает все детали сборки в один набор контактов, так что никакие две детали не проходят друг сквозь друга, пока двигаются сопряжения. Используйте для быстрой проверки всего механизма.",
"help.contact.enable_sets": "Включает контакт для заданных вами наборов контактов, так что приводимые сопряжения и исследования движения останавливаются там, где их детали прошли бы друг сквозь друга. Используйте, чтобы вернуть свои наборы после отключения контакта.",
"help.contact.list": "Перечисляет наборы контактов дизайна, детали в каждом из них и включён ли контакт. Используйте, чтобы проверить настройку контактов перед приводом сборки.",
"help.document.inspect": "Показывает обзор дизайна: его компоненты, тела, эскизы, операции и параметры. Используйте, чтобы сориентироваться в дизайне, который вы создавали не сами.",
"help.document.units": "Меняет единицы, в которых дизайн показывает длины, например мм или дюймы. Размер геометрии не меняется; подстраиваются только отображение и числа без единиц.",
"help.edit.checkpoint": "Запоминает дизайн, активный эскиз и историю отмен в их текущем состоянии. Используйте перед рискованной попыткой, от которой, возможно, захочется вернуться.",
"help.edit.restore_checkpoint": "Возвращается к контрольной точке вместе с историей отмен. Используйте, чтобы одним шагом отбросить всё сделанное после контрольной точки.",
"help.explode.create": "Создаёт разобранный вид: детали отодвигаются от центра сборки или на заданные вами смещения. Используйте, чтобы показать, как собирается сборка, для инструкций или презентаций.",
"help.face.delete": "Удаляет выбранные грани, и соседние грани закрывают промежуток, либо промежуток остаётся открытым как поверхность. Используйте, чтобы убрать скруглённое ребро, отверстие или выступ, которые мешают.",
"help.file.insert_mesh": "Добавляет файл 3MF, STL, OBJ, PLY или glTF в дизайн как тела-меши в новом компоненте. Используйте, чтобы разместить рядом с дизайном скачанную или отсканированную модель как образец.",
"help.file.insert_step": "Добавляет файл STEP, IGES, FreeCAD или Rhino в дизайн как твёрдые тела в новом компоненте; детали сборки становятся вложенными компонентами. Используйте, чтобы подключать модели от поставщиков или из других CAD-программ.",
"help.file.save_mesh": "Сохраняет тела как меш STL, 3MF или OBJ с выбранной гладкостью криволинейных граней. Используйте для подготовки деталей к 3D-печати или для программ, читающих меши.",
"help.file.send_to_slicer": "Сохраняет выбранные тела как файл 3MF и открывает его в выбранном слайсере. Используйте, чтобы перейти от модели к печати одним шагом.",
"help.font_details": "Интерфейс использует встроенные шрифты egui (Ubuntu Light и Hack под лицензиями Ubuntu Font Licence и MIT/Bitstream Vera; Noto Emoji и шрифт значков эмодзи под лицензиями OFL и MIT). Для текста эскизов используется Liberation Sans (SIL OFL 1.1). Необязательный пакет шрифтов The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), не входит в сборки SolidCraft.",
"help.fonts": "Шрифты",
"help.frame.member": "Размещает профиль, например трубу или алюминиевый экструзионный профиль, вдоль линий эскиза рамы, по одному элементу на линию. Используйте для рам, стеллажей и сварных конструкций.",
"help.inspect.accessibility": "Раскрашивает грани, до которых инструмент может дотянуться с одного направления, и те, до которых не может. Используйте, чтобы проверить деталь на обработку или формование с этой стороны.",
"help.inspect.center_of_mass": "Показывает центр масс тел, каждого по отдельности и всех вместе, при равномерной плотности. Используйте, чтобы проверить баланс и понять, куда деталь опрокинется.",
"help.inspect.colored_components": "Задаёт каждому компоненту свой временный цвет, не меняя внешний вид. Используйте, чтобы с первого взгляда различать детали сборки.",
"help.inspect.compare": "Сравнивает два тела и показывает, где у одного есть материал, которого нет у другого, с объёмом и отклонением. Используйте, чтобы сверить изменённую или импортированную деталь с оригиналом.",
"help.inspect.continuity": "Проверяет, насколько плавно грани стыкуются по рёбрам (зазоры, изломы или скачки кривизны), и закрашивает швы. Используйте, чтобы найти места, где поверхности стыкуются неаккуратно.",
"help.inspect.curvature_comb": "Рисует гребёнки вдоль кривых, рёбер или граней: их зубья показывают, насколько сильно они изгибаются. Используйте, чтобы заметить плоские участки, изломы и неравномерную кривизну.",
"help.inspect.curvature_map": "Раскрашивает грани по силе кривизны, красным там, где радиус мал. Используйте, чтобы находить тесные места и оценивать гладкость поверхности.",
"help.inspect.draft": "Раскрашивает грани по углу уклона относительно направления вытягивания и отмечает те, где он слишком мал. Используйте, чтобы проверить деталь перед формованием или литьём.",
"help.inspect.environment_map": "Показывает модель с отражающей, зеркальной отделкой. Используйте, чтобы оценить плавность поверхностей по тому, как по ним бегут отражения.",
"help.inspect.interference": "Находит перекрывающиеся тела и показывает каждое перекрытие с его объёмом. Используйте, чтобы проверить сборку на сталкивающиеся детали.",
"help.inspect.isocurve": "Рисует изопараметрические кривые граней: сетку, на которой построена поверхность. Используйте, чтобы увидеть, как течёт поверхность и где она сгущается.",
"help.inspect.measure": "Измеряет длины, расстояния, углы, площади и радиусы между выбранными элементами либо объём и массу тел. Используйте, чтобы проверить размеры и зазоры.",
"help.inspect.minimum_radius": "Находит наименьший радиус кривизны кривых, рёбер или граней и показывает, где он. Используйте, чтобы убедиться, что инструмент или сопло смогут пройти по форме.",
"help.inspect.section": "Разрезает вид по плоскости и штрихует срезанные грани, не меняя модель. Используйте, чтобы заглянуть внутрь деталей и сборок.",
"help.inspect.zebra": "Показывает чёрно-белые полосы, отражённые на гранях модели. Используйте для оценки гладкости: полосы, которые обрываются или ломаются на ребре, указывают на плохой переход.",
"help.joint.as_built": "Соединяет две детали там, где они уже стоят, не двигая ни одну, и задаёт, как они ещё могут двигаться. Используйте для деталей, которые вы уже поставили в окончательное положение.",
"help.joint.cam": "Связывает вращающийся кулачок с толкателем, так что толкатель скользит или качается, пока под ним проходит контур кулачка. Используйте для кулачков, эксцентриков и приводов клапанов.",
"help.joint.create": "Соединяет две детали в выбранных точках сопряжения и ставит вторую деталь на место; тип сопряжения задаёт, как они ещё могут двигаться. Используйте, чтобы собрать сборку, детали которой движутся как настоящий механизм.",
"help.joint.drive": "Задаёт угол или расстояние сопряжения и двигает вместе с ним детали в пределах ограничений; связанные сопряжения и замкнутые контуры следуют за ним. Используйте, чтобы проверить, что механизм движется как задумано.",
"help.joint.gear": "Связывает два вращающихся сопряжения передаточным отношением, так что поворот одного вращает другое. Используйте для цилиндрических и внутренних зубчатых колёс, задавая отношение по радиусам делительных окружностей или числам зубьев.",
"help.joint.limits": "Задаёт, насколько сопряжение может повернуться или сдвинуться, минимумом и максимумом, либо снимает эти пределы. Используйте, чтобы удерживать шарнир, ползун или рычаг в реальном диапазоне хода.",
"help.joint.motion_link": "Связывает два сопряжения отношением и смещением, так что приведение одного двигает другое. Используйте для фиксированной связи между сопряжениями, которую не охватывают «Зубчатая передача», «Реечная передача» и «Винт».",
"help.joint.origin": "Размещает именованную точку сопряжения на детали: в центре грани или круглого торца, в середине ребра, в углу или в точке. Используйте, чтобы подготовить места крепления сопряжений; точка, выбранная на грани, следует за ней при изменении размера детали.",
"help.joint.rack_pinion": "Связывает вращающуюся шестерню с движущейся рейкой, так что рейка смещается на длину делительной окружности, которую прокатывает шестерня. Используйте для реечных приводов, рулевых механизмов и линейных столов.",
"help.joint.reach": "Перемещает точку детали к цели, меняя сопряжения её цепи в пределах ограничений. Используйте, чтобы ставить механизм в позу по тому, где должен оказаться его конец, например кончик руки робота.",
"help.joint.rigid_group": "Жёстко скрепляет выбранные детали друг с другом как есть, так что они движутся как одно целое. Используйте для деталей, которые составляют единое целое и не должны двигаться друг относительно друга.",
"help.joint.screw": "Связывает вращение и скольжение шагом, так что каждый оборот смещает на это расстояние. Используйте для ходовых винтов, болтов и резьбовых шпинделей.",
"help.library.apply": "Применяет материал или внешний вид из файла локальной библиотеки к телам, граням или компонентам. Используйте, чтобы повторно применять материалы, сохранённые в другом дизайне.",
"help.library.export": "Сохраняет материалы и внешние виды дизайна в файл библиотеки. Используйте, чтобы поделиться ими или применять их в других дизайнах.",
"help.library.inspect": "Проверяет файл библиотеки и перечисляет её материалы и внешние виды, не меняя дизайн. Используйте, чтобы узнать, что содержит библиотека, перед применением.",
"help.licences": "Лицензии",
"help.material.assign": "Задаёт, из чего сделаны выбранные тела, например сталь, алюминий или ABS. Используйте, чтобы масса и вес соответствовали реальной детали.",
"help.material.properties.validate": "Проверяет карточку данных материала на согласованность чисел и пропущенные значения. Используйте перед тем, как полагаться на эти данные при расчёте массы или жёсткости.",
"help.mesh.convert": "Перестраивает тело-меш, например STL, в редактируемое твёрдое тело с плоскими и цилиндрическими гранями. Используйте, чтобы менять импортированные механические детали как смоделированное тело.",
"help.mit": "Лицензия MIT",
"help.motion.study": "Задаёт значения сопряжений по шагам вдоль временной шкалы и воспроизводит их как анимацию. Используйте, чтобы наблюдать движение механизма по последовательности и проверять зазоры по ходу.",
"help.motion.trace": "Прослеживает путь точки детали во время воспроизведения исследования движения или привода сопряжения и после подтверждения сохраняет его как кривую 3D-эскиза. Используйте, чтобы увидеть, куда достаёт механизм, или строить по его траектории.",
"help.notice": "NOTICE",
"help.occurrence.ground": "Закрепляет деталь на месте или снова освобождает её, так что сопряжения двигают остальные детали вокруг неё. Используйте для рамы или основания, на которых построена остальная сборка.",
"help.occurrence.move": "Перемещает деталь сборки только по степеням свободы, разрешённым её сопряжениями. Выберите грань детали, затем перетащите доступную стрелку или дугу.",
"help.parameters.change": "Открывает таблицу именованных значений и выражений, управляющих размерами дизайна. Используйте, чтобы изменить размер в одном месте, а все операции, которые его используют, обновились следом.",
"help.parameters.export": "Сохраняет параметры дизайна как CSV или JSON. Используйте, чтобы править их в электронной таблице или переносить в другой дизайн.",
"help.parameters.graph": "Показывает, какие параметры и операции используют какие другие, а также циклы и ошибки среди них. Используйте, чтобы увидеть, на что повлияет изменение параметра.",
"help.parameters.import": "Читает параметры из CSV или JSON в дизайн: все или ни одного. Используйте, чтобы подтянуть значения из электронной таблицы или другого дизайна.",
"help.parameters.move": "Перемещает пользовательский параметр в списке перед другим параметром или после него. Используйте, чтобы держать связанные параметры рядом.",
"help.parts.fastener": "Добавляет стандартный винт, болт, гайку, шайбу, штифт или подшипник выбранного размера. Поставленный на отверстие, он садится в него через сопряжение, так что крепёж не нужно моделировать самому.",
"help.parts.insert": "Вставляет стандартную деталь выбранного семейства, размера и длины. Поставленная на кромку или стенку отверстия, она садится на это отверстие через жёсткое сопряжение.",
"help.parts.library": "Открывает стандартные детали: винты, болты, гайки, шайбы, установочные винты, цилиндрические штифты и подшипники стандартных размеров. Используйте, чтобы добавить стандартную деталь, а не моделировать её самому.",
"help.parts.resize": "Меняет размер или длину вставленной стандартной детали и перестраивает её; сопряжения удерживают её на месте. Используйте, если винт оказался слишком длинным или отверстию нужен другой размер.",
"help.plastic.assign_rule": "Назначает правило пластика телам, так что их пластиковые операции берут из него уклон и зазор по умолчанию. Используйте, чтобы один раз настроить формованную деталь для всех её операций.",
"help.plastic.boss": "Добавляет бобышку под винт на грани: стойку с необязательным отверстием, уклоном, скруглением у основания и поддерживающими рёбрами жёсткости. Используйте для точек крепления винтов в формованных или печатных корпусах.",
"help.plastic.lip": "Добавляет выступ или ответный паз вдоль кромки тонкостенного тела. Используйте, чтобы две половины корпуса фиксировались друг на друге и плотно закрывались.",
"help.plastic.manage_rules": "Создаёт и изменяет правила пластика: материал, толщину стенки, радиусы, зазор и уклон. Используйте, чтобы операции формованной детали были согласованы.",
"help.plastic.rest": "Добавляет опору на грани: круглую или прямоугольную площадку, сплошную или полую, с уклоном. Используйте как опору или стойку в формованном корпусе.",
"help.plastic.snap_fit": "Добавляет защёлку-крючок на грани с заданными длиной, толщиной и зацепом. Используйте, чтобы скреплять детали без винтов.",
"help.sheet.convert": "Превращает пластину или оболочку одинаковой толщины в тело из листового металла с гибами на выбранных рёбрах. Используйте, чтобы сделать существующую деталь разворачиваемой.",
"help.sheet.export_bend_table": "Сохраняет таблицу гибов тела из листового металла как CSV или JSON: углы, радиусы и положения гибов на развёртке. Используйте для листогибочного пресса или мастерской.",
"help.sheet.export_dxf": "Сохраняет развёртку как DXF с контурами, линиями гибов и необязательными примечаниями о гибах. Используйте для лазерной, плазменной или гидроабразивной резки.",
"help.sheet.flange": "Добавляет фланец: пластину-основание из эскиза, стенку вдоль открытого контура эскиза или отогнутую стенку на рёбрах листа. Используйте для построения деталей из листового металла.",
"help.sheet.flat_pattern": "Создаёт развёртку тела из листового металла с её размером, вырезами и линиями гибов. Используйте, чтобы проверить заготовку перед резкой.",
"help.sheet.fold": "Сгибает лист вдоль линии эскиза на заданный угол с заданным радиусом. Используйте, чтобы согнуть плоский лист после того, как в нём вырезаны элементы.",
"help.sheet.hem": "Загибает кромку листа назад на себя. Используйте для безопасных жёстких кромок без острых заусенцев.",
"help.sheet.manage_rules": "Создаёт и изменяет правила листового металла: толщину, радиус гиба, коэффициент K, разгрузки и зазоры. Используйте, чтобы подогнать их под материал и инструменты вашей мастерской.",
"help.sheet.path_flange": "Добавляет цепочку фланцев вдоль кромки листа по открытой цепочке линий эскиза. Используйте для профилей вроде U- или Z-образного за один шаг.",
"help.sheet.perforate": "Добавляет поле отверстий по всему плоскому листу в шахматном или квадратном порядке. Используйте для вентиляционных отверстий, решёток и облегчённых панелей.",
"help.sheet.refold": "Снова сгибает разогнутое тело из листового металла. Используйте после добавления элементов на плоский лист.",
"help.sheet.unfold": "Разгибает все гибы тела из листового металла. Используйте, чтобы добавить отверстия или вырезы, которые должны пересекать гиб, а затем согните снова.",
"help.sketch.arc.center_point": "Рисует дугу окружности вокруг центральной точки от начальной точки до конечной. Используйте, когда центр дуги известен, например для радиуса вокруг отверстия или оси.",
"help.sketch.arc.tangent": "Рисует дугу, которая начинается в конце линии или дуги и продолжается по касательной к ней. Используйте, чтобы продолжить цепочку плавным изгибом вместо угла.",
"help.sketch.arc.three_point": "Рисует дугу от начальной точки до конечной через третью точку. Используйте, когда дуга должна проходить через заданные точки, а не вокруг известного центра.",
"help.sketch.arc3d": "Рисует точную дугу окружности в пространстве через три точки при включённом 3D-эскизе. Используйте для изгибов траекторий и направляющих, выходящих из плоскости эскиза.",
"help.sketch.auto_constrain": "Добавляет ограничения, которым эскиз уже почти соответствует (горизонтальность, параллельность, совпадение), а затем проставляет размеры, пока он не станет полностью определён. Используйте, чтобы зафиксировать грубо нарисованный эскиз за один шаг.",
"help.sketch.auto_constrain_from_datum": "Работает как «Автоограничения», но отсчитывает положения от выбранной базовой точки, а не от начала координат. Используйте, когда деталь размечена от одной опорной точки, например угла или отверстия.",
"help.sketch.blend_curve": "Соединяет концы двух кривых плавным сплайном, касательным к обеим, а при желании и с непрерывной кривизной. Используйте, чтобы перекрыть зазор между кривыми без излома.",
"help.sketch.break": "Разделяет кривую на части там, где её пересекают другие кривые, или в точках, которые вы на ней поставите. Используйте, когда часть кривой должна получить собственное ограничение или быть удалена отдельно.",
"help.sketch.cancel": "Выходит из эскиза и отбрасывает всё, что изменено с момента его создания или открытия для правки. Отмена последнего действия вернёт изменения.",
"help.sketch.centerline": "Переключает линии между обычными и осевыми: осями, которые профили не учитывают. Используйте осевую линию как ось для «Вращения», зеркала или ограничения симметрии.",
"help.sketch.chamfer.distance_angle": "Снимает фаску на углу между двумя линиями по расстоянию вдоль первой линии и углу. Используйте, когда на чертеже фаска задана длиной и углом, например 2 мм × 30°.",
"help.sketch.chamfer.equal_distance": "Снимает фаску на углу между двумя линиями на одинаковое расстояние по обеим. Используйте для обычной фаски 45° на контуре.",
"help.sketch.chamfer.two_distance": "Снимает фаску на углу между двумя линиями с отдельным расстоянием вдоль каждой линии. Используйте для фасок не под 45°, заданных двумя длинами.",
"help.sketch.circle.center": "Рисует окружность по центру и диаметру. Используйте для отверстий, бобышек и других круглых элементов с известным центром.",
"help.sketch.circle.three_point": "Рисует окружность через три точки. Используйте, когда окружность должна пройти через заданные точки, например существующей геометрии.",
"help.sketch.circle.three_tangent": "Рисует окружность, касающуюся трёх линий, окружностей или дуг. Используйте, чтобы вписать окружность в пространство между тремя кривыми, например вписанную окружность треугольника.",
"help.sketch.circle.two_point": "Рисует окружность по двум концам её диаметра. Используйте, когда известны точки на краю окружности, а не её центр.",
"help.sketch.circle.two_tangent": "Рисует окружность заданного размера, касающуюся двух линий, окружностей или дуг. Используйте для скругления фиксированного радиуса между двумя кривыми.",
"help.sketch.conic": "Рисует коническую кривую между двумя конечными точками; её форму задают вершина и значение rho. Используйте для плавных переходов, наполненность которых нужно подстроить: от плоской до почти острого угла.",
"help.sketch.constrainer": "Применяет ограничение, которому одна или две выбранные кривые уже почти соответствуют (горизонтальность, параллельность, касание или равенство). Используйте, чтобы привести в порядок нарисованный от руки эскиз по одному ограничению за раз.",
"help.sketch.constraint.coincident": "Помещает точку на другую точку, кривую или ось эскиза. Используйте, чтобы закрыть зазоры в профиле или поставить точку на линию.",
"help.sketch.constraint.collinear": "Располагает две линии на одной прямой. Используйте, чтобы выровнять прерванные рёбра, например обе стороны выемки.",
"help.sketch.constraint.concentric": "Задаёт двум окружностям или дугам общий центр. Используйте для колец, цековок и валов с уступами.",
"help.sketch.constraint.curvature": "Заставляет две кривые, встречающиеся в конечной точке, продолжаться с одинаковой кривизной (G2). Используйте там, где касательного стыка недостаточно для плавности, например на видимых поверхностях.",
"help.sketch.constraint.equal": "Делает линии одинаковой длины, а окружности и дуги — одинакового радиуса. Используйте, чтобы один размер управлял несколькими одинаковыми элементами.",
"help.sketch.constraint.fix": "Фиксирует точки или кривые на месте или снова освобождает их. Используйте для геометрии, которая не должна двигаться при перетаскивании или решении эскиза.",
"help.sketch.constraint.horizontal_vertical": "Делает линию, или линию между двумя точками, горизонтальной или вертикальной, в зависимости от того, что ближе. Используйте, чтобы выровнять контур по осям эскиза.",
"help.sketch.constraint.midpoint": "Ставит точку на середину линии или совмещает середины двух линий. Используйте, чтобы центрировать геометрию на ребре.",
"help.sketch.constraint.parallel": "Делает две линии параллельными. Используйте для сторон, которые должны оставаться параллельными при изменении других размеров.",
"help.sketch.constraint.perpendicular": "Делает так, чтобы две линии встречались под прямым углом. Используйте для прямых углов между линиями, не лежащими на осях эскиза.",
"help.sketch.constraint.polygon": "Превращает замкнутую цепочку линий в правильный многоугольник: равные стороны, все углы на одной окружности. Используйте, чтобы сделать нарисованный от руки многоугольник правильным и сохранить это.",
"help.sketch.constraint.symmetry": "Делает две точки или кривые зеркальными друг для друга относительно линии или центральной точки. Используйте, чтобы профиль оставался симметричным при изменении одной стороны.",
"help.sketch.constraint.tangent": "Заставляет две кривые касаться, например линию и дугу. Используйте для плавных стыков без излома.",
"help.sketch.construction": "Переключает кривые между обычной и вспомогательной геометрией, которую профили не учитывают. Используйте вспомогательные кривые как помощники для разметки, ориентиры и размеры.",
"help.sketch.construction_line": "Создайте бесконечную вспомогательную прямую через две точки. С её помощью можно разметить эскиз, не разделяя профили.",
"help.sketch.construction_line.angle": "Создайте бесконечную вспомогательную прямую под углом к оси X эскиза. Укажите точку и введите угол либо задайте направление.",
"help.sketch.construction_line.bisector": "Выберите две пересекающиеся прямые эскиза, затем одну из двух биссектрис. Результат изменяется вместе с исходными прямыми.",
"help.sketch.construction_line.horizontal": "Создайте бесконечную горизонтальную вспомогательную прямую через точку. Её горизонтальное ограничение сохраняется при последующих изменениях.",
"help.sketch.construction_line.parallel": "Выберите прямую эскиза, затем разместите параллельную бесконечную вспомогательную прямую. Щелчок правой кнопкой — выбрать другую исходную прямую.",
"help.sketch.construction_line.perpendicular": "Выберите прямую эскиза, затем разместите перпендикулярную бесконечную вспомогательную прямую. Щелчок правой кнопкой — выбрать другую исходную прямую.",
"help.sketch.construction_line.tangent_at": "Создайте касательную в точке окружности или дуги окружности либо в конечной точке открытого сплайна или коники. Выберите кривую, затем точку касания.",
"help.sketch.construction_line.tangent_external": "Выберите окружность или дугу, затем внешнюю точку и решение для касательной. В предпросмотре показаны только точки касания на нарисованной дуге.",
"help.sketch.construction_line.tangent_two": "Выберите две окружности или дуги, затем в предпросмотре выберите общую касательную. Точки касания должны лежать на нарисованных кривых.",
"help.sketch.construction_line.vertical": "Создайте бесконечную вертикальную вспомогательную прямую через точку. Её вертикальное ограничение сохраняется при последующих изменениях.",
"help.sketch.copy": "Копирует выбранные кривые и точки эскиза вместе с ограничениями и размерами между ними. Вставьте их в этот эскиз или в другой.",
"help.sketch.create": "Начинает новый 2D-эскиз на плоскости или плоской грани модели. Каждое «Выдавливание», «Вращение» и «Сдвиг» начинается с эскиза.",
"help.sketch.cut": "Копирует выбранную геометрию эскиза и удаляет её из эскиза одним шагом отмены. Вставьте её в другое место этого эскиза или в другой эскиз.",
"help.sketch.dimension": "Добавляет управляющий размер: длину, расстояние, угол, радиус или диаметр, в зависимости от выбранного. Введите значение или имя параметра, и эскиз последует за ним.",
"help.sketch.edit": "Открывает существующий эскиз для правки. Операции, построенные на нём, обновятся, когда вы завершите эскиз.",
"help.sketch.ellipse": "Рисует эллипс по центру, концу большой оси и точке на малой стороне. Используйте для овальных проёмов, кулачков и других вытянутых круглых форм.",
"help.sketch.equation_curve": "Рисует кривую по формулам: x(t) и y(t) либо y = f(x) на заданном интервале. В формулах можно использовать параметры, и кривая следует за ними. Используйте для синусоиды, спирали или эвольвенты зуба шестерни.",
"help.sketch.extend": "Удлиняет линию или дугу до ближайшей кривой на её пути либо наращивает две кривые, пока они не встретятся. Используйте, чтобы закрыть зазоры в профиле после обрезки или правки.",
"help.sketch.fillet": "Скругляет угол, где встречаются две линии или дуги, дугой заданного радиуса. Несколько углов, скруглённых вместе, получают один радиус.",
"help.sketch.finish": "Выходит из эскиза и возвращается к модели. Его замкнутые профили становятся готовы для «Выдавливания», «Вращения» и других операций.",
"help.sketch.finish_auto_constrain": "Запускает «Автоограничения» для эскиза, затем завершает его. Используйте, чтобы выйти из эскиза полностью определённым за один шаг.",
"help.sketch.fit_curves_to_section": "Разрезает меш или твёрдое тело плоскостью эскиза и подбирает линии и дуги по сечению. Используйте, чтобы воссоздать отсканированную или импортированную деталь как редактируемый эскиз.",
"help.sketch.include_3d": "Переносит рёбра модели, вершины и кривые других эскизов в этот эскиз как связанные ссылки, проецируя на его плоскость те, что лежат вне её. Используйте, чтобы строить по геометрии, не лежащей в плоскости эскиза.",
"help.sketch.infinite": "Переключает линии между обычными и бесконечными вспомогательными, идущими через весь вид. Используйте их как ориентиры разметки, которые никогда не оказываются слишком короткими.",
"help.sketch.insert_dxf": "Читает кривые чертежа DXF в эскиз, масштабируя, поворачивая и размещая их так, как вы зададите. Используйте, чтобы строить по 2D-чертежу из другой программы, например по контуру для лазерной резки.",
"help.sketch.insert_svg": "Читает контуры рисунка SVG в эскиз, масштабируя, поворачивая и размещая их так, как вы зададите. Используйте, чтобы превратить логотип или векторный контур в кривые, которые можно выдавить.",
"help.sketch.inspect": "Открывает отчёт об открытом эскизе: сколько осталось степеней свободы и определён ли он полностью. Используйте, чтобы выяснить, что ещё двигается, прежде чем завершить эскиз.",
"help.sketch.intersect": "Добавляет кривые или точки, где тела, грани, рёбра или другие эскизы пересекают плоскость эскиза. Они остаются связанными и следуют за моделью при её изменении.",
"help.sketch.intersection_curve": "Создаёт 3D-кривые там, где встречаются два тела или две грани, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
"help.sketch.isoparametric_curve": "Создаёт 3D-кривую вдоль направления U или V грани через выбранную точку, связанную с гранью. Используйте как траекторию или линию разделения, следующую за поверхностью.",
"help.sketch.line": "Рисует соединённые линии точка за точкой, как открытую цепочку или замкнутый контур. Это основной инструмент для большинства профилей.",
"help.sketch.line.midpoint": "Рисует линию от её середины до одного конца, второй конец отражается зеркально. Используйте для линий с центром в точке, например симметричного ребра.",
"help.sketch.line3d": "Рисует соединённые прямые линии между точками в пространстве при включённом 3D-эскизе. Используйте для траекторий и рам, выходящих из плоскости эскиза.",
"help.sketch.mirror": "Отражает кривые и точки эскиза относительно линии или оси и добавляет ограничения симметрии. Используйте, чтобы нарисовать половину симметричного профиля и достроить его.",
"help.sketch.move": "Перемещает или поворачивает кривые и точки эскиза либо их копию. Используйте, чтобы переставить геометрию, сохранив её форму и ограничения.",
"help.sketch.offset": "Создаёт копию цепочки кривых на заданном расстоянии с одной или с обеих сторон. Используйте для стенок, контуров и зазоров; расстояние остаётся редактируемым размером.",
"help.sketch.paste": "Вставляет скопированную или вырезанную геометрию эскиза в активный эскиз. Поместите её в точку или сместите на заданную величину.",
"help.sketch.pattern.circular": "Копирует геометрию эскиза вокруг центральной точки на полный круг или заданный угол. Используйте для окружностей болтов, спиц и зубьев.",
"help.sketch.pattern.rectangular": "Копирует геометрию эскиза рядами и столбцами с заданным шагом. Используйте для сеток отверстий, пазов или вентиляционных прорезей.",
"help.sketch.point": "Ставит точку эскиза. Используйте как ориентир для размеров, ограничений, центров отверстий или последующих операций.",
"help.sketch.point3d": "Ставит точку в пространстве при включённом 3D-эскизе. Используйте как ориентир для 3D-кривых и траекторий.",
"help.sketch.polygon.circumscribed": "Рисует правильный многоугольник вокруг окружности; размер задаётся от центра до середины стороны. Используйте, когда известен размер под ключ, например у шестигранной гайки.",
"help.sketch.polygon.edge": "Рисует правильный многоугольник по двум концам одной стороны. Используйте, когда известны длина и положение стороны.",
"help.sketch.polygon.inscribed": "Рисует правильный многоугольник внутри окружности; размер задаётся от центра до угла. Используйте, когда известен размер между углами.",
"help.sketch.project": "Переносит рёбра, грани, вершины модели и кривые других эскизов в этот эскиз, проецируя их на его плоскость. Они остаются связанными и следуют за моделью при её изменении.",
"help.sketch.project_to_surface": "Проецирует кривые других эскизов по нормали этого эскиза на грань как связанные 3D-кривые. Используйте, чтобы нанести логотип, линию разделения или траекторию на изогнутую поверхность.",
"help.sketch.rectangle.center": "Рисует прямоугольник по центру и одному углу. Используйте для контуров с центром в точке, например в начале координат.",
"help.sketch.rectangle.rounded": "Рисует прямоугольник со всеми четырьмя углами, скруглёнными одним радиусом. Используйте для пластин, крышек и проёмов со скруглёнными углами.",
"help.sketch.rectangle.three_point": "Рисует прямоугольник по одной стороне и точке на противоположной. Используйте для прямоугольников под углом к осям эскиза.",
"help.sketch.rectangle.two_point": "Рисует прямоугольник по двум противоположным углам. Используйте для контуров пластин, блоков и карманов.",
"help.sketch.scale": "Масштабирует кривые и точки эскиза на коэффициент относительно базовой точки. Используйте, чтобы изменить размер части эскиза, не рисуя её заново.",
"help.sketch.slot.arc_center": "Рисует изогнутый паз заданной ширины вдоль дуги, заданной центром, началом и концом. Используйте для регулировочных пазов вокруг оси.",
"help.sketch.slot.arc_three_point": "Рисует изогнутый паз заданной ширины вдоль дуги через три точки. Используйте, когда траектория паза должна пройти через заданные точки.",
"help.sketch.slot.center_point": "Рисует прямой паз заданной ширины от его центра до центра одной торцевой дуги. Используйте для пазов с центром в точке.",
"help.sketch.slot.center_to_center": "Рисует прямой паз заданной ширины между центрами двух торцевых дуг. Используйте для пазов, размеры которых заданы от центра до центра, например регулировочных пазов под винты.",
"help.sketch.slot.overall": "Рисует прямой паз заданной ширины от одного внешнего конца до другого. Используйте, когда важна полная длина паза.",
"help.sketch.spline.control_point": "Рисует кубический сплайн по контрольным точкам, через которые он не проходит. Используйте для плавных текучих кривых, форму которых вы задаёте перемещением точек.",
"help.sketch.spline.control_point_5": "Рисует сплайн степени 5 по контрольным точкам. Используйте там, где кривые должны стыковаться с более плавной кривизной, например на видимых стилизованных поверхностях.",
"help.sketch.spline.fit_point": "Рисует плавный сплайн, проходящий через каждую поставленную вами точку, открытый или замкнутый. Используйте, чтобы обвести форму по известным точкам.",
"help.sketch.spline3d": "Рисует плавную кривую в пространстве через точки при включённом 3D-эскизе. Используйте для траекторий сдвига и трасс кабелей, выходящих из плоскости эскиза.",
"help.sketch.spun_profile": "Создаёт в плоскости эскиза контур, который тело заметает, вращаясь вокруг оси. Используйте, чтобы получить силуэт точёной детали для «Вращения» или чертежа.",
"help.sketch.text": "Пишет в эскизе текст выбранным шрифтом, высотой и под выбранным углом; слова могут следовать за параметрами. Выдавите его или нанесите «Тиснением» для надписей и маркировки.",
"help.sketch.transform": "Масштабирует, поворачивает и перемещает геометрию эскиза относительно центральной точки за один шаг. Используйте, чтобы сразу переставить и изменить размер выбранного.",
"help.sketch.trim": "Удаляет участок кривой между ближайшими пересечениями вокруг места щелчка. Используйте, чтобы превратить перекрывающиеся линии в замкнутый профиль.",
"help.solid.align": "Перемещает выбранные тела так, чтобы точка, грань или плоскость на них совпала с точкой, гранью или плоскостью в другом месте. Используйте, чтобы приставить деталь к другой, не измеряя смещение.",
"help.solid.boundary_fill": "Создаёт тела из областей, ограниченных перекрывающимися телами-инструментами, заполняя только выбранные области. Используйте, чтобы построить форму из пространства, которое совместно ограничивают несколько тел.",
"help.solid.bounding_solid": "Создаёт бокс, охватывающий выбранные тела, с необязательным запасом. Используйте, чтобы увидеть габариты детали или как заготовку, из которой её обрабатывают.",
"help.solid.box": "Создаёт бокс по длине, ширине и высоте от угловой точки или с центром в точке. Используйте для быстрого блока, заглушки или тела, в котором вырезают элементы.",
"help.solid.chamfer": "Срезает выбранные рёбра плоской фаской на заданное расстояние. Используйте, чтобы притупить острые кромки или облегчить вход штифта или винта.",
"help.solid.coil": "Создаёт винтовое тело, например пружину, по диаметру, числу витков, высоте или шагу и круглому либо квадратному сечению. Используйте для пружин, спиралей и винтовых гребней.",
"help.solid.combine": "Объединяет тела, вырезает их из целевого тела или оставляет только общий для них объём; тела-инструменты можно сохранить. Используйте, чтобы слить детали, вырезать одной формой другую или найти, где два тела пересекаются.",
"help.solid.cone": "Создаёт конус по окружности основания и высоте или усечённый конус, если у верхней части тоже есть радиус. Используйте для сужений, воронок, зенковок и острий.",
"help.solid.cylinder": "Создаёт цилиндр по радиусу или диаметру и высоте, стоящий на точке вдоль оси. Используйте для валов, штифтов, бобышек и круглых заготовок.",
"help.solid.draft": "Наклоняет выбранные грани на заданный угол, отсчитываемый от нейтральной плоскости. Используйте для литых и формованных деталей, чтобы они свободно извлекались из формы.",
"help.solid.emboss": "Выпячивает профили эскиза, например текст или логотип, из грани или вдавливает их в неё. Используйте для надписей, табличек и маркировки на плоских и круглых гранях.",
"help.solid.extrude": "Выдавливает профиль эскиза в 3D на заданное расстояние, добавляя или удаляя материал. Используйте, чтобы превратить плоский контур в брусок, выступ или карман.",
"help.solid.fillet": "Скругляет выбранные рёбра плавным круговым переходом заданного радиуса. Используйте, чтобы смягчить острые рёбра ради прочности, удобства или внешнего вида.",
"help.solid.hole": "Сверлит круглое отверстие заданного диаметра в выбранной точке грани. Используйте для отверстий под винты и штифты и других простых круглых проёмов.",
@@ -1397,20 +1638,60 @@
"help.solid.move": "Перемещает или поворачивает выбранные тела либо их копию на заданное расстояние и угол. Используйте, чтобы переставить тело или создать второе в другом месте.",
"help.solid.offset_face": "Смещает выбранные грани внутрь или наружу на заданное расстояние, а окружающие грани следуют за ними. Используйте, чтобы сделать стенку толще, карман глубже или выступ выше, не редактируя эскиз.",
"help.solid.pattern.circular": "Повторяет выбранные объекты вокруг оси с заданным количеством и общим углом. Используйте для болтов по окружности, спиц, рёбер и зубьев.",
"help.solid.pattern.path": "Копирует операции или тела вдоль траектории эскиза по числу и шагу. Используйте для отверстий вдоль изогнутого ребра или деталей вдоль направляющей.",
"help.solid.pattern.rectangular": "Повторяет выбранные объекты в одном или двух направлениях с заданным количеством и шагом. Используйте для рядов и сеток отверстий, рёбер жёсткости, штифтов или ножек.",
"help.solid.pipe": "Создаёт круглую трубу вдоль траектории из кривых эскиза, сплошную или полую с заданной толщиной стенки. Используйте для труб, проводов, кабелей и направляющих.",
"help.solid.press_pull": "Добавляет или удаляет материал при перетаскивании грани либо скругляет рёбра при их перетаскивании. Используйте для быстрых изменений прямо на модели.",
"help.solid.remove": "Убирает выбранные тела из дизайна начиная с этого места в истории; предыдущие шаги их сохраняют. Используйте, чтобы убрать вспомогательное тело, например режущий инструмент, который больше не нужен.",
"help.solid.replace_face": "Переносит выбранные плоские грани на плоскость или на параллельную грань, и тело растёт или уменьшается, чтобы достичь её. Используйте, чтобы совместить грань вровень с другой деталью или вспомогательной плоскостью.",
"help.solid.revolve": "Вращает профиль эскиза вокруг оси, создавая тело вращения. Используйте для валов, ручек, чаш и любых деталей, круглых относительно осевой линии.",
"help.solid.rib": "Добавляет ребро жёсткости: тонкую стенку вдоль открытой кривой эскиза, доведённую до ближайших граней тела. Используйте, чтобы усилить стенки и углы, например в формованных или печатных деталях.",
"help.solid.scale": "Увеличивает или уменьшает выбранные тела на коэффициент, одинаковый по всем направлениям или свой для каждой оси. Используйте, чтобы изменить размер импортированной формы или учесть усадку.",
"help.solid.shell": "Делает тело полым, оставляя стенки заданной толщины, и может открывать выбранные грани. Используйте для коробок, крышек и других тонкостенных деталей.",
"help.solid.sphere": "Создаёт сферу по радиусу или диаметру вокруг центральной точки. Используйте для шаров, ручек и скруглённых концов.",
"help.solid.split_body": "Разрезает тело на отдельные тела плоскостью, гранью другого тела или линией эскиза. Используйте, чтобы получить детали, которые печатаются, обрабатываются или окрашиваются отдельно.",
"help.solid.split_face": "Делит выбранные грани по плоскости, грани другого тела или кривым эскиза, не разрезая тело. Используйте, чтобы задать части грани собственный уклон, цвет или смещение.",
"help.solid.sweep": "Протягивает профиль эскиза вдоль траектории, создавая тело. Используйте для труб, направляющих, ручек и любых деталей постоянного сечения, следующих по кривой.",
"help.solid.thread": "Добавляет метрическую резьбу ISO на цилиндрическую грань: условную или вырезанную в теле. Используйте для болтов, резьбовых отверстий и печатных винтовых деталей.",
"help.solid.torus": "Создаёт кольцо по большому радиусу вокруг его оси и малому радиусу круглого сечения. Используйте для уплотнительных колец, ручек и круглых бусин.",
"help.solid.web": "Добавляет тонкую перегородку вдоль каждой открытой кривой эскиза, каждую доведённую до ближайших граней тела. Используйте для сетки поддерживающих перегородок внутри корпусов и лотков.",
"help.surface.blend": "Создаёт поверхность, соединяющую рёбра двух тел, касательную или с непрерывной кривизной к обоим. Используйте, чтобы плавно соединить две поверхности через зазор.",
"help.surface.boundary": "Создаёт поверхность, заполняющую контур из двух–четырёх кривых эскиза, или сеть кривых. Используйте для свободных панелей, заданных своими краями.",
"help.surface.extend": "Удлиняет поверхность за её кромки на расстояние или до плоскости или грани. Используйте, чтобы поверхности перекрывались перед обрезкой или сшиванием.",
"help.surface.extrude": "Выдавливает кривые или профили эскиза на расстояние в поверхность: только стенки, без торцов. Используйте для тонких стенок и режущих или разделяющих инструментов.",
"help.surface.fair": "Сглаживает внутреннюю часть свободных граней, пока их кромки остаются на месте. Используйте, чтобы выровнять бугры и волнистые отражения на поверхности.",
"help.surface.loft": "Создаёт поверхность, проходящую по порядку через два или более сечений, без торцов. Используйте для оболочек и переходов между разными формами.",
"help.surface.match": "Перестраивает кромку поверхности так, чтобы она стыковалась с кромкой другого тела, с согласованными касательными или кривизной. Используйте, чтобы плавно закрыть шов между двумя поверхностями.",
"help.surface.offset": "Создаёт копию граней на расстоянии по их нормалям как новое тело-поверхность. Используйте для оболочек, зазоров или другой стороны стенки.",
"help.surface.patch": "Заполняет область эскиза или замкнутый контур рёбер гранью, плоской или плавно изогнутой. Используйте, чтобы закрывать отверстия в теле-поверхности.",
"help.surface.reverse_normal": "Переворачивает грани тела-поверхности, так что их внешняя сторона смотрит в другую сторону. Используйте перед утолщением или сшиванием, если грани направлены внутрь.",
"help.surface.revolve": "Вращает кривые или профили эскиза вокруг оси в поверхность, без торцов. Используйте для круглых оболочек, таких как абажуры, чаши и сопла.",
"help.surface.ruled": "Создаёт полосу из прямых линий от цепочки рёбер вдоль нормали грани, по касательной или под углом. Используйте для стенок с уклоном, переходных участков и поверхностей разъёма.",
"help.surface.stitch": "Сшивает тела-поверхности по общим рёбрам, а если они замыкаются, получается твёрдое тело. Используйте, чтобы превратить набор поверхностей в одно тело.",
"help.surface.sweep": "Протягивает кривые или профили эскиза вдоль траектории в поверхность, без торцов. Используйте для желобов, накладок и других тонких форм, следующих за траекторией.",
"help.surface.thicken": "Придаёт телу-поверхности толщину, превращая его в твёрдое тело. Используйте, чтобы получать из поверхностей детали для печати или обработки.",
"help.surface.trim": "Обрезает поверхность плоскостью, другой поверхностью или кривыми эскиза и оставляет выбранную сторону. Используйте, чтобы подрезать поверхности по форме перед сшиванием.",
"help.surface.unstitch": "Разделяет грани тела на отдельные тела-поверхности; твёрдое тело распадается на поверхности. Используйте, чтобы переработать отдельные грани тела.",
"help.surface.untrim": "Удаляет отверстия из грани или возвращает её внешнюю границу к естественным границам поверхности. Используйте, чтобы вернуть полную поверхность после обрезки.",
"help.third_party": "Лицензии сторонних компонентов",
"help.timeline.compute_all": "Перестраивает все операции в истории с самого начала. Используйте, чтобы обновить модель и увидеть, какие операции завершаются с ошибкой.",
"help.timeline.delete": "Удаляет выбранные операции из истории; при удалении эскиза также удаляются операции, построенные на нём. Используйте, чтобы убрать шаг, который больше не нужен.",
"help.timeline.dependents": "Перечисляет операции, которые будут удалены вместе с выбранной, и те, что перестанут работать без неё. Используйте перед удалением или изменением операции.",
"help.timeline.play": "Воспроизводит оставшиеся операции по одной с паузой в полсекунды после каждой перестройки. Чтобы остановить, запустите эту команду снова или нажмите Esc; «Перейти в конец» переходит мгновенно.",
"help.view.apply": "Поворачивает вид к стандартному направлению, например спереди, сверху или изометрии, либо к сохранённому именованному виду. Используйте, чтобы смотреть на модель под фиксированным углом.",
"help.view.delete": "Удаляет сохранённый именованный вид. Модель не меняется.",
"help.view.fit": "Масштабирует и сдвигает вид так, чтобы вся модель или выделенное заполнили его. Используйте, когда модель ушла из поля зрения.",
"help.view.grid": "Показывает или скрывает сетку на видовом экране. Используйте как ориентир размера и направления при моделировании.",
"help.view.ground_shadow": "Показывает или скрывает тень модели на земле. Используйте, чтобы легче оценивать высоту и положение.",
"help.view.home": "Возвращает вид в главное направление и вписывает модель. Используйте, чтобы быстро вернуться к известному виду.",
"help.view.list": "Перечисляет именованные виды, сохранённые в дизайне. Используйте, чтобы найти вид, сохранённый ранее.",
"help.view.navigate": "Поворачивает, сдвигает или масштабирует вид шагами, например четверть оборота влево. Используйте, чтобы точно двигать камеру вместо перетаскивания.",
"help.view.navigation_bar": "Показывает или скрывает панель навигации внизу видового экрана. Скройте её, чтобы освободить место, и покажите, чтобы пользоваться инструментами вида мышью.",
"help.view.rename": "Даёт сохранённому именованному виду новое имя. Сам вид не меняется.",
"help.view.save": "Сохраняет текущее положение камеры как именованный вид в дизайне. Используйте, чтобы позже вернуться к тому же ракурсу, например для снимков экрана.",
"help.view.spin": "Непрерывно вращает вид вокруг модели до следующего щелчка или нажатия клавиши. Используйте, чтобы показать модель со всех сторон.",
"help.view.tool": "Закрепляет инструмент камеры (орбиту, панорамирование или масштаб), так что перетаскивание левой кнопкой мыши двигает вид, пока вы его не отключите. Используйте на трекпаде или без средней кнопки мыши.",
"help.view.visual_style": "Переключает способ отрисовки модели: тонированная с рёбрами или без них, каркас или со скрытыми рёбрами. Используйте каркас, чтобы заглянуть внутрь, а тонированный вид — для чистого вида.",
"hole_presets.confirm_delete": "Подтвердить удаление",
"hole_presets.delete": "Удалить",
"hole_presets.delete_prompt": "Удалить этот личный пресет? Существующие отверстия не изменятся.",
@@ -1585,6 +1866,10 @@
"material.selection.mixed": "Разные материалы",
"measure.at": "В точке",
"measure.item": "Элемент {number}",
"measure.quick_copied": "Измерение скопировано",
"measure.quick_copy": "Щёлкните, чтобы скопировать измерение",
"measure.quick_edges": "Рёбер: {count}",
"measure.quick_faces": "Граней: {count}",
"messages.ago_h": "{n} ч назад",
"messages.ago_min": "{n} мин назад",
"messages.ago_s": "{n} с назад",
@@ -1618,6 +1903,7 @@
"nav.hfss.help": "Alt+перетаскивание левой кнопкой панорамирует, Shift+Alt+левая кнопка меняет масштаб, Ctrl+перетаскивание левой кнопкой по пустому месту вращает",
"nav.inventor.help": "Перетаскивание средней кнопкой панорамирует, Shift+средняя вращает, Ctrl+средняя меняет масштаб, перетаскивание правой кнопкой вращает",
"nav.maya.help": "Alt+перетаскивание левой кнопкой вращает, Alt+средняя кнопка панорамирует, Alt+правая кнопка меняет масштаб; перетаскивание средней кнопкой тоже панорамирует",
"nav.nx.help": "Перетаскивание средней кнопкой вращает, Shift+средняя панорамирует (в NX — средняя + правая), Ctrl+средняя меняет масштаб (в NX — средняя + левая); перетаскивание правой кнопкой камеру не двигает",
"nav.openscad.help": "Перетаскивание левой кнопкой по пустому месту вращает, Shift+левая выделяет рамкой, перетаскивание геометрии эскиза изменяет её; правая или средняя кнопка панорамирует, Shift+правая или Shift+средняя меняет масштаб",
"nav.perspective.sketch": "При редактировании эскиза используется ортогональный вид. Завершите эскиз, чтобы включить перспективу.",
"nav.right_drag_orbits.help": "Выкл.: перетаскивание правой кнопкой не двигает камеру, а правая кнопка только открывает контекстное меню. Выбор назначения кнопок мыши SolidWorks отключает этот параметр.",
@@ -1737,6 +2023,11 @@
"not a move": "это не перемещение",
"not a number": "не число",
"open a sketch first": "сначала откройте эскиз",
"parameters.apply_live": "Применять каждое изменение при выходе из поля",
"parameters.apply_live_tip": "Выкл.: изменения ждут «Применить» или OK, после чего дизайн один раз пересобирается для всех сразу. «Отмена» или Esc отбрасывает их.",
"parameters.held": "Изменений в ожидании применения: {count}",
"parameters.held_hint": "Правьте выражения и единицы. Изменения ждут «Применить» или OK.",
"parameters.held_one": "1 изменение ждёт применения",
"parts.automatic_name": "автоматически",
"parts.dimension.across_flats": "Размер под ключ",
"parts.dimension.bore": "Диаметр отверстия",
@@ -1759,6 +2050,8 @@
"parts.family.set_screw": "Установочный винт с внутренним шестигранником и плоским концом",
"parts.family.socket_head_cap_screw": "Винт с цилиндрической головкой и внутренним шестигранником",
"parts.family.washer": "Плоская шайба",
"parts.holes": "Отверстия (необязательно)",
"parts.placement_hint": "выберите одно или несколько отверстий, чтобы посадить деталь в каждое, или OK поместит её в начало координат",
"pattern.alignment_2": "Выравнивание 2",
"pattern.alignment_hint": "Количество включает оригинал. Центрирование распределяет копии по обе стороны; при чётном количестве смена направления выбирает сторону с дополнительной копией.",
"pattern.direction_1": "Направление 1",
@@ -1807,6 +2100,8 @@
"prefs.bodies_folders_several": "Только если тел несколько",
"prefs.bottom": "Снизу",
"prefs.browser": "Браузер",
"prefs.browser_eye_left": "Глаз рядом со стрелкой свёртывания",
"prefs.browser_eye_left_note": "Строки браузера показывают глаз показа/скрытия рядом со стрелкой свёртывания, как раньше, а не в столбце на правом конце строк.",
"prefs.browser_reveal_on_hover": "Появляется при наведении",
"prefs.browser_show_note": "«Показан» оставляет браузер на экране. «Появляется при наведении» прячет его, пока указатель не окажется на его месте или на тонкой вкладке у его левого края; он остаётся открытым, пока указатель находится внутри него или открыто его меню.",
"prefs.browser_shown": "Показан",
@@ -1826,6 +2121,8 @@
"prefs.ctrl_cmd": "Ctrl / Cmd",
"prefs.curved_surfaces": "Криволинейные поверхности",
"prefs.curves": "Криволинейные поверхности",
"prefs.dark_parts_shading": "Сохранять форму тёмных деталей",
"prefs.dark_parts_shading_help": "Затенять вид в линейном свете с мягкой заполняющей подсветкой неба, чтобы тёмные грани сохраняли градиент, а не становились чёрными",
"prefs.dead_zone_note": "Заштрихованная полоса — мёртвая зона: движение внутри неё игнорируется.",
"prefs.default": "по умолчанию",
"prefs.design": "Параметры дизайна по умолчанию",
@@ -1840,6 +2137,8 @@
"prefs.edges": "Рёбра и затенение",
"prefs.extend": "Удлинить",
"prefs.extra_navigation": "Ctrl+перетаскивание левой кнопкой по пустому месту вращает вид; удерживайте Space для панорамирования.",
"prefs.extrude_view_isometric": "Поворачивать вид в изометрию при открытии «Выдавливания»",
"prefs.extrude_view_return": "Возвращать прежний вид при закрытии «Выдавливания»",
"prefs.follows_os": "Как в системе",
"prefs.font": "Шрифт",
"prefs.font_fallback": "Символы, которых нет в выбранном шрифте, берутся из встроенного шрифта, поэтому ни одна подпись не показывает пустой квадрат.",
@@ -1855,8 +2154,11 @@
"prefs.graphics_card_auto": "Автоматически",
"prefs.graphics_card_missing": "Сохранённая видеокарта недоступна; используется «Автоматически».",
"prefs.graphics_card_restart": "Вступит в силу после перезапуска SolidCraft. WGPU_ADAPTER_NAME переопределяет этот выбор.",
"prefs.graphics_card_unused": "Сохранённая видеокарта для этого окна не использовалась; используемая видеокарта показана в меню Справка › О SolidCraft и в строке состояния. Попробуйте «Автоматически» или задайте для WGPU_ADAPTER_NAME уникальную часть названия видеокарты.",
"prefs.graphics_note": "Выбирайте между скоростью и плавностью изображения. Каждый вариант показывает свой эффект на образце.",
"prefs.graphics_presets_note": "Пресеты задают сразу все настройки ниже.",
"prefs.hide_constraint_glyphs_zoomed_out": "Скрывать знаки ограничений при уменьшении масштаба",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Знаки ограничений исчезают из вида, когда эскиз уменьшен настолько, что они загромождали бы его. Выкл.: при любом масштабе они сохраняют фиксированный размер на экране.",
"prefs.history": "История",
"prefs.history_origin_bodies": "Показывать начало координат и тела сверху",
"prefs.history_origin_bodies_note": "История слева или под браузером начинается с папки «Тела» активного компонента и начала координат, с их глазками и меню, как в браузере. История внизу не меняется.",
@@ -1867,6 +2169,8 @@
"prefs.history_sketches_note": "Если для эскизов выбрано «Под своей операцией», История слева показывает каждый эскиз на один уровень глубже, под первой операцией, построенной по нему, когда она достаточно широкая для названий. Шкала внизу всегда сохраняет порядок создания.",
"prefs.history_sketches_under_feature": "Под своей операцией",
"prefs.hold_stop_snapping": "Удерживайте, чтобы отключить привязку",
"prefs.home_fit_size": "Размер для «Главная» и «Вписать»",
"prefs.home_fit_size_note": "Какую часть вида занимает модель, когда её кадрируют «Главная», «Вписать», открытый дизайн или смена вида в навигаторе, независимо от того, включён ли параметр ниже. 100 %: заполняет вид между панелями, компасом и панелью навигации.",
"prefs.icons_only": "Только значки",
"prefs.imported_file": "Импортированный файл",
"prefs.input": "Ввод",
@@ -1874,13 +2178,20 @@
"prefs.interface_language": "Язык интерфейса",
"prefs.interface_note": "Как выглядит SolidCraft и где расположены его части.",
"prefs.interval_minutes": "{n} мин",
"prefs.invert_navigator_arrows": "Инвертировать стрелки вращения куба видов",
"prefs.invert_navigator_arrows_note": "Стрелки вокруг куба вращают камеру в ту сторону, куда указывают, как в Fusion и Inventor: стрелка слева указывает вправо и поворачивает камеру вправо. Выкл.: стрелка поворачивает куб в сторону, где она расположена, поэтому левая стрелка поворачивает камеру влево. Изогнутые стрелки крена не затрагиваются.",
"prefs.keep_work_on_tab_switch": "Сохранять открытые диалоги и инструменты при переключении между дизайнами",
"prefs.keep_work_on_tab_switch_note": "Каждая вкладка дизайна сохраняет свой открытый диалог или инструмент вместе с тем, что вы в нём выбрали и ввели, а также свою вкладку панели инструментов. Они снова на месте, когда вы возвращаетесь к дизайну. Выкл.: при переключении дизайнов они отменяются, и показывается вкладка «Модель».",
"prefs.labels": "Подписи",
"prefs.language_system": "Системный",
"prefs.layout": "Расположение",
"prefs.layout_note": "«Компактная» прикрепляет панели к краям окна. Прозрачность браузера доступна в режиме «Плавающая». Сторону ассистента можно изменить на странице «Ассистент».",
"prefs.left": "Слева",
"prefs.left_click": "Щелчок левой кнопкой",
"prefs.line_drag_draws_one": "Нажатие и перетаскивание рисует одну линию",
"prefs.line_drag_draws_one_note": "В инструменте «Линия» нажатие, перетаскивание и отпускание рисуют одну линию от места нажатия до места отпускания, и на этом она заканчивается. Щелчки по-прежнему рисуют цепочку. Выкл.: перетаскивание ставит точку там, где его отпустили, как и щелчок.",
"prefs.line_hover_tangent_arc": "Задержаться на последней точке линии, чтобы перейти к касательной дуге",
"prefs.line_hover_tangent_arc_note": "В инструменте «Линия» после того, как сегмент нарисован, отведите указатель от последней точки цепочки, вернитесь и удерживайте его на ней неподвижно: следующий щелчок завершит касательную дугу, а тот же жест вернёт к линиям. Выкл.: задержка там ничего не делает.",
"prefs.linked_designs": "Связанные дизайны",
"prefs.live_input": "Ввод в реальном времени",
"prefs.live_preview": "Предпросмотр в реальном времени",
@@ -1894,6 +2205,9 @@
"prefs.max_file_size_step": "Файлы STEP и импорта",
"prefs.max_file_size_web": "Веб-приложение открывает файлы STEP и импорта до 512 МБ и дизайны до 256 МБ: вкладка браузера не вмещает более крупные файлы. В настольном приложении эти пределы можно увеличить.",
"prefs.megabytes": "МБ",
"prefs.menu_grid": "Крупные значки",
"prefs.menu_list": "Список",
"prefs.menus": "Меню",
"prefs.minutes": "мин.",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Мышь и тачпад",
@@ -1901,6 +2215,8 @@
"prefs.mouse_mapping": "Назначение кнопок мыши",
"prefs.navigation": "Навигация",
"prefs.navigation_note": "Как мышь, тачпад и 3D-мышь двигают вид.",
"prefs.navigator_fit": "Вписывать модель после смены вида в навигаторе",
"prefs.navigator_fit_note": "Грань, угол или стрелка куба видов либо ось гизмо также направляет взгляд на середину модели с единственного расстояния, с которого она целиком видна со всех сторон (её габаритная сфера). «Главная» при включённом параметре использует тот же масштаб и центр, поэтому выбранные после неё виды только поворачиваются. Выкл.: вид поворачивается на месте, сохраняя свою цель и масштаб.",
"prefs.network_host_remove": "Убрать",
"prefs.network_locations": "Разрешить сетевые расположения в дизайнах",
"prefs.network_locations_note": "Выкл.: связанные файлы дизайна на другом компьютере (\\\\server\\share) открываются только с перечисленных здесь серверов или после того, как вы разрешите сервер по запросу.",
@@ -1913,10 +2229,16 @@
"prefs.origin_plane_names": "Имена плоскостей начала координат",
"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.persistent_ribbon_pressed": "Подсвечивать активные команды на ленте",
"prefs.persistent_ribbon_pressed_help": "Подсвечивает команды на ленте, пока их действие включено. Отключите, чтобы сохранить прежний нейтральный вид и справку по командам.",
"prefs.perspective_fov": "Угол обзора",
"prefs.placement": "Размещение",
"prefs.prefer_free_sketch_points": "Предпочитать подвижные точки эскиза, когда точки перекрываются",
"prefs.preview_choices": "Предпросмотр вашего выбора",
"prefs.print_face_highlight": "Подсветка грани печати",
"prefs.print_face_highlight_tip": "Насколько сильно грань печати тела окрашивается и обводится в виде. 0 % — без подсветки.",
"prefs.profile_active": "Активный профиль",
"prefs.profile_applied": "Профиль применён.",
"prefs.profile_changed": "Настройки изменились после сохранения профиля. «Сохранить» запоминает их в профиле.",
@@ -1938,9 +2260,13 @@
"prefs.quality_fastest": "быстрее всего",
"prefs.quality_slowest": "медленнее всего",
"prefs.quality_smoother": "глаже",
"prefs.quick_measure": "Быстрое измерение в углу вида",
"prefs.quick_measure_note": "Размер выделенного в правом нижнем углу вида: длина ребра, Ø и R для круглого, площадь грани, положение точки, расстояние и угол между двумя элементами, общее значение для нескольких рёбер или граней. Щелчок копирует его.",
"prefs.render_previous_geometry_budget": "Использовать прежний бюджет геометрии предпросмотра рендера",
"prefs.render_previous_geometry_budget_help": "Вернуть прежний фиксированный предел геометрии (512 МиБ на 64-разрядном настольном компьютере, 128 МиБ на остальных устройствах) вместо бюджета, зависящего от устройства.",
"prefs.reports_saved": "Сохранено: {n}",
"prefs.ribbon_click_ends_tool": "Щелчок по кнопке работающего инструмента завершает его",
"prefs.ribbon_click_ends_tool_note": "Щелчок по подсвеченной кнопке используемого инструмента эскиза на ленте завершает инструмент, и щелчки в виде снова выделяют объекты. Выкл.: щелчок снова запускает инструмент.",
"prefs.ribbon_reveal": "Показывать результаты поиска на ленте",
"prefs.ribbon_reveal_note": "Наведение на результат в «Поиске команд» показывает, где команда находится на ленте: её вкладку, кнопку или строку в раскрывающемся списке панели. Команда, запущенная из поиска, мигает там, а выполняемая команда из раскрывающегося списка отмечает свою панель.",
"prefs.right_click": "Правый щелчок",
@@ -1951,6 +2277,8 @@
"prefs.search_empty": "Нет подходящих настроек",
"prefs.select": "Выделить",
"prefs.select_one_item": "Выбрать один элемент",
"prefs.select_other_hidden": "Выбирать скрытую геометрию командой «Выбрать другое»",
"prefs.select_other_hidden_help": "Вкл.: учитывать скрытые тела, эскизы и перекрытые рёбра рядом с указателем. Выкл.: использовать исходный список видимой геометрии под указателем.",
"prefs.selecting": "Выделение",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift только добавляет; Ctrl / Cmd переключает",
@@ -1966,6 +2294,10 @@
"prefs.sketch_exit_view.previous": "Вернуться к прежнему виду",
"prefs.sketch_exit_view.unless_rotated": "Вернуться, если вид не повёрнут",
"prefs.sketch_exit_view_note": "Куда переходит вид после «Завершить эскиз», «Отмена» или двойного Esc.",
"prefs.sketch_face_activates_part": "Эскиз на грани детали делает эту деталь активной",
"prefs.sketch_face_activates_part_note": "Эскиз, начатый на грани тела другого компонента, сначала активирует этот компонент, поэтому эскиз принадлежит ему и в браузере остаётся с этой деталью. После этого деталь остаётся активной. Выкл.: эскиз принадлежит активному компоненту.",
"prefs.sketch_open_look_at": "Поворачивать камеру к эскизу при его открытии",
"prefs.sketch_open_look_at_note": "Выкл.: при создании или редактировании эскиза вид не меняется. «Взгляд на» поворачивает вид к эскизу, когда это нужно.",
"prefs.sketch_recent_snap": "Автопривязка к недавним центрам и серединам",
"prefs.sketch_recent_snap_note": "При рисовании курсор привязывается к месту пересечения направляющих двух последних центров окружностей или середин прямых отрезков. Выкл.: эти направляющие остаются только визуальными, а курсор не смещается.",
"prefs.sketch_selection": "Эскиз и выделение",
@@ -1975,6 +2307,8 @@
"prefs.sketch_tab_always_note": "Вкладка «Эскиз» остаётся на ленте сразу после «Модель», пока не открыт ни один эскиз. Её инструменты рисования создают эскиз на выбранной плоскости или грани либо на плоскости, которую вы выберете следом, и сразу включают нужный инструмент.",
"prefs.sketch_tracking_guides": "Направляющие выравнивания по центру и середине",
"prefs.sketch_tracking_guides_note": "Наведите курсор на центр окружности или середину прямого отрезка, затем двигайтесь по горизонтали или вертикали, чтобы показать временную пунктирную направляющую.",
"prefs.sketch_tracking_points": "Направляющие выравнивания от концов и углов при рисовании",
"prefs.sketch_tracking_points_note": "Пока инструмент рисования ждёт точку, наведение на конец линии или дуги, угол прямоугольника или свободную точку показывает ту же временную пунктирную направляющую, что и для центра или середины. Выкл.: только центры и середины.",
"prefs.sketching": "Эскизирование",
"prefs.snap_drawing_note": "При рисовании или перетаскивании точки эскиза.",
"prefs.source": "Источник",
@@ -1986,6 +2320,24 @@
"prefs.status_error": "Ошибка",
"prefs.status_selected": "Выбрано",
"prefs.status_warning": "Предупреждение",
"prefs.structure.appearance": "Внешний вид",
"prefs.structure.appearance_note": "Цвета, текст и специальные возможности.",
"prefs.structure.in_profile": "В профиле",
"prefs.structure.layout": "Расположение",
"prefs.structure.layout_note": "Где находятся части окна.",
"prefs.structure.look": "Оформление",
"prefs.structure.measure_pointer": "Измерение и указатель",
"prefs.structure.mouse3d": "3D-мышь",
"prefs.structure.mouse3d_note": "SpaceMouse и SpaceNavigator.",
"prefs.structure.navigation_note": "Мышь и тачпад. У 3D-мыши есть своя страница.",
"prefs.structure.profile": "Профиль",
"prefs.structure.profile_edited": "Изменено после сохранения",
"prefs.structure.profile_hover": "Сохранено в профилях настроек",
"prefs.structure.scale_percent": "{percent}%",
"prefs.structure.sketch": "Эскиз",
"prefs.structure.tools": "Инструменты",
"prefs.structure.tools_note": "Как ведут себя инструменты моделирования.",
"prefs.structure.work": "Работа",
"prefs.supersampling_brief": "Рисует вид в большем размере и уменьшает его: очень чётко, но тяжелее.",
"prefs.tab_accent_note": "Акцентный цвет следует за активной вкладкой",
"prefs.theme_file": "файл темы",
@@ -1997,6 +2349,16 @@
"prefs.theme_follow_off": "Выкл.",
"prefs.theme_follow_stop": "Прекратить отслеживание",
"prefs.theme_import_hint": "Щёлкните, чтобы выбрать файл темы, перетащите его сюда или нажмите Ctrl+V (Cmd+V на Mac) при скопированной теме",
"prefs.theme_option_opacity": "Непрозрачность",
"prefs.theme_option_opacity_help": "Настройте непрозрачность плавающих панелей. Нижний порог читаемости защищает текст поверх цветов видового экрана.",
"prefs.theme_option_tab_colours": "Цвета вкладок",
"prefs.theme_option_tab_colours_help": "Использовать цвета панелей и видового экрана этой темы для каждой вкладки ленты.",
"prefs.theme_option_tab_fade_ms": "Переход между вкладками",
"prefs.theme_option_tab_fade_ms_help": "Плавно менять цвета панелей и видового экрана при переключении вкладок. Ноль переключает мгновенно.",
"prefs.theme_options_badge": "Параметры",
"prefs.theme_options_more": "Ещё · {count}",
"prefs.theme_options_reset": "Сбросить",
"prefs.theme_options_unavailable": "Недоступно на этом устройстве",
"prefs.theme_paste": "Вставить тему",
"prefs.theme_paste_none": "В буфере обмена нет темы SolidCraft",
"prefs.timeline_half_hour": "−30 мин",
@@ -2017,13 +2379,23 @@
"prefs.under_cursor": "Под курсором",
"prefs.units_precision": "Единицы и точность",
"prefs.updates": "Обновления",
"prefs.value_boxes_in_view": "Показывать значения рядом с манипуляторами на виде",
"prefs.value_boxes_in_view_tip": "Значение команды (Расстояние, Радиус…) в рамке рядом с её манипулятором, поверх модели. Выкл.: вы вводите его в окне команды, которое получает клавиатуру после выбора, и ничто не закрывает модель.",
"prefs.view": "Поведение вида",
"prefs.view_confirm_corner": "OK и Отмена в углу вида",
"prefs.view_confirm_corner_note": "Пока открыт диалог или эскиз, галочка и крестик в правом верхнем углу вида делают то же, что OK и Отмена в диалоге, либо завершают и отменяют эскиз. Навигатор видов сдвигается вниз под ними.",
"prefs.viewport": "Видовой экран",
"prefs.viewport_analysis_status": "Показывать состояние анализа над видовым экраном",
"prefs.viewport_analysis_status_help": "Показывает активные анализы над видовым экраном, открывает их легенды из каждого элемента, а клавиша Esc выключает самый новый анализ. Выключите, чтобы вернуть легенды в углах и прежнее поведение Esc.",
"prefs.viewport_background": "Фон",
"prefs.viewport_background_colour": "Цвет",
"prefs.viewport_background_theme": "Градиент темы",
"prefs.viewport_section_status": "Показывать состояние сечения на видовом экране",
"prefs.viewport_section_status_help": "Показывает настройку сечения на видовом экране. Esc и активная кнопка ленты отключают её. Выключите, чтобы сохранить прежние элементы управления сечением.",
"prefs.viewport_status_details": "Интерактивные сведения о видовом экране",
"prefs.viewport_status_details_help": "Редактируйте настройки активных анализов и сечения в стандартных карточках сведений. Выключите, чтобы вернуть клавиши анализа только для чтения и диалог «Сечение».",
"prefs.viewport_status_summary": "Сворачивать элементы состояния видового экрана",
"prefs.viewport_status_summary_help": "Сворачивать три и более элемента состояния видового экрана в один сводный значок. Выключите, чтобы показывать все элементы в полной строке.",
"prefs.wheel": "Колесо",
"prefs.wheel_trackpad": "Колесо и тачпад",
"prefs.workspace": "Рабочее пространство",
@@ -2033,6 +2405,10 @@
"properties.title": "Свойства: {name}",
"recovery.days_ago": "{count} дн. назад",
"recovery.detail": "{file} · операций: {features} · автосохранение {when}",
"recovery.discard_all": "Отбросить все",
"recovery.discard_all_detail": "Это действие нельзя отменить.",
"recovery.discard_all_title": "Отбросить автосохранения ({count})?",
"recovery.discard_all_title_one": "Отбросить 1 автосохранение?",
"recovery.hours_ago": "{count} ч назад",
"recovery.just_now": "только что",
"recovery.min_ago": "{count} мин назад",
@@ -2875,6 +3251,8 @@
"render.output_ratio_refused": "Связанный размер превышает предел для этого метода. Прежний размер сохранён; разблокируйте соотношение или укажите меньшее значение.",
"render.output_run_check": "Выполнить проверку",
"render.output_size_limit": "{method}: до {dimension} px по стороне и {pixels} пикселей. Наибольший подходящий размер: {width} × {height}.",
"render.output_size_method_profile": "Чтобы отрендерить {width} × {height}, выберите {method} с профилем ресурсов CPU {profile} или меньший размер.",
"render.output_size_profile": "Чтобы отрендерить {width} × {height}, выберите профиль ресурсов CPU {profile} или меньший размер.",
"render.output_swap_orientation": "Поменять местами ширину и высоту",
"render.output_tools_inspector": "Измените размер и формат в инспекторе «Вывод».",
"render.page_aging": "Старение и загрязнение",
@@ -3075,6 +3453,7 @@
"render.size_4k_uhd": "4K UHD",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · требуется {profile}",
"render.size_presets": "Пресеты размера",
"render.size_presets_pending": "Появится в редакторе «Вывод»",
"render.sky_ground": "Земля · линейный RGB",
@@ -3356,6 +3735,24 @@
"sheet.flange_type_contour": "Контурный",
"sheet.flange_type_edge": "Краевой",
"sheet.no_body": "нет тела листового металла: создайте его командой Фланец > Базовый или преобразуйте пластину в листовой металл командой «Преобразовать в листовой металл» (выберите её большую плоскую грань)",
"sheet.personal_rules.confirm_delete": "Подтвердить удаление",
"sheet.personal_rules.delete": "Удалить",
"sheet.personal_rules.delete_prompt": "Удалить это личное правило? Правила, уже применённые в дизайнах, не меняются.",
"sheet.personal_rules.full": "В личной библиотеке уже 64 правила.",
"sheet.personal_rules.invalid_name": "Введите короткое имя без переводов строк и управляющих символов.",
"sheet.personal_rules.invalid_values": "Заполните значения правила: каждое должно вычисляться в этом дизайне, толщина должна быть положительной, а коэффициент K — от 0 до 1.",
"sheet.personal_rules.missing": "Выбранное личное правило больше недоступно.",
"sheet.personal_rules.name": "Имя правила",
"sheet.personal_rules.name_exists": "Личное правило с таким именем уже существует. Используйте другое имя.",
"sheet.personal_rules.name_hint": "Уникальное имя",
"sheet.personal_rules.overwrite": "Сохранить изменения в выбранном личном правиле",
"sheet.personal_rules.rename": "Переименовать",
"sheet.personal_rules.rule": "Личное правило",
"sheet.personal_rules.save": "Сохранить как новое",
"sheet.personal_rules.scope": "Сохраняется в вашем профиле для всех дизайнов.",
"sheet.personal_rules.select": "Выберите правило",
"sheet.personal_rules.title": "Личные правила",
"sheet.personal_rules.unsupported": "В этом правиле есть значения, которые эта версия не может использовать.",
"shortcut.json_error": "параметры должны быть в формате JSON: {error}",
"shortcut.no_command": "команда `{id}` не найдена; текст перед JSON должен быть ID команды",
"shortcut.pin": "Закрепить вверху поля S",
@@ -3392,8 +3789,10 @@
"sketch_text.align_right": "По правому краю",
"sketch_text.bold": "Полужирный",
"sketch_text.builtin_font": "{family} (встроенный)",
"sketch_text.formula": "Начните с =, чтобы составить слова из параметров: ='W' + number(Width)",
"sketch_text.italic": "Курсив",
"sketch_text.loading_fonts": "Поиск установленных шрифтов…",
"sketch_text.no_bold": "У встроенного шрифта нет полужирного начертания: выберите установленный шрифт",
"sketch_text.on_path": "Текст следует по траектории: кривая задаёт его положение",
"sketch_text.path_clear": "Снять текст с кривой",
"sketch_text.path_hint": "Кривая эскиза, вдоль которой идёт текст: щёлкните здесь, затем по кривой",
@@ -3530,6 +3929,8 @@
"ui.3d_mouse_section": "3D-мышь",
"ui.3d_mouse_show_in_view": "Показывать 3D-мышь в 3D-виде",
"ui.3d_mouse_show_in_view_tip": "Изображение вашей 3D-мыши в левом нижнем углу, которое двигается вместе с ней.",
"ui.3d_mouse_sketch_orbit": "Вращать эскизы 3D-мышью",
"ui.3d_mouse_sketch_orbit_tip": "3D-мышь вращает вид и при открытом эскизе, если смотреть на него прямо, как это умеет обычная мышь. Выкл.: она панорамирует и масштабирует эскиз, пока вы не наклоните вид.",
"ui.3d_mouse_skip": "Моё устройство так не умеет",
"ui.3d_mouse_skip_tip": "Пропускает это движение в обе стороны; калибровка его не учитывает.",
"ui.3d_mouse_speed": "Скорость 3D-мыши",
@@ -3558,6 +3959,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Поверните колпачок против часовой стрелки, удержите и отпустите.",
"ui.3d_mouse_switch_cap": "Калибровать как возврат в центр",
"ui.3d_mouse_switch_trackball": "Калибровать как трекбол",
"ui.3d_mouse_use_direct": "Использовать прямой драйвер",
"ui.3d_mouse_use_direct_tip": "Читать 3D-мышь напрямую (hidraw), а не через spacenavd.",
"ui.3d_mouse_view_axis": "Ось вида",
"ui.3d_mouse_view_off": "Выкл.",
"ui.3d_mouse_view_off_tip": "Профиль драйвера 3D-мыши отключает эту ось, поэтому её движение не двигает вид. Если выбрать здесь движение другой оси, они поменяются местами, и это движение перестанет двигать вид.",
@@ -3596,6 +3999,7 @@
"ui.agent_chat_auth_terminal": "Выполните это в терминале, затем снова нажмите «Подключить»:",
"ui.agent_chat_auth_title": "Вход в агент",
"ui.agent_chat_cancel": "Отмена",
"ui.agent_chat_cannot_start": "Установлен, но не запускается: {error}",
"ui.agent_chat_changes": "Изменений: {n}, один шаг отмены",
"ui.agent_chat_changes_n": "Изменений: {n}",
"ui.agent_chat_changes_of": "Изменений: {n}/{limit}",
@@ -3688,9 +4092,12 @@
"ui.agent_chat_queued_stopped": "В очереди (остановлено, не отправлено)",
"ui.agent_chat_refresh": "Обновить",
"ui.agent_chat_refused": "Агенту не разрешено: {reason}",
"ui.agent_chat_reinstall": "{agent} установлен ({program}), но не запустился, когда SolidCraft его проверил: {error}\nВозможно, установка неполная или сделана для другого типа компьютера. Установите его заново командой `{install}`, убедитесь, что в терминале он выводит свою версию, затем нажмите «Обновить».",
"ui.agent_chat_remove": "Убрать из очереди",
"ui.agent_chat_remove_file": "Убрать из этого сообщения",
"ui.agent_chat_remove_imported": "Убрать из этого сообщения; деталь останется в дизайне (Отмена удалит её)",
"ui.agent_chat_reply_cut_off": "Ответ достиг предела длины, не закончившись, поэтому может быть неполным. Попросите агента продолжить или дать более короткий ответ.",
"ui.agent_chat_reply_declined": "Модель отказалась отвечать на это сообщение.",
"ui.agent_chat_retry": "Повторить",
"ui.agent_chat_right": "Справа",
"ui.agent_chat_save": "Сохранить",
@@ -3749,12 +4156,17 @@
"ui.ai_copy_suffix": "копия",
"ui.ai_custom_url": "Свой URL",
"ui.ai_default": "По умолчанию",
"ui.ai_edit": "Изменить",
"ui.ai_edit_agent_title": "Изменить агент",
"ui.ai_edit_gone": "Этот агент удалён, сохранять нечего.",
"ui.ai_edit_tip": "Измените имя, команду или конечную точку этого агента. Сохранённый ключ остаётся скрытым.",
"ui.ai_http_busy": "конечная точка занята или превысила лимит; повторите позже",
"ui.ai_http_key": "конечная точка отклонила ключ: проверьте его в Настройки > ИИ",
"ui.ai_http_not_found": "нет такой конечной точки или модели: проверьте базовый URL и имя модели",
"ui.ai_http_too_large": "разговор слишком велик для этой конечной точки; начните новый чат",
"ui.ai_key": "Ключ API",
"ui.ai_key_hint": "необязательно; хранится в системной связке ключей",
"ui.ai_key_keep_hint": "оставьте пустым, чтобы сохранить имеющийся ключ",
"ui.ai_key_needs_https": "Ключ отправляется только по HTTPS или на этот компьютер.",
"ui.ai_key_note": "Хранится в системной связке ключей, а не в настройках SolidCraft. Для локального сервера оставьте пустым.",
"ui.ai_keychain_error": "Системная связка ключей отказала: {error}",
@@ -3781,11 +4193,15 @@
"ui.ai_permissions": "Разрешения",
"ui.ai_preset": "Пресет",
"ui.ai_protocol": "Протокол",
"ui.ai_reasoning_effort": "Уровень рассуждений",
"ui.ai_reasoning_effort_field_hint": "пусто = значение поставщика по умолчанию",
"ui.ai_reasoning_effort_hint": "Необязательно. Отправляется на конечную точку как `reasoning_effort`; допустимые значения зависят от поставщика (например, `low`, `high` или `max`). Заданное здесь значение SolidCraft никогда не заменяет. Оставьте поле пустым, чтобы ничего не отправлять и сохранить значение поставщика по умолчанию.",
"ui.ai_remove": "Удалить",
"ui.ai_request_timed_out": "Модель отвечала дольше {seconds} с. Тайм-аут запроса для этой конечной точки можно увеличить в Настройки › Ассистент.",
"ui.ai_request_timeout": "Тайм-аут запроса",
"ui.ai_request_timeout_hint": "Сколько может длиться один запрос к модели на этой конечной точке: от 30 с до часа. Увеличьте для медленной локальной модели.",
"ui.ai_reset": "Сбросить",
"ui.ai_save": "Сохранить",
"ui.ai_settings": "Настройки ИИ…",
"ui.ai_show_features": "Показывать функции ИИ",
"ui.ai_show_features_note": "Чат с агентом: панель, в которой уже используемый вами кодирующий агент или выбранная конечная точка модели работает с открытым дизайном. Выключено: нет кнопки ИИ, сочетания клавиш, пункта меню и панели, ничего не ищется и не запускается.",
@@ -3872,6 +4288,7 @@
"ui.circle": "Окружность",
"ui.circular_pattern": "Круговой массив",
"ui.clear": "Очистить",
"ui.clear_print_face": "Сбросить грань печати",
"ui.clear_recent": "Очистить недавние",
"ui.clear_the_selection": "Снять выделение",
"ui.click_selects_whole_bodies": "Щелчок выделяет тела целиком",
@@ -3942,6 +4359,7 @@
"ui.dark": "Тёмная",
"ui.default_modelling_orientation": "Ориентация моделирования по умолчанию",
"ui.delete": "Удалить",
"ui.delete_select_first": "Сначала выберите, что удалить: операции, тела, компоненты или геометрию эскиза",
"ui.depth": "Глубина",
"ui.design": "Дизайн",
"ui.design_properties.author": "Автор",
@@ -3956,6 +4374,40 @@
"ui.design_properties.project": "Проект",
"ui.design_properties.revision": "Редакция",
"ui.design_saved": "Сохранено: {path}",
"ui.details_access_caption": "Грани показывают, может ли инструмент достать до них с выбранного направления.",
"ui.details_angle": "Угол",
"ui.details_continuity_caption": "Правка выбранного шва. Остальные швы сохраняют свои настройки.",
"ui.details_continuity_stale": "Прежняя геометрия: повторите анализ, чтобы править или кадрировать этот шов.",
"ui.details_copy_list": "Копировать список",
"ui.details_curvature": "Допуск кривизны",
"ui.details_curvature_caption": "Грани окрашены по локальному радиусу кривизны.",
"ui.details_direction": "Направление инструмента",
"ui.details_draft_caption": "Грани окрашены по углу к направлению вытягивания.",
"ui.details_edit_seam": "Править шов",
"ui.details_fixture": "Образец компонента — только пример данных",
"ui.details_fixture_caption": "Образец компонента с иллюстративными значениями; это не результат по модели.",
"ui.details_fixture_problem": "Пример находки с проблемой",
"ui.details_fixture_readout": "Пример измеренного значения",
"ui.details_flip": "Перевернуть",
"ui.details_gap": "Зазор",
"ui.details_hatch": "Штриховать срезанные грани",
"ui.details_jump": "Скачок кривизны",
"ui.details_key": "Легенда",
"ui.details_offset": "Смещение",
"ui.details_plane": "Плоскость",
"ui.details_pull": "Направление вытягивания",
"ui.details_radius": "Радиус",
"ui.details_readout": "Показание",
"ui.details_samples": "Точки выборки",
"ui.details_seam": "{body} · Ребро {edge}",
"ui.details_settings": "Настройки",
"ui.details_sharp_gap": "Излом или зазор",
"ui.details_show_all": "Показать все {count}",
"ui.details_smooth": "Гладко",
"ui.details_stripes": "Полосы",
"ui.details_target": "Кадрировать геометрию",
"ui.details_worst_seams": "Худшие швы",
"ui.details_zebra_caption": "Отражённые полосы показывают, насколько плавно стыкуются грани.",
"ui.diameter": "Диаметр",
"ui.dimension": "Размер",
"ui.direct_interaction": "Прямое взаимодействие",
@@ -4000,6 +4452,7 @@
"ui.edit_in_place_saved_elsewhere": "Деталь сохранена в другой файл; сборка по-прежнему связана с {name}",
"ui.edit_in_place_wait": "Дождитесь завершения того, что выполняется в дизайне",
"ui.edit_section_analysis": "Изменить анализ сечения",
"ui.edit_sketch_empty": "Нет эскизов для редактирования",
"ui.ellipse": "Эллипс",
"ui.emboss_wrap_note": "Профили из ортогональных линий, касательные к одному поддерживаемому цилиндру, оборачиваются автоматически. Выберите стенку, чтобы задать цель.",
"ui.end_radius": "Конечный радиус",
@@ -4010,12 +4463,15 @@
"ui.equation_explicit": "Явная",
"ui.equation_form": "Форма",
"ui.equation_note": "Переменная — обычное число: sin(t) принимает её в радианах. Результат без единицы измерения задаётся в единицах дизайна.",
"ui.equation_note_range": "Переменная — длина, как и её диапазон: сочетайте её с параметрами длины, а для sin() делите её на длину. Результат без единицы измерения задаётся в единицах дизайна.",
"ui.equation_parametric": "Параметрическая",
"ui.equation_variable_unit": "Переменная имеет единицу измерения диапазона",
"ui.equation_x_of_t": "x(t)",
"ui.equation_y_of_t": "y(t)",
"ui.equation_y_of_x": "y(x)",
"ui.equivalent_radius": "Эквивалентный радиус",
"ui.expand": "Развернуть",
"ui.explode_needs_two_components": "«Разобранный вид» раздвигает компоненты, поэтому в дизайне их должно быть два или больше («Новый компонент», «Вставить дизайн» или «Создать компоненты из тел»)",
"ui.exportDxf": "Экспорт DXF…",
"ui.export_ellipsis_keymap": "Экспорт…",
"ui.export_keymap_tip": "Сочетания клавиш, избранное панели S и панель инструментов — в файл",
@@ -4184,12 +4640,14 @@
"ui.joints": "Сопряжения",
"ui.keyboard_shortcuts": "Сочетания клавиш",
"ui.language": "Язык",
"ui.lasso_select": "Лассо (перетащите произвольный контур: выделяет всё, что целиком внутри)",
"ui.layout_compact": "Компактная",
"ui.layout_floating": "Плавающая",
"ui.length": "Длина",
"ui.length_decimals": "Знаков после запятой (длины)",
"ui.light": "Светлая",
"ui.line": "Линия",
"ui.line_arc_hint": "Касательная дуга: щёлкните в конце дуги. Снова задержитесь на последней точке, чтобы вернуться к линиям; щёлкните правой кнопкой или нажмите Esc, чтобы завершить",
"ui.link_copies": "Связать одинаковые копии",
"ui.link_copies_keep": "Оставить",
"ui.link_copies_link": "Связать",
@@ -4278,6 +4736,7 @@
"ui.part_size_shared": "Перестраивает деталь, заменяя все добавленные операции. Обновляются все вхождения этого компонента.",
"ui.paste": "Вставить",
"ui.path": "Траектория",
"ui.pattern_object_type_tip": "Тела: твёрдые формы, выбираемые в виде или в браузере; каждое копируется целиком.\nОперации: шаги Истории, например выдавливание или отверстие, выбираемые в Истории или на созданной ими грани; шаг повторяется.\nЭскиз — ни то, ни другое: повторите его кривые собственным инструментом «Массив» или «Зеркало» в эскизе либо сначала выдавите его.",
"ui.pattern_on_path": "Массив по траектории",
"ui.perforate_margin_default": "от диаметра",
"ui.perforate_pitch": "Шаг",
@@ -4304,11 +4763,21 @@
"ui.plugins_skipped": "Не удалось прочитать папок: {count}.",
"ui.plugins_version": "{version}",
"ui.point": "Точка",
"ui.point_cloud_add_filter": "Добавить фильтр…",
"ui.point_cloud_filter_by": "Фильтровать по",
"ui.point_cloud_legend_filter": "Показано и скрыто в легенде выше",
"ui.point_cloud_pause": "Пауза",
"ui.point_cloud_play": "Воспроизвести",
"ui.point_under_the_cursor": "Точка под курсором",
"ui.points": "Точки",
"ui.polygon": "Многоугольник",
"ui.preferences": "Настройки",
"ui.press_a_key": "нажмите клавишу…",
"ui.print_face_body_not_sent": "Тело этой грани не отправляется: сначала добавьте его в Тела",
"ui.print_face_cleared": "{body}: грань печати сброшена",
"ui.print_face_input": "Грань печати",
"ui.print_face_pick": "Выберите плоскую грань твёрдого тела, чтобы сделать её гранью печати",
"ui.print_face_set": "{body}: грань печати задана; «Отправить в слайсер» кладёт тело на неё",
"ui.profile": "Профиль",
"ui.profile_plane": "Плоскость профиля",
"ui.profiles": "Профили",
@@ -4324,6 +4793,7 @@
"ui.properties.inertia_y": "Iyx, Iyy, Iyz",
"ui.properties.inertia_z": "Izx, Izy, Izz",
"ui.properties.measuring": "Измерение…",
"ui.properties.measuring_bodies": "Измерение… {done} из {total} тел",
"ui.properties.measuring_failed": "Измерение остановлено, не закончив работу.",
"ui.properties.principal_axis": "Главная ось {number}",
"ui.properties.principal_moments": "Главные моменты",
@@ -4370,6 +4840,7 @@
"ui.revolve": "Вращение",
"ui.ribbon_exit": "Выход",
"ui.ribbon_labels": "Показывать текст под значками ленты",
"ui.ribbon_menus_skip_pinned": "Не включать закреплённые команды в выпадающие меню",
"ui.ribbon_pin_footer": "Закреплённые команды отображаются на ленте. Наведите курсор на строку, чтобы закрепить или открепить её.",
"ui.ribbon_workspace": "Рабочая область",
"ui.right": "Справа",
@@ -4388,6 +4859,7 @@
"ui.selectOther": "Выбрать другое…",
"ui.selectTangentChain": "Выбрать касательную цепочку",
"ui.select_all": "Выделить всё",
"ui.select_components_by_face": "Выбор компонентов (грань выбирает свой компонент)",
"ui.selection": "Выделение",
"ui.selection_modifiers": "Модификаторы выбора",
"ui.selection_shift_add_only": "Shift+щелчок добавляет без удаления",
@@ -4429,6 +4901,11 @@
"ui.sketch_intersect_selection_filter": "Фильтр выделения",
"ui.sketch_modifiers": "Модификаторы эскиза",
"ui.sketch_project_geometry": "Геометрия",
"ui.sketch_report.all_constrained": "Всё полностью определено.",
"ui.sketch_report.dof": "Степени свободы",
"ui.sketch_report.loose": "Определён не полностью",
"ui.sketch_report.status": "Состояние",
"ui.sketch_report.title": "Отчёт об эскизе",
"ui.sketch_scale": "Масштаб эскиза",
"ui.sketches": "Эскизы",
"ui.slice": "Срез",
@@ -4524,19 +5001,31 @@
"ui.view_angles": "Углы вида",
"ui.view_angles_convention": "Градусы вокруг вертикальной оси. Рыскание 0° — вид спереди, 90° — слева, −90° — справа, 180° — сзади; тангаж 90° — вид сверху; положительный крен поворачивает модель по часовой стрелке.",
"ui.view_angles_help": "Показывает тангаж, рыскание и крен вида в градусах, а также его проекцию под гизмо ориентации. Щёлкните по показанию, чтобы ввести точные углы.",
"ui.view_angles_lock": "Заблокировать {axis}",
"ui.view_angles_ortho": "Орто",
"ui.view_angles_persp": "Персп.",
"ui.view_angles_pitch": "Тангаж",
"ui.view_angles_readout": "Тангаж {pitch} · Рыскание {yaw} · Крен {roll}",
"ui.view_angles_roll": "Крен",
"ui.view_angles_unlock": "Разблокировать {axis}",
"ui.view_angles_yaw": "Рыскание",
"ui.view_angles_zero": "Установить {axis} в 0°",
"ui.view_centre": "Центр вида",
"ui.viewport_status_clear": "Выключить {name}",
"ui.viewport_status_clear_hint": "Выключить {name} · Esc",
"ui.viewport_status_edges": "Рёбер: {count}",
"ui.viewport_status_from": "со стороны {direction}",
"ui.viewport_status_key": "Показать легенду для {name}",
"ui.viewport_status_minimum_radius": "Минимальный радиус",
"ui.viewport_status_pairs": "Пар: {count}",
"ui.viewport_status_section_hatch": "{setting} · штриховка",
"ui.viewport_status_section_plane": "Текущая плоскость",
"ui.viewport_status_section_tilted": "наклонено · {offset}",
"ui.viewport_status_stripes": "Полос: {count}",
"ui.viewport_status_summary_clear": "Выключить всё",
"ui.viewport_status_summary_heading": "На этом виде",
"ui.viewport_status_summary_label": "{name} и ещё элементов: {count}",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Следовать теме",
"ui.visual_shaded_hidden_edges": "Заполненный с невидимыми рёбрами",
"ui.visual_shaded_with_edges": "Заполненный с рёбрами",
@@ -4573,7 +5062,9 @@
"update.button": "Обновить",
"update.button_version": "Обновить до {version}",
"update.check_automatically": "Проверять обновления автоматически",
"update.check_for_updates": "Проверить обновления",
"update.check_now": "Проверить сейчас",
"update.check_unavailable": "Проверка обновлений недоступна для этой установки.",
"update.checking": "Проверка…",
"update.copy": "Копировать",
"update.dmg_opened": "Образ диска открыт и проверен: перетащите SolidCraft в «Программы», заменив старую копию, затем перезапустите SolidCraft.",
+472
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "ПРЕЛАЗНА ПОВРШ",
"BOUNDARY SURFACE": "ГРАНИЧНА ПОВРШ",
"BROWSER": "СТАБЛО МОДЕЛА",
"Back to the original order": "Врати на првобитни редослед",
"Ball": "Кугласто",
"Bar": "Шипка",
"Base Center": "Центар основе",
@@ -595,6 +596,8 @@
"Snap to face and edge centres": "Привлачи на центре лица и ивица",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft не може директно да шаље извештаје. Пошаљите на GitHub; у апликацији није потребан токен.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft се затворио пре него што су ови дизајни сачувани. Може се отворити њихово последње аутоматско чување.",
"Sort A to Z": "Поређај од А до Ш",
"Sort Z to A": "Поређај од Ш до А",
"Spacing 2 must be a non-zero number": "Размак 2 мора бити број различит од нуле",
"Spacing must be a non-zero number": "Размак мора бити број различит од нуле",
"Specialise": "Специјализуј",
@@ -790,6 +793,13 @@
"browser.sketch_attachment_lost": "{name} (причвршћење изгубљено)",
"browser.sketch_editing": "{name} (у измени)",
"browser.sketch_name": "Скица{number}",
"browser.sketches_to_parts_already_in_part": "већ у свом делу",
"browser.sketches_to_parts_design_body": "на телу самог дизајна",
"browser.sketches_to_parts_moved": "Скице премештене у део на којем се налазе: {moved}",
"browser.sketches_to_parts_no_exact_attachment": "на лицу без тачног причвршћења",
"browser.sketches_to_parts_not_on_face": "не на лицу дела",
"browser.sketches_to_parts_part_missing": "њихов део више не постоји",
"browser.sketches_to_parts_result": "Скице премештене у део на којем се налазе: {moved}; остале на месту: {stayed} ({why})",
"browser.units": "Јединице: {units}",
"cam.flat": "Равно лице",
"cam.follower": "Пратилац",
@@ -819,6 +829,8 @@
"cmd.appearance_pick_up": "Преузми изглед",
"cmd.appearance_replace": "Замени изглед",
"cmd.assembly_bom": "Саставница",
"cmd.assembly_mass_properties": "Својства масе",
"cmd.body.print_face": "Површина за штампу",
"cmd.body_rename": "Преименуј тело",
"cmd.browser_collapse_all": "Скупи све",
"cmd.browser_expand_all": "Прошири све",
@@ -855,6 +867,7 @@
"cmd.component_move_bodies": "Премести тела у компоненту",
"cmd.component_move_features": "Премести операције у компоненту",
"cmd.component_move_sketches": "Премести скице у компоненту",
"cmd.component_move_sketches_to_parts": "Премести скице у њихове делове",
"cmd.component_paste_from_design": "Налепи из другог дизајна",
"cmd.component_paste_new": "Налепи као ново",
"cmd.component_properties": "Физичка својства компоненте",
@@ -1190,6 +1203,7 @@
"cmd.sketch_spline_control_point_5": "Сплајн контролних тачака (степен 5)",
"cmd.sketch_spline_fit_point": "Сплајн кроз тачке",
"cmd.sketch_spline_insert_point": "Додај тачку сплајна",
"cmd.sketch_spline_remove_point": "Уклони тачку сплајна",
"cmd.sketch_spline_reverse": "Обрни смер сплајна",
"cmd.sketch_spun_profile": "Ротирани профил",
"cmd.sketch_toggle_diameter": "Пребаци полупречник / пречник",
@@ -1243,6 +1257,7 @@
"cmd.view.render_browser": "Стабло рендеровања",
"cmd.view.render_dock": "Премести панел рендеровања",
"cmd.view.render_focus": "Фокусни приказ рендеровања",
"cmd.view.status_details": "Детаљи статуса у прозору приказа",
"cmd.view.status_items": "Ставке статуса у прозору приказа",
"cmd.view_ambient_occlusion": "Амбијентална оклузија",
"cmd.view_angle_readout": "Углови приказа",
@@ -1281,8 +1296,14 @@
"color.hex_hint": "#rrggbb",
"color.hue": "Нијанса",
"color.linear": "Приказ вредности боје у линеарном простору 0-1",
"color.recent": "Недавне боје",
"color.recent_help": "Овде се појављују боје које изаберете у било ком пољу боје",
"color.saturation": "Засићеност (проценат)",
"color.saturation_value": "Засићеност и вредност",
"color.selected": "Изабрана боја",
"color.step_down": "Смањи за корак",
"color.step_up": "Повећај за корак",
"color.value": "Вредност, светлина (проценат)",
"config.column_filter_empty": "Нема одговарајућих колона",
"contact.components_count": "Компоненте: {count}",
"contact.enabled_label": "омогућено",
@@ -1329,6 +1350,7 @@
"dialog.dimension_name": "Кота: {name}",
"dialog.edit_title": "ИЗМЕНИ {title}",
"dialog.no_shown_body": "Нема приказаног тела за измену: прикажите једно или изаберите Ново тело",
"dialog.pattern_sketch_not_object": "Изаберите тела или операције: скица није ни једно ни друго. Поновите њене криве помоћу алата Низ или Пресликај у самој скици или је прво екструдирајте.",
"dialog.select": "↖ Изабери",
"dialog.selected_count": "↖ Изабрано: {count}",
"dialog.selection_guidance": "Изаберите улаз да га промените. Кликните изабране објекте да их уклоните; Ctrl/Cmd-клик их такође укључује и искључује. Погледајте преглед, а затим притисните У реду.",
@@ -1386,21 +1408,168 @@
"from the rule": "из правила",
"help.apache": "Apache лиценца 2.0",
"help.appearance.assign": "Даје изабраним телима, лицима или компонентама боју или изглед материјала, без промене њихове тежине. Користите га да разликујете делове или прегледате завршну обраду.",
"help.assembly.bom": "Наводи делове дизајна или компоненте са њиховим бројем комада, по броју дела, и може да сачува списак као CSV или JSON. Користите је као списак делова за наручивање и монтажу.",
"help.attributions": "Атрибуције (ATTRIBUTION.md)",
"help.build": "Верзија {version} · commit {commit} · изграђено {date}",
"help.canvas.decal": "Поставља слику на раван лице модела, као налепницу. Користите је да на делу прикажете ознаку, логотип или одштампан узорак.",
"help.canvas.edit": "Мења платно: његов положај, величину, угао, непрозирност, пресликавање, видљивост или назив. Користите га да референтну слику поравнате са скицом коју цртате преко ње.",
"help.canvas.insert": "Поставља PNG или JPEG слику на раван или лице као провидну референцу. Користите га да у скици прецртате фотографију или цртеж, са сликом скалираном на познату величину.",
"help.component.break_link": "Одваја повезану компоненту од њене датотеке дизајна, а њена тела остају каква јесу. Користите је када део више не треба да прати измене изворне датотеке.",
"help.component.capture_position": "Задржава положаје делова које сте ручно померили, па они постају нови положаји тих делова. Користите га након превлачења делова на место које ниједан спој не држи.",
"help.component.create": "Додаје нову, празну компоненту дизајну, унутар активне компоненте осим ако изаберете другу надређену компоненту. Користите је да нови део склопа добије своје место за тела и скице.",
"help.component.finish_edit": "Чува део који мењате на месту, враћа се у његов склоп и ажурира склоп према њему. Користите када завршите измене повезаног дела.",
"help.component.from_bodies": "Претвара свако изабрано тело у засебну компоненту, названу по телу. Користите га када дизајн направљен од појединачних тела треба да постане склоп чији се делови могу спајати и померати.",
"help.component.insert_design": "Умеће други сачувани дизајн као компоненту повезану са његовом датотеком, а његова пуна тела се постављају онако како су у њему. Користите га да поново употребите део у склопу и да касније измене преузмете командом Ажурирај везе.",
"help.component.properties": "Приказује запремину, масу, центар масе и моменте инерције компоненте, према материјалима њених тела. Користите га да проверите тежину и равнотежу склопа.",
"help.component.revert_position": "Враћа делове које сте ручно померили на њихова претходна места и одбацује померања која нису забележена. Користите га да поништите пробна померања у склопу.",
"help.component.update_links": "Поново чита повезане датотеке дизајна и замењује тела која су се променила. Користите га након измене дела који овај склоп користи.",
"help.config.configure": "Додаје параметре, операције, материјале или изгледе као колоне табеле конфигурација. Користите га да направите варијанте једног дизајна, на пример величине једног дела.",
"help.config.preview": "Проверава да ли се ред табеле конфигурација успешно поново гради, без промене дизајна или његове историје. Користите га пре преласка на варијанту у коју нисте сигурни.",
"help.config.rules": "Приказује правила табеле конфигурација, њене редове и који ред је активан. Користите га да видите како су подешене варијанте дизајна.",
"help.config.table": "Приказује табелу конфигурација: један ред по варијанти, једну колону по параметру или операцији и да ли дизајн још одговара активном реду. Користите је да прегледате варијанте дизајна.",
"help.connect_note": "Поново покрените сваки асистент да учита SolidCraft сервер. Свака датотека је безбедносно копирана као .bak пре промене.",
"help.connect_title": "Повежи AI асистенте",
"help.construct.axis.edge": "Прави помоћну осу дуж праве ивице или линије скице. Користите је као осу за ротацију, кружни низ или спој.",
"help.construct.axis.normal_to_face": "Прави помоћну осу кроз тачку, окомиту на лице. Користите је као осу која излази право из површине.",
"help.construct.axis.through_cylinder": "Прави помоћну осу кроз центар цилиндричног, конусног или торусног лица, или кружне ивице. Користите је да операције окрећете или распоређујете у низ око округлог дела.",
"help.construct.axis.two_planes": "Прави помоћну осу тамо где се састају две равни или равна лица. Користите је када је оса која вам треба линија између два лица.",
"help.construct.axis.two_points": "Прави помоћну осу кроз две тачке, врхове или тачке скице. Користите је за осу која пролази кроз два позната положаја.",
"help.construct.plane.along_path": "Прави помоћну раван окомиту на ивицу или криву, на положају дуж ње. Користите је да скицирате профил за повлачење или попречни пресек путање.",
"help.construct.plane.at_angle": "Прави помоћну раван заокренуту под углом у односу на раван или лице, око осе или ивице. Користите је за нагнуте скице, на пример за угаони носач.",
"help.construct.plane.midplane": "Прави помоћну раван на половини између две равни или равна лица, или раван која полови угао под којим се састају. Користите је као раван симетрије за пресликавање.",
"help.construct.plane.offset": "Прави помоћну раван паралелну равни или равном лицу, на задатом растојању или кроз тачку. Користите је да скицирате на одређеној висини изнад лица.",
"help.construct.plane.perpendicular": "Прави помоћну раван која садржи линију или ивицу и стоји окомито на раван или лице. Користите је да скицирате усправно на моделу дуж ивице.",
"help.construct.plane.tangent": "Прави помоћну раван која у једној тачки додирује цилиндар, конус, сферу или торус. Користите је да скицирате на боку округлог дела.",
"help.construct.plane.three_points": "Прави помоћну раван кроз три тачке. Користите је за раван одређену положајима на моделу, на пример за три угла.",
"help.construct.plane.two_edges": "Прави помоћну раван кроз две праве ивице или линије које леже у једној равни. Користите је за раван кроз део, на пример кроз две наспрамне ивице.",
"help.construct.point.along_path": "Прави помоћну тачку на положају дуж ивице или криве. Користите је као референцу негде на путањи.",
"help.construct.point.center": "Прави помоћну тачку у центру кружне ивице, круга или лука скице, сфере или торуса. Користите је као референцу за рупе, осе и спојеве.",
"help.construct.point.edge_plane": "Прави помоћну тачку где ивица или линија пролази кроз раван или равно лице. Користите је да означите где путања сече раван.",
"help.construct.point.three_planes": "Прави помоћну тачку где се састају три равни или равна лица. Користите је као референцу за угао, чак и тамо где модел нема врх.",
"help.construct.point.two_edges": "Прави помоћну тачку где се две ивице или линије састају или су најближе једна другој. Користите је као референцу у виртуелном углу.",
"help.construct.point.vertex": "Прави помоћну тачку на врху или тачки скице. Користите је као референцу која остаје и када су скица или тело око ње сакривени.",
"help.contact.check": "Наводи парове делова у скуповима контаката који у свом тренутном положају пролазе један кроз други. Користите га да пронађете сударе пре него што покренете спојеве или студију кретања.",
"help.contact.create": "Групише два или више делова у скуп контаката чија тела не могу да прођу један кроз други док се спојеви крећу. Користите га за делове који треба да стану када се додирну, као што је полуга уз свој граничник.",
"help.contact.disable_all": "Искључује контакт, па покренути спојеви и студије кретања више не стају тамо где се делови додирују. Користите га да поново слободно померате делове или да велики склоп остане брз.",
"help.contact.edit": "Мења скуп контаката: делове које садржи, његов назив или да ли је потиснут; може и да избрише скуп. Користите га да подесите који делови не могу да прођу један кроз други.",
"help.contact.enable_all": "Ставља сваки део склопа у један скуп контаката, па никоја два дела не могу да прођу један кроз други док се спојеви крећу. Користите га за брзу проверу целог механизма.",
"help.contact.enable_sets": "Укључује контакт за скупове контаката које сте дефинисали, па покренути спојеви и студије кретања стају тамо где би њихови делови прошли један кроз други. Користите га да вратите своје скупове након искључивања контакта.",
"help.contact.list": "Наводи скупове контаката дизајна, делове у сваком од њих и да ли је контакт укључен. Користите га да проверите како је контакт подешен пре него што покренете склоп.",
"help.document.inspect": "Приказује преглед дизајна: његове компоненте, тела, скице, операције и параметре. Користите га да се снађете у дизајну који нисте сами направили.",
"help.document.units": "Мења јединице у којима дизајн приказује дужине, на пример mm или инчи. Геометрија задржава своју величину; мењају се само приказ и обични бројеви.",
"help.edit.checkpoint": "Памти дизајн, активну скицу и историју поништавања онакве какви су сада. Користите је пре него што испробате нешто ризично од чега ћете се можда хтети вратити.",
"help.edit.restore_checkpoint": "Враћа се на контролну тачку, укључујући историју поништавања. Користите га да једним кораком одбаците све што је урађено од контролне тачке.",
"help.explode.create": "Прави растављени приказ који делове одгура од центра склопа, или за померања која зададете. Користите га да прикажете како се склоп саставља, за упутства или презентације.",
"help.face.delete": "Уклања изабрана лица и пушта да суседна лица затворе отвор, или оставља отвор отвореним као површ. Користите га да уклоните заобљену ивицу, рупу или испупчење које смета.",
"help.file.insert_mesh": "Додаје 3MF, STL, OBJ, PLY или glTF датотеку дизајну као мрежна тела у новој компоненти. Користите га да преузети или скенирани модел поставите поред свог дизајна као референцу.",
"help.file.insert_step": "Додаје STEP, IGES, FreeCAD или Rhino датотеку дизајну као пуна тела у новој компоненти; делови склопа постају угнежђене компоненте. Користите га да унесете моделе од добављача или из других CAD програма.",
"help.file.save_mesh": "Чува тела као STL, 3MF или OBJ мрежу, са глаткоћом закривљених лица коју изаберете. Користите је да припремите делове за 3D штампу или за програме који читају мреже.",
"help.file.send_to_slicer": "Чува изабрана тела као 3MF датотеку и отвара је у слајсеру који изаберете. Користите га да једним кораком пређете од модела до штампе.",
"help.font_details": "Интерфејс користи уграђене фонтове библиотеке egui (Ubuntu Light и Hack, под лиценцама Ubuntu Font Licence и MIT/Bitstream Vera; Noto Emoji и фонт емоџи икона, под лиценцама OFL и MIT). Текст скице користи Liberation Sans (SIL OFL 1.1). Необавезни пакет фонтова Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), није укључен у издања SolidCraft-а.",
"help.fonts": "Фонтови",
"help.frame.member": "Поставља профил, на пример цев или алуминијумски екструдирани профил, дуж линија скице оквира, по један елемент по линији. Користите га да направите оквире, регале и заварене конструкције.",
"help.inspect.accessibility": "Боји лица до којих алат може да допре из једног смера и она до којих не може. Користите је да проверите део за обраду или израду у калупу са те стране.",
"help.inspect.center_of_mass": "Приказује центар масе тела, сваког посебно и свих заједно, при уједначеној густини. Користите га да проверите равнотежу и где ће се део преврнути.",
"help.inspect.colored_components": "Даје свакој компоненти сопствену привремену боју, без промене изгледа. Користите га да на први поглед разликујете делове склопа.",
"help.inspect.compare": "Упоређује два тела и показује где једно има материјал којег друго нема, са запремином и одступањем. Користите га да проверите измењен или увезен део у односу на оригинал.",
"help.inspect.continuity": "Проверава колико глатко се лица спајају дуж својих ивица: размаке, прегибе или скокове закривљености, и осенчава шавове. Користите је да пронађете где се површи не спајају уредно.",
"help.inspect.curvature_comb": "Црта чешљеве дуж кривих, ивица или лица чији зуби показују колико су јако закривљени. Користите је да уочите равна места, прегибе и неуједначену закривљеност.",
"help.inspect.curvature_map": "Боји лица према јачини закривљености, црвено где је полупречник мали. Користите је да пронађете оштре кривине и да процените колико је површ глатка.",
"help.inspect.draft": "Боји лица према углу нагиба у односу на смер вађења из калупа и означава она са премало нагиба. Користите је да проверите део пре израде у калупу или ливења.",
"help.inspect.environment_map": "Приказује модел са рефлектујућом завршном обрадом налик огледалу. Користите је да процените ток површи по начину на који се одблесци простиру преко њих.",
"help.inspect.interference": "Проналази тела која се преклапају и приказује свако преклапање са његовом запремином. Користите га да проверите да ли у склопу има делова који се сударају.",
"help.inspect.isocurve": "Црта изопараметарске криве лица, мрежу на којој је површ изграђена. Користите је да видите како површ тече и где се згушњава.",
"help.inspect.measure": "Мери дужине, растојања, углове, површине и полупречнике између онога што изаберете, или запремину и масу тела. Користите га да проверите мере и зазоре.",
"help.inspect.minimum_radius": "Проналази најмањи полупречник закривљености кривих, ивица или лица и где се налази. Користите је да проверите да ли алат или млазница може да прати облик.",
"help.inspect.section": "Расеца приказ дуж равни и шрафира пресечена лица, без промене модела. Користите га да погледате унутрашњост делова и склопова.",
"help.inspect.zebra": "Приказује црне и беле пруге одбијене на лицима модела. Користите је да процените глаткоћу: пруге које се прекидају или ломе на ивици показују лош прелаз.",
"help.joint.as_built": "Спаја два дела тамо где већ јесу, не померајући ниједан, и одређује како се још могу кретати. Користите га за делове које сте већ поставили у њихов коначни положај.",
"help.joint.cam": "Повезује брег који се окреће са пратиоцем, па пратилац клизи или се њише док обрис брега пролази испод њега. Користите га за брегове, ексцентре и погоне вентила.",
"help.joint.create": "Спаја два дела у исходиштима споја која изаберете и помера други део на место; врста споја одређује како се још могу кретати. Користите га да направите склоп чији се делови крећу као прави механизам.",
"help.joint.drive": "Задаје угао или растојање споја и са њим помера делове, у оквиру његових граница; повезани спојеви и затворене петље га прате. Користите га да проверите да ли се механизам креће како је замишљено.",
"help.joint.gear": "Повезује два окретна споја преносним односом, па покретање једног окреће други. Користите га за чеоне и унутрашње зупчанике, са односом из полупречника подеоних кругова или броја зуба.",
"help.joint.limits": "Одређује докле спој сме да се окреће или клизи, као минимум и максимум, или брише те границе. Користите га да шарку, клизач или полугу задржите у њиховом стварном опсегу кретања.",
"help.joint.motion_link": "Спреже два споја односом и помаком, па покретање једног помера други. Користите је за сталну везу између спојева коју Зупчаник, Зупчаста летва и зупчаник или Завртањ не покривају.",
"help.joint.origin": "Поставља именовано исходиште споја на део: у центар лица или округлог краја, средину ивице, угао или тачку. Користите га да припремите места на која се спојеви причвршћују; исходиште изабрано на лицу прати лице када део промени величину.",
"help.joint.rack_pinion": "Повезује мали зупчаник који се окреће са зупчастом летвом која клизи, па се летва помера за дужину подеоног круга коју зупчаник пређе котрљањем. Користите га за погоне са зупчастом летвом, управљање и линеарне столове.",
"help.joint.reach": "Помера тачку на делу ка циљу променом спојева на њеном ланцу, у оквиру њихових граница. Користите га да механизам поставите према томе где би његов крај требало да буде, на пример врх руке робота.",
"help.joint.rigid_group": "Закључава изабране делове један за други онакве какви јесу, па се крећу као једно. Користите је за делове који припадају заједно и међу којима нема кретања.",
"help.joint.screw": "Повезује окретање и клизање кораком, па сваки окретај помера за то растојање. Користите га за навојна вретена, завртње и навојне осовине.",
"help.library.apply": "Примењује материјал или изглед из датотеке локалне библиотеке на тела, лица или компоненте. Користите га да поново употребите материјале које сте сачували из другог дизајна.",
"help.library.export": "Чува материјале и изгледе дизајна у датотеку библиотеке. Користите га да их делите или поново користите у другим дизајнима.",
"help.library.inspect": "Проверава датотеку библиотеке и наводи њене материјале и изгледе, без промене дизајна. Користите га да видите шта библиотека садржи пре него што је примените.",
"help.licences": "Лиценце",
"help.material.assign": "Одређује од чега су изабрана тела направљена, нпр. челик, алуминијум или ABS. Користите га да маса и тежина одговарају стварном делу.",
"help.material.properties.validate": "Проверава да ли су бројеви на картици података о материјалу међусобно усклађени и да ли недостају вредности. Користите га пре него што се ослоните на податке за масу или крутост.",
"help.mesh.convert": "Поново гради мрежно тело, на пример STL, као тело које се може мењати, са равним и цилиндричним лицима. Користите га да мењате увезене механичке делове као моделована тела.",
"help.mit": "MIT лиценца",
"help.motion.study": "Задаје вредности спојева у корацима дуж временске осе и репродукује их као анимацију. Користите је да гледате како се механизам креће кроз низ положаја и успут проверите зазоре.",
"help.motion.trace": "Прати путању којом иде тачка на делу док се студија кретања репродукује или спој покреће и, када потврдите, чува је као 3D криву скице. Користите га да видите докле механизам досеже или да на његовој путањи градите даље.",
"help.notice": "NOTICE",
"help.occurrence.ground": "Учвршћује део на месту или га поново ослобађа, тако да спојеви померају остале делове око њега. Користите га за оквир или основу на којој је изграђен остатак склопа.",
"help.occurrence.move": "Помера део склопа само дуж слобода које дозвољавају његови спојеви. Изаберите лице дела, а затим превуците доступну стрелицу или лук.",
"help.parameters.change": "Отвара табелу именованих вредности и израза који управљају котама дизајна. Користите је да промените меру на једном месту, а све операције које је користе је прате.",
"help.parameters.export": "Чува параметре дизајна као CSV или JSON. Користите га да их уређујете у табели или пренесете у други дизајн.",
"help.parameters.graph": "Показује који параметри и операције користе које друге, као и евентуалне петље или грешке међу њима. Користите га да видите на шта ће утицати промена параметра.",
"help.parameters.import": "Учитава параметре из CSV или JSON у дизајн, и то све или ниједан. Користите га да унесете вредности из табеле или другог дизајна.",
"help.parameters.move": "Помера кориснички параметар испред или иза другог на списку. Користите га да сродни параметри остану заједно.",
"help.parts.fastener": "Додаје стандардни завртањ, вијак, навртку, подлошку, клин или лежај у величини коју изаберете. Постављен на рупу, належе у рупу помоћу споја, па не морате сами да моделујете спојне елементе.",
"help.parts.insert": "Умеће стандардни део у породици, величини и дужини које изаберете. Постављен на обод или зид рупе, належе на рупу крутим спојем.",
"help.parts.library": "Отвара стандардне делове: завртње, вијке, навртке, подлошке, завртње без главе, паралелне клинове (колчиће) и лежајеве у стандардним величинама. Користите га да додате стандардни део уместо да га сами моделујете.",
"help.parts.resize": "Мења величину или дужину уметнутог стандардног дела и поново га гради; његови спојеви га држе на месту. Користите га када се покаже да је завртањ предугачак или када рупа захтева другу величину.",
"help.plastic.assign_rule": "Додељује правило за пластику телима, па њихове пластичне операције преузимају његов нагиб и зазор као подразумеване вредности. Користите га да једном подесите део израђен у калупу за све његове операције.",
"help.plastic.boss": "Додаје испуст за завртањ на лицу: стуб са необавезном рупом, нагибом, заобљењем у корену и потпорним ребрима. Користите га за места за завртње у кућиштима израђеним у калупу или штампаним.",
"help.plastic.lip": "Додаје усну или одговарајући жлеб дуж обода издубљеног тела. Користите је да се две половине кућишта позиционирају једна на другу и чврсто затворе.",
"help.plastic.manage_rules": "Прави и мења правила за пластику: материјал, дебљину зида, полупречнике, зазор и нагиб. Користите их да операције дела израђеног у калупу буду усклађене.",
"help.plastic.rest": "Додаје ослонац на лицу: округло или правоугаоно постоље, пуно или шупље, са нагибом. Користите га као подлогу или дистанцер у кућишту израђеном у калупу.",
"help.plastic.snap_fit": "Додаје куку шкљоцајућег споја на лицу, са њеном дужином, дебљином и зупцем. Користите га да делове спојите шкљоцањем, без завртања.",
"help.pointcloud.insert": "Уметните PLY, XYZ или PTS скенирање као облак тачака поред модела. Користите га као референцу при изради модела на основу измерених података.",
"help.sheet.convert": "Претвара плочу или издубљено тело једнаке дебљине у тело од лима, са савијањима на ивицама које изаберете. Користите га да постојећи део буде могуће развити.",
"help.sheet.export_bend_table": "Чува табелу савијања тела од лима као CSV или JSON: углове, полупречнике и положаје савијања у развијеном облику. Користите је за пресу за савијање или радионицу.",
"help.sheet.export_dxf": "Чува развијени облик као DXF, са обрисима, линијама савијања и необавезним белешкама о савијању. Користите га за ласерско, плазма или водено сечење.",
"help.sheet.flange": "Додаје прирубницу: основну плочу из скице, зид дуж отворене контуре скице или савијени зид на ивицама лима. Користите је да изградите делове од лима.",
"help.sheet.flat_pattern": "Прави развијени облик тела од лима, са његовом величином, изрезима и линијама савијања. Користите га да проверите заготовак пре сечења.",
"help.sheet.fold": "Савија лим дуж линије скице за задати угао и полупречник. Користите га да пресавијете раван лим након што сте у њега исекли изрезе.",
"help.sheet.hem": "Пресавија ивицу лима натраг на саму себе. Користите га за безбедне, круте ивице без оштрог грата.",
"help.sheet.manage_rules": "Прави и мења правила за лим: дебљину, полупречник савијања, K-фактор, растерећења и размаке. Користите их да ускладите материјал и алате своје радионице.",
"help.sheet.path_flange": "Додаје низ прирубница дуж ивице лима, пратећи отворени ланац линија скице. Користите је за профиле као што су U или Z облик у једном кораку.",
"help.sheet.perforate": "Додаје поље рупа преко целог равног лима, у помереном или квадратном распореду. Користите га за отворе за проветравање, решетке и лакше панеле.",
"help.sheet.refold": "Поново савија развијено тело од лима. Користите га након додавања операција на раван лим.",
"help.sheet.unfold": "Развија сва савијања тела од лима. Користите га да додате рупе или изрезе који морају да пређу преко савијања, а затим поново пресавијте.",
"help.sketch.arc.center_point": "Црта кружни лук око центра, од почетне до крајње тачке. Користите га када је центар лука познат, на пример за полупречник око рупе или осовине.",
"help.sketch.arc.tangent": "Црта лук који почиње на крају линије или лука и наставља тангентно на њега. Користите га да ланац наставите глатком кривином уместо углом.",
"help.sketch.arc.three_point": "Црта лук од почетне до крајње тачке кроз трећу тачку. Користите га када лук мора да прође кроз задате тачке, а не око познатог центра.",
"help.sketch.arc3d": "Црта тачан кружни лук у простору кроз три тачке, са укљученом 3D скицом. Користите га за кривине путања и водилица које напуштају раван скице.",
"help.sketch.auto_constrain": "Додаје ограничења која скица скоро већ има, као што су хоризонталност, паралелност или подударност, а затим је котира док није потпуно дефинисана. Користите ову команду да једним кораком учврстите грубо нацртану скицу.",
"help.sketch.auto_constrain_from_datum": "Ради као команда Аутоматска ограничења, али положаје мери од референтне тачке коју изаберете уместо од исходишта. Користите је када је део распоређен од једне референтне тачке, на пример угла или рупе.",
"help.sketch.blend_curve": "Спаја крајеве две криве глатким сплајном који је тангентан на обе, или и непрекидан по закривљености ако тако изаберете. Користите је да премостите размак између кривих без прегиба.",
"help.sketch.break": "Дели криву на делове тамо где је секу друге криве или у тачкама које поставите на њу. Користите га када део криве треба да има своје ограничење или да се сам избрише.",
"help.sketch.cancel": "Напушта скицу и одбацује све што је измењено откако је направљена или отворена за измену. Поништи враћа измене.",
"help.sketch.centerline": "Пребацује линије између обичних линија и средишњих линија, оса које профили изостављају. Користите средишњу линију као осу за ротацију, пресликавање или ограничење симетрије.",
"help.sketch.chamfer.distance_angle": "Скошава угао између две линије; задаје се растојањем дуж прве линије и углом. Користите га када цртеж даје скошење као дужину и угао, на пример 2 mm × 30°.",
"help.sketch.chamfer.equal_distance": "Скошава угао између две линије за исто растојање уназад дуж обе. Користите га за уобичајено скошење од 45° на обрису.",
"help.sketch.chamfer.two_distance": "Скошава угао између две линије; задаје се засебним растојањем дуж сваке линије. Користите га за скошења која нису од 45°, задата као две дужине.",
"help.sketch.circle.center": "Црта круг из његовог центра и пречника. Користите га за рупе, испусте и друге округле облике чији је центар познат.",
"help.sketch.circle.three_point": "Црта круг кроз три тачке. Користите га када круг мора да прође кроз задате тачке, на пример кроз тачке постојеће геометрије.",
"help.sketch.circle.three_tangent": "Црта круг који додирује три елемента: линије, кругове или лукове. Користите га да уклопите круг у простор између три криве, на пример као уписани круг троугла.",
"help.sketch.circle.two_point": "Црта круг из две крајње тачке његовог пречника. Користите га када су познате тачке на ивици круга, а не његов центар.",
"help.sketch.circle.two_tangent": "Црта круг задате величине који додирује два елемента: линије, кругове или лукове. Користите га за заобљење сталног полупречника између две криве.",
"help.sketch.conic": "Црта конику између две крајње тачке, обликовану теменом и параметром ρ. Користите је за глатке прелазе чију пуноћу желите да подесите, од равног до скоро оштрог угла.",
"help.sketch.constrainer": "Примењује ограничење које једна или две изабране криве већ скоро имају, као што су хоризонтално, паралелно, тангентно или једнако. Користите га да ручно нацртану скицу сређујете ограничење по ограничење.",
"help.sketch.constraint.coincident": "Поставља тачку на другу тачку, криву или осу скице. Користите је да затворите размаке у профилу или да поставите тачку на линију.",
"help.sketch.constraint.collinear": "Поставља две линије на једну праву. Користите је да поравнате прекинуте ивице, на пример обе стране зареза.",
"help.sketch.constraint.concentric": "Поставља два круга или лука на исти центар. Користите је за прстенове, цилиндрична упуштења и вратила са степеницама.",
"help.sketch.constraint.curvature": "Чини да две криве које се сусрећу у крајњој тачки настављају са истом закривљеношћу (G2). Користите је тамо где тангентни спој није довољно глатак, на пример на видљивим површинама.",
"help.sketch.constraint.equal": "Чини линије исте дужине, а кругове и лукове истог полупречника. Користите је да једна кота одређује неколико одговарајућих облика.",
"help.sketch.constraint.fix": "Фиксира тачке или криве где јесу или их поново ослобађа. Користите га за геометрију која се не сме померати када превлачите или решавате скицу.",
"help.sketch.constraint.horizontal_vertical": "Чини линију, или линију између две тачке, хоризонталном или вертикалном, зависно од тога која је ближа. Користите га да обрис поравнате са осама скице.",
"help.sketch.constraint.midpoint": "Поставља тачку на средину линије или чини да се средишње тачке две линије поклопе. Користите га да геометрију центрирате на ивици.",
"help.sketch.constraint.parallel": "Чини да две линије буду паралелне. Користите је за странице које морају остати паралелне када се друге коте промене.",
"help.sketch.constraint.perpendicular": "Чини да се две линије састају под правим углом. Користите је за праве углове између линија које нису на осама скице.",
"help.sketch.constraint.polygon": "Чини затворени ланац линија правилним полигоном: једнаких страница, са сваким теменом на једном кругу. Користите га да ручно нацртани полигон учините правилним и да такав остане.",
"help.sketch.constraint.symmetry": "Чини да две тачке или криве буду једна другој одраз преко линије или око тачке. Користите је да профил остане симетричан када мењате једну страну.",
"help.sketch.constraint.tangent": "Чини да се две криве додирују тангентно, на пример линија и лук. Користите га за глатке спојеве без прегиба.",
"help.sketch.construction": "Пребацује криве између обичне и помоћне геометрије, коју профили изостављају. Користите помоћне криве као помагала за распоред, референце и коте.",
"help.sketch.construction_line": "Направите бесконачну помоћну праву кроз две тачке. Користите је да распоредите скицу без раздвајања профила.",
"help.sketch.construction_line.angle": "Направите бесконачну помоћну праву под углом од осе X скице. Изаберите тачку и упишите угао или изаберите смер.",
"help.sketch.construction_line.bisector": "Изаберите две праве линије скице које се секу, па једну од две симетрале угла. Резултат прати изворне линије.",
@@ -1411,11 +1580,71 @@
"help.sketch.construction_line.tangent_external": "Изаберите круг или кружни лук, па спољашњу тачку и решење тангенте. Прегледи приказују само тачке додира на нацртаном луку.",
"help.sketch.construction_line.tangent_two": "Изаберите два круга или кружна лука, па у прегледу изаберите заједничку тангенту. Тачке додира морају лежати на нацртаним кривама.",
"help.sketch.construction_line.vertical": "Направите бесконачну вертикалну помоћну праву кроз тачку. Њено ограничење вертикалности остаје на снази при каснијим изменама.",
"help.sketch.copy": "Копира изабране криве и тачке скице, заједно са ограничењима и котама међу њима. Налепите их у ову или у другу скицу.",
"help.sketch.create": "Започиње нову 2D скицу на равни или на равном лицу модела. Свака екструзија, ротација и повлачење почиње скицом.",
"help.sketch.cut": "Копира изабрану геометрију скице и брише је из скице, у једном кораку поништавања. Налепите је на другом месту у овој скици или у другу.",
"help.sketch.dimension": "Додаје погонску коту: дужину, растојање, угао, полупречник или пречник, зависно од тога шта изаберете. Упишите вредност или назив параметра, а скица га прати.",
"help.sketch.edit": "Отвара постојећу скицу за измену. Операције изграђене на њој ажурирају се када завршите скицу.",
"help.sketch.ellipse": "Црта елипсу из њеног центра, краја велике осе и тачке на њеној малој оси. Користите је за овалне отворе, брегове и друге издужене округле облике.",
"help.sketch.equation_curve": "Црта криву из формула: x(t) и y(t), или y = f(x), у задатом опсегу. Формуле могу да користе параметре и крива их прати: користите је за синусоиду, спиралу или евольвенту зупца зупчаника.",
"help.sketch.extend": "Продужава линију или лук до најближе криве на свом путу или продужава две криве док се не сретну. Користите га да затворите размаке у профилу након одсецања или измене.",
"help.sketch.fillet": "Заобљује угао у којем се састају линије или лукови, луком задатог полупречника. Више углова заобљених заједно деле један полупречник.",
"help.sketch.finish": "Напушта скицу и враћа се на модел. Њени затворени профили су тада спремни за Екструдирај, Ротирај и друге операције.",
"help.sketch.finish_auto_constrain": "Покреће команду Аутоматска ограничења на скици, а затим је завршава. Користите га да једним кораком оставите потпуно дефинисану скицу.",
"help.sketch.fit_curves_to_section": "Пресеца мрежно или пуно тело равни скице и уклапа линије и лукове у пресек. Користите га да скенирани или увезени део поново направите као скицу која се може мењати.",
"help.sketch.include_3d": "Уноси ивице модела, врхове и криве других скица у ову скицу као повезане референце, а оне ван њене равни спљоштава на њу. Користите га да градите на геометрији која не лежи у равни скице.",
"help.sketch.infinite": "Пребацује линије између обичних линија и бесконачних помоћних линија које се пружају преко целог приказа. Користите је за референце распореда које никад не завршавају прерано.",
"help.sketch.insert_dxf": "Учитава криве DXF цртежа у скицу, скалиране, окренуте и постављене како зададете. Користите га да градите на 2D цртежу из другог програма, на пример на обрису за ласерско сечење.",
"help.sketch.insert_svg": "Учитава путање SVG цртежа у скицу, скалиране, окренуте и постављене како зададете. Користите га да логотип или векторски обрис претворите у криве које можете екструдирати.",
"help.sketch.inspect": "Отвара извештај о отвореној скици: колико је степени слободе остало и да ли је потпуно ограничена. Користите га да сазнате шта се још помера пре него што завршите скицу.",
"help.sketch.intersect": "Додаје криве или тачке у којима тела, лица, ивице или друге скице секу раван скице. Оне остају повезане и прате модел када се он промени.",
"help.sketch.intersection_curve": "Прави 3D криве тамо где се два тела или лица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
"help.sketch.isoparametric_curve": "Прави 3D криву дуж U или V смера лица кроз тачку коју изаберете, повезану са лицем. Користите је као путању или линију поделе која прати површ.",
"help.sketch.line": "Црта повезане линије тачку по тачку, као отворени ланац или затворени обрис. То је основни алат за већину профила.",
"help.sketch.line.midpoint": "Црта линију од њене средишње тачке до једног краја, а други крај се пресликава. Користите је за линије центриране на тачки, на пример за симетричну ивицу.",
"help.sketch.line3d": "Црта повезане праве линије између тачака у простору, са укљученом 3D скицом. Користите је за путање и оквире који напуштају раван скице.",
"help.sketch.mirror": "Пресликава криве и тачке скице преко линије или осе и додаје ограничења симетрије. Користите га да нацртате половину симетричног профила и допуните га.",
"help.sketch.move": "Помера или ротира криве и тачке скице, или њихову копију. Користите га да промените положај геометрије уз задржавање њеног облика и ограничења.",
"help.sketch.offset": "Прави копију ланца кривих на задатом растојању, на једној или обе стране. Користите га за зидове, обрисе и зазоре; растојање остаје кота која се може мењати.",
"help.sketch.paste": "Налепљује геометрију скице коју сте копирали или исекли у активну скицу. Поставите је на тачку или је померите за задати помак.",
"help.sketch.pattern.circular": "Копира геометрију скице око централне тачке, преко пуног круга или задатог угла. Користите га за кружне распореде рупа за завртње, паоке и зубе.",
"help.sketch.pattern.rectangular": "Копира геометрију скице у редове и колоне на задатим размацима. Користите га за мреже рупа, жлебова или отвора за проветравање.",
"help.sketch.point": "Поставља тачку скице. Користите је као референцу за коте, ограничења, центре рупа или касније операције.",
"help.sketch.point3d": "Поставља тачку у простору, са укљученом 3D скицом. Користите је као референцу за 3D криве и путање.",
"help.sketch.polygon.circumscribed": "Црта правилан полигон око круга, чија се величина задаје од центра до средине странице. Користите га када је познат отвор кључа, као код шестоугаоне навртке.",
"help.sketch.polygon.edge": "Црта правилан полигон из два краја једне странице. Користите га када су познате дужина и положај странице.",
"help.sketch.polygon.inscribed": "Црта правилан полигон унутар круга, чија се величина задаје од центра до темена. Користите га када је позната мера преко темена.",
"help.sketch.project": "Уноси ивице, лица и врхове модела и криве других скица у ову скицу, пројектоване на њену раван. Оне остају повезане и прате модел када се он промени.",
"help.sketch.project_to_surface": "Пројектује криве других скица дуж нормале ове скице на лице, као повезане 3D криве. Користите га да логотип, линију поделе или путању ставите на закривљену површину.",
"help.sketch.rectangle.center": "Црта правоугаоник из његовог центра и једног угла. Користите га за обрисе центриране на тачки, на пример на исходишту.",
"help.sketch.rectangle.rounded": "Црта правоугаоник са сва четири угла заобљена истим полупречником. Користите га за плоче, поклопце и отворе са заобљеним угловима.",
"help.sketch.rectangle.three_point": "Црта правоугаоник из једне ивице и тачке на супротној страни. Користите га за правоугаонике под углом у односу на осе скице.",
"help.sketch.rectangle.two_point": "Црта правоугаоник из два супротна угла. Користите га за обрисе плоча, блокова и џепова.",
"help.sketch.scale": "Скалира криве и тачке скице задатим фактором око базне тачке. Користите га да промените величину дела скице без поновног цртања.",
"help.sketch.slot.arc_center": "Црта закривљени жлеб задате ширине дуж лука одређеног центром, почетком и крајем. Користите га за жлебове за подешавање око осовине.",
"help.sketch.slot.arc_three_point": "Црта закривљени жлеб задате ширине дуж лука кроз три тачке. Користите га када путања жлеба мора да прође кроз задате тачке.",
"help.sketch.slot.center_point": "Црта прави жлеб задате ширине од његовог центра до центра једног крајњег лука. Користите га за жлебове центриране на тачки.",
"help.sketch.slot.center_to_center": "Црта прави жлеб задате ширине између центара његова два крајња лука. Користите га за жлебове котиране од центра до центра, на пример за жлебове за подешавање завртња.",
"help.sketch.slot.overall": "Црта прави жлеб задате ширине од једног спољног краја до другог. Користите га када је важна укупна дужина жлеба.",
"help.sketch.spline.control_point": "Црта кубни сплајн обликован контролним тачкама кроз које не пролази. Користите га за глатке, течне криве које обликујете померањем тачака.",
"help.sketch.spline.control_point_5": "Црта сплајн степена 5 обликован контролним тачкама. Користите га тамо где се криве морају спојити са глаткијом закривљеношћу, на пример на видљивим дизајнерским површинама.",
"help.sketch.spline.fit_point": "Црта глатки сплајн који пролази кроз сваку тачку коју поставите, отворен или затворен. Користите га да пратите облик кроз познате тачке.",
"help.sketch.spline3d": "Црта глатку криву у простору кроз тачке, са укљученом 3D скицом. Користите га за путање повлачења и трасе каблова који напуштају раван скице.",
"help.sketch.spun_profile": "Прави у равни скице обрис који тело опише када се окреће око осе. Користите га да добијете силуету струганог дела за ротацију или цртеж.",
"help.sketch.text": "Уписује текст у скицу изабраним фонтом, висином и углом; његове речи могу да прате параметре. Екструдирајте га или направите рељеф за ознаке и натписе.",
"help.sketch.transform": "Скалира, окреће и помера геометрију скице око централне тачке у једном кораку. Користите га да истовремено промените положај и величину избора.",
"help.sketch.trim": "Уклања део криве између најближих пресека око места где кликнете. Користите га да преклапајуће линије средите у затворен профил.",
"help.solid.align": "Помера изабрана тела тако да се тачка, лице или раван на њима поклопи са тачком, лицем или равни негде другде. Користите га да поставите део уз други без мерења одмака.",
"help.solid.boundary_fill": "Прави тела од области затворених преклапајућим алатним телима, попуњавајући само области које изаберете. Користите је да направите облик од простора који заједно ограничава неколико тела.",
"help.solid.bounding_solid": "Прави кутију која обухвата изабрана тела, са необавезним додатком. Користите га да видите укупну величину дела или као комад материјала од којег се обрађује.",
"help.solid.box": "Прави кутију из дужине, ширине и висине, из угаоне тачке или центрирану на тачки. Користите је за брз блок, привремену замену или тело у које се секу облици.",
"help.solid.chamfer": "Одсеца изабране ивице равним скошењем задатог растојања. Користите га да ублажите оштре ивице или олакшате увођење чивије или завртња.",
"help.solid.coil": "Прави завојно тело, на пример опругу, из пречника, броја окретаја, висине или корака и округлог или квадратног пресека. Користите је за опруге, завојнице и завојне гребене.",
"help.solid.combine": "Спаја тела, одузима их од циљног тела или задржава само заједничку запремину, а може да задржи и алатна тела. Користите га да спојите делове, да једним обликом одсечете други или да пронађете где се два тела преклапају.",
"help.solid.cone": "Прави конус из основног круга и висине, или зарубљени конус када и врх има полупречник. Користите га за сужења, левкове, упуштања и шиљке.",
"help.solid.cylinder": "Прави цилиндар из полупречника или пречника и висине, постављен на тачку дуж осе. Користите га за вратила, клинове, испусте и округле заготовке.",
"help.solid.draft": "Нагиње изабрана лица за угао који задате, мерено од неутралне равни. Користите га на ливеним или бризганим деловима како би изашли из калупа.",
"help.solid.emboss": "Издиже профиле скице, на пример текст или логотип, из лица или их утискује у њега. Користите га за натписе, ознаке и обележја на равним или округлим лицима.",
"help.solid.extrude": "Извлачи профил скице у 3D дуж задатог растојања, додајући или уклањајући материјал. Користите га да раван обрис претворите у блок, испуст или џеп.",
"help.solid.fillet": "Заобљује изабране ивице глатким кружним прелазом задатог полупречника. Користите га да омекшате оштре ивице ради чврстоће, удобности или изгледа.",
"help.solid.hole": "Буши округлу рупу задатог пречника на изабраној тачки лица. Користите је за пролазе завртања, чивије и друге једноставне округле отворе.",
@@ -1424,20 +1653,60 @@
"help.solid.move": "Помера или ротира изабрана тела, или њихову копију, за растојање и угао које задате. Користите га да преместите тело или направите друго на другом месту.",
"help.solid.offset_face": "Гура изабрана лица унутра или напоље за растојање које задате, а околна лица их прате. Користите га да подебљате зид, продубите џеп или повисите испупчење без мењања скице.",
"help.solid.pattern.circular": "Понавља изабране објекте око осе, за задати број и укупни угао. Користите га за кругове завртања, жбице, ребра хладњака и зупце.",
"help.solid.pattern.path": "Копира операције или тела дуж путање скице, према броју и размаку. Користите га за рупе дуж закривљене ивице или делове дуж шине.",
"help.solid.pattern.rectangular": "Понавља изабране објекте у једном или два смера са задатим бројем и размаком. Користите га за редове и мреже рупа, ребара, чивија или ножица.",
"help.solid.pipe": "Прави округлу цев дуж путање кривих скице, пуну или шупљу са задатом дебљином зида. Користите је за цеви, жице, каблове и шине.",
"help.solid.press_pull": "Додаје или уклања материјал док превлачите лице, или заобљава ивице док их превлачите. Користите га за брзе измене директно на моделу.",
"help.solid.remove": "Уклања изабрана тела из дизајна од ове тачке временске траке надаље; ранији кораци их задржавају. Користите га да одбаците помоћно тело, нпр. алат за сечење који вам више не треба.",
"help.solid.replace_face": "Премешта изабрана равна лица на раван или на паралелно лице, а тело расте или се смањује да га досегне. Користите га да поравнате лице са другим делом или помоћном равни.",
"help.solid.revolve": "Окреће профил скице око осе и прави округло тело. Користите га за вратила, дугмад, посуде и сваки део који је округао око средишње линије.",
"help.solid.rib": "Додаје танак зид дуж отворене криве скице, испуњен до наредних лица тела. Користите га да ојачате зидове и углове, на пример код делова израђених у калупу или штампаних.",
"help.solid.scale": "Повећава или смањује изабрана тела за фактор, једнако у свим смеровима или посебно по оси. Користите га да промените величину увезеног облика или урачунате скупљање.",
"help.solid.shell": "Издубљује тело и оставља зидове задате дебљине, а може да отвори лица која изаберете. Користите га за кутије, поклопце и друге танкозидне делове.",
"help.solid.sphere": "Прави сферу из полупречника или пречника око централне тачке. Користите је за кугле, дугмад и заобљене крајеве.",
"help.solid.split_body": "Сече тело на засебна тела равни, лицем другог тела или линијом скице. Користите га за делове који се засебно штампају, обрађују или боје.",
"help.solid.split_face": "Дели изабрана лица по равни, лицу другог тела или кривама скице, без сечења тела. Користите га да делу лица дате сопствени нагиб, боју или одмак.",
"help.solid.sweep": "Повлачи профил скице дуж путање и прави тело. Користите га за цеви, шине, ручке и сваки део константног пресека који прати криву.",
"help.solid.thread": "Додаје ISO метрички навој на цилиндрично лице, као козметички навој или урезан у тело. Користите га за завртње, навојне рупе и штампане делове са навојем.",
"help.solid.torus": "Прави прстен из великог полупречника око његове осе и малог полупречника његовог округлог пресека. Користите га за О-прстенове, ручке и округле перле.",
"help.solid.web": "Додаје танак зид дуж сваке отворене криве скице, сваки испуњен до наредних лица тела. Користите је за мрежу потпорних зидова унутар кућишта и тацни.",
"help.surface.blend": "Прави површ која премошћује ивице два тела, тангентно или непрекидно по закривљености са оба. Користите је да глатко спојите две површи преко размака.",
"help.surface.boundary": "Прави површ која испуњава петљу од две до четири криве скице или мрежу кривих. Користите је за слободне панеле дефинисане њиховим ивицама.",
"help.surface.extend": "Продужава површ преко њених ивица, за растојање или до равни или лица. Користите га да се површи преклопе пре него што их одсечете или сашијете.",
"help.surface.extrude": "Извлачи криве или профиле скице дуж растојања у површ, само зидове, без поклопаца. Користите је за танке зидове и за алате за сечење или поделу.",
"help.surface.fair": "Углађује унутрашњост слободних лица док њихове ивице остају на месту. Користите га да изравнате избочине и таласасте одблеске на површи.",
"help.surface.loft": "Прави површ која редом пролази кроз два или више профила, без поклопаца. Користите је за љуске и прелазе између различитих облика.",
"help.surface.match": "Поново гради ивицу површи тако да се поклопи са ивицом другог тела, уз одговарајућу тангентност или закривљеност. Користите га да глатко затворите шав између две површи.",
"help.surface.offset": "Прави копију лица на растојању дуж њихових нормала, као ново површинско тело. Користите је за опне, зазоре или другу страну зида.",
"help.surface.patch": "Испуњава област скице или затворену петљу ивица лицем, равним или глатко закривљеним. Користите је да затворите рупе у површинском телу.",
"help.surface.reverse_normal": "Преврће лица површинског тела, тако да њихова спољна страна гледа на другу страну. Користите га пре подебљавања или сашивања када лица гледају ка унутра.",
"help.surface.revolve": "Окреће криве или профиле скице око осе у површ, без поклопаца. Користите је за округле љуске као што су абажури, посуде и млазнице.",
"help.surface.ruled": "Прави траку од правих линија из ланца ивица, дуж нормале лица, тангенте или под углом. Користите је за зидове са нагибом, прелазе и површи раздвајања.",
"help.surface.stitch": "Шије површинска тела заједно дуж њихових заједничких ивица, у тело када се затворе. Користите га да скуп површи претворите у једно тело.",
"help.surface.sweep": "Помера криве или профиле скице дуж путање у површ, без поклопаца. Користите је за олуке, лајсне и друге танке облике који прате путању.",
"help.surface.thicken": "Даје површинском телу дебљину и претвара га у пуно тело. Користите га да од површи направите делове погодне за штампу или обраду.",
"help.surface.trim": "Сече површ помоћу равни, друге површи или кривих скице и задржава страну коју изаберете. Користите га да површи обликујете сечењем пре сашивања.",
"help.surface.unstitch": "Раздваја лица тела у засебна површинска тела; пуно тело се распада на површи. Користите га да прерадите појединачна лица тела.",
"help.surface.untrim": "Уклања рупе са лица или враћа његову спољну границу на природне границе површи. Користите га да вратите целу површ након одсецања.",
"help.third_party": "Лиценце трећих страна",
"help.timeline.compute_all": "Поново гради сваку операцију на временској траци од почетка. Користите га да освежите модел и видите које операције не успевају.",
"help.timeline.delete": "Брише изабране операције са временске траке; брисање скице брише и операције направљене из ње. Користите га да уклоните корак који више не желите.",
"help.timeline.dependents": "Наводи операције које би биле избрисане заједно са једном операцијом и оне које не би успеле без ње. Користите га пре брисања или промене операције.",
"help.timeline.play": "Поново репродукује преостале операције једну по једну, са паузом од пола секунде након сваке обнове. Покрените ову команду поново или притисните Escape да зауставите; Иди на крај одмах скаче на крај.",
"help.view.apply": "Окреће приказ у стандардни смер, као што су предњи, горњи или изометријски, или на сачувани именовани приказ. Користите га да модел гледате из одређеног угла.",
"help.view.delete": "Брише сачувани именовани приказ. Модел се не мења.",
"help.view.fit": "Зумира и помера приказ тако да цео модел, или избор, испуни приказ. Користите га када је модел ван видног поља.",
"help.view.grid": "Приказује или сакрива мрежу у прозору приказа. Користите је као водич за величину и оријентацију током моделовања.",
"help.view.ground_shadow": "Приказује или сакрива сенку модела на подлози. Користите је да лакше процените висину и положај.",
"help.view.home": "Враћа приказ на почетни смер и уклапа модел. Користите га да се брзо вратите на познати приказ.",
"help.view.list": "Наводи именоване приказе сачуване у дизајну. Користите га да пронађете приказ који сте раније сачували.",
"help.view.navigate": "Окреће, помера или зумира приказ по корацима, на пример четвртином окрета улево. Користите га да прецизно померате камеру уместо превлачења.",
"help.view.navigation_bar": "Приказује или сакрива навигациону траку на дну прозора приказа. Сакријте је за више простора, а прикажите је да алатима приказа приступате мишем.",
"help.view.rename": "Даје сачуваном именованом приказу нови назив. Сам приказ остаје исти.",
"help.view.save": "Чува тренутни положај камере као именовани приказ у дизајну. Користите га да се касније вратите на исти угао, на пример за снимке екрана.",
"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": "Потврди брисање",
@@ -1614,6 +1883,10 @@
"material.selection.mixed": "Различити материјали",
"measure.at": "На",
"measure.item": "Ставка {number}",
"measure.quick_copied": "Мерење је копирано",
"measure.quick_copy": "Кликните да копирате мерење",
"measure.quick_edges": "Ивица: {count}",
"measure.quick_faces": "Лица: {count}",
"messages.ago_h": "пре {n} h",
"messages.ago_min": "пре {n} min",
"messages.ago_s": "пре {n} s",
@@ -1647,6 +1920,7 @@
"nav.hfss.help": "Alt+превлачење левим тастером помера, Shift+Alt+леви зумира, Ctrl+превлачење левим на празном простору орбитира",
"nav.inventor.help": "Превлачење средњим дугметом помера, Shift+средње орбитира, Ctrl+средње зумира, превлачење десним орбитира",
"nav.maya.help": "Alt+превлачење левим орбитира, Alt+средњи помера, Alt+десни зумира; превлачење средњим такође помера",
"nav.nx.help": "Превлачење средњим дугметом орбитира, Shift+средње помера (у NX-у средње + десно), Ctrl+средње зумира (у NX-у средње + лево); превлачење десним дугметом не дира камеру",
"nav.openscad.help": "Превлачење левим дугметом по празном простору орбитира, Shift+превлачење левим бира оквиром, превлачење геометрије скице је мења; превлачење десним или средњим дугметом помера, Shift+десно или Shift+средње зумира",
"nav.perspective.sketch": "Измена скице користи ортографски приказ. Завршите скицу да пребаците на перспективу.",
"nav.right_drag_orbits.help": "Искључено: превлачење десним дугметом не дира камеру, а десно дугме само отвара контекстни мени. Избор SolidWorks мапирања миша га искључује.",
@@ -1766,6 +2040,11 @@
"not a move": "није померање",
"not a number": "није број",
"open a sketch first": "прво отворите скицу",
"parameters.apply_live": "Примени сваку измену чим напустите њено поље",
"parameters.apply_live_tip": "Искључено: измене чекају на Примени или У реду, а затим се дизајн једном поново гради за све њих. Откажи или Esc их одбацује.",
"parameters.held": "Измена на чекању за Примени: {count}",
"parameters.held_hint": "Мењајте изразе и јединице. Измене чекају на Примени или У реду.",
"parameters.held_one": "1 измена на чекању за Примени",
"parts.automatic_name": "аутоматски",
"parts.dimension.across_flats": "Отвор кључа",
"parts.dimension.bore": "Отвор",
@@ -1788,6 +2067,8 @@
"parts.family.set_screw": "Завртањ без главе са унутрашњим шестоугаоником, равног врха",
"parts.family.socket_head_cap_screw": "Завртањ са цилиндричном главом и унутрашњим шестоугаоником",
"parts.family.washer": "Обична подлошка",
"parts.holes": "Рупе (необавезно)",
"parts.placement_hint": "изаберите једну или више рупа да у сваку поставите део, или У реду поставља део у исходиште",
"pattern.alignment_2": "Поравнање 2",
"pattern.alignment_hint": "Број укључује оригинал. Центар равномерно распоређује копије; Обрни бира додатну страну за парне укупне бројеве.",
"pattern.direction_1": "Смер 1",
@@ -1840,6 +2121,8 @@
"prefs.bottom_centre": "Доле у средини",
"prefs.bottom_right": "Доле десно",
"prefs.browser": "Стабло модела",
"prefs.browser_eye_left": "Око поред стрелице за скупљање",
"prefs.browser_eye_left_note": "Редови стабла модела приказују око за приказивање/скривање поред стрелице за скупљање, као раније, уместо у колони на десном крају редова.",
"prefs.browser_reveal_on_hover": "Прикажи при преласку мишем",
"prefs.browser_show_note": "Приказано држи стабло модела на екрану. Прикажи при преласку мишем га скрива док показивач не дође на његово место или на танак језичак на његовој левој ивици, а остаје отворено док је показивач у њему или док је отворен мени из њега.",
"prefs.browser_shown": "Приказано",
@@ -1859,6 +2142,8 @@
"prefs.ctrl_cmd": "Ctrl / Cmd",
"prefs.curved_surfaces": "Закривљене површи",
"prefs.curves": "Закривљене површи",
"prefs.dark_parts_shading": "Задржи облик тамних делова",
"prefs.dark_parts_shading_help": "Сенчи приказ у линеарном светлу уз меко допунско светло неба, тако да тамна лица задрже градијент уместо да поцрне",
"prefs.dead_zone_note": "Шрафирана трака је мртва зона: кретање унутар ње се занемарује.",
"prefs.default": "подразумевано",
"prefs.design": "Подразумевана подешавања дизајна",
@@ -1873,6 +2158,8 @@
"prefs.edges": "Ивице и сенчење",
"prefs.extend": "Продужи",
"prefs.extra_navigation": "Ctrl + превлачење левим дугметом по празном простору орбитира; држите размакницу за померање.",
"prefs.extrude_view_isometric": "Окрени приказ у изометријски када се отвори Екструдирај",
"prefs.extrude_view_return": "Врати се на претходни приказ када се Екструдирај затвори",
"prefs.follows_os": "Прати оперативни систем",
"prefs.font": "Фонт",
"prefs.font_fallback": "Знакови који недостају изабраном фонту приказују се уграђеним фонтом, тако да ниједна ознака не приказује празан квадрат.",
@@ -1888,8 +2175,11 @@
"prefs.graphics_card_auto": "Аутоматски",
"prefs.graphics_card_missing": "Сачувана графичка картица није доступна; користи се Аутоматски.",
"prefs.graphics_card_restart": "Ступа на снагу након поновног покретања SolidCraft-а. WGPU_ADAPTER_NAME има предност над овим избором.",
"prefs.graphics_card_unused": "Сачувана графичка картица није коришћена за овај прозор; картица која се користи приказана је у Помоћ › О програму и у статусној траци. Покушајте са опцијом Аутоматски или поставите WGPU_ADAPTER_NAME на јединствени део назива картице.",
"prefs.graphics_note": "Жртвујте брзину за глаткоћу. Сваки избор показује свој учинак на примеру.",
"prefs.graphics_presets_note": "Унапред подешене вредности подешавају све изборе испод одједном.",
"prefs.hide_constraint_glyphs_zoomed_out": "Сакриј симболе ограничења када је приказ умањен",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Симболи ограничења нестају из приказа када је скица толико умањена да би је затрпали. Искључено: остају фиксне величине на екрану при сваком зумирању.",
"prefs.history": "Временска трака",
"prefs.history_origin_bodies": "Прикажи исходиште и тела на врху",
"prefs.history_origin_bodies_note": "Временска трака лево или испод стабла модела почиње фасциклом Тела активне компоненте и Исходиштем, са њиховим оком и менијима као у стаблу модела. Временска трака на дну остаје непромењена.",
@@ -1900,6 +2190,8 @@
"prefs.history_sketches_note": "Када је Скице подешено на Испод своје операције, временска трака лево приказује сваку скицу један ниво увучено, испод прве операције направљене из ње, кад год је довољно широка за називе. Временска трака на дну увек задржава редослед настанка.",
"prefs.history_sketches_under_feature": "Испод своје операције",
"prefs.hold_stop_snapping": "Држите да искључите привлачење",
"prefs.home_fit_size": "Величина за Почетни приказ и Уклопи",
"prefs.home_fit_size_note": "Колики део приказа модел испуњава када га уоквири Почетни приказ, Уклопи, отварање дизајна или промена приказа у навигатору, без обзира на то да ли је подешавање испод укључено. 100 %: испуњава приказ између панела, компаса и навигационе траке.",
"prefs.icons_only": "Само иконе",
"prefs.imported_file": "Увезена датотека",
"prefs.in_browser": "У стаблу модела",
@@ -1908,13 +2200,20 @@
"prefs.interface_language": "Језик интерфејса",
"prefs.interface_note": "Како SolidCraft изгледа и где су његови делови.",
"prefs.interval_minutes": "{n} min",
"prefs.invert_navigator_arrows": "Обрни стрелице за ротацију коцке приказа",
"prefs.invert_navigator_arrows_note": "Стрелице око коцке орбитирају камеру у смеру у којем показују, као у програмима Fusion и Inventor: стрелица са леве стране показује удесно и закреће камеру удесно. Искључено: стрелица окреће коцку ка страни на којој се налази, па лева стрелица закреће камеру улево. Подешавање не утиче на закривљене стрелице за ваљање.",
"prefs.keep_work_on_tab_switch": "Задржи дијалоге и алате отвореним при пребацивању између дизајна",
"prefs.keep_work_on_tab_switch_note": "Свака картица дизајна задржава свој отворени дијалог или алат, са оним што сте у њему изабрали и уписали, те своју картицу траке са алатима. Када се вратите на дизајн, поново су ту. Искључено: пребацивање између дизајна отказује их и приказује картицу Модел.",
"prefs.labels": "Ознаке",
"prefs.language_system": "Систем",
"prefs.layout": "Распоред",
"prefs.layout_note": "Компактно причвршћује панеле уз ивице прозора. Провидност стабла модела доступна је у опцији Плутајуће. Страну Асистента промените на страници Асистент.",
"prefs.left": "Лево",
"prefs.left_click": "Леви клик",
"prefs.line_drag_draws_one": "Притисак и превлачење цртају једну линију",
"prefs.line_drag_draws_one_note": "У алату Линија, притисак, превлачење и пуштање цртају једну линију од места притиска до места пуштања и ту се завршава. Кликови и даље цртају ланац. Искључено: превлачење поставља тачку где се пусти, као и клик.",
"prefs.line_hover_tangent_arc": "Задржите показивач на последњој тачки линије да пређете на тангентни лук",
"prefs.line_hover_tangent_arc_note": "У алату Линија, након што је нацртан сегмент, одмакните показивач од последње тачке ланца, вратите га на њу и држите га мирно: следећи клик завршава тангентни лук, а исти покрет враћа на линије. Искључено: задржавање на тој тачки не ради ништа.",
"prefs.linked_designs": "Повезани дизајни",
"prefs.live_input": "Улаз уживо",
"prefs.live_preview": "Преглед уживо",
@@ -1928,6 +2227,9 @@
"prefs.max_file_size_step": "STEP и датотеке за увоз",
"prefs.max_file_size_web": "Веб апликација отвара STEP и датотеке за увоз до 512 MB и дизајне до 256 MB: картица прегледача не може да садржи веће датотеке. Десктоп апликација може да повећа ова ограничења.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Велике иконе",
"prefs.menu_list": "Списак",
"prefs.menus": "Менији",
"prefs.minutes": "минута",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Миш и додирна табла",
@@ -1935,6 +2237,8 @@
"prefs.mouse_mapping": "Мапирање миша",
"prefs.navigation": "Навигација",
"prefs.navigation_note": "Како миш, додирна табла и 3D миш померају приказ.",
"prefs.navigator_fit": "Уклопи модел након промене приказа у навигатору",
"prefs.navigator_fit_note": "Лице, угао или стрелица коцке приказа, односно оса гизма оријентације, такође усмерава поглед на средину модела са једног растојања на којем се уклапа из сваког смера (његова гранична сфера). Почетни приказ користи исти зум и центар док је ова опција укључена, па се прикази које изаберете после њега само окрећу. Искључено: приказ се окреће на месту и задржава свој циљ и зум.",
"prefs.network_host_remove": "Уклони",
"prefs.network_locations": "Дозволи мрежне локације у дизајнима",
"prefs.network_locations_note": "Искључено: повезане датотеке дизајна на другом рачунару (\\\\server\\share) отварају се само са сервера наведених овде или након што га дозволите када се то затражи.",
@@ -1947,12 +2251,16 @@
"prefs.origin_plane_names": "Називи равни исходишта",
"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.persistent_ribbon_pressed": "Истакни активне команде на траци",
"prefs.persistent_ribbon_pressed_help": "Истиче команде на траци док су њихови ефекти укључени. Искључите да бисте задржали претходни неутрални изглед и помоћ за команде.",
"prefs.perspective_fov": "Видно поље",
"prefs.placement": "Постављање",
"prefs.prefer_free_sketch_points": "Дај предност покретним тачкама скице када се тачке преклапају",
"prefs.preview_choices": "Преглед ваших избора",
"prefs.print_face_highlight": "Истицање површине за штампу",
"prefs.print_face_highlight_tip": "Колико се јако површина за штампу тела боји и обрубљује у приказу. 0 % не приказује истицање.",
"prefs.profile_active": "Активни профил",
"prefs.profile_applied": "Профил је примењен.",
"prefs.profile_changed": "Подешавања су промењена откако је профил сачуван. Притисните Сачувај да их задржите у њему.",
@@ -1974,9 +2282,13 @@
"prefs.quality_fastest": "најбрже",
"prefs.quality_slowest": "најспорије",
"prefs.quality_smoother": "глађе",
"prefs.quick_measure": "Брзо мерење у углу приказа",
"prefs.quick_measure_note": "Величина избора у доњем десном углу приказа: дужина ивице, или Ø и R за округлу, површина лица, положај тачке, растојање и угао две ставке, збир неколико ивица или лица. Клик га копира.",
"prefs.render_previous_geometry_budget": "Користи претходни буџет геометрије за преглед у Рендеровању",
"prefs.render_previous_geometry_budget_help": "Враћа претходно фиксно ограничење геометрије (512 MiB на 64-битним десктоп рачунарима, 128 MiB другде) уместо буџета према уређају.",
"prefs.reports_saved": "Сачувано: {n}",
"prefs.ribbon_click_ends_tool": "Клик на дугме активног алата завршава га",
"prefs.ribbon_click_ends_tool_note": "Клик на истакнуто дугме на траци алата за скицирање који је у употреби завршава тај алат, па кликови у приказу поново служе за избор. Искључено: клик поново покреће алат.",
"prefs.ribbon_reveal": "Прикажи резултате претраге на траци",
"prefs.ribbon_reveal_note": "Задржавање показивача изнад резултата у Претражи команде показује где се команда налази на траци: њену картицу, њено дугме или њен ред у падајућем менију панела. Команда покренута из претраге тамо трепери, а покренута команда из падајућег менија означава свој панел.",
"prefs.right_click": "Десни клик",
@@ -1987,6 +2299,8 @@
"prefs.search_empty": "Нема одговарајућих подешавања",
"prefs.select": "Избор",
"prefs.select_one_item": "Изабери једну ставку",
"prefs.select_other_hidden": "Изабери скривену геометрију помоћу Изабери друго",
"prefs.select_other_hidden_help": "Укључено: обухвата скривена тела, скице и заклоњене ивице у близини показивача. Искључено: користи се изворни списак видљиве геометрије испод показивача.",
"prefs.selecting": "Бирање",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift само додаје; Ctrl / Cmd укључује и искључује",
@@ -2004,6 +2318,8 @@
"prefs.sketch_exit_view.previous": "Врати се на претходни приказ",
"prefs.sketch_exit_view.unless_rotated": "Врати се осим ако је ротирано",
"prefs.sketch_exit_view_note": "Куда приказ иде након Заврши скицу, Откажи или двоструког Esc.",
"prefs.sketch_face_activates_part": "Скицирање на лицу дела чини тај део активним",
"prefs.sketch_face_activates_part_note": "Скица започета на лицу тела друге компоненте прво активира ту компоненту, па скица припада њој и у стаблу модела остаје уз тај део. Тај део након тога остаје активан. Искључено: скица припада активној компоненти.",
"prefs.sketch_open_look_at": "Окрени камеру према скици када се отвори",
"prefs.sketch_open_look_at_note": "Искључено: када направите или измените скицу, приказ остаје онакав какав јесте. Погледај окреће приказ према скици када то желите.",
"prefs.sketch_recent_snap": "Аутоматски привлачи на недавне центре и средишње тачке",
@@ -2017,6 +2333,8 @@
"prefs.sketch_tab_always_note": "Картица Скица остаје на траци, одмах после картице Модел, док ниједна скица није отворена. Њени алати за цртање тада започињу скицу на изабраној равни или лицу, или на равни коју следећу изаберете, са спремним алатом у њој.",
"prefs.sketch_tracking_guides": "Водичи за поравнање центра и средишње тачке",
"prefs.sketch_tracking_guides_note": "Задржите показивач изнад центра круга или средишта праве ивице, па се померите хоризонтално или вертикално да би се приказао привремени тачкасти водич.",
"prefs.sketch_tracking_points": "Водичи за поравнање од крајњих тачака и углова током цртања",
"prefs.sketch_tracking_points_note": "Док алат за цртање чека на тачку, задржавање показивача изнад краја линије или лука, угла правоугаоника или слободне тачке приказује исти привремени тачкасти водич као код центра или средишње тачке. Искључено: само центри и средишње тачке.",
"prefs.sketching": "Скицирање",
"prefs.snap_drawing_note": "Током цртања или превлачења тачке скице.",
"prefs.source": "Извор",
@@ -2033,6 +2351,24 @@
"prefs.status_error": "Грешка",
"prefs.status_selected": "Изабрано",
"prefs.status_warning": "Упозорење",
"prefs.structure.appearance": "Изглед",
"prefs.structure.appearance_note": "Боје, текст и приступачност.",
"prefs.structure.in_profile": "У профилу",
"prefs.structure.layout": "Распоред",
"prefs.structure.layout_note": "Где се налазе делови прозора.",
"prefs.structure.look": "Визуелно",
"prefs.structure.measure_pointer": "Мерење и показивач",
"prefs.structure.mouse3d": "3D миш",
"prefs.structure.mouse3d_note": "SpaceMouse и SpaceNavigator.",
"prefs.structure.navigation_note": "Миш и додирна табла. 3D миш има сопствену страницу.",
"prefs.structure.profile": "Профил",
"prefs.structure.profile_edited": "Промењено откако је сачувано",
"prefs.structure.profile_hover": "Сачувано у профилима подешавања",
"prefs.structure.scale_percent": "{percent}%",
"prefs.structure.sketch": "Скица",
"prefs.structure.tools": "Алати",
"prefs.structure.tools_note": "Како се понашају алати за моделовање.",
"prefs.structure.work": "Рад",
"prefs.supersampling_brief": "Исцртава приказ већим и смањује га филтрирањем: веома оштро и захтевније.",
"prefs.tab_accent_note": "Нагласна боја прати активну картицу",
"prefs.theme_file": "датотека теме",
@@ -2044,6 +2380,16 @@
"prefs.theme_follow_off": "Искључено",
"prefs.theme_follow_stop": "Престани да пратиш",
"prefs.theme_import_hint": "Кликните да изаберете датотеку теме, превуците је овде или притисните Ctrl+V (Cmd+V на Mac рачунару) када је тема копирана",
"prefs.theme_option_opacity": "Непрозирност",
"prefs.theme_option_opacity_help": "Подесите непрозирност плутајућих панела. Доња граница читљивости штити текст преко боја прозора приказа.",
"prefs.theme_option_tab_colours": "Боје картица",
"prefs.theme_option_tab_colours_help": "Користи боје панела и прозора приказа ове теме за сваку картицу на траци.",
"prefs.theme_option_tab_fade_ms": "Прелаз између картица",
"prefs.theme_option_tab_fade_ms_help": "Постепено мења боје панела и прозора приказа при промени картице. Нула прелази одмах.",
"prefs.theme_options_badge": "Опције",
"prefs.theme_options_more": "Више · {count}",
"prefs.theme_options_reset": "Ресетуј",
"prefs.theme_options_unavailable": "Није доступно на овом уређају",
"prefs.theme_paste": "Налепи тему",
"prefs.theme_paste_none": "Клипборд не садржи SolidCraft тему",
"prefs.timeline_half_hour": "−30 min",
@@ -2065,13 +2411,23 @@
"prefs.under_cursor": "Испод показивача",
"prefs.units_precision": "Јединице и прецизност",
"prefs.updates": "Ажурирања",
"prefs.value_boxes_in_view": "Прикажи вредности поред њихових ручица у приказу",
"prefs.value_boxes_in_view_tip": "Вредност команде (Растојање, Полупречник…) у пољу поред њене ручице, преко модела. Искључено: уносите је у прозору команде, који преузима тастатуру након избора, а ништа не прекрива модел.",
"prefs.view": "Понашање приказа",
"prefs.view_confirm_corner": "У реду и Откажи у углу приказа",
"prefs.view_confirm_corner_note": "Док је отворен дијалог или скица, квачица и крстић горе десно у приказу раде исто што и дугмад У реду и Откажи у дијалогу, односно завршавају и отказују скицу. Навигатор приказа помера се испод њих.",
"prefs.viewport": "Прозор приказа",
"prefs.viewport_analysis_status": "Прикажи статус анализе у прозору приказа",
"prefs.viewport_analysis_status_help": "Приказује активне анализе изнад прозора приказа, отвара њихове легенде из сваке ставке, а тастер Esc искључује најновију анализу. Искључите да бисте вратили легенде у угловима и претходно понашање тастера Esc.",
"prefs.viewport_background": "Позадина",
"prefs.viewport_background_colour": "Боја",
"prefs.viewport_background_theme": "Градијент теме",
"prefs.viewport_section_status": "Прикажи статус пресека у прозору приказа",
"prefs.viewport_section_status_help": "Приказује подешавање пресека у прозору приказа. Esc и активно дугме на траци га искључују. Искључите да задржите претходне контроле пресека.",
"prefs.viewport_status_details": "Детаљи прозора приказа уживо",
"prefs.viewport_status_details_help": "Мењајте подешавања активне анализе и пресека у стандардним картицама са детаљима. Искључите да вратите легенде анализе само за читање и дијалог Пресек.",
"prefs.viewport_status_summary": "Сажми ставке статуса у прозору приказа",
"prefs.viewport_status_summary_help": "Сажима три или више ставки статуса у прозору приказа у једну збирну значку. Искључите да свака ставка остане у пуном реду.",
"prefs.wheel": "Точкић",
"prefs.wheel_trackpad": "Точкић и додирна табла",
"prefs.workspace": "Радни простор",
@@ -2950,7 +3306,9 @@
"render.output_ready": "Спремно за рендеровање",
"render.output_run_check": "Покрени проверу",
"render.output_size_limit": "{method}: до {dimension} px по страни и {pixels} пиксела. Највећа величина која стаје: {width} × {height}.",
"render.output_size_method_profile": "Да бисте рендеровали {width} × {height}, изаберите {method} са CPU профилом ресурса {profile}, или мању величину.",
"render.output_size_ok": "Величина је у реду · {width} × {height} px",
"render.output_size_profile": "Да бисте рендеровали {width} × {height}, изаберите CPU профил ресурса {profile}, или мању величину.",
"render.output_swap_orientation": "Замени ширину и висину",
"render.output_tools_inspector": "Величину и формат измените у инспектору Излаз.",
"render.output_view_image": "Прикажи слику",
@@ -3163,6 +3521,7 @@
"render.size_custom": "Прилагођено…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · захтева {profile}",
"render.size_presets": "Унапред подешене величине",
"render.size_presets_pending": "Стиже са уређивачем излаза",
"render.size_preview": "Преглед",
@@ -3450,6 +3809,24 @@
"sheet.flange_type_contour": "Контура",
"sheet.flange_type_edge": "Ивица",
"sheet.no_body": "нема лименог тела: направите га помоћу Прирубница > Основа или претворите плочу у лим помоћу Претвори у лим (изаберите њено велико равно лице)",
"sheet.personal_rules.confirm_delete": "Потврди брисање",
"sheet.personal_rules.delete": "Избриши",
"sheet.personal_rules.delete_prompt": "Избрисати ово лично правило? Правила која су већ у дизајнима остају непромењена.",
"sheet.personal_rules.full": "Лична библиотека већ садржи 64 правила.",
"sheet.personal_rules.invalid_name": "Унесите кратак назив без прелома реда или контролних знакова.",
"sheet.personal_rules.invalid_values": "Допуните вредности правила: свака мора да се израчуна у овом дизајну, са позитивном дебљином и K-фактором од 0 до 1.",
"sheet.personal_rules.missing": "Изабрано лично правило више није доступно.",
"sheet.personal_rules.name": "Назив правила",
"sheet.personal_rules.name_exists": "Лично правило са овим називом већ постоји. Користите други назив.",
"sheet.personal_rules.name_hint": "Јединствен назив",
"sheet.personal_rules.overwrite": "Сачувај промене у изабраном личном правилу",
"sheet.personal_rules.rename": "Преименуј",
"sheet.personal_rules.rule": "Лично правило",
"sheet.personal_rules.save": "Сачувај ново",
"sheet.personal_rules.scope": "Сачувано у вашем профилу за све дизајне.",
"sheet.personal_rules.select": "Изаберите правило",
"sheet.personal_rules.title": "Лична правила",
"sheet.personal_rules.unsupported": "Ово правило има вредности које ова верзија не може да користи.",
"shortcut.json_error": "параметри морају бити JSON: {error}",
"shortcut.no_command": "нема команде `{id}`; текст пре JSON-а мора бити id команде",
"shortcut.pin": "Прикачи на врх S оквира",
@@ -3486,8 +3863,10 @@
"sketch_text.align_right": "Десно",
"sketch_text.bold": "Подебљано",
"sketch_text.builtin_font": "{family} (уграђено)",
"sketch_text.formula": "Почните са = да речи саставите од параметара: ='W' + number(Width)",
"sketch_text.italic": "Курзив",
"sketch_text.loading_fonts": "Тражење инсталираних фонтова…",
"sketch_text.no_bold": "Уграђени фонт нема подебљану варијанту: изаберите инсталирани фонт",
"sketch_text.on_path": "Текст прати своју путању: крива га поставља",
"sketch_text.path_clear": "Скини текст са његове криве",
"sketch_text.path_hint": "Крива скице коју текст прати: кликните овде, а затим криву",
@@ -3626,6 +4005,8 @@
"ui.3d_mouse_section": "3D миш",
"ui.3d_mouse_show_in_view": "Прикажи 3D миш у 3D приказу",
"ui.3d_mouse_show_in_view_tip": "Слика вашег 3D миша у доњем левом углу, која се креће док га померате.",
"ui.3d_mouse_sketch_orbit": "Окрећи скице 3D мишем",
"ui.3d_mouse_sketch_orbit_tip": "3D миш окреће приказ и док је скица отворена и гледана под правим углом, као што то може и миш. Искључено: помера и зумира скицу док не нагнете приказ.",
"ui.3d_mouse_skip": "Мој уређај ово не може",
"ui.3d_mouse_skip_tip": "Прескаче ово кретање у оба смера, а калибрација га изоставља.",
"ui.3d_mouse_speed": "Брзина 3D миша",
@@ -3654,6 +4035,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Закрените капицу супротно од казаљке на сату, држите је, а затим пустите.",
"ui.3d_mouse_switch_cap": "Калибриши као Повратак у центар",
"ui.3d_mouse_switch_trackball": "Калибриши као трекбол",
"ui.3d_mouse_use_direct": "Користи директни драјвер",
"ui.3d_mouse_use_direct_tip": "Читај 3D миш директно (hidraw) уместо преко spacenavd.",
"ui.3d_mouse_view_axis": "Оса приказа",
"ui.3d_mouse_view_off": "Искључено",
"ui.3d_mouse_view_off_tip": "Профил драјвера 3D миша искључује ову осу, па њено кретање не помера приказ. Избор кретања друге осе овде их замењује, а то кретање престаје да помера приказ.",
@@ -3692,6 +4075,7 @@
"ui.agent_chat_auth_terminal": "Покрените ово у терминалу, а затим притисните Повежи поново:",
"ui.agent_chat_auth_title": "Пријави се код агента",
"ui.agent_chat_cancel": "Откажи",
"ui.agent_chat_cannot_start": "Инсталиран, али се не покреће: {error}",
"ui.agent_chat_changes": "Промене: {n}, један корак поништавања",
"ui.agent_chat_changes_n": "Промене: {n}",
"ui.agent_chat_changes_of": "Промене: {n}/{limit}",
@@ -3784,9 +4168,12 @@
"ui.agent_chat_queued_stopped": "У реду чекања (заустављено, није послато)",
"ui.agent_chat_refresh": "Освежи",
"ui.agent_chat_refused": "Није дозвољено агенту: {reason}",
"ui.agent_chat_reinstall": "{agent} је инсталиран ({program}), али се није покренуо када га је SolidCraft проверио: {error}\nЊегова инсталација је можда непотпуна или направљена за другу врсту рачунара. Инсталирајте га поново помоћу `{install}`, проверите да у терминалу исписује своју верзију, а затим притисните Освежи.",
"ui.agent_chat_remove": "Уклони из реда",
"ui.agent_chat_remove_file": "Скини са ове поруке",
"ui.agent_chat_remove_imported": "Скини са ове поруке; део остаје у дизајну (Поништи га уклања)",
"ui.agent_chat_reply_cut_off": "Одговор је достигао ограничење дужине пре него што је завршен, па је можда непотпун. Затражите од агента да настави или да одговори сажетије.",
"ui.agent_chat_reply_declined": "Модел је одбио да одговори на ову поруку.",
"ui.agent_chat_retry": "Покушај поново",
"ui.agent_chat_right": "Десно",
"ui.agent_chat_save": "Сачувај",
@@ -3845,12 +4232,17 @@
"ui.ai_copy_suffix": "копирај",
"ui.ai_custom_url": "Прилагођени URL",
"ui.ai_default": "Подразумевано",
"ui.ai_edit": "Измени",
"ui.ai_edit_agent_title": "Измени агента",
"ui.ai_edit_gone": "Овај агент је уклоњен, па нема шта да се сачува.",
"ui.ai_edit_tip": "Промените назив, команду или крајњу тачку овог агента. Сачувани кључ остаје скривен.",
"ui.ai_http_busy": "крајња тачка је заузета или је преко ограничења; покушајте поново касније",
"ui.ai_http_key": "крајња тачка је одбила кључ: проверите га у Подешавања > AI",
"ui.ai_http_not_found": "нема такве крајње тачке или модела: проверите основни URL и назив модела",
"ui.ai_http_too_large": "разговор је превелик за ову крајњу тачку; започните нови разговор",
"ui.ai_key": "API кључ",
"ui.ai_key_hint": "необавезно; чува се у системском привеску кључева",
"ui.ai_key_keep_hint": "оставите празно да задржите сачувани кључ",
"ui.ai_key_needs_https": "Кључ се шаље само преко HTTPS-а или на овај рачунар.",
"ui.ai_key_note": "Чува се у системском привеску кључева, никад у подешавањима SolidCraft-а. За локални сервер оставите празно.",
"ui.ai_keychain_error": "Системски привесак кључева је одбио: {error}",
@@ -3877,11 +4269,15 @@
"ui.ai_permissions": "Дозволе",
"ui.ai_preset": "Унапред подешено",
"ui.ai_protocol": "Протокол",
"ui.ai_reasoning_effort": "Ниво закључивања",
"ui.ai_reasoning_effort_field_hint": "празно = подразумевана вредност пружаоца",
"ui.ai_reasoning_effort_hint": "Необавезно. Шаље се крајњој тачки као `reasoning_effort`; прихваћене вредности се разликују од пружаоца до пружаоца (на пример `low`, `high` или `max`). SolidCraft никад не мења вредност коју овде поставите. Оставите празно да се ништа не шаље и да се задржи подразумевана вредност пружаоца.",
"ui.ai_remove": "Уклони",
"ui.ai_request_timed_out": "Модел је трајао дуже од {seconds} s. Временско ограничење захтева за ову крајњу тачку можете повећати у Подешавања › Асистент.",
"ui.ai_request_timeout": "Временско ограничење захтева",
"ui.ai_request_timeout_hint": "Колико дуго један захтев моделу на овој крајњој тачки сме да траје, од 30 s до једног сата. Повећајте за спори локални модел.",
"ui.ai_reset": "Ресетуј",
"ui.ai_save": "Сачувај",
"ui.ai_settings": "AI подешавања…",
"ui.ai_show_features": "Прикажи AI функције",
"ui.ai_show_features_note": "Разговор са агентом: панел у ком агент за кодирање који већ користите или крајња тачка модела коју изаберете ради на отвореном дизајну. Искључено: нема AI дугмета, пречице, ставке менија ни панела, и ништа се не тражи нити покреће.",
@@ -3970,6 +4366,7 @@
"ui.circle": "Круг",
"ui.circular_pattern": "Кружни низ",
"ui.clear": "Очисти",
"ui.clear_print_face": "Уклони површину за штампу",
"ui.clear_recent": "Очисти недавно",
"ui.clear_the_selection": "Поништи избор",
"ui.click_selects_whole_bodies": "Клик бира цела тела",
@@ -4040,6 +4437,7 @@
"ui.dark": "Тамна",
"ui.default_modelling_orientation": "Подразумевана оријентација моделовања",
"ui.delete": "Избриши",
"ui.delete_select_first": "Прво изаберите шта желите да избришете: операције, тела, компоненте или геометрију скице",
"ui.depth": "Дубина",
"ui.design": "Дизајн",
"ui.design_properties.author": "Аутор",
@@ -4054,6 +4452,40 @@
"ui.design_properties.project": "Пројекат",
"ui.design_properties.revision": "Ревизија",
"ui.design_saved": "Сачувано: {path}",
"ui.details_access_caption": "Лица показују да ли алат може да допре до њих из изабраног смера.",
"ui.details_angle": "Угао",
"ui.details_continuity_caption": "Измените изабрани шав. Остали шавови задржавају своја подешавања.",
"ui.details_continuity_stale": "Старија геометрија: поново покрените анализу да бисте изменили овај шав или га уклопили у приказ.",
"ui.details_copy_list": "Копирај списак",
"ui.details_curvature": "Толеранција закривљености",
"ui.details_curvature_caption": "Лица су обојена према локалном полупречнику закривљености.",
"ui.details_direction": "Смер алата",
"ui.details_draft_caption": "Лица су обојена према углу у односу на смер вађења из калупа.",
"ui.details_edit_seam": "Измени шав",
"ui.details_fixture": "Узорак компоненте — само пробни подаци",
"ui.details_fixture_caption": "Узорак компоненте са илустративним вредностима; ово није резултат модела.",
"ui.details_fixture_problem": "Пример налаза са проблемом",
"ui.details_fixture_readout": "Пример измерене вредности",
"ui.details_flip": "Обрни",
"ui.details_gap": "Размак",
"ui.details_hatch": "Шрафирај пресечена лица",
"ui.details_jump": "Скок закривљености",
"ui.details_key": "Легенда",
"ui.details_offset": "Одмак",
"ui.details_plane": "Раван",
"ui.details_pull": "Смер вађења из калупа",
"ui.details_radius": "Полупречник",
"ui.details_readout": "Очитавање",
"ui.details_samples": "Узорци",
"ui.details_seam": "{body} · Ивица {edge}",
"ui.details_settings": "Подешавања",
"ui.details_sharp_gap": "Оштро или са размаком",
"ui.details_show_all": "Прикажи свих {count}",
"ui.details_smooth": "Глатко",
"ui.details_stripes": "Пруге",
"ui.details_target": "Уклопи геометрију",
"ui.details_worst_seams": "Најгори шавови",
"ui.details_zebra_caption": "Одбијене пруге показују колико се глатко лица спајају.",
"ui.diameter": "Пречник",
"ui.dimension": "Кота",
"ui.direct_interaction": "Директна интеракција",
@@ -4098,6 +4530,7 @@
"ui.edit_in_place_saved_elsewhere": "Део је сачуван у другу датотеку; склоп и даље повезује {name}",
"ui.edit_in_place_wait": "Сачекајте док се оно што ради у дизајну не заврши",
"ui.edit_section_analysis": "Измени анализу пресека",
"ui.edit_sketch_empty": "Нема скица за измену",
"ui.ellipse": "Елипса",
"ui.emboss_wrap_note": "Профили од ортогоналних линија тангентни на један подржани цилиндар омотавају се аутоматски. Изаберите зид да бисте изабрали циљ.",
"ui.end_radius": "Крајњи полупречник",
@@ -4108,12 +4541,15 @@
"ui.equation_explicit": "Експлицитни",
"ui.equation_form": "Облик",
"ui.equation_note": "Променљива је обичан број: sin(t) је узима у радијанима. Резултат без јединице је у јединици дизајна.",
"ui.equation_note_range": "Променљива је дужина, као и њен опсег: комбинујте је са параметрима дужине, а за sin() је поделите дужином. Резултат без јединице је у јединици дизајна.",
"ui.equation_parametric": "Параметарски",
"ui.equation_variable_unit": "Променљива има јединицу опсега",
"ui.equation_x_of_t": "x(t)",
"ui.equation_y_of_t": "y(t)",
"ui.equation_y_of_x": "y(x)",
"ui.equivalent_radius": "Еквивалентни полупречник",
"ui.expand": "Прошири",
"ui.explode_needs_two_components": "Растављени приказ раздваја компоненте, па дизајн мора имати две или више њих (Нова компонента, Уметни дизајн или Направи компоненте из тела)",
"ui.exportDxf": "Извези DXF…",
"ui.export_ellipsis_keymap": "Извези…",
"ui.export_keymap_tip": "Тастери, омиљено из оквира S и трака са алатима, као датотека",
@@ -4274,6 +4710,7 @@
"ui.insert_design.saved_file": "Дизајн {name} има несачуване промене, па је уметнута његова сачувана датотека. Сачувајте га, а затим покрените Ажурирај везе да бисте унели промене.",
"ui.insert_design.unsaved": "Несачуване промене",
"ui.inspect": "Прегледај",
"ui.instances": "Инстанце",
"ui.intersect": "Пресек",
"ui.isolate": "Изолуј",
"ui.join": "Споји",
@@ -4282,12 +4719,14 @@
"ui.joints": "Спојеви",
"ui.keyboard_shortcuts": "Пречице на тастатури",
"ui.language": "Језик",
"ui.lasso_select": "Ласо (превуците петљу слободном руком: узима све што је у целини унутра)",
"ui.layout_compact": "Компактно",
"ui.layout_floating": "Плутајуће",
"ui.length": "Дужина",
"ui.length_decimals": "Децимале дужине",
"ui.light": "Светла",
"ui.line": "Линија",
"ui.line_arc_hint": "Тангентни лук: кликните крај лука. Поново се задржите на последњој тачки за линије; десни клик или Esc за завршетак",
"ui.link_copies": "Повежи идентичне копије",
"ui.link_copies_keep": "Задржи",
"ui.link_copies_link": "Повежи",
@@ -4376,6 +4815,7 @@
"ui.part_size_shared": "Поново гради део, замењујући све додате операције. Сви примерци ове компоненте се ажурирају.",
"ui.paste": "Налепи",
"ui.path": "Путања",
"ui.pattern_object_type_tip": "Тела: пуни облици, бирају се у приказу или у стаблу модела; свако се копира у целини.\nОперације: кораци временске траке, као што су екструзија или рупа, бирају се на временској траци или на лицу које су направили; корак се понавља.\nСкица није ни једно ни друго: поновите њене криве помоћу алата Низ или Пресликај у самој скици или је прво екструдирајте.",
"ui.pattern_on_path": "Низ дуж путање",
"ui.perforate_margin_default": "из пречника",
"ui.perforate_pitch": "Корак",
@@ -4402,12 +4842,17 @@
"ui.plugins_skipped": "Фасцикле није било могуће прочитати: {count}.",
"ui.plugins_version": "{version}",
"ui.point": "Тачка",
"ui.point_cloud_add_filter": "Додај филтер…",
"ui.point_cloud_auto_range": "Аутоматски опсег",
"ui.point_cloud_color_by": "Боја према",
"ui.point_cloud_file_colors": "Боје из датотеке",
"ui.point_cloud_filter_by": "Филтрирај према",
"ui.point_cloud_height_high": "Највише",
"ui.point_cloud_height_low": "Најниже",
"ui.point_cloud_legend_filter": "Приказује се и сакрива у легенди изнад",
"ui.point_cloud_no_file_colors": "Датотека нема боје",
"ui.point_cloud_pause": "Паузирај",
"ui.point_cloud_play": "Репродукуј",
"ui.point_cloud_single_color": "Једна боја",
"ui.point_cloud_size": "Величина тачке",
"ui.point_cloud_units": "Јединице датотеке",
@@ -4416,6 +4861,11 @@
"ui.polygon": "Полигон",
"ui.preferences": "Подешавања",
"ui.press_a_key": "притисните тастер…",
"ui.print_face_body_not_sent": "Тело те површине се не шаље: прво га додајте у Тела",
"ui.print_face_cleared": "{body}: површина за штампу уклоњена",
"ui.print_face_input": "Површина за штампу",
"ui.print_face_pick": "Изаберите равну површину пуног тела да постане површина за штампу",
"ui.print_face_set": "{body}: површина за штампу постављена; Пошаљи у слајсер полаже тело на њу",
"ui.profile": "Профил",
"ui.profile_plane": "Раван профила",
"ui.profiles": "Профили",
@@ -4431,6 +4881,7 @@
"ui.properties.inertia_y": "Iyx, Iyy, Iyz",
"ui.properties.inertia_z": "Izx, Izy, Izz",
"ui.properties.measuring": "Мерење…",
"ui.properties.measuring_bodies": "Мерење… {done} од {total} тела",
"ui.properties.measuring_failed": "Мерење је заустављено пре завршетка.",
"ui.properties.principal_axis": "Главна оса {number}",
"ui.properties.principal_moments": "Главни моменти",
@@ -4479,6 +4930,7 @@
"ui.revolve": "Ротирај",
"ui.ribbon_exit": "Изађи",
"ui.ribbon_labels": "Прикажи текст испод икона на траци",
"ui.ribbon_menus_skip_pinned": "Изостави прикачене команде из падајућих менија",
"ui.ribbon_pin_footer": "Прикачене команде приказују се на траци. Задржите показивач изнад реда да га прикачите или откачите.",
"ui.ribbon_workspace": "Радни простор",
"ui.right": "Десно",
@@ -4497,6 +4949,7 @@
"ui.selectOther": "Изабери друго…",
"ui.selectTangentChain": "Изабери тангентни ланац",
"ui.select_all": "Изабери све",
"ui.select_components_by_face": "Изабери компоненте (лице бира своју компоненту)",
"ui.selection": "Избор",
"ui.selection_modifiers": "Модификатори избора",
"ui.selection_shift_add_only": "Shift+клик додаје без уклањања",
@@ -4538,6 +4991,11 @@
"ui.sketch_intersect_selection_filter": "Филтер избора",
"ui.sketch_modifiers": "Модификатори скице",
"ui.sketch_project_geometry": "Геометрија",
"ui.sketch_report.all_constrained": "Све је потпуно ограничено.",
"ui.sketch_report.dof": "Степени слободе",
"ui.sketch_report.loose": "Није потпуно ограничено",
"ui.sketch_report.status": "Статус",
"ui.sketch_report.title": "Извештај о скици",
"ui.sketch_scale": "Скалирање скице",
"ui.sketches": "Скице",
"ui.slice": "Пресеци",
@@ -4633,19 +5091,31 @@
"ui.view_angles": "Углови приказа",
"ui.view_angles_convention": "Степени око осе нагоре. Скретање 0° је Поглед спреда, 90° слева, −90° здесна, 180° отпозади; корак 90° је Поглед одозго; позитивно ваљање окреће модел у смеру казаљке на сату.",
"ui.view_angles_help": "Приказује корак, скретање и ваљање приказа у степенима и његову пројекцију испод гизма оријентације. Кликните на очитање да унесете тачне углове.",
"ui.view_angles_lock": "Закључај {axis}",
"ui.view_angles_ortho": "Орто",
"ui.view_angles_persp": "Перс.",
"ui.view_angles_pitch": "Нагиб",
"ui.view_angles_readout": "Нагиб {pitch} · Скретање {yaw} · Ваљање {roll}",
"ui.view_angles_roll": "Ваљање",
"ui.view_angles_unlock": "Откључај {axis}",
"ui.view_angles_yaw": "Скретање",
"ui.view_angles_zero": "Постави {axis} на 0°",
"ui.view_centre": "Центар приказа",
"ui.viewport_status_clear": "Искључи {name}",
"ui.viewport_status_clear_hint": "Искључи {name} · Esc",
"ui.viewport_status_edges": "Ивица: {count}",
"ui.viewport_status_from": "из смера {direction}",
"ui.viewport_status_key": "Прикажи легенду за {name}",
"ui.viewport_status_minimum_radius": "Минимални полупречник",
"ui.viewport_status_pairs": "Парова: {count}",
"ui.viewport_status_section_hatch": "{setting} · шрафура",
"ui.viewport_status_section_plane": "Тренутна раван",
"ui.viewport_status_section_tilted": "нагнуто · {offset}",
"ui.viewport_status_stripes": "Пруга: {count}",
"ui.viewport_status_summary_clear": "Искључи све",
"ui.viewport_status_summary_heading": "У овом приказу",
"ui.viewport_status_summary_label": "{name} и још ставки: {count}",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Прати тему",
"ui.visual_shaded_hidden_edges": "Осенчено са скривеним ивицама",
"ui.visual_shaded_with_edges": "Осенчено са ивицама",
@@ -4682,7 +5152,9 @@
"update.button": "Ажурирај",
"update.button_version": "Ажурирај на {version}",
"update.check_automatically": "Аутоматски проверавај ажурирања",
"update.check_for_updates": "Провери ажурирања",
"update.check_now": "Провери сада",
"update.check_unavailable": "Провера ажурирања није доступна за ову инсталацију.",
"update.checking": "Провера…",
"update.copy": "Копирај",
"update.dmg_opened": "Слика диска је отворена и проверена: превуците SolidCraft у Applications, замените стару копију, а затим поново покрените SolidCraft.",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+567 -10
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+567 -10
View File
File diff suppressed because it is too large Load Diff
+242 -28
View File
@@ -20,6 +20,7 @@ struct U {
sel: vec4<f32>, // xyz a whole selected body's colour before lighting
product_lights_meta: vec4<f32>, // explicit preview flag and bounded count
product_lights: array<vec4<f32>,84>, // position/kind, radiance, axis/cone, attenuation
sky: vec4<f32>, // xyz = world up; w = 1: shade in linear light with a sky fill (#7974)
};
fn reversed() -> bool {
@@ -34,6 +35,7 @@ fn near_z(c: vec4<f32>) -> f32 {
@group(1) @binding(2) var acc_t: texture_2d<f32>;
@group(2) @binding(0) var ao_depth: texture_2d<u32>;
@group(2) @binding(1) var ao_map: texture_2d<f32>;
fn clipped(p: vec3<f32>) -> bool {
return length(u.clip.xyz) > 0.5 && dot(u.clip.xyz, p) > u.clip.w;
@@ -61,7 +63,7 @@ fn out_color(c: vec4<f32>) -> vec4<f32> {
}
struct TOut {
@builtin(position) pos: vec4<f32>,
@invariant @builtin(position) pos: vec4<f32>,
@location(0) n: vec3<f32>,
@location(1) c: vec4<f32>,
@location(2) wp: vec3<f32>,
@@ -106,7 +108,7 @@ fn vs_tri_inst(
/// stores every vertex's outputs: more of them cost more than the triangles' shared vertices
/// save).
struct FOut {
@builtin(position) pos: vec4<f32>,
@invariant @builtin(position) pos: vec4<f32>,
@location(0) rp: vec3<f32>,
@location(1) c: vec4<f32>,
};
@@ -208,6 +210,12 @@ fn fs_ao_depth_flat(i: FOut, @builtin(front_facing) front: bool) -> AoDepth {
return opaque_depth(t, front);
}
@fragment
fn fs_ao_term_flat(i: FOut, @builtin(front_facing) front: bool) -> @location(0) vec4<f32> {
let t = flat_fragment(i, front);
return ao_term(t, front);
}
/// Opaque model surfaces: cut by the section plane/// Opaque model surfaces: cut by the section plane, the inside shows as a flat cap colour.
@fragment
fn fs_solid(i: TOut, @builtin(front_facing) front: bool) -> @location(0) vec4<f32> {
@@ -243,6 +251,44 @@ fn pushed(z: f32) -> f32 {
return clamp(z - select(-push, push, reversed()), 0.0, 1.0);
}
/// A far wall seen through the cut where another body's face lying on it was drawn in front of
/// it (a shaft in its bore, #9407): its cap colour again, at its depth pulled toward the eye by
/// `touch_depth`, so it wins over that face, which is faceted another way.
@fragment
fn fs_solid_touch(i: TOut, @builtin(front_facing) front: bool) -> CutOut {
var o: CutOut;
// Derivatives (in `touch_depth`) first, before `solid` may discard.
o.depth = touch_depth(i.wp, i.pos.z);
o.c = solid(i, front);
return o;
}
@fragment
fn fs_solid_touch_flat(i: FOut, @builtin(front_facing) front: bool) -> CutOut {
let t = flat_fragment(i, front);
var o: CutOut;
o.depth = touch_depth(t.wp, t.pos.z);
o.c = solid(t, front);
return o;
}
/// The depth of `wp` (depth `z`) pulled toward the eye by what may part two faceted copies of
/// one surface: 3 pixels' width along the view ray (display refinement keeps a facet's sag about
/// a pixel) and 2 pixels of the face's own depth slope (more where it turns from the eye).
fn touch_depth(wp: vec3<f32>, z: f32) -> f32 {
let px = min(length(dpdx(wp)), length(dpdy(wp)));
let slope = max(abs(dpdx(z)), abs(dpdy(z)));
var dir = -u.back.xyz;
if (u.eye.w > 0.5) { dir = wp - u.eye.xyz; }
let len = length(dir);
if (len < 1e-12) { return z; }
let c = u.vp * vec4<f32>(wp - dir * (3.0 * px / len), 1.0);
if (c.w <= 0.0) { return z; }
let pulled = c.z / c.w;
if (reversed()) { return clamp(max(pulled, z) + 2.0 * slope, 0.0, 1.0); }
return clamp(min(pulled, z) - 2.0 * slope, 0.0, 1.0);
}
/// Depth of the section plane on the view ray through `wp` (a far wall seen through the cut),
/// never behind the wall itself (`z`).
fn cap_depth(wp: vec3<f32>, z: f32) -> f32 {
@@ -307,11 +353,89 @@ fn opaque_depth(i: TOut, front: bool) -> AoDepth {
return AoDepth(depth, bitcast<u32>(depth));
}
/// The term for an opaque surface's fragment, on the prepass's depth. A section's cut face has none.
fn ao_term(i: TOut, front: bool) -> vec4<f32> {
if (clipped(i.wp) || length(i.n) < 0.5) { discard; }
if (!front && length(u.clip.xyz) > 0.5) { return vec4<f32>(1.0); }
return vec4<f32>(occlusion_term(i));
}
@fragment
fn fs_ao_term(i: TOut, @builtin(front_facing) front: bool) -> @location(0) vec4<f32> {
return ao_term(i, front);
}
@vertex
fn vs_ao_blur(@builtin(vertex_index) v: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((v << 1u) & 2u), f32(v & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
/// The depth map at `p`, reading 0 near and 1 far whichever way the buffer runs.
fn map_depth(p: vec2<i32>) -> f32 {
let at = clamp(p, vec2<i32>(0), vec2<i32>(textureDimensions(ao_depth)) - vec2<i32>(1));
let d = bitcast<f32>(textureLoad(ao_depth, at, 0).x);
return select(d, 1.0 - d, reversed());
}
/// The raw term (`ao_map` in this pass) averaged over a 4 x 4 window, which holds every turn of
/// [`ao_turn`]'s pattern once. A neighbour counts when it lies on this pixel's plane, the slope
/// taken from the nearer side across each axis, so no surface's shade crosses an edge.
@fragment
fn fs_ao_blur(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
let c = vec2<i32>(pos.xy);
let d = map_depth(c);
if (d >= 1.0) { return vec4<f32>(1.0); }
let left = d - map_depth(c + vec2<i32>(-1, 0));
let right = map_depth(c + vec2<i32>(1, 0)) - d;
let above = d - map_depth(c + vec2<i32>(0, -1));
let below = map_depth(c + vec2<i32>(0, 1)) - d;
let dx = select(right, left, abs(left) < abs(right));
let dy = select(below, above, abs(above) < abs(below));
let tolerance = 1.5 * (abs(dx) + abs(dy)) + 1e-6;
let last = vec2<i32>(textureDimensions(ao_map)) - vec2<i32>(1);
var sum = 0.0;
var weight = 0.0;
for (var y = -2; y < 2; y = y + 1) {
for (var x = -2; x < 2; x = x + 1) {
let p = c + vec2<i32>(x, y);
let s = map_depth(p);
if (s < 1.0 && abs(s - (d + dx * f32(x) + dy * f32(y))) <= tolerance) {
sum = sum + textureLoad(ao_map, clamp(p, vec2<i32>(0), last), 0).x;
weight = weight + 1.0;
}
}
}
return vec4<f32>(sum / max(weight, 1.0));
}
/// A pixel's turn of the sample pattern: every 4 x 4 window holds each of 16 turns once.
fn ao_turn(pos: vec2<f32>) -> f32 {
let x = u32(pos.x) & 3u;
let y = u32(pos.y) & 3u;
// WGSL won't mix shifts and `|` without parentheses; naga takes it, browsers refuse it (#11080).
let v = (((x ^ y) & 1u) << 3u) | ((y & 1u) << 2u) | (((x ^ y) >> 1u) << 1u) | (y >> 1u);
return (f32(v) + 0.5) / 16.0;
}
/// The lit pass's occlusion: the blurred term where this point falls in the map, whatever the
/// window's viewport offset and supersampling.
fn occlusion(i: TOut) -> f32 {
let size = vec2<i32>(textureDimensions(ao_map));
// The maps are 1 × 1 stand-ins until a frame draws into them (the product preview draws no
// prepass, and AO off releases them): they tell nothing, so leave the surface unoccluded.
if (u.ao.x <= 0.0 || length(i.n) < 0.5 || size.x < 2 || size.y < 2) { return 1.0; }
let q = u.vp * vec4<f32>(i.wp, 1.0);
if (q.w <= 0.0) { return 1.0; }
let uv = vec2<f32>(q.x / q.w * 0.5 + 0.5, 0.5 - q.y / q.w * 0.5);
return textureLoad(ao_map, clamp(vec2<i32>(uv * vec2<f32>(size)), vec2<i32>(0), size - vec2<i32>(1)), 0).x;
}
// Cosine-weighted hemisphere samples, the pattern turned by a different angle at each pixel.
// Projecting the sample and a point one radius toward the eye supplies a depth range in both
// orthographic and perspective views; distant foreground geometry cannot cast a dark halo onto
// a background surface.
fn occlusion(i: TOut) -> f32 {
fn occlusion_term(i: TOut) -> f32 {
let radius = u.ao.x;
if (radius <= 0.0 || length(i.n) < 0.5) { return 1.0; }
var n = normalize(i.n);
@@ -325,10 +449,9 @@ fn occlusion(i: TOut) -> f32 {
if (size.x < 2 || size.y < 2) { return 1.0; }
// The same offsets at every pixel made each sample darken its own shifted copy of a nearby
// part's outline: faint ghosts of the part where the radius is large on screen (#4167),
// stepped bands along contacts where it is small (#3407). Turned by interleaved gradient
// noise (Jimenez 2014), neighbouring pixels sample different points and the copies break up
// into an even grain.
let turn = fract(52.9829189 * fract(dot(i.pos.xy, vec2<f32>(0.06711056, 0.00583715))));
// stepped bands along contacts where it is small (#3407). Turned, neighbouring pixels sample
// different points, the copies break up into grain, and the blur takes the grain out (#9327).
let turn = ao_turn(i.pos.xy);
let t = tangent * cos(turn * 6.28318531) + bitangent * sin(turn * 6.28318531);
let b = cross(n, t);
// One comparison for both depth directions: depths read as 0 near / 1 far (mirrored when
@@ -385,8 +508,13 @@ fn analysis(i: TOut, k: f32) -> vec4<f32> {
let r = reflect(-v, n);
var side = cross(v, vec3<f32>(0.0, 0.0, 1.0));
if (length(side) < 1e-3) { side = vec3<f32>(1.0, 0.0, 0.0); }
let s = sin(dot(r, normalize(side)) * u.ana.y * 3.14159265);
let c = select(0.08, 0.95, s > 0.0);
let x = dot(r, normalize(side));
let s = sin(x * u.ana.y * 3.14159265);
// A face whose normal lies in the upright plane through the view (every horizontal
// face) reflects straight ahead: its sideways part is zero, on the edge between a black
// and a white stripe, and rounding picked one or the other from frame to frame while
// the view turned (#2153). Straight ahead is white.
let c = select(0.08, 0.95, s > 0.0 || abs(x) < 1e-4);
return out_color(vec4<f32>(vec3<f32>(c), 1.0));
}
if (u.ana.x > 4.5) {
@@ -507,6 +635,31 @@ fn lit(i: TOut, visibility: f32) -> vec4<f32> {
return lit_color(i, visibility, i.c);
}
/// sRGB to linear light, exact (the straight segment too, which `to_linear` leaves out).
fn srgb_decode(c: vec3<f32>) -> vec3<f32> {
return select(c / 12.92, pow((c + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4)), c > vec3<f32>(0.04045));
}
fn srgb_encode(c: vec3<f32>) -> vec3<f32> {
let l = clamp(c, vec3<f32>(0.0), vec3<f32>(1.0));
return select(l * 12.92, 1.055 * pow(l, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055), l > vec3<f32>(0.0031308));
}
/// The studio light summed in linear light, encoded once (#7974): the key and the eye fill as in
/// [`lit_color`], over a sky fill that grows as the face turns up, and the 4% every dielectric
/// reflects of that sky (a little more toward grazing), so a near-black face keeps a gradient. The sky
/// and the fill are what ambient occlusion (`visibility`) takes away. sRGB out.
fn sky_lit(base: vec3<f32>, n: vec3<f32>, diff: f32, fill: f32, visibility: f32) -> vec3<f32> {
let up = dot(n, normalize(u.sky.xyz)) * 0.5 + 0.5;
let sky = mix(0.18, 0.42, up);
// Schlick's rise toward grazing, tempered so mid-tones only gain a soft rim.
let fresnel = 0.04 + 0.2 * pow(1.0 - fill, 5.0);
let albedo = srgb_decode(base);
// Scaled so light and mid-grey faces land near where the sRGB shading puts them.
let light = albedo * (0.41 * diff + (0.66 * sky + 0.2 * fill) * visibility) + vec3<f32>(fresnel * sky * visibility);
return srgb_encode(light);
}
/// [`lit`] in a colour of its own: a whole highlighted body is lit in its colour lifted by the
/// hover amount, or in the selection colour, from the model's own plan triangles.
fn lit_color(i: TOut, visibility: f32, base: vec4<f32>) -> vec4<f32> {
@@ -533,6 +686,9 @@ fn lit_color(i: TOut, visibility: f32, base: vec4<f32>) -> vec4<f32> {
let fill = max(dot(n, v), 0.0);
let h = normalize(key + v);
let spec = pow(max(dot(n, h), 0.0), 40.0) * 0.25;
if u.sky.w > 0.5 {
return out_color(vec4<f32>(min(sky_lit(base.rgb, n, diff, fill, visibility) + vec3<f32>(spec), vec3<f32>(1.0)), base.a));
}
let k = 0.42 + 0.38 * diff + 0.25 * fill - (0.42 + 0.25 * fill) * (1.0 - visibility);
return out_color(vec4<f32>(min(base.rgb * k + vec3<f32>(spec), vec3<f32>(1.0)), base.a));
}
@@ -697,38 +853,96 @@ fn fs_line_all(i: LOut) -> @location(0) vec4<f32> {
return out_color(i.c);
}
// Point clouds: 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 colour's alpha carries the dot's size (eighths of a pixel).
// 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.
struct POut {
struct Cloud {
style: vec4<f32>, // x: colour (0 own, 1 ramp, 2 palette), y: dot size (px), z, w: ramp range
filters: array<vec4<f32>, 4>, // x: kind (0 off, 1 range y…z, 2 whole values y + i for the bits i of w)
};
@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>,
};
struct COut {
@builtin(position) pos: vec4<f32>,
@location(0) c: vec4<f32>,
@location(1) wp: vec3<f32>,
@location(2) at: vec2<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,
};
@vertex
fn vs_point(@builtin(vertex_index) vi: u32, @location(0) p: vec3<f32>, @location(1) c: vec4<f32>) -> POut {
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(c.a * 255.0 / 8.0, 1.0);
let centre = u.vp * vec4<f32>(p, 1.0);
let half = u.screen.xy * 0.5;
var o: POut;
o.pos = vec4<f32>(centre.xy + at * size * 0.5 / half * centre.w, centre.z, centre.w);
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.c = vec4<f32>(c.rgb, 1.0);
o.wp = p;
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));
// 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;
}
@fragment
fn fs_point(i: POut) -> @location(0) vec4<f32> {
if (clipped(i.wp) || dot(i.at, i.at) > 1.0) { discard; }
return out_color(i.c);
/// Whether filter `f` keeps the value `v`.
fn cloud_keeps(f: vec4<f32>, v: f32) -> bool {
if (f.x < 0.5) { return true; }
if (f.x < 1.5) { return v >= f.y && v <= f.z; }
let k = i32(round(v)) - i32(round(f.y));
return k >= 0 && k < 32 && ((bitcast<u32>(f.w) >> u32(k)) & 1u) != 0u;
}
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),
vec3<f32>(240.0, 220.0, 60.0), vec3<f32>(230.0, 70.0, 50.0));
let k = clamp(t, 0.0, 1.0) * 4.0;
let j = min(u32(k), 3u);
return mix(stops[j], stops[j + 1u], k - f32(j)) / 255.0;
}
fn cloud_palette(v: f32) -> vec3<f32> {
var colours = array<vec3<f32>, 10>(
vec3<f32>(240.0, 140.0, 40.0), vec3<f32>(245.0, 205.0, 65.0), vec3<f32>(230.0, 65.0, 65.0),
vec3<f32>(165.0, 75.0, 190.0), vec3<f32>(65.0, 190.0, 115.0), vec3<f32>(75.0, 150.0, 220.0),
vec3<f32>(240.0, 120.0, 180.0), vec3<f32>(140.0, 105.0, 75.0), vec3<f32>(120.0, 215.0, 215.0),
vec3<f32>(190.0, 190.0, 80.0));
if (v < -0.5) { return vec3<f32>(150.0, 160.0, 176.0) / 255.0; }
return colours[u32(round(v)) % 10u] / 255.0;
}
@fragment
fn fs_cloud(o: COut) -> @location(0) vec4<f32> {
var out = clipped(o.wp) || dot(o.at, o.at) > 1.0;
for (var k = 0u; k < 4u; k++) {
out = out || (((o.has >> k) & 1u) != 0u && !cloud_keeps(cloud.filters[k], o.f[k]));
}
if (out) { discard; }
var c = o.rgb;
let mode = cloud.style.x;
if ((o.has & 16u) != 0u && mode > 1.5) {
c = cloud_palette(o.v);
} 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));
}
return out_color(vec4<f32>(c, 1.0));
}
+47
View File
@@ -75,6 +75,7 @@ impl Tokens {
font_compass: 13.5,
font_subtitle: 15.0,
font_intro: 16.0,
font_page_heading: 20.0,
font_article_heading: 17.0,
font_title: 22.0,
font_display: 26.0,
@@ -226,6 +227,31 @@ impl Tokens {
dialog_label: Color32::from_rgb(58, 70, 86),
selected_ink: Color32::from_rgb(26, 80, 127),
selected_bg: Color32::from_rgb(232, 241, 250),
figure_ground: Color32::from_rgb(247, 248, 250),
figure_line: Color32::from_rgb(111, 124, 143),
figure_highlight: Color32::WHITE,
figure_shadow: Color32::BLACK,
figure_face_top: Color32::from_rgb(255, 255, 255),
figure_face_left: Color32::from_rgb(228, 233, 240),
figure_face_right: Color32::from_rgb(201, 210, 222),
figure_card: Color32::from_rgb(255, 255, 255),
figure_white: Color32::WHITE,
figure_black: Color32::BLACK,
figure_accent: Color32::from_rgb(35, 110, 173),
figure_reference: Color32::from_rgb(138, 83, 0),
figure_axis_x: Color32::from_rgb(214, 69, 69),
figure_axis_y: Color32::from_rgb(46, 158, 91),
figure_axis_z: Color32::from_rgb(47, 111, 208),
figure_ok: Color32::from_rgb(47, 125, 91),
figure_warn: Color32::from_rgb(201, 138, 0),
figure_conflict: Color32::from_rgb(184, 74, 0),
pref_well: Color32::from_rgb(238, 241, 245),
pref_note: Color32::from_rgb(183, 74, 0),
pref_note_fill: Color32::from_rgb(251, 235, 221),
pref_on_accent: Color32::from_rgb(255, 255, 255),
pref_ok: Color32::from_rgb(39, 107, 44),
pref_ok_fill: Color32::from_rgb(229, 242, 230),
pref_disabled: Color32::from_rgb(138, 148, 163),
timeline_item: Color32::WHITE,
timeline_item_error: Color32::from_rgb(250, 220, 220),
timeline_marker: Color32::from_rgb(70, 76, 88),
@@ -345,6 +371,27 @@ impl Tokens {
t.dialog_label = t.text;
t.selected_ink = Color32::from_rgb(156, 199, 245);
t.selected_bg = Color32::from_rgb(42, 70, 110);
t.figure_ground = Color32::from_rgb(28, 31, 38);
t.figure_line = Color32::from_rgb(163, 174, 194);
t.figure_face_top = Color32::from_rgb(107, 119, 144);
t.figure_face_left = Color32::from_rgb(81, 91, 114);
t.figure_face_right = Color32::from_rgb(57, 66, 86);
t.figure_card = Color32::from_rgb(47, 52, 62);
t.figure_accent = Color32::from_rgb(140, 190, 245);
t.figure_reference = Color32::from_rgb(232, 194, 74);
t.figure_axis_x = Color32::from_rgb(242, 122, 122);
t.figure_axis_y = Color32::from_rgb(98, 212, 142);
t.figure_axis_z = Color32::from_rgb(108, 168, 247);
t.figure_ok = Color32::from_rgb(127, 209, 174);
t.figure_warn = Color32::from_rgb(232, 194, 74);
t.figure_conflict = Color32::from_rgb(240, 162, 122);
t.pref_well = Color32::from_rgb(47, 52, 62);
t.pref_note = Color32::from_rgb(240, 162, 122);
t.pref_note_fill = Color32::from_rgb(74, 51, 38);
t.pref_on_accent = Color32::from_rgb(15, 22, 32);
t.pref_ok = Color32::from_rgb(127, 209, 174);
t.pref_ok_fill = Color32::from_rgb(31, 58, 49);
t.pref_disabled = Color32::from_rgb(138, 148, 163);
t.construction_geom = icon_primary(crate::icon_svg::CONSTRUCTION_PRIMARY[1]);
t.error = Color32::from_rgb(240, 100, 96);
t.sketch_conflict = t.error;
+2 -2
View File
@@ -57,7 +57,7 @@
"error": "#fff0ca",
"field": "#100b04",
"form_accent": "#ca892b",
"form_dim": "#ad7a2c",
"form_dim": "#9b6922",
"form_error": "#fff0ca",
"form_error_fill": "#46300f",
"form_fill": "#e19e45",
@@ -180,7 +180,7 @@
"surface_soft": "#2a1e0b",
"tab_on_fill_dark": "#170d03",
"text": "#f3ca87",
"text_dim": "#ad7a2c",
"text_dim": "#9b6922",
"timeline": "#090602",
"timeline_item": "#1d1406",
"timeline_item_error": "#46300f",
@@ -1,171 +0,0 @@
{
"order": 20,
"provenance": "Original SolidCraft mapping of Gruvbox (MIT palette)",
"format": "solidcraft-theme/1",
"name": "Gruvbox Dark Hard",
"base": "dark",
"colors": {
"app_bar": "#141617",
"toolbar": "#1d2021",
"panel": "#1d2021",
"panel_header": "#3c3836",
"border": "#504945",
"text": "#ebdbb2",
"text_dim": "#a89984",
"accent": "#83a598",
"accent_soft": "#414f4b",
"sketch_accent": "#a1a322",
"error": "#fb4934",
"warning": "#fabd2f",
"icon": "#d5c4a1",
"timeline": "#141617",
"hover": "#46403e",
"sel_face": "#458588",
"sel_edge": "#83a598",
"sel_edge_rim": "#aec4bc",
"sel_profile": "#076678",
"sel_profile_edge": "#458588",
"hover_profile": "#83a59830",
"hover_profile_edge": "#83a598",
"sel_vertex": "#83a598",
"origin_plane": "#fe801950",
"origin_plane_edge": "#d65d0ec8",
"origin_plane_hover": "#83a59896",
"origin_point": "#d5c4a1",
"axis_x": "#fb4934",
"axis_y": "#b8bb26",
"axis_z": "#83a598",
"construction_plane": "#fe801940",
"construction_geom": "#e97926",
"body": "#a89984",
"body_edge": "#141617",
"manipulator": "#83a598",
"preview_add": "#83a598",
"preview_new": "#fabd2f",
"preview_sketch": "#b8bb26",
"preview_cut": "#fb49346e",
"cube_face": "#ebdbb2",
"cube_border": "#665c54",
"cube_text": "#282828",
"cube_hover": "#83a598",
"viewport_top": "#2c2c2c",
"viewport_bottom": "#2c2c2c",
"grid": "#443f3c",
"grid_major": "#504945",
"body_selected": "#83a598",
"sketch": "#83a598",
"sketch_fixed": "#ebdbb2",
"sketch_halo": "#141617",
"sketch_locked": "#b8bb26",
"dimension": "#fabd2f",
"dimension_driven": "#b57614",
"sketch_point": "#ebdbb2",
"sketch_point_free": "#83a598",
"dialog_bg": "#3c3836",
"dialog_border": "#504945",
"chip": "#458588",
"section_cap": "#d65d0e",
"profile_fill": "#83a59828",
"glyph_bg": "#2c2c2c",
"glyph_edge": "#fe614c",
"glyph_text": "#fe614c",
"overlay": "#1d2021f0",
"rubber_band": "#83a598",
"app_bar_text": "#ebdbb2",
"app_bar_hover": "#ebdbb21c",
"timeline_item": "#3c3836",
"timeline_item_error": "#63221f",
"timeline_marker": "#bdae93",
"field": "#141617",
"box_window": "#83a598",
"box_crossing": "#b8bb26",
"sketch_projected": "#d3869b",
"sketch_lost": "#fb4934",
"sketch_construction": "#fe8019",
"sketch_centerline": "#d65d0e",
"bend_up": "#b8bb26",
"bend_down": "#fe8019",
"interference": "#fb4934",
"icons.agent": "#d3869b",
"surface_fill": "#fe9755",
"surface_soft": "#382c20",
"surface_ink": "#fe8019",
"surface_accent": "#e97926",
"mesh_fill": "#e1ab32",
"mesh_soft": "#383323",
"mesh_ink": "#fabd2f",
"mesh_accent": "#c39429",
"form_fill": "#ea9bb0",
"form_soft": "#332c30",
"form_tab_ink": "#d3869b",
"form_accent": "#cf8297",
"sheet_fill": "#93c681",
"sheet_soft": "#2b332c",
"sheet_ink": "#8ec07c",
"render_fill": "#fc9483",
"render_soft": "#382523",
"render_ink": "#fb5d4a",
"render_accent": "#fe614c",
"sketch_soft": "#303322",
"sketch_ink": "#b8bb26",
"form_ink": "#ebdbb2",
"form_dim": "#bdae93",
"form_primary": "#83a598",
"form_error": "#fb4934",
"form_error_fill": "#63221f",
"form_success": "#b8bb26",
"form_success_fill": "#343a28",
"dialog_body": "#1d2021",
"dialog_footer": "#3c3836",
"dialog_rule": "#504945",
"control_fill": "#141617",
"control_border": "#7c6f64",
"dialog_label": "#ebdbb2",
"selected_ink": "#ebdbb2",
"selected_bg": "#414f4b",
"on_accent": "#1d2021"
},
"icon_palette": {
"ink": "#141617",
"line": "#d5c4a1",
"passive-1": "#c5c0b8",
"passive-2": "#9a948d",
"passive-3": "#706b64",
"guide": "#a89984",
"solid": [
"#b8dbcd",
"#9bbeb1",
"#83a598"
],
"surface": [
"#fcc4a3",
"#fe9755",
"#e97926"
],
"mesh": [
"#fbca67",
"#e1ab32",
"#c39429"
],
"form": [
"#fbbfce",
"#ea9bb0",
"#cf8297"
],
"sketch": [
"#d7db50",
"#babd2a",
"#a1a322"
],
"constraint": [
"#fcc2b7",
"#fc9483",
"#fe614c"
],
"construction": [
"#fcc4a3",
"#fe9755",
"#e97926"
]
}
}
@@ -1,172 +0,0 @@
{
"order": 30,
"provenance": "Original SolidCraft mapping of Gruvbox (MIT palette)",
"format": "solidcraft-theme/1",
"name": "Gruvbox Dark Soft",
"base": "dark",
"colors": {
"app_bar": "#1d2021",
"toolbar": "#32302f",
"panel": "#32302f",
"panel_header": "#3c3836",
"border": "#504945",
"text": "#ebdbb2",
"text_dim": "#a89984",
"accent": "#83a598",
"accent_soft": "#4e5954",
"selected_ink": "#ebdbb2",
"sketch_accent": "#a1a322",
"error": "#fb4934",
"warning": "#fabd2f",
"icon": "#d5c4a1",
"timeline": "#1d2021",
"hover": "#46403e",
"sel_face": "#458588",
"sel_edge": "#83a598",
"sel_edge_rim": "#aec4bc",
"sel_profile": "#076678",
"sel_profile_edge": "#458588",
"hover_profile": "#83a59830",
"hover_profile_edge": "#83a598",
"sel_vertex": "#83a598",
"origin_plane": "#fe801950",
"origin_plane_edge": "#d65d0ec8",
"origin_plane_hover": "#83a59896",
"origin_point": "#d5c4a1",
"axis_x": "#fb4934",
"axis_y": "#b8bb26",
"axis_z": "#83a598",
"construction_plane": "#fe801940",
"construction_geom": "#e97926",
"body": "#a89984",
"body_edge": "#1d2021",
"manipulator": "#83a598",
"preview_add": "#83a598",
"preview_new": "#fabd2f",
"preview_sketch": "#b8bb26",
"preview_cut": "#fb49346e",
"cube_face": "#ebdbb2",
"cube_border": "#665c54",
"cube_text": "#282828",
"cube_hover": "#83a598",
"viewport_top": "#373432",
"viewport_bottom": "#373432",
"grid": "#443f3c",
"grid_major": "#504945",
"body_selected": "#83a598",
"sketch": "#83a598",
"sketch_fixed": "#ebdbb2",
"sketch_halo": "#1d2021",
"sketch_locked": "#b8bb26",
"dimension": "#fabd2f",
"dimension_driven": "#b57614",
"sketch_point": "#ebdbb2",
"sketch_point_free": "#83a598",
"dialog_bg": "#3c3836",
"dialog_border": "#504945",
"chip": "#458588",
"section_cap": "#d65d0e",
"profile_fill": "#83a59828",
"glyph_bg": "#373432",
"glyph_edge": "#fe614c",
"glyph_text": "#fe614c",
"overlay": "#32302ff0",
"rubber_band": "#83a598",
"app_bar_text": "#ebdbb2",
"app_bar_hover": "#ebdbb21c",
"timeline_item": "#3c3836",
"timeline_item_error": "#702b28",
"timeline_marker": "#bdae93",
"field": "#1d2021",
"box_window": "#83a598",
"box_crossing": "#b8bb26",
"sketch_projected": "#d3869b",
"sketch_lost": "#fb4934",
"sketch_construction": "#fe8019",
"sketch_centerline": "#d65d0e",
"bend_up": "#b8bb26",
"bend_down": "#fe8019",
"interference": "#fb4934",
"icons.agent": "#d3869b",
"surface_fill": "#fe9755",
"surface_soft": "#4a3a2c",
"surface_ink": "#fe8b2e",
"surface_accent": "#e97926",
"mesh_fill": "#e1ab32",
"mesh_soft": "#4a412f",
"mesh_ink": "#fabd2f",
"mesh_accent": "#c39429",
"form_fill": "#ea9bb0",
"form_soft": "#453a3c",
"form_tab_ink": "#d996a8",
"form_accent": "#cf8297",
"sheet_fill": "#93c681",
"sheet_soft": "#3d4138",
"sheet_ink": "#8ec07c",
"render_fill": "#fc9483",
"render_soft": "#4a3330",
"render_ink": "#fc7e6f",
"render_accent": "#fe614c",
"sketch_soft": "#42412e",
"sketch_ink": "#b8bb26",
"form_ink": "#ebdbb2",
"form_dim": "#bdae93",
"form_primary": "#83a598",
"form_error": "#fb4934",
"form_error_fill": "#5f2320",
"form_success": "#b8bb26",
"form_success_fill": "#343a28",
"dialog_body": "#32302f",
"dialog_footer": "#3c3836",
"dialog_rule": "#504945",
"control_fill": "#1d2021",
"control_border": "#7c6f64",
"dialog_label": "#ebdbb2",
"selected_ink": "#ebdbb2",
"selected_bg": "#4e5954",
"on_accent": "#1d2021"
},
"icon_palette": {
"ink": "#1d2021",
"line": "#d5c4a1",
"passive-1": "#c5c0b8",
"passive-2": "#9a948d",
"passive-3": "#706b64",
"guide": "#a89984",
"solid": [
"#b8dbcd",
"#9bbeb1",
"#83a598"
],
"surface": [
"#fcc4a3",
"#fe9755",
"#e97926"
],
"mesh": [
"#fbca67",
"#e1ab32",
"#c39429"
],
"form": [
"#fbbfce",
"#ea9bb0",
"#cf8297"
],
"sketch": [
"#d7db50",
"#babd2a",
"#a1a322"
],
"constraint": [
"#fcc2b7",
"#fc9483",
"#fe614c"
],
"construction": [
"#fcc4a3",
"#fe9755",
"#e97926"
]
}
}
+120 -2
View File
@@ -122,7 +122,6 @@
"control_fill": "#1d2021",
"control_border": "#7c6f64",
"dialog_label": "#ebdbb2",
"selected_ink": "#ebdbb2",
"selected_bg": "#48544f",
"on_accent": "#1d2021"
},
@@ -168,5 +167,124 @@
"#fe9755",
"#e97926"
]
}
},
"options": [
{
"id": "contrast",
"type": "choice",
"default": "standard",
"label": {
"de": "Kontrast",
"en": "Contrast",
"ja": "コントラスト",
"ko": "대비",
"zh": "对比度"
},
"help": {
"de": "Wähle die weiche, normale oder harte Gruvbox-Palette.",
"en": "Choose the soft, standard or hard Gruvbox palette.",
"ja": "Gruvboxのソフト・標準・ハードのパレットを選びます。",
"ko": "Gruvbox의 부드러운, 표준 또는 강한 팔레트를 선택합니다.",
"zh": "选择柔和、标准或强对比的 Gruvbox 配色。"
},
"values": [
{
"id": "soft",
"label": {
"de": "Weich",
"en": "Soft",
"ja": "ソフト",
"ko": "부드럽게",
"zh": "柔和"
}
},
{
"id": "standard",
"label": {
"de": "Standard",
"en": "Standard",
"ja": "標準",
"ko": "표준",
"zh": "标准"
}
},
{
"id": "hard",
"label": {
"de": "Hart",
"en": "Hard",
"ja": "ハード",
"ko": "강하게",
"zh": "强对比"
}
}
]
}
],
"overlays": [
{
"when": {
"opt": "contrast",
"eq": "soft"
},
"colors": {
"toolbar": "#32302f",
"panel": "#32302f",
"accent_soft": "#4e5954",
"viewport_top": "#373432",
"viewport_bottom": "#373432",
"glyph_bg": "#373432",
"overlay": "#32302ff0",
"timeline_item_error": "#702b28",
"surface_soft": "#4a3a2c",
"surface_ink": "#fe8b2e",
"mesh_soft": "#4a412f",
"form_soft": "#453a3c",
"form_tab_ink": "#d996a8",
"sheet_soft": "#3d4138",
"render_soft": "#4a3330",
"render_ink": "#fc7e6f",
"sketch_soft": "#42412e",
"form_error_fill": "#702b28",
"dialog_body": "#32302f",
"selected_bg": "#4e5954"
}
},
{
"when": {
"opt": "contrast",
"eq": "hard"
},
"colors": {
"app_bar": "#141617",
"toolbar": "#1d2021",
"panel": "#1d2021",
"accent_soft": "#414f4b",
"timeline": "#141617",
"body_edge": "#141617",
"viewport_top": "#2c2c2c",
"viewport_bottom": "#2c2c2c",
"sketch_halo": "#141617",
"glyph_bg": "#2c2c2c",
"overlay": "#1d2021f0",
"timeline_item_error": "#63221f",
"field": "#141617",
"surface_soft": "#382c20",
"mesh_soft": "#383323",
"form_soft": "#332c30",
"form_tab_ink": "#d3869b",
"sheet_soft": "#2b332c",
"render_soft": "#382523",
"render_ink": "#fb5d4a",
"sketch_soft": "#303322",
"form_error_fill": "#63221f",
"dialog_body": "#1d2021",
"control_fill": "#141617",
"selected_bg": "#414f4b"
},
"icon_palette": {
"ink": "#141617"
}
}
]
}
@@ -1,171 +0,0 @@
{
"order": 50,
"provenance": "Original SolidCraft mapping of Gruvbox (MIT palette)",
"format": "solidcraft-theme/1",
"name": "Gruvbox Light Hard",
"base": "light",
"colors": {
"app_bar": "#3c3836",
"toolbar": "#f9f5d7",
"panel": "#f9f5d7",
"panel_header": "#ebdbb2",
"border": "#d5c4a1",
"text": "#3c3836",
"text_dim": "#7c6f64",
"accent": "#076678",
"accent_soft": "#c9d8c4",
"sketch_accent": "#625e15",
"error": "#9d0006",
"warning": "#b57614",
"icon": "#504945",
"timeline": "#ebdbb2",
"hover": "#f1e5c1",
"sel_face": "#458588",
"sel_edge": "#076678",
"sel_edge_rim": "#83a598",
"sel_profile": "#076678",
"sel_profile_edge": "#458588",
"hover_profile": "#45858830",
"hover_profile_edge": "#458588",
"sel_vertex": "#076678",
"origin_plane": "#fe80195e",
"origin_plane_edge": "#d65d0ec8",
"origin_plane_hover": "#45858896",
"origin_point": "#d5c4a1",
"axis_x": "#cc241d",
"axis_y": "#98971a",
"axis_z": "#458588",
"construction_plane": "#fe801946",
"construction_geom": "#9b3407",
"body": "#bdae93",
"body_edge": "#3c3836",
"manipulator": "#076678",
"preview_add": "#458588",
"preview_new": "#d79921",
"preview_sketch": "#98971a",
"preview_cut": "#cc241d6e",
"cube_face": "#fbf1c7",
"cube_border": "#7c6f64",
"cube_text": "#3c3836",
"cube_hover": "#83a598",
"viewport_top": "#fbf8e3",
"viewport_bottom": "#ebdbb2",
"grid": "#d5c4a1",
"grid_major": "#bdae93",
"body_selected": "#83a598",
"sketch": "#076678",
"sketch_fixed": "#282828",
"sketch_halo": "#fbf8e3",
"sketch_locked": "#79740e",
"dimension": "#1f5c40",
"dimension_driven": "#4f7d64",
"sketch_point": "#282828",
"sketch_point_free": "#076678",
"dialog_bg": "#f9f5d7",
"dialog_border": "#d5c4a1",
"chip": "#458588",
"section_cap": "#d65d0e",
"profile_fill": "#fabd2f46",
"glyph_bg": "#fcfaeb",
"glyph_edge": "#a91714",
"glyph_text": "#a91714",
"overlay": "#f9f5d7eb",
"rubber_band": "#076678",
"app_bar_text": "#fbf1c7",
"app_bar_hover": "#fbf1c71c",
"timeline_item": "#f9f5d7",
"timeline_item_error": "#f0cbb2",
"timeline_marker": "#504945",
"field": "#fcfaeb",
"box_window": "#076678",
"box_crossing": "#79740e",
"sketch_projected": "#8f3f71",
"sketch_lost": "#9d0006",
"sketch_construction": "#af3a03",
"sketch_centerline": "#d65d0e",
"bend_up": "#79740e",
"bend_down": "#af3a03",
"interference": "#cc241d",
"icons.agent": "#8f3f71",
"surface_fill": "#df663c",
"surface_soft": "#f3e6c6",
"surface_ink": "#af3a03",
"surface_accent": "#9b3407",
"mesh_fill": "#c08024",
"mesh_soft": "#f4ebc7",
"mesh_ink": "#915e10",
"mesh_accent": "#7b5112",
"form_fill": "#c46fa2",
"form_soft": "#f1e6cf",
"form_tab_ink": "#8f3f71",
"form_accent": "#893a6c",
"sheet_fill": "#659f7a",
"sheet_soft": "#eaebcd",
"sheet_ink": "#3d7252",
"render_fill": "#e85a4d",
"render_soft": "#f2e1c6",
"render_ink": "#9d0006",
"render_accent": "#a91714",
"sketch_soft": "#efebc7",
"sketch_ink": "#6e6a0d",
"form_ink": "#3c3836",
"form_dim": "#665c54",
"form_primary": "#076678",
"form_error": "#9d0006",
"form_error_fill": "#f0cbb2",
"form_success": "#427b58",
"form_success_fill": "#dae3b6",
"dialog_body": "#f9f5d7",
"dialog_footer": "#ebdbb2",
"dialog_rule": "#d5c4a1",
"control_fill": "#fcfaeb",
"control_border": "#a89984",
"dialog_label": "#3c3836",
"selected_ink": "#3c3836",
"selected_bg": "#c9d8c4",
"on_accent": "#fbf1c7"
},
"icon_palette": {
"ink": "#3c3836",
"line": "#504945",
"passive-1": "#e9e3df",
"passive-2": "#b8b3af",
"passive-3": "#8a8581",
"guide": "#7c6f64",
"solid": [
"#87d5e9",
"#4e9caf",
"#076678"
],
"surface": [
"#feb299",
"#df663c",
"#9b3407"
],
"mesh": [
"#fcb864",
"#c08024",
"#7b5112"
],
"form": [
"#fbaad9",
"#c46fa2",
"#893a6c"
],
"sketch": [
"#d0cc73",
"#989439",
"#625e15"
],
"constraint": [
"#fab3a8",
"#e85a4d",
"#a91714"
],
"construction": [
"#feb299",
"#df663c",
"#9b3407"
]
}
}
@@ -1,171 +0,0 @@
{
"order": 60,
"provenance": "Original SolidCraft mapping of Gruvbox (MIT palette)",
"format": "solidcraft-theme/1",
"name": "Gruvbox Light Soft",
"base": "light",
"colors": {
"app_bar": "#3c3836",
"toolbar": "#f2e5bc",
"panel": "#f2e5bc",
"panel_header": "#ebdbb2",
"border": "#d5c4a1",
"text": "#3c3836",
"text_dim": "#7c6f64",
"accent": "#076678",
"accent_soft": "#c3ccae",
"sketch_accent": "#625e15",
"error": "#9d0006",
"warning": "#b57614",
"icon": "#504945",
"timeline": "#ebdbb2",
"hover": "#eedfb6",
"sel_face": "#458588",
"sel_edge": "#076678",
"sel_edge_rim": "#83a598",
"sel_profile": "#076678",
"sel_profile_edge": "#458588",
"hover_profile": "#45858830",
"hover_profile_edge": "#458588",
"sel_vertex": "#076678",
"origin_plane": "#fe80195e",
"origin_plane_edge": "#d65d0ec8",
"origin_plane_hover": "#45858896",
"origin_point": "#d5c4a1",
"axis_x": "#cc241d",
"axis_y": "#98971a",
"axis_z": "#458588",
"construction_plane": "#fe801946",
"construction_geom": "#9b3407",
"body": "#bdae93",
"body_edge": "#3c3836",
"manipulator": "#076678",
"preview_add": "#458588",
"preview_new": "#d79921",
"preview_sketch": "#98971a",
"preview_cut": "#cc241d6e",
"cube_face": "#fbf1c7",
"cube_border": "#7c6f64",
"cube_text": "#3c3836",
"cube_hover": "#83a598",
"viewport_top": "#f6edd0",
"viewport_bottom": "#ebdbb2",
"grid": "#d5c4a1",
"grid_major": "#bdae93",
"body_selected": "#83a598",
"sketch": "#076678",
"sketch_fixed": "#282828",
"sketch_halo": "#f6edd0",
"sketch_locked": "#79740e",
"dimension": "#1f5c40",
"dimension_driven": "#4f7d64",
"sketch_point": "#282828",
"sketch_point_free": "#076678",
"dialog_bg": "#f2e5bc",
"dialog_border": "#d5c4a1",
"chip": "#458588",
"section_cap": "#d65d0e",
"profile_fill": "#fabd2f46",
"glyph_bg": "#f8f2de",
"glyph_edge": "#a91714",
"glyph_text": "#a91714",
"overlay": "#f2e5bceb",
"rubber_band": "#076678",
"app_bar_text": "#fbf1c7",
"app_bar_hover": "#fbf1c71c",
"timeline_item": "#f2e5bc",
"timeline_item_error": "#eabe9c",
"timeline_marker": "#504945",
"field": "#f8f2de",
"box_window": "#076678",
"box_crossing": "#79740e",
"sketch_projected": "#8f3f71",
"sketch_lost": "#9d0006",
"sketch_construction": "#af3a03",
"sketch_centerline": "#d65d0e",
"bend_up": "#79740e",
"bend_down": "#af3a03",
"interference": "#cc241d",
"icons.agent": "#8f3f71",
"surface_fill": "#df663c",
"surface_soft": "#edd7ad",
"surface_ink": "#a83803",
"surface_accent": "#9b3407",
"mesh_fill": "#c08024",
"mesh_soft": "#eddcaf",
"mesh_ink": "#84560f",
"mesh_accent": "#7b5112",
"form_fill": "#c46fa2",
"form_soft": "#ead8b6",
"form_tab_ink": "#8f3f71",
"form_accent": "#893a6c",
"sheet_fill": "#659f7a",
"sheet_soft": "#e4ddb4",
"sheet_ink": "#38694b",
"render_fill": "#e85a4d",
"render_soft": "#ebd3ad",
"render_ink": "#9d0006",
"render_accent": "#a91714",
"sketch_soft": "#e8dcae",
"sketch_ink": "#66610c",
"form_ink": "#3c3836",
"form_dim": "#665c54",
"form_primary": "#076678",
"form_error": "#9d0006",
"form_error_fill": "#eabe9c",
"form_success": "#427b58",
"form_success_fill": "#dae3b6",
"dialog_body": "#f2e5bc",
"dialog_footer": "#ebdbb2",
"dialog_rule": "#d5c4a1",
"control_fill": "#f8f2de",
"control_border": "#a89984",
"dialog_label": "#3c3836",
"selected_ink": "#3c3836",
"selected_bg": "#c3ccae",
"on_accent": "#fbf1c7"
},
"icon_palette": {
"ink": "#3c3836",
"line": "#504945",
"passive-1": "#e9e3df",
"passive-2": "#b8b3af",
"passive-3": "#8a8581",
"guide": "#7c6f64",
"solid": [
"#87d5e9",
"#4e9caf",
"#076678"
],
"surface": [
"#feb299",
"#df663c",
"#9b3407"
],
"mesh": [
"#fcb864",
"#c08024",
"#7b5112"
],
"form": [
"#fbaad9",
"#c46fa2",
"#893a6c"
],
"sketch": [
"#d0cc73",
"#989439",
"#625e15"
],
"constraint": [
"#fab3a8",
"#e85a4d",
"#a91714"
],
"construction": [
"#feb299",
"#df663c",
"#9b3407"
]
}
}
+123 -1
View File
@@ -167,5 +167,127 @@
"#df663c",
"#9b3407"
]
}
},
"options": [
{
"id": "contrast",
"type": "choice",
"default": "standard",
"label": {
"de": "Kontrast",
"en": "Contrast",
"ja": "コントラスト",
"ko": "대비",
"zh": "对比度"
},
"help": {
"de": "Wähle die weiche, normale oder harte Gruvbox-Palette.",
"en": "Choose the soft, standard or hard Gruvbox palette.",
"ja": "Gruvboxのソフト・標準・ハードのパレットを選びます。",
"ko": "Gruvbox의 부드러운, 표준 또는 강한 팔레트를 선택합니다.",
"zh": "选择柔和、标准或强对比的 Gruvbox 配色。"
},
"values": [
{
"id": "soft",
"label": {
"de": "Weich",
"en": "Soft",
"ja": "ソフト",
"ko": "부드럽게",
"zh": "柔和"
}
},
{
"id": "standard",
"label": {
"de": "Standard",
"en": "Standard",
"ja": "標準",
"ko": "표준",
"zh": "标准"
}
},
{
"id": "hard",
"label": {
"de": "Hart",
"en": "Hard",
"ja": "ハード",
"ko": "강하게",
"zh": "强对比"
}
}
]
}
],
"overlays": [
{
"when": {
"opt": "contrast",
"eq": "soft"
},
"colors": {
"toolbar": "#f2e5bc",
"panel": "#f2e5bc",
"accent_soft": "#c3ccae",
"hover": "#eedfb6",
"viewport_top": "#f6edd0",
"sketch_halo": "#f6edd0",
"dialog_bg": "#f2e5bc",
"glyph_bg": "#f8f2de",
"overlay": "#f2e5bceb",
"timeline_item": "#f2e5bc",
"timeline_item_error": "#eabe9c",
"field": "#f8f2de",
"surface_soft": "#edd7ad",
"surface_ink": "#a83803",
"mesh_soft": "#eddcaf",
"mesh_ink": "#84560f",
"form_soft": "#ead8b6",
"sheet_soft": "#e4ddb4",
"sheet_ink": "#38694b",
"render_soft": "#ebd3ad",
"sketch_soft": "#e8dcae",
"sketch_ink": "#66610c",
"form_error_fill": "#eabe9c",
"dialog_body": "#f2e5bc",
"control_fill": "#f8f2de",
"selected_bg": "#c3ccae"
}
},
{
"when": {
"opt": "contrast",
"eq": "hard"
},
"colors": {
"toolbar": "#f9f5d7",
"panel": "#f9f5d7",
"accent_soft": "#c9d8c4",
"hover": "#f1e5c1",
"viewport_top": "#fbf8e3",
"sketch_halo": "#fbf8e3",
"dialog_bg": "#f9f5d7",
"glyph_bg": "#fcfaeb",
"overlay": "#f9f5d7eb",
"timeline_item": "#f9f5d7",
"timeline_item_error": "#f0cbb2",
"field": "#fcfaeb",
"surface_soft": "#f3e6c6",
"mesh_soft": "#f4ebc7",
"mesh_ink": "#915e10",
"form_soft": "#f1e6cf",
"sheet_soft": "#eaebcd",
"sheet_ink": "#3d7252",
"render_soft": "#f2e1c6",
"sketch_soft": "#efebc7",
"sketch_ink": "#6e6a0d",
"form_error_fill": "#f0cbb2",
"dialog_body": "#f9f5d7",
"control_fill": "#fcfaeb",
"selected_bg": "#c9d8c4"
}
}
]
}
+1 -1
View File
@@ -59,7 +59,7 @@
"form_accent": "#cfa7c7",
"form_dim": "#a4b0c2",
"form_error": "#bf616a",
"form_error_fill": "#352b33",
"form_error_fill": "#4b3d48",
"form_fill": "#e7bfe0",
"form_ink": "#eceff4",
"form_primary": "#88c0d0",
+1 -1
View File
@@ -180,7 +180,7 @@
"surface_soft": "#363332",
"tab_on_fill_dark": "#000000",
"text": "#abb2bf",
"text_dim": "#90959f",
"text_dim": "#7f848e",
"timeline": "#1e2227",
"timeline_item": "#323842",
"timeline_item_error": "#5c343d",
+3 -5
View File
@@ -59,12 +59,12 @@
"form_accent": "#6b70c3",
"form_dim": "#657b83",
"form_error": "#dc322f",
"form_error_fill": "#f7ddd3",
"form_error_fill": "#eac4b4",
"form_fill": "#a2a9f8",
"form_ink": "#586e75",
"form_primary": "#268bd2",
"form_soft": "#e4ded4",
"form_success": "#5f6e00",
"form_success": "#859900",
"form_success_fill": "#dcdbb1",
"form_tab_ink": "#565a9d",
"glyph_bg": "#e3ddc9",
@@ -130,8 +130,6 @@
"mesh_fill": "#d7aa3c",
"mesh_ink": "#7b5d00",
"mesh_soft": "#e9e0c4",
"nav_tip": "#073642",
"nav_tip_ink": "#eee8d5",
"on_accent": "#ffffff",
"origin_plane": "#b5890028",
"origin_plane_edge": "#b5890088",
@@ -181,7 +179,7 @@
"surface_ink": "#a83e12",
"surface_soft": "#ebdbc6",
"tab_on_fill_dark": "#fdf6e3",
"text": "#536970",
"text": "#586e75",
"text_dim": "#657b83",
"timeline": "#e3ddc9",
"timeline_item": "#e3ddc9",
+2 -6
View File
@@ -64,7 +64,7 @@
"form_ink": "#000000",
"form_primary": "#000080",
"form_soft": "#bbb1bb",
"form_success": "#006000",
"form_success": "#008000",
"form_success_fill": "#c0dcc0",
"form_tab_ink": "#7d007d",
"glyph_bg": "#ffffff",
@@ -130,10 +130,6 @@
"mesh_fill": "#808000",
"mesh_ink": "#4c4c00",
"mesh_soft": "#bbbbb1",
"nav_press": "#a0a0a0",
"nav_tip": "#ffffe1",
"nav_tip_ink": "#000000",
"nav_tip_key_border": "#808080",
"on_accent": "#ffffff",
"origin_plane": "#ffff005e",
"origin_plane_edge": "#808000c8",
@@ -184,7 +180,7 @@
"surface_soft": "#bbb6b1",
"tab_on_fill_dark": "#000000",
"text": "#000000",
"text_dim": "#606060",
"text_dim": "#808080",
"timeline": "#c0c0c0",
"timeline_item": "#c0c0c0",
"timeline_item_error": "#ffffc0",
+26 -2
View File
@@ -202,12 +202,16 @@ fn map_plane_ref(m: &Mat, p: &mut PlaneRef) {
}
// Named origin planes and construction planes are the component's own.
PlaneRef::Origin { .. } | PlaneRef::Construction { .. } => {}
PlaneRef::Tangent { face, at } => {
PlaneRef::Tangent { face, at, reference, .. } => {
*at = apply_point(m, *at);
map_geo_ref(m, face);
if let Some(r) = reference {
map_geo_ref(m, r);
}
}
PlaneRef::Midplane { .. }
| PlaneRef::TwoEdges { .. }
| PlaneRef::AxisPoint { .. }
| PlaneRef::ThreePoints { .. }
| PlaneRef::AlongPath { .. }
| PlaneRef::Perpendicular { .. } => {
@@ -418,6 +422,19 @@ impl Document {
false
}
/// Is `c` (or one of its ancestors) marked out of the History bar (#10393)? The root (0)
/// never is: its own features always show.
pub fn component_history_hidden(&self, mut c: u64) -> bool {
for _ in 0..1000 {
match self.components.iter().find(|x| x.id == c) {
Some(x) if x.history_hidden => return true,
Some(x) if x.parent != 0 => c = x.parent,
_ => return false,
}
}
false
}
/// Would putting a placement of `component` into `parent` put it inside itself: is `parent`
/// the component, or inside it, created in it or placed in it at any depth? Placements can sit
/// apart from where their component was created ([`Self::component_within`] follows only
@@ -456,7 +473,14 @@ impl Document {
let id = self.next_id.max(1);
self.next_id = id.checked_add(1).ok_or_else(|| DocError::Invalid("too many items in the design".into()))?;
let cname = self.components.iter().find(|c| c.id == component).map(|c| c.name.clone()).unwrap_or_default();
let k = self.occurrences.iter().filter(|o| o.component == component).count() + 1;
// The next number, or, when an occurrence already has that name (one before it was
// deleted, or of another component of that name), the first number none has (#9738).
let next = self.occurrences.iter().filter(|o| o.component == component).count() + 1;
let free = |k: &usize| {
let name = format!("{cname}:{k}");
!self.occurrences.iter().any(|o| o.name == name)
};
let k = std::iter::once(next).chain(1..).find(free).unwrap_or(next);
self.occurrences.push(crate::Occurrence { id, component, parent, name: format!("{cname}:{k}"), transform, grounded: false });
Ok(id)
}
+182
View File
@@ -0,0 +1,182 @@
//! The browser's groups as a tree: how deep a group sits, who holds whom, and pruning what a
//! saved design says about the browser down to what still exists.
use crate::document::{BROWSER_FOLDERS, BrowserEntry, BrowserGroup, Document, MAX_GROUP_NESTING};
use std::collections::{BTreeMap, BTreeSet};
impl Document {
pub fn browser_group(&self, id: u64) -> Option<&BrowserGroup> {
self.browser_groups.iter().find(|g| g.id == id)
}
/// The level of a group: 1 for a top-level group, 2 for a child of one, and so on. A chain
/// that loops or runs past the limit counts as one level too deep.
pub fn group_level(&self, id: u64) -> usize {
let mut level = 0;
let mut at = Some(id);
while let Some(g) = at {
level += 1;
if level > MAX_GROUP_NESTING {
return level;
}
at = self.browser_group(g).and_then(|g| g.parent);
}
level
}
/// Whether `ancestor` is `id` or holds it, at any depth.
pub fn group_within(&self, id: u64, ancestor: u64) -> bool {
let mut at = Some(id);
for _ in 0..=MAX_GROUP_NESTING {
match at {
Some(g) if g == ancestor => return true,
Some(g) => at = self.browser_group(g).and_then(|g| g.parent),
None => return false,
}
}
false
}
/// How many levels the group and the groups under it take: 1 for a group with no child group.
pub fn group_height(&self, id: u64) -> usize {
fn height(d: &Document, id: u64, left: usize) -> usize {
if left == 0 {
return 1;
}
1 + d.browser_groups.iter().filter(|g| g.parent == Some(id)).map(|g| height(d, g.id, left - 1)).max().unwrap_or(0)
}
height(self, id, MAX_GROUP_NESTING + 1)
}
/// The entries of a holder's list in display order: a group's contents, or the folder's top
/// level when `holder` is `None`. Groups the list names come where it names them. The child
/// groups it does not name (in creation order) go just after the last group it names, or
/// first when it names none, so they sit above the items as they always have. Entries for
/// groups that are not the holder's children are dropped.
pub fn ordered_entries(&self, component: u64, folder: &str, holder: Option<u64>) -> Vec<BrowserEntry> {
let listed: &[BrowserEntry] = match holder {
Some(g) => self.browser_group(g).map_or(&[], |g| &g.items),
None => self.browser_order.get(&format!("{component}/{folder}")).map_or(&[], Vec::as_slice),
};
let mine: Vec<u64> =
self.browser_groups.iter().filter(|g| g.component == component && g.folder == folder && g.parent == holder).map(|g| g.id).collect();
let mut out: Vec<BrowserEntry> = Vec::with_capacity(listed.len() + mine.len());
for e in listed {
match e {
BrowserEntry::Group(id) if !mine.contains(id) || out.contains(e) => {}
_ => out.push(e.clone()),
}
}
let at = out.iter().rposition(|e| matches!(e, BrowserEntry::Group(_))).map_or(0, |i| i + 1);
let unlisted: Vec<BrowserEntry> = mine.iter().map(|id| BrowserEntry::Group(*id)).filter(|e| !out.contains(e)).collect();
out.splice(at..at, unlisted);
out
}
/// The items in a group and in the groups inside it.
pub fn group_item_keys(&self, id: u64) -> Vec<String> {
let inside = self.browser_groups.iter().filter(|g| self.group_within(g.id, id));
inside.flat_map(|g| g.item_keys().map(str::to_string)).collect()
}
/// The group holding the item directly, in a component's folder.
pub fn group_of_item(&self, component: u64, folder: &str, key: &str) -> Option<&BrowserGroup> {
self.browser_groups.iter().find(|g| g.component == component && g.folder == folder && g.has_item(key))
}
/// Only the browser's groups and order changed (a group made, nested, moved into, ordered):
/// nothing is evaluated and no placement moves. The same whole-Document proof as
/// [`Document::differs_only_in_render`]; `next_id` may differ, as a new group takes an id.
pub fn differs_only_in_browser(&self, previous: &Document) -> bool {
if self.browser_groups == previous.browser_groups && self.browser_order == previous.browser_order {
return false;
}
let mut normalized = previous.clone();
normalized.browser_groups.clone_from(&self.browser_groups);
normalized.browser_order.clone_from(&self.browser_order);
normalized.next_id = self.next_id;
*self == normalized
}
/// What [`Document::browser_pruned`] depends on, cheaply: it can only find something to drop
/// after a feature, placement, component or group was added or removed. Ids are never
/// reused, so a removal followed by an addition still moves `next_id`.
pub fn browser_stamp(&self) -> [u64; 6] {
let n = |len: usize| u64::try_from(len).unwrap_or(u64::MAX);
[
self.next_id,
n(self.features.len()),
n(self.occurrences.len()),
n(self.components.len()),
n(self.browser_groups.len()),
n(self.browser_order.len()),
]
}
/// The groups and order lists with what they name that is gone dropped, or `None` when
/// nothing is: groups of a missing component or folder, a parent that is missing, elsewhere
/// or loops, entries for groups and sketches, construction items and placements that no
/// longer exist, repeats, and order lists of missing components. Body names are left alone:
/// only an evaluated model knows them.
pub fn browser_pruned(&self) -> Option<(Vec<BrowserGroup>, BTreeMap<String, Vec<BrowserEntry>>)> {
if self.browser_groups.is_empty() && self.browser_order.is_empty() {
return None;
}
let components: BTreeSet<u64> = self.components.iter().map(|c| c.id).collect();
let features: BTreeSet<u64> = self.features.iter().map(|f| f.id).collect();
let placed_in: BTreeMap<u64, u64> = self.occurrences.iter().map(|o| (o.id, o.parent)).collect();
let component_exists = |c: u64| c == 0 || components.contains(&c);
let placed = |component: u64, key: &str| key.parse::<u64>().ok().and_then(|o| placed_in.get(&o)) == Some(&component);
// Body names cannot be checked here; the other folders hold feature ids.
let item_exists = |folder: &str, component: u64, key: &str| match folder {
"bodies" => true,
"components" => placed(component, key),
_ => key.parse::<u64>().is_ok_and(|id| features.contains(&id)),
};
let mut groups = self.browser_groups.clone();
let mut order = self.browser_order.clone();
groups.retain(|g| component_exists(g.component) && BROWSER_FOLDERS.contains(&g.folder.as_str()));
let known: BTreeMap<u64, (u64, String)> = groups.iter().map(|g| (g.id, (g.component, g.folder.clone()))).collect();
let parents: BTreeMap<u64, Option<u64>> = groups
.iter()
.map(|g| (g.id, g.parent.filter(|p| *p != g.id && known.get(p).is_some_and(|(c, f)| *c == g.component && *f == g.folder))))
.collect();
// A loop, or a chain too deep, is cut at the group that makes it.
let too_deep = |id: u64| std::iter::successors(Some(id), |g| parents.get(g).copied().flatten()).nth(MAX_GROUP_NESTING).is_some();
let kept: BTreeMap<u64, Option<u64>> = parents.iter().map(|(id, p)| (*id, if too_deep(*id) { None } else { *p })).collect();
for g in &mut groups {
g.parent = kept[&g.id];
}
let mut taken: BTreeSet<(u64, String, String)> = BTreeSet::new();
for g in &mut groups {
let (component, folder, id) = (g.component, g.folder.clone(), g.id);
let mut seen = BTreeSet::new();
g.items.retain(|e| match e {
BrowserEntry::Group(c) => kept.get(c) == Some(&Some(id)) && seen.insert(*c),
BrowserEntry::Item(k) => item_exists(&folder, component, k) && taken.insert((component, folder.clone(), k.clone())),
});
}
let top = |c: u64, folder: &str, id: u64| kept.get(&id) == Some(&None) && known.get(&id).is_some_and(|(kc, kf)| *kc == c && kf == folder);
order.retain(|key, list| {
let Some((c, folder)) = key.split_once('/') else { return false };
let Some(c) = c.parse::<u64>().ok().filter(|c| component_exists(*c)) else { return false };
if !BROWSER_FOLDERS.contains(&folder) {
return false;
}
let mut seen = BTreeSet::new();
list.retain(|e| match e {
BrowserEntry::Group(id) => top(c, folder, *id) && seen.insert(e.clone()),
BrowserEntry::Item(k) => item_exists(folder, c, k) && seen.insert(e.clone()),
});
!list.is_empty()
});
(groups != self.browser_groups || order != self.browser_order).then_some((groups, order))
}
/// Put placements of one component in the order the browser shows them: the saved
/// `components` order, then the rest as they were.
pub fn sort_placements(&self, parent: u64, ids: &mut [u64]) {
let Some(order) = self.browser_order.get(&format!("{parent}/components")) else { return };
let rank: BTreeMap<&str, usize> = order.iter().enumerate().filter_map(|(i, e)| Some((e.as_item()?, i))).collect();
ids.sort_by_cached_key(|id| rank.get(id.to_string().as_str()).copied().unwrap_or(usize::MAX));
}
}
+4 -2
View File
@@ -15,8 +15,10 @@ pub struct Canvas {
pub name: String,
/// `png` or `jpeg`.
pub format: String,
/// The image file, base64.
pub data: String,
/// The image file, base64. Shared rather than owned: every edit clones the document (the undo
/// history keeps the old one), and an image of up to 16 MiB copied into each of the 200 undo
/// states would hold gigabytes. Serialized as the same JSON string as before.
pub data: std::sync::Arc<str>,
/// Pixel size of the image.
pub pixels: [u32; 2],
pub plane: PlaneRef,
+9 -2
View File
@@ -176,6 +176,8 @@ pub(crate) fn remap_link(
remap_link(b, ids, names, bodies, depth + 1);
}
LinkSource::Plane { name } => mapped_name(name, names),
// A construction axis, by name (#9324); the origin axes are no feature's.
LinkSource::Axis { name } if !matches!(name.as_str(), "X" | "Y" | "Z") => mapped_name(name, names),
_ => {}
}
}
@@ -314,7 +316,7 @@ fn checked_plane(p: &PlaneRef, depth: usize) -> crate::Result<()> {
checked_geo(g, depth + 1)?;
}
}
PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } => {
PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } | PlaneRef::AxisPoint { axis: a, point: b } => {
checked_geo(a, depth + 1)?;
checked_geo(b, depth + 1)?;
}
@@ -331,7 +333,12 @@ fn checked_plane(p: &PlaneRef, depth: usize) -> crate::Result<()> {
checked_geo(b, depth + 1)?;
checked_geo(c, depth + 1)?;
}
PlaneRef::Tangent { face, .. } => checked_geo(face, depth + 1)?,
PlaneRef::Tangent { face, reference, .. } => {
checked_geo(face, depth + 1)?;
if let Some(r) = reference {
checked_geo(r, depth + 1)?;
}
}
PlaneRef::AlongPath { path, .. } => checked_geo(path, depth + 1)?,
_ => {}
}
+113 -4
View File
@@ -116,6 +116,11 @@ pub struct Construct {
pub feature: u64,
pub name: String,
pub geom: ConstructGeom,
/// The bodies it was built from, when every pick it reads lies on a body
/// ([`source_bodies`]): a later Move of all of them carries it along (#10127). Empty when
/// it also reads something fixed (a point, a plane, a sketch), which keeps it in place.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bodies: Vec<String>,
}
/// Everything a reference needs to be resolved.
@@ -497,6 +502,48 @@ fn surface_normal(s: FaceSurface, p: Vec3) -> Option<(Vec3, Vec3)> {
}
}
/// A point over `s` where the face turns towards `towards` (a reference plane's normal): around
/// the axis of a cylinder, cone or torus, turned `angle` about it; straight at it on a sphere. Of
/// the two sides, the one `at` is on (#8822).
fn facing(s: FaceSurface, at: Vec3, towards: Vec3, angle: f64) -> Result<Vec3> {
let side = |d: Vec3, w: Vec3| if d.dot(w) < 0.0 { d * -1.0 } else { d };
let around = |axis: Vec3, w: Vec3| -> Result<(Vec3, f64)> {
let d = (towards - axis * towards.dot(axis))
.normalized()
.ok_or_else(|| bad("the reference is square to the face's axis: every plane tangent to the face meets it at a right angle"))?;
let d = d * angle.cos() + axis.cross(d) * angle.sin();
let r = w - axis * w.dot(axis);
Ok((side(d, r), r.len()))
};
if !angle.is_finite() {
return Err(bad("the angle is not a number"));
}
Ok(match s {
FaceSurface::Plane { .. } => at,
FaceSurface::Cylinder { origin, axis, radius } => {
let w = at - origin;
let (d, _) = around(axis.into(), w)?;
origin + axis * w.dot(axis) + d * radius
}
FaceSurface::Cone { apex, axis, .. } => {
let w = at - apex;
let (d, r) = around(axis.into(), w)?;
apex + axis * w.dot(axis) + d * r.max(1e-6)
}
// On the outer equator, where the tube faces away from the axis.
FaceSurface::Torus { center, axis, major, minor } => {
let (d, _) = around(axis.into(), at - center)?;
center + d * (major + minor)
}
FaceSurface::Sphere { center, radius } => {
if angle.abs() > 1e-12 {
return Err(bad("a sphere has no axis to turn the plane about: leave the angle out"));
}
center + side(towards, at - center) * radius
}
})
}
fn face_surface_at(ctx: &Ctx, g: &GeoRef) -> Result<(FaceSurface, Vec3)> {
match g {
GeoRef::Face { at, name } => {
@@ -538,6 +585,46 @@ fn line_line(a: (Vec3, Vec3), b: (Vec3, Vec3)) -> Option<Vec3> {
Some(((p + u * s) + (q + v * t)) * 0.5)
}
/// The bodies a construction built from `refs` lies on: each face, edge and vertex pick's body,
/// and the bodies of the constructions it is built from. Empty when any pick is not on a body
/// (a fixed point, the origin, a plane, a world axis, a sketch) or is not found: such a
/// construction is held by something a move of the bodies leaves where it is, so it stays.
pub(crate) fn source_bodies(ctx: &Ctx, refs: &[&GeoRef]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for g in refs {
let found: Vec<String> = match g {
GeoRef::Face { at, name } => face_of(ctx.st, *at, name).map(|(b, ..)| vec![b.definition_name().to_string()]).unwrap_or_default(),
GeoRef::Edge { at, name } => edge_pick(ctx.st, *at, name).map(|(b, ..)| vec![b.definition_name().to_string()]).unwrap_or_default(),
GeoRef::Vertex { .. } => {
point(ctx, g).ok().and_then(|p| vertex_body(ctx.st, p)).map(|b| vec![b.definition_name().to_string()]).unwrap_or_default()
}
GeoRef::Axis { name } | GeoRef::ConstructPoint { name } => {
ctx.st.construct.iter().find(|c| c.name.as_str() == AsRef::<str>::as_ref(name)).map(|c| c.bodies.clone()).unwrap_or_default()
}
_ => Vec::new(),
};
if found.is_empty() {
return Vec::new();
}
for b in found {
if !out.contains(&b) {
out.push(b);
}
}
}
out
}
/// The body with a vertex (an end of one of its edges) at `p`.
fn vertex_body(st: &ModelState, p: Vec3) -> Option<&ModelBody> {
bodies(st).find(|b| {
let tol = (b.body.size() * 1e-3).max(1e-3);
b.body
.edges(tol)
.is_ok_and(|edges| edges.iter().flat_map(|e| [e.points.first(), e.points.last()]).flatten().any(|v| v.dist(p) <= 1e-6 * (1.0 + p.len())))
})
}
pub(crate) fn resolve_axis(ctx: &Ctx, d: &AxisDef) -> Result<(Vec3, Vec3)> {
match d {
AxisDef::ThroughFace { face } => line(ctx, face),
@@ -620,6 +707,16 @@ pub(crate) fn resolve_geo_plane(ctx: &Ctx, p: &PlaneRef) -> Result<Plane> {
};
Plane::new(o1, d1, n.cross(d1)).ok_or_else(degenerate)
}
PlaneRef::AxisPoint { axis, point: at } => {
let (origin, dir) = line(ctx, axis)?;
let dir = dir.normalized().ok_or_else(|| bad("the axis has no direction"))?;
let delta = point(ctx, at)? - origin;
let across = delta - dir * delta.dot(dir);
if !across.len().is_finite() || across.len() < 1e-9 {
return Err(bad("the point is on the axis: pick a point off the axis to define a unique plane"));
}
Plane::new(origin, dir, across).ok_or_else(degenerate)
}
PlaneRef::ThreePoints { a, b, c } => {
let (pa, pb, pc) = (point(ctx, a)?, point(ctx, b)?, point(ctx, c)?);
if (pb - pa).cross(pc - pa).len() < 1e-9 {
@@ -627,9 +724,17 @@ pub(crate) fn resolve_geo_plane(ctx: &Ctx, p: &PlaneRef) -> Result<Plane> {
}
Plane::new(pa, pb - pa, pc - pa).ok_or_else(degenerate)
}
PlaneRef::Tangent { face, at } => {
PlaneRef::Tangent { face, at, reference, angle } => {
let (s, _) = face_surface_at(ctx, face)?;
let (on, n) = surface_normal(s, *at).ok_or_else(|| bad("the point is on the face's axis"))?;
let at = match reference {
Some(r) => {
let towards = plane(ctx, r)?.normal().normalized().ok_or_else(degenerate)?;
let angle = angle.as_ref().map(|a| Document::eval_in(ctx.vals, a, Kind::Angle)).transpose()?.unwrap_or(0.0);
facing(s, *at, towards, angle)?
}
None => *at,
};
let (on, n) = surface_normal(s, at).ok_or_else(|| bad("the point is on the face's axis"))?;
Plane::from_normal(on, n).ok_or_else(degenerate)
}
PlaneRef::Perpendicular { plane: base, line: l } => {
@@ -768,10 +873,14 @@ impl PlaneRef {
/// The references of a plane built from model geometry.
pub fn refs_mut(&mut self) -> Vec<&mut GeoRef> {
match self {
PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } => vec![a.as_mut(), b.as_mut()],
PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } | PlaneRef::AxisPoint { axis: a, point: b } => vec![a.as_mut(), b.as_mut()],
PlaneRef::Perpendicular { plane, line } => vec![plane.as_mut(), line.as_mut()],
PlaneRef::ThreePoints { a, b, c } => vec![a.as_mut(), b.as_mut(), c.as_mut()],
PlaneRef::Tangent { face, .. } => vec![face.as_mut()],
PlaneRef::Tangent { face, reference, .. } => {
let mut v = vec![face.as_mut()];
v.extend(reference.as_deref_mut());
v
}
PlaneRef::AlongPath { path, .. } => vec![path.as_mut()],
PlaneRef::ThroughPoint { base, point } => {
let mut refs = base.refs_mut();
+372 -25
View File
@@ -97,10 +97,21 @@ pub enum PlaneRef {
Midplane { a: Box<GeoRef>, b: Box<GeoRef> },
/// Through two straight edges (lines) that lie in one plane.
TwoEdges { a: Box<GeoRef>, b: Box<GeoRef> },
/// Through a line (axis) and a point not on that line.
AxisPoint { axis: Box<GeoRef>, point: Box<GeoRef> },
/// Through three points.
ThreePoints { a: Box<GeoRef>, b: Box<GeoRef>, c: Box<GeoRef> },
/// Tangent to a face (cylinder, cone, sphere, torus) where the point `at` lies over it.
Tangent { face: Box<GeoRef>, at: Vec3 },
/// Tangent to a face (cylinder, cone, sphere, torus) where the point `at` lies over it. With
/// a `reference` (a plane or planar face), it touches where the face turns towards the
/// reference instead, turned by `angle` about the face's axis, on the side of `at` (#8822).
Tangent {
face: Box<GeoRef>,
at: Vec3,
#[serde(default, skip_serializing_if = "Option::is_none")]
reference: Option<Box<GeoRef>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
angle: Option<Expr>,
},
/// Square to an edge or sketch curve, a fraction `t` (expression, 0…1) of the way along it.
AlongPath { path: Box<GeoRef>, t: Expr },
/// Through a straight edge (or line, axis) and square to a plane (or planar face).
@@ -1083,6 +1094,11 @@ pub enum FeatureKind {
/// bodies follow. Replaces `to`.
#[serde(default, skip_serializing_if = "Option::is_none")]
to_plane: Option<PlaneRef>,
/// Point-to-point alignment: the directions the offset moves along (the chosen world
/// X/Y/Z axes in the frame of the bodies being moved). `None`: all three axes, the
/// offset in full.
#[serde(default, skip_serializing_if = "Option::is_none")]
axes: Option<Vec<Dir>>,
},
/// A helical coil: a circular or square section swept `turns` times about `axis`, rising
/// `pitch` per turn, on a helix of `diameter` (the section's centre, or its inside or outside).
@@ -1201,9 +1217,10 @@ pub enum FeatureKind {
body: Option<BodyName>,
},
/// A perforation field (#732): round holes of `diameter` on a staggered (60°) or square grid of `pitch` over the
/// whole flat sheet, kept `margin` (default: the diameter) clear of its edges, cut-outs and bend zones. Stored like
/// a cosmetic thread: the holes go to the flat pattern, the DXF and the measured volume, not into the B-rep, so a
/// panel can carry tens of thousands of them.
/// whole flat sheet, or inside the closed profiles of `sketch` when it is given, kept `margin` (default: the
/// diameter) clear of its edges, cut-outs, bend zones and the outline. Stored like a cosmetic thread: the holes go
/// to the flat pattern, the DXF and the measured volume, not into the B-rep, so a panel can carry tens of thousands
/// of them.
SheetPerforate {
#[walk(length = "Diameter")]
diameter: Expr,
@@ -1216,6 +1233,13 @@ pub enum FeatureKind {
pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
body: Option<BodyName>,
/// The field's outline: a sketch on a face of the sheet. None: the whole sheet.
#[serde(default, skip_serializing_if = "Option::is_none")]
sketch: Option<SketchId>,
/// Which of the outline sketch's profiles bound the field. None: all of them.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[walk(profiles_of = sketch)]
profiles: Option<ProfileSel>,
},
/// Fold a sheet along a line on its base face: a sketch line (followed when the sketch
/// changes), else the points `a`, `b`.
@@ -2014,7 +2038,7 @@ fn fair_keep_edges() -> bool {
/// Triangles of one imported mesh body (millimetres). The arrays are shared by clones: every
/// edit keeps the design as it was for undo, and a scanned part's triangles (hundreds of
/// megabytes) are not copied for that.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MeshData {
pub name: String,
/// x, y, z per vertex.
@@ -2025,6 +2049,20 @@ pub struct MeshData {
pub color: Option<[f32; 3]>,
}
/// The same mesh: shared arrays are the same without reading them, so the dirty check and
/// autosave after an edit elsewhere do not walk a scan's millions of coordinates. Arrays that
/// are not shared compare value by value, as before. Coordinates are finite (a design that
/// holds others does not load), so the shortcut only skips work.
impl PartialEq for MeshData {
fn eq(&self, other: &Self) -> bool {
use std::sync::Arc;
self.name == other.name
&& self.color == other.color
&& (Arc::ptr_eq(&self.triangles, &other.triangles) || self.triangles == other.triangles)
&& (Arc::ptr_eq(&self.positions, &other.positions) || self.positions == other.positions)
}
}
/// Points of a sketch, by id.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
pub struct SketchPoints {
@@ -2062,6 +2100,11 @@ pub struct Component {
/// Free text properties of the definition (material grade, finish, supplier…), by name.
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub properties: std::collections::BTreeMap<String, String>,
/// Keeps this component's features (and its children's) out of the History bar (#10393).
/// The features still rebuild and the component still shows in the Browser; imported copies
/// whose features only exist to rebuild the copy can carry this flag.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub history_hidden: bool,
}
/// One loft section: profiles of a sketch.
@@ -2197,7 +2240,11 @@ pub enum PatternKind {
reversed2: bool,
},
Circular {
origin: Position,
/// A point on the axis the copies turn about: numbers, or length expressions it follows
/// as the parameters change (#10852).
#[serde(with = "point_expr")]
#[walk(placement)]
origin: PointExpr,
axis: Dir,
#[walk(unitless = "Quantity")]
count: Expr,
@@ -2407,6 +2454,167 @@ impl FeatureKind {
FeatureKind::MeshConvert { .. } => "MeshConvertFeature",
}
}
/// Design-file variant key (e.g. `"extrude"`, `"box"`, `"sheet_flange"`).
pub fn kind_key(&self) -> &'static str {
match self {
FeatureKind::BodyTransfer { .. } => "body_transfer",
FeatureKind::Sketch { .. } => "sketch",
FeatureKind::Extrude { .. } => "extrude",
FeatureKind::Revolve { .. } => "revolve",
FeatureKind::Fillet { .. } => "fillet",
FeatureKind::Chamfer { .. } => "chamfer",
FeatureKind::Box { .. } => "box",
FeatureKind::Cylinder { .. } => "cylinder",
FeatureKind::Sphere { .. } => "sphere",
FeatureKind::Torus { .. } => "torus",
FeatureKind::Cone { .. } => "cone",
FeatureKind::Combine { .. } => "combine",
FeatureKind::Pattern { .. } => "pattern",
FeatureKind::Mirror { .. } => "mirror",
FeatureKind::Hole { .. } => "hole",
FeatureKind::Thread { .. } => "thread",
FeatureKind::Loft { .. } => "loft",
FeatureKind::Sweep { .. } => "sweep",
FeatureKind::Shell { .. } => "shell",
FeatureKind::Draft { .. } => "draft",
FeatureKind::ConstructionPlane { .. } => "construction_plane",
FeatureKind::Split { .. } => "split",
FeatureKind::ConstructionAxis { .. } => "construction_axis",
FeatureKind::ConstructionPoint { .. } => "construction_point",
FeatureKind::SplitFace { .. } => "split_face",
FeatureKind::Scale { .. } => "scale",
FeatureKind::OffsetFace { .. } => "offset_face",
FeatureKind::DeleteFace { .. } => "delete_face",
FeatureKind::BoundingSolid { .. } => "bounding_solid",
FeatureKind::Pipe { .. } => "pipe",
FeatureKind::FrameMember { .. } => "frame_member",
FeatureKind::Emboss { .. } => "emboss",
FeatureKind::Rib { .. } => "rib",
FeatureKind::ReplaceFace { .. } => "replace_face",
FeatureKind::Align { .. } => "align",
FeatureKind::Coil { .. } => "coil",
FeatureKind::SheetBase { .. } => "sheet_base",
FeatureKind::SheetContour { .. } => "sheet_contour",
FeatureKind::SheetFlange { .. } => "sheet_flange",
FeatureKind::SheetHem { .. } => "sheet_hem",
FeatureKind::SheetPerforate { .. } => "sheet_perforate",
FeatureKind::SheetFold { .. } => "sheet_fold",
FeatureKind::SheetPath { .. } => "sheet_path",
FeatureKind::SheetUnfold { .. } => "sheet_unfold",
FeatureKind::SheetConvert { .. } => "sheet_convert",
FeatureKind::Boss { .. } => "boss",
FeatureKind::Lip { .. } => "lip",
FeatureKind::SnapFit { .. } => "snap_fit",
FeatureKind::Rest { .. } => "rest",
FeatureKind::BoundaryFill { .. } => "boundary_fill",
FeatureKind::Remove { .. } => "remove",
FeatureKind::Patch { .. } => "patch",
FeatureKind::Stitch { .. } => "stitch",
FeatureKind::Thicken { .. } => "thicken",
FeatureKind::SurfaceExtrude { .. } => "surface_extrude",
FeatureKind::Unstitch { .. } => "unstitch",
FeatureKind::ReverseNormal { .. } => "reverse_normal",
FeatureKind::SurfaceUntrim { .. } => "surface_untrim",
FeatureKind::SurfaceRevolve { .. } => "surface_revolve",
FeatureKind::SurfaceSweep { .. } => "surface_sweep",
FeatureKind::SurfaceLoft { .. } => "surface_loft",
FeatureKind::SurfaceOffset { .. } => "surface_offset",
FeatureKind::SurfaceRuled { .. } => "surface_ruled",
FeatureKind::SurfaceExtend { .. } => "surface_extend",
FeatureKind::SurfaceBlend { .. } => "surface_blend",
FeatureKind::SurfaceBoundary { .. } => "surface_boundary",
FeatureKind::SurfaceNetwork { .. } => "surface_network",
FeatureKind::SurfaceMatch { .. } => "surface_match",
FeatureKind::SurfaceFair { .. } => "surface_fair",
FeatureKind::SurfaceTrim { .. } => "surface_trim",
FeatureKind::Move { .. } => "move",
FeatureKind::Import { .. } => "import",
FeatureKind::MeshConvert { .. } => "mesh_convert",
FeatureKind::MeshImport { .. } => "mesh_import",
}
}
/// Map a display or Rust type name (e.g. `"HoleFeature"`, `"EdgeFlangeFeature"`)
/// to the canonical design-file variant key (`"hole"`, `"sheet_flange"`).
pub fn type_name_to_kind_key(name: &str) -> Option<&'static str> {
Some(match name {
"BodyTransferFeature" => "body_transfer",
"Sketch" => "sketch",
"ExtrudeFeature" => "extrude",
"RevolveFeature" => "revolve",
"FilletFeature" => "fillet",
"ChamferFeature" => "chamfer",
"BoxFeature" => "box",
"CylinderFeature" => "cylinder",
"SphereFeature" => "sphere",
"TorusFeature" => "torus",
"ConeFeature" => "cone",
"CombineFeature" => "combine",
"RectangularPatternFeature" | "PathPatternFeature" | "CircularPatternFeature" => "pattern",
"MirrorFeature" => "mirror",
"ConstructionPlane" => "construction_plane",
"HoleFeature" => "hole",
"ThreadFeature" => "thread",
"LoftFeature" => "loft",
"SweepFeature" => "sweep",
"ShellFeature" => "shell",
"DraftFeature" => "draft",
"SplitBodyFeature" => "split",
"SplitFaceFeature" => "split_face",
"ConstructionAxis" => "construction_axis",
"ConstructionPoint" => "construction_point",
"MoveFeature" => "move",
"ScaleFeature" => "scale",
"OffsetFacesFeature" => "offset_face",
"DeleteFaceFeature" => "delete_face",
"OffsetSurfaceFeature" => "surface_offset",
"BoundingSolidFeature" => "bounding_solid",
"PipeFeature" => "pipe",
"FrameMemberFeature" => "frame_member",
"EmbossFeature" => "emboss",
"BossFeature" => "boss",
"LipFeature" | "GrooveFeature" => "lip",
"SnapFitFeature" => "snap_fit",
"RestFeature" => "rest",
"BoundaryFillFeature" => "boundary_fill",
"BaseFlangeFeature" => "sheet_base",
"ContourFlangeFeature" => "sheet_contour",
"EdgeFlangeFeature" => "sheet_flange",
"HemFeature" => "sheet_hem",
"PerforateFeature" => "sheet_perforate",
"FoldFeature" => "sheet_fold",
"PathFlangeFeature" => "sheet_path",
"UnfoldFeature" | "RefoldFeature" => "sheet_unfold",
"ConvertToSheetMetalFeature" => "sheet_convert",
"CoilFeature" => "coil",
"RibFeature" | "WebFeature" => "rib",
"ReplaceFaceFeature" => "replace_face",
"AlignFeature" => "align",
"RemoveFeature" => "remove",
"PatchFeature" => "patch",
"SurfaceExtrudeFeature" => "surface_extrude",
"SurfaceRevolveFeature" => "surface_revolve",
"SurfaceSweepFeature" => "surface_sweep",
"SurfaceLoftFeature" => "surface_loft",
"BlendFeature" => "surface_blend",
"BoundaryFeature" => "surface_boundary",
"MatchFeature" => "surface_match",
"FairFeature" => "surface_fair",
"StitchFeature" => "stitch",
"ThickenFeature" => "thicken",
"TrimFeature" => "surface_trim",
"ExtendFeature" => "surface_extend",
"RuledSurfaceFeature" => "surface_ruled",
"UnstitchFeature" => "unstitch",
"ReverseNormalFeature" => "reverse_normal",
"UntrimFeature" => "surface_untrim",
"BaseFeature" => "import",
"MeshFeature" => "mesh_import",
"MeshConvertFeature" => "mesh_convert",
_ => return None,
})
}
/// Default name prefix (`Extrude` → `Extrude1`).
pub fn base_name(&self) -> &'static str {
match self {
@@ -2628,6 +2836,19 @@ pub struct DesignLink {
/// local overrides and survive a source update. Older links start with no baseline.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub inherited: Vec<LinkedBodyProperties>,
/// SHA-256 of the design file's bytes as this design last read them ([`Self::file_digest`]):
/// opened again, this design tells which linked files changed meanwhile (#9739). Links made
/// before it was kept have none and are taken as up to date.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_sha256: Option<String>,
}
impl DesignLink {
/// What [`Self::file_sha256`] keeps for a design file's `bytes`.
pub fn file_digest(bytes: &[u8]) -> String {
use sha2::{Digest, Sha256};
format!("{:x}", Sha256::digest(bytes))
}
}
/// The values last inherited by a linked body, not another editable material library.
@@ -2736,6 +2957,9 @@ pub struct Document {
/// Physical material per body (by body name); others use the default.
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub materials: std::collections::BTreeMap<String, String>,
/// The flat face per body (by body name) that a slicer export lays on the print bed.
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub print_faces: std::collections::BTreeMap<String, crate::config::ConfigFaceTarget>,
/// Physical materials defined in this design (`material.create`), next to the built-in ones.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub user_materials: Vec<UserMaterial>,
@@ -2756,9 +2980,11 @@ pub struct Document {
/// order; feature ids remain the source of truth when features are renamed or reordered.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub timeline_groups: Vec<TimelineGroup>,
/// Browser item order per component folder (`"<component>/<folder>"` → item keys).
/// Browser order per component folder (`"<component>/<folder>"` → groups and item keys).
/// The `components` folder orders a component's sub-components (by placement id); it is not
/// drawn as a folder.
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub browser_order: std::collections::BTreeMap<String, Vec<String>>,
pub browser_order: std::collections::BTreeMap<String, Vec<BrowserEntry>>,
/// Named views: saved cameras.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub named_views: Vec<NamedView>,
@@ -2847,8 +3073,69 @@ pub struct BrowserGroup {
#[serde(default)]
pub component: u64,
pub folder: String,
/// The group that holds this one; `None` is the top level of its folder.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<u64>,
/// The group's items and child groups in display order. A child group missing here still
/// belongs to it (its `parent` says so) and is listed after the ones that are here.
#[serde(default)]
pub items: Vec<String>,
pub items: Vec<BrowserEntry>,
}
/// One line of a browser order list: a group (a number in a saved design) or an item (a string:
/// an item key or body name, so a body named `"1"` never clashes with group 1).
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BrowserEntry {
Group(u64),
Item(String),
}
impl From<&str> for BrowserEntry {
fn from(key: &str) -> Self {
BrowserEntry::Item(key.to_string())
}
}
impl From<String> for BrowserEntry {
fn from(key: String) -> Self {
BrowserEntry::Item(key)
}
}
impl BrowserEntry {
pub fn item(key: &str) -> Self {
BrowserEntry::Item(key.to_string())
}
pub fn as_item(&self) -> Option<&str> {
match self {
BrowserEntry::Item(k) => Some(k),
BrowserEntry::Group(_) => None,
}
}
pub fn is_item(&self, key: &str) -> bool {
self.as_item() == Some(key)
}
}
/// How many groups may sit inside one another (a top-level group is level 1).
pub const MAX_GROUP_NESTING: usize = 8;
/// The folders a browser group or order list can be for. `components` only orders sub-components.
pub const BROWSER_FOLDERS: [&str; 4] = ["bodies", "sketches", "construction", "components"];
impl BrowserGroup {
/// The keys of the items directly in this group, in order.
pub fn item_keys(&self) -> impl Iterator<Item = &str> {
self.items.iter().filter_map(BrowserEntry::as_item)
}
/// Whether this group holds the item directly.
pub fn has_item(&self, key: &str) -> bool {
self.items.iter().any(|e| e.is_item(key))
}
}
/// A saved history folder. Folders may nest; each feature or joint belongs to at most one folder.
@@ -2982,6 +3269,7 @@ impl Document {
body_components: Default::default(),
body_offsets: Default::default(),
materials: Default::default(),
print_faces: Default::default(),
user_materials: Vec::new(),
appearances: Default::default(),
favorites: Default::default(),
@@ -2997,7 +3285,7 @@ impl Document {
}
/// Rename body `old` to `new` where the design keeps things by body name outside its
/// features: materials, appearances, plastic rules, configuration columns, offsets, components and the
/// features: materials, print faces, appearances, plastic rules, configuration columns, offsets, components and the
/// browser's groups and order. (The features are the rename command's to change.)
///
/// A name the design numbered (`Body1`) is not used again after the rename: the design's
@@ -3013,6 +3301,10 @@ impl Document {
if let Some(m) = self.materials.remove(old) {
self.materials.insert(new.to_string(), m);
}
if let Some(mut face) = self.print_faces.remove(old) {
face.body = new.to_string();
self.print_faces.insert(new.to_string(), face);
}
if let Some(rule) = self.plastic.assigned.remove(old) {
self.plastic.assigned.insert(new.to_string(), rule);
}
@@ -3039,22 +3331,23 @@ impl Document {
if let Some(c) = self.body_components.remove(old) {
self.body_components.insert(new.to_string(), c);
}
for g in self.browser_groups.iter_mut().filter(|g| g.folder == "bodies") {
for k in g.items.iter_mut().filter(|k| *k == old) {
*k = new.to_string();
let rename = |e: &mut BrowserEntry| {
if e.is_item(old) {
*e = BrowserEntry::item(new);
}
};
for g in self.browser_groups.iter_mut().filter(|g| g.folder == "bodies") {
g.items.iter_mut().for_each(rename);
}
for (_, v) in self.browser_order.iter_mut().filter(|(k, _)| k.ends_with("/bodies")) {
for k in v.iter_mut().filter(|k| *k == old) {
*k = new.to_string();
}
v.iter_mut().for_each(rename);
}
}
/// The single body rename (old name, new name) that turns `previous` into this design,
/// when that is all that changed: the features' stored body names (`made` gives the names
/// each feature's bodies have now, by feature id) and what [`Document::rename_body_keys`]
/// renames. `None` for any other change.
/// each feature's bodies have now, by feature id), typed body references, participants and
/// what [`Document::rename_body_keys`] renames. `None` for any other change.
pub fn body_rename_from(&self, previous: &Document, made: impl Fn(u64) -> Vec<String>) -> Option<(String, String)> {
if self.features.len() != previous.features.len() {
return None;
@@ -3085,8 +3378,16 @@ impl Document {
}
let (old, new) = rename?;
let mut expected = previous.clone();
let bodies = std::collections::BTreeMap::from([(old.clone(), new.clone())]);
for (f, g) in expected.features.iter_mut().zip(&self.features) {
f.body_names.clone_from(&g.body_names);
// Updating references to the renamed body changes no feature input other than its
// name (#6287). Keep the full comparison below: geometry, expressions, sketch links
// and face anchors must still agree before the model may reuse its geometry.
f.kind.remap_bodies(&bodies);
for body in f.participants.iter_mut().filter(|body| **body == old) {
body.clone_from(&new);
}
}
expected.rename_body_keys(&old, &new);
(expected == *self).then_some((old, new))
@@ -3366,10 +3667,14 @@ 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)?;
let d = crate::format::read(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()));
}
// A design from elsewhere may nest groups too deep, loop, or name things that are gone.
if let Some((groups, order)) = d.browser_pruned() {
(d.browser_groups, d.browser_order) = (groups, order);
}
Ok(d)
}
pub fn to_json(&self) -> String {
@@ -3377,6 +3682,8 @@ impl Document {
}
pub fn feature(&self, id: u64) -> Option<&Feature> {
#[cfg(test)]
crate::eval::cost_tests::count_feature_search();
self.features.iter().find(|f| f.id == id)
}
pub fn feature_mut(&mut self, id: u64) -> Option<&mut Feature> {
@@ -3395,15 +3702,35 @@ impl Document {
self.features.iter().find(|f| f.name.eq_ignore_ascii_case(r))
}
/// Resolve a textual timeline reference. An exact name wins even when it looks like an id.
/// Older scripts also pass ids as strings or use a different case for a unique name.
pub fn resolve_feature_name(&self, name: &str) -> Result<&Feature> {
if let Some(feature) = self.features.iter().find(|f| f.name == name) {
return Ok(feature);
}
if let Ok(id) = name.parse::<u64>()
&& let Some(feature) = self.feature(id)
{
return Ok(feature);
}
let mut matches = self.features.iter().filter(|f| f.name.eq_ignore_ascii_case(name));
let feature = matches.next().ok_or_else(|| DocError::Invalid(format!("no feature `{name}`")))?;
if matches.next().is_some() {
return Err(DocError::Invalid(format!("ambiguous feature `{name}`. Use its exact name or numeric id")));
}
Ok(feature)
}
/// The feature a pattern or mirror seed names, resolved the way `replay()` resolved seed
/// names before seeds were keyed by id (#1134): an exact name match on a feature that is
/// not a sketch or a construction plane wins — a sketch and its extrude often share a
/// name, and the feature that makes geometry is the one to copy — else [`find_feature`].
/// name, and the feature that makes geometry is the one to copy. Other references use
/// the timeline lookup.
pub fn seed_feature(&self, name: &str) -> Option<&Feature> {
self.features
.iter()
.find(|x| x.name == name && !matches!(x.kind, FeatureKind::Sketch { .. } | FeatureKind::ConstructionPlane { .. }))
.or_else(|| self.find_feature(name))
.or_else(|| self.resolve_feature_name(name).ok())
}
fn unique_name(&self, base: &str) -> String {
@@ -3459,12 +3786,18 @@ impl Document {
} else {
0
};
let id = self.next_id.max(1);
self.next_id = id.checked_add(1).ok_or_else(|| DocError::Invalid("too many items in the design".into()))?;
let name = match name {
Some(n) if !n.trim().is_empty() => n.trim().to_string(),
_ => self.unique_name(kind.base_name()),
};
// Plane references share the feature namespace. A duplicate would silently select
// the first feature. Other shared names (a sketch and its extrude) remain supported.
let is_plane = matches!(kind, FeatureKind::ConstructionPlane { .. });
if self.features.iter().any(|f| f.name == name && (is_plane || matches!(f.kind, FeatureKind::ConstructionPlane { .. }))) {
return Err(DocError::Invalid(format!("another feature is called `{name}`")));
}
let id = self.next_id.max(1);
self.next_id = id.checked_add(1).ok_or_else(|| DocError::Invalid("too many items in the design".into()))?;
let f = Feature {
id,
name,
@@ -3862,6 +4195,7 @@ impl Document {
}
PlaneRef::Midplane { .. }
| PlaneRef::TwoEdges { .. }
| PlaneRef::AxisPoint { .. }
| PlaneRef::ThreePoints { .. }
| PlaneRef::Tangent { .. }
| PlaneRef::AlongPath { .. }
@@ -4014,7 +4348,7 @@ impl Document {
return Err(DocError::Invalid("a text's height must be positive".into()));
}
if let Some(e) = &exprs.text {
let words = self.eval_words(e).map_err(|error| DocError::Expr(format!("text: {error}")))?;
let words = self.eval_words(e, vals).map_err(|error| DocError::Expr(format!("text: {error}")))?;
if words.trim().is_empty() || words.chars().count() > solidcraft_sketch::MAX_TEXT_CHARS {
return Err(DocError::Invalid(format!("text: `{e}` gives no words, or too many")));
}
@@ -4059,6 +4393,11 @@ impl Document {
analysis: solidcraft_sketch::Analysis,
) -> Result<solidcraft_sketch::SolveReport> {
self.apply_dimension_values(vals, sk)?;
// A text built anew since it was stored (an expression gave it other words) has other
// outline entities: the stored sketch is taken with this sketch's, so the points still
// pair up for the side checks and the walk below.
let paired = moved_from.and_then(|from| sk.stored_with_rebuilt_texts(from));
let moved_from = paired.as_ref().or(moved_from);
let was = |sk: &Sketch| moved_from.and_then(|from| sk.positions_in(from));
let offsets = !sk.offsets.is_empty() || !sk.freeform_offsets.is_empty();
// Offsets always require another solve after refresh. Only that last report is
@@ -4097,6 +4436,14 @@ impl Document {
} else {
solidcraft_sketch::solve_after_edit_with_analysis(sk, stored, was(sk).as_deref(), analysis)
};
// An attachment to the old offset can prevent the source from moving at all.
// Retry from a source-only seed, adopting it only if every constraint still holds.
if !report.ok()
&& report.oversized.is_none()
&& let Some(retried) = sk.solve_after_offset_edit(stored, was(sk).as_deref(), analysis)
{
report = retried;
}
}
Ok(report)
}
+504 -71
View File
@@ -40,6 +40,11 @@ pub struct ModelBody {
joint_faces: Arc<OnceLock<JointFaces>>,
/// Lazy canonical reference triangles for a proper rigid occurrence placement.
mesh_placement: Option<Arc<placed_mesh::PlacedMesh>>,
/// For a mirrored copy: its faces as a world state shows them, a rigid move of the source's
/// [`placed_mesh::OppositeHand`]. Display only: the model reads the copy's own triangles.
mirrored_mesh: Option<Arc<placed_mesh::PlacedMesh>>,
/// The first mirrored copy made of this body, shared by its clones.
opposite_hand: Arc<OnceLock<placed_mesh::OppositeHand>>,
/// The display plan (none when the body has no plan: a mesh body, or one the planner
/// refuses), shared like the mesh. A GPU viewport draws it.
plan: Arc<[OnceLock<Option<Arc<TessPlan>>>; 4]>,
@@ -86,6 +91,8 @@ impl ModelBody {
mesh: Arc::new(OnceLock::new()),
joint_faces: Arc::new(OnceLock::new()),
mesh_placement: None,
mirrored_mesh: None,
opposite_hand: Arc::new(OnceLock::new()),
plan: Arc::new(Default::default()),
plan_mesh: Arc::new(Default::default()),
key: 0,
@@ -458,7 +465,12 @@ fn thread_on_impl(
let (body, cyl) = st
.bodies
.iter()
.filter_map(|b| kernel::cylinder_face_at(&b.body, p).map(|c| (b.name.clone(), c)))
.filter(|b| !more::beyond_cylinder_reach(b, p))
.filter_map(|b| {
#[cfg(test)]
cost_tests::count(|c| c.cylinder_search += 1);
kernel::cylinder_face_at(&b.body, p).map(|c| (b.name.clone(), c))
})
.min_by(|a, b| {
let dist = |c: &kernel::CylinderFace| ((p - c.axis_point - c.axis * (p - c.axis_point).dot(c.axis)).len() - c.radius).abs();
dist(&a.1).total_cmp(&dist(&b.1))
@@ -993,6 +1005,7 @@ impl ModelState {
&& let Some(lines) = olds.first().and_then(|x| x.body.split_lines())
{
b.body = b.body.clone().with_split_lines(Some(lines.clone()));
b.mirrored_mesh = None;
}
// An imported look outlives the operations on the body.
if b.body.paint().is_none()
@@ -1194,17 +1207,27 @@ impl ModelState {
/// to the budget keep only its last few states, so setting a parameter back recomputed what
/// had dropped out.
fn added_bytes(&self, before: &ModelState) -> usize {
// `before`'s bodies by solid, and its sketches by address: looked up per body and sketch,
// not searched (every feature's result is charged, so searching was bodies² per feature).
// Bodies sharing a solid are still told apart by `same_shape` (the mesh as well).
let mut solids: std::collections::HashMap<usize, Vec<&Body>> = std::collections::HashMap::with_capacity(before.bodies.len());
for o in &before.bodies {
solids.entry(o.body.solid_id()).or_default().push(&o.body);
}
let bodies: usize = self
.bodies
.iter()
.filter(|b| !before.bodies.iter().any(|o| o.body.same_shape(&b.body)))
.filter(|b| !solids.get(&b.body.solid_id()).is_some_and(|os| os.iter().any(|o| o.same_shape(&b.body))))
.map(|b| if b.bytes > 0 { b.bytes } else { b.body.retained_bytes() })
.sum();
let sketches: usize = self.sketches.iter().filter(|s| !before.sketches.iter().any(|o| Arc::ptr_eq(o, s))).map(|s| s.retained_bytes()).sum();
let kept: std::collections::HashSet<*const SolvedSketch> = before.sketches.iter().map(Arc::as_ptr).collect();
let sketches: usize = self.sketches.iter().filter(|s| !kept.contains(&Arc::as_ptr(s))).map(|s| s.retained_bytes()).sum();
bodies + sketches + self.sheets.len() * 4096 + self.threads.len() * 192 + self.construct.len() * 192
}
pub fn body(&self, name: &str) -> Option<&ModelBody> {
#[cfg(test)]
cost_tests::count(|c| c.body_searches += 1);
self.bodies.iter().find(|b| b.name == name)
}
@@ -1299,6 +1322,10 @@ pub struct Model {
kept: KeptSlot,
/// The imported texts' digests the last `evaluate` fingerprinted the features with.
step_digests: StepDigests,
/// The address of the shared document whose fingerprints the results were last given in
/// full, with [`fingerprints_digest`] of them then ([`Model::pinned_prefix`]). Only an
/// address: a reference to the document would make every in-place edit of it a copy.
fingerprinted: Option<(usize, u64)>,
/// The document context the last `evaluate` ran in ([`context_digest`]).
context: u64,
/// The last active result of each feature hidden by the history marker, with its document
@@ -1418,32 +1445,40 @@ fn quantize(sk: &mut solidcraft_sketch::Sketch) {
/// file's text by its digest.
/// Hash [`feature_json`] of `f` into `h`. Every evaluation fingerprints every feature, and in a
/// design built command by command almost all of them are the same as last time: the text of a
/// feature is kept (per thread, by id) with the feature it was written from, and written again
/// only when the feature differs. An Import's text also depends on its STEP digest, so it is
/// written each time as before.
/// feature is kept (by id) with the feature it was written from, and written again only when the
/// feature differs. An Import's text also depends on its STEP digest, so it is written each time
/// as before.
///
/// The texts are shared by every thread: a command evaluated ahead runs on a new worker thread
/// each time (the desktop's deferred commands and delete checks, the control channel's ahead
/// jobs), and a per-thread store started empty there, so every ahead run wrote out every
/// feature again. The lock is held only to look up or store an entry, never while a feature is
/// compared or written; an entry is the same whichever thread wrote it, since the text is a
/// function of the feature alone.
fn hash_feature_json(f: &Feature, steps: &StepDigests, h: &mut impl Hasher) {
type Written = std::collections::HashMap<u64, Arc<(Feature, String)>>;
static WRITTEN: std::sync::LazyLock<std::sync::Mutex<Written>> = std::sync::LazyLock::new(Default::default);
if matches!(f.kind, FeatureKind::Import { .. }) {
feature_json(f, steps).hash(h);
return;
}
thread_local! {
static WRITTEN: std::cell::RefCell<std::collections::HashMap<u64, (Feature, String)>> = std::cell::RefCell::new(std::collections::HashMap::new());
let seen = WRITTEN.lock().unwrap_or_else(|e| e.into_inner()).get(&f.id).cloned();
if let Some(seen) = seen
&& seen.0 == *f
{
seen.1.hash(h);
return;
}
WRITTEN.with(|written| {
let mut written = written.borrow_mut();
if let Some((seen, json)) = written.get(&f.id)
&& seen == f
{
json.hash(h);
return;
}
let json = feature_json(f, steps);
json.hash(h);
if written.len() >= 1 << 15 {
written.clear();
}
written.insert(f.id, (f.clone(), json));
});
#[cfg(test)]
cost_tests::count_written();
let json = feature_json(f, steps);
json.hash(h);
let entry = Arc::new((f.clone(), json));
let mut written = WRITTEN.lock().unwrap_or_else(|e| e.into_inner());
if written.len() >= 1 << 15 {
written.clear();
}
written.insert(f.id, entry);
}
fn feature_json(f: &Feature, steps: &StepDigests) -> String {
@@ -1788,6 +1823,8 @@ fn hash_placement(m: &Mat, h: &mut impl Hasher) {
fn mirror_placements_digest(doc: &Document, f: &Feature, placements: &[Vec<u64>], bodies: &[crate::BodyName]) -> u64 {
let mut h = std::collections::hash_map::DefaultHasher::new();
doc.component_transform(f.component).iter().flatten().for_each(|x| x.to_bits().hash(&mut h));
// A feature added after the mirror that builds on its copies needs them made for that.
placement_reuse::copies_only_shown(doc, f).hash(&mut h);
for placed in placements.iter().map(|path| mirror_copy_frame(doc, path)) {
placed.is_some().hash(&mut h);
if let Some((component, frame, picked)) = placed {
@@ -1938,13 +1975,7 @@ fn placement_digest(doc: &Document, f: &Feature, st: Option<&ModelState>) -> u64
None => u8::MAX.hash(&mut h),
}
}
for name in &reads.bodies {
name.hash(&mut h);
match st.and_then(|st| st.body(name)) {
Some(b) => hash_placement(&crate::mat_mul(&inverse, &body_world(doc, b)), &mut h),
None => u8::MAX.hash(&mut h),
}
}
hash_placed_bodies(doc, st, &reads.bodies, &inverse, &mut h);
// Legacy and component-wide reads still need the conservative offset dependency. Body
// reads above already include just their own offsets, in the frame that evaluates them.
if reads.all || !reads.components.is_empty() || !reads.paths.is_empty() {
@@ -1954,6 +1985,38 @@ fn placement_digest(doc: &Document, f: &Feature, st: Option<&ModelState>) -> u64
h.finish() | 1
}
/// The bodies part of [`placement_digest`]: each body read, in name order, and where it is placed
/// relative to the reading frame `inverse` (`u8::MAX` for one not in `st`), as [`body_world`]
/// places it. The bodies, their components and the components' frames are looked up once: a
/// join or cut without targets reads every body of its component, and searching the model, the
/// timeline and the placements again for each of them made every evaluation, even one served
/// from the cache, quadratic in the bodies.
fn hash_placed_bodies(doc: &Document, st: Option<&ModelState>, names: &std::collections::BTreeSet<String>, inverse: &Mat, h: &mut impl Hasher) {
let (Some(st), false) = (st, names.is_empty()) else {
names.iter().for_each(|name| (name, u8::MAX).hash(h));
return;
};
// The first body of a name, as `ModelState::body` finds it.
let mut index: std::collections::HashMap<&str, usize> = std::collections::HashMap::with_capacity(st.bodies.len());
for (i, b) in st.bodies.iter().enumerate() {
index.entry(b.name.as_str()).or_insert(i);
}
let components = doc.model_body_components(st);
let mut frames: std::collections::HashMap<u64, Mat> = std::collections::HashMap::new();
for name in names {
name.hash(h);
let found = index.get(name.as_str()).and_then(|&i| Some((st.bodies.get(i)?, *components.get(i)?)));
match found {
Some((b, component)) => {
let m = *frames.entry(component).or_insert_with(|| doc.component_transform(component));
let world = doc.model_body_offset(b).map(|offset| crate::mat_mul(&m, offset)).unwrap_or(m);
hash_placement(&crate::mat_mul(inverse, &world), h);
}
None => u8::MAX.hash(h),
}
}
}
/// Every occurrence's placement and every body offset (a feature read from a frame that cannot
/// be inverted).
fn placements_hash(doc: &Document) -> u64 {
@@ -2131,6 +2194,7 @@ impl Model {
/// Re-evaluate the document, reusing results up to the first changed feature.
pub fn evaluate(&mut self, doc: &Document) {
self.placement_inputs = None;
self.fingerprinted = None;
self.placement_local = true;
let (vals, perr) = evaluation_values(doc);
self.param_errors = perr;
@@ -2364,6 +2428,16 @@ impl Model {
_ => mats,
};
next.stamp(&state, f, key, mats);
// Evaluated again only for what came before it (a mirror moved by a drag).
if let Some(old) = self.results.get(i).filter(|old| old.id == f.id) {
let made = old.state.bodies.iter().filter(|b| b.feature == f.id).map(|b| (b.name.as_str(), b));
let made: std::collections::HashMap<&str, &ModelBody> = made.collect();
for b in next.bodies.iter_mut().filter(|b| b.feature == f.id) {
if let Some(old) = made.get(b.name.as_str()) {
b.keep_meshes_of(old);
}
}
}
for b in &mut next.bodies {
if !b.transferred {
b.component = match doc.body_components.get(&b.name) {
@@ -2532,19 +2606,76 @@ impl Model {
/// Set every result's fingerprint to `doc`'s, so the next evaluation reuses every result
/// (after a change that builds nothing differently).
pub fn refingerprint(&mut self, doc: &Document) {
self.refingerprint_from(doc, 0);
}
/// How many leading results keep their fingerprints when `doc` has the body names of the
/// features `renamed` pinned, and nothing else changed (see [`Model::refingerprint_shared`]).
/// Every command pins, and fingerprinting the whole timeline again for it cost as much as
/// the fingerprinting of the evaluation itself. When the results hold `doc`'s fingerprints
/// (the address and digest kept when they were given prove it), those before the first
/// renamed feature stand, since nothing they read changes, unless a pattern or mirror of
/// features among them replays a renamed one (it fingerprints its sources' definitions
/// wherever they are). Without that proof: none.
pub fn pinned_prefix(&self, doc: &Arc<Document>, renamed: &[u64]) -> usize {
let proven = self.fingerprinted.is_some_and(|(of, digest)| of == Arc::as_ptr(doc) as usize && digest == fingerprints_digest(&self.results));
if !proven {
return 0;
}
let Some(first) = doc.features.iter().position(|f| renamed.contains(&f.id)) else { return doc.features.len() };
let replays_renamed = |f: &Feature| match &f.kind {
FeatureKind::Pattern { features, bodies, .. } | FeatureKind::Mirror { features, bodies, .. } => {
bodies.is_empty() && !features.is_empty() && replayed_sources(doc, features).iter().any(|g| renamed.contains(&g.id))
}
_ => false,
};
doc.features.iter().take(first).position(replays_renamed).unwrap_or(first)
}
/// Drop the proof [`Model::pinned_prefix`] reads: the document at that address is about to
/// change in place.
pub fn forget_fingerprints(&mut self) {
self.fingerprinted = None;
}
/// Fingerprint the results from `start` on for `doc`, chained onto the fingerprint before.
pub(crate) fn refingerprint_from(&mut self, doc: &Document, start: usize) {
self.placement_inputs = None;
self.fingerprinted = None;
self.step_digests = StepDigests::of(doc, &self.step_digests);
let start = start.min(self.results.len()).min(doc.features.len());
if start == self.results.len().min(doc.features.len()) {
return;
}
let (vals, _) = evaluation_values(doc);
let marker = doc.marker.unwrap_or(usize::MAX);
self.step_digests = StepDigests::of(doc, &self.step_digests);
let mut prev = 0;
let last = start.checked_sub(1).and_then(|i| self.results.get(i));
let mut prev = last.map_or(0, |r| r.fingerprint);
// Each feature's fingerprint reads the model it starts from, as evaluating does.
let mut before = Arc::new(ModelState::default());
for (i, (r, f)) in self.results.iter_mut().zip(&doc.features).enumerate() {
let mut before = last.map_or_else(|| Arc::new(ModelState::default()), |r| r.state.clone());
for (i, (r, f)) in self.results.iter_mut().zip(&doc.features).enumerate().skip(start) {
prev = chain_fingerprint(doc, &vals, f, prev, i >= marker, &self.step_digests, Some(&before));
r.fingerprint = prev;
before = r.state.clone();
}
}
/// Note that the results now hold `doc`'s fingerprints (for [`Model::pinned_prefix`]).
pub(crate) fn mark_fingerprinted(&mut self, doc: &Arc<Document>) {
self.fingerprinted = Some((Arc::as_ptr(doc) as usize, fingerprints_digest(&self.results)));
}
}
/// A digest of the results' features and fingerprints, which proves they are still the ones
/// [`Model::mark_fingerprinted`] saw (whatever changed them since).
fn fingerprints_digest(results: &[FeatureResult]) -> u64 {
let mut h = std::collections::hash_map::DefaultHasher::new();
results.len().hash(&mut h);
for r in results {
r.id.hash(&mut h);
r.fingerprint.hash(&mut h);
}
h.finish()
}
/// Key of the design's length unit (mm per unit) among the values (no parameter can be named
@@ -3182,10 +3313,29 @@ fn apply_traced_with(
let affected: std::collections::BTreeMap<String, usize> =
target_idx.iter().filter_map(|i| state.bodies.get(*i)).map(|b| (b.name.clone(), b.body.lumps().map_or(1, |l| l.len()))).collect();
let mut remove = Vec::new();
// A cut hands a body its tools cannot reach back as it was, but only after a whole
// boolean: in a design of many bodies that was most of a hole's time (#10225). Such a
// body is left as it is. Its box and the tools' are their meshes' grown past how far
// the exact faces can curve out from them (a dome rises above the outline of its
// edges), so a tool that touches a body, or only comes near it, still meets it in the
// boolean. A body shown in another frame, and a mesh body, are cut as before; an
// intersect removes the bodies it misses, and is left as it was.
let reach: Option<Vec<Option<solidcraft_geom::Aabb3>>> =
(op == Operation::Cut && !tools.iter().any(|t| t.body.is_mesh())).then(|| tools.iter().map(|t| more::body_box(&t.body)).collect());
for &ti in &target_idx {
let Some(mut acc) = state.bodies.get(ti).map(|b| Some(b.traced())) else { continue };
// The tools meet the target where it is shown: in its frame (#3383).
let into = state.bodies.get(ti).and_then(|b| tool_frame(&b.name));
if let (Some(reach), None, Some(mb)) = (&reach, into, state.bodies.get(ti))
&& !reach.is_empty()
&& !mb.body.is_mesh()
&& let Some(target) = more::mesh_box(mb)
&& reach.iter().all(|tool| more::boxes_apart(&Some(target), tool))
{
continue;
}
#[cfg(test)]
cost_tests::count(|c| c.cut += 1);
for t in &tools {
let t = &tool_in(t, into)?;
acc = match acc {
@@ -3238,6 +3388,11 @@ fn apply_traced_with(
// Not yet: a cut that splits one piece of such a body still splits all of them; keeping
// the untouched ones together needs each new piece traced to the piece it came from.
let mut taken: std::collections::BTreeSet<String> = state.bodies.iter().map(|b| b.name.clone()).collect();
// The pieces past the first belong to this feature, like a split's pieces (#10123):
// `body.rename` stores their names in this feature's `body_names`, in this order, so a
// renamed piece keeps its own name through a rebuild instead of landing on a sibling
// of the body it was cut from.
let mut extra = 0usize;
let mut split: Vec<ModelBody> = Vec::new();
for mb in std::mem::take(&mut state.bodies) {
let Some(&had) = affected.get(&mb.name).filter(|_| !state.sheets.iter().any(|s| s.body == mb.name)) else {
@@ -3253,13 +3408,18 @@ fn apply_traced_with(
// (so pieces of one face in different bodies are told apart).
let lineages = crate::lineage::Lineage::lumps(&mb.body, mb.lineage().as_deref(), &pieces);
for (k, (p, lineage)) in pieces.into_iter().zip(lineages).enumerate() {
let name = if k == 0 {
mb.name.clone()
let (name, made_by) = if k == 0 {
(mb.name.clone(), mb.feature)
} else {
(k..=k + taken.len()).map(|j| format!("{} ({j})", mb.name)).find(|n| !taken.contains(n)).unwrap_or_default()
let stored = f.body_names.get(extra).filter(|n| !n.trim().is_empty() && !taken.contains(*n)).cloned();
extra += 1;
let name = stored.unwrap_or_else(|| {
(k..=k + taken.len()).map(|j| format!("{} ({j})", mb.name)).find(|n| !taken.contains(n)).unwrap_or_default()
});
(name, f.id)
};
taken.insert(name.clone());
split.push(ModelBody::new(name, p, mb.feature).with_lineage(lineage));
split.push(ModelBody::new(name, p, made_by).with_lineage(lineage));
}
}
state.bodies = split;
@@ -3776,8 +3936,9 @@ fn body_at(st: &ModelState, body: &Option<crate::BodyName>, points: &[Vec3]) ->
/// A hole stays on its face when the model changes: a position now inside a body moves back
/// against the drilling direction to the surface, one now above it moves down onto it. Only the
/// bodies in `scope` count (every body when it is empty, see [`hole_scope`]).
fn hole_on_surface(st: &ModelState, p: Vec3, dir: Vec3, scope: &[String]) -> Vec3 {
/// bodies in `scope` count (every body when it is empty, see [`hole_scope`]). Edit Hole seeds from
/// this point so the dialog's face pick matches where evaluation drills (#10689).
pub fn hole_on_surface(st: &ModelState, p: Vec3, dir: Vec3, scope: &[String]) -> Vec3 {
let size = st.bodies.iter().map(|b| b.body.size()).fold(1.0, f64::max);
let eps = size * 1e-6;
let mut best: Option<Vec3> = None;
@@ -3825,6 +3986,18 @@ fn faces_through(st: &ModelState, p: Vec3, reach: f64) -> Vec<(f64, Vec3, Option
((v - axis * v.dot(axis)).normalized(), None)
}
Some(kernel::FaceSurface::Sphere { center, .. }) => ((p - center).normalized(), None),
// Square to the line through the apex, not the nearest facet's normal (#10698).
Some(kernel::FaceSurface::Cone { apex, axis, half_angle }) => {
let v = p - apex;
let out = (v - axis * v.dot(axis)).normalized();
(out.and_then(|u| (u * half_angle.cos() - axis * half_angle.sin()).normalized()), None)
}
// Away from the nearest point of the ring the tube goes round.
Some(kernel::FaceSurface::Torus { center, axis, major, .. }) => {
let v = p - center;
let ring = (v - axis * v.dot(axis)).normalized().map(|u| center + u * major);
(ring.and_then(|q| (p - q).normalized()), None)
}
_ => (None, None),
};
let n = match exact {
@@ -4194,7 +4367,8 @@ fn blend_edges_on(mb: &ModelBody, f: &Feature, edges: &[crate::Position], idx: &
Some((_, Err(_))) | None => {
let mut w = None;
let refs: Vec<EdgeRef> = f.edge_refs.get(i).cloned().into_iter().collect();
let skipped = format!("edge `{}` no longer exists and was skipped", stored.map(String::as_str).unwrap_or("?"));
let skipped =
format!("edge `{}` no longer exists and was skipped", stored.map_or_else(|| "?".into(), |n| crate::name_lookup::quoted(n)));
// The name is gone. A pick on the axis of a round must not fall through to the
// tangency seam (#775). A sharp edge that moved still resolves from the point,
// which an edit relies on.
@@ -4203,7 +4377,7 @@ fn blend_edges_on(mb: &ModelBody, f: &Feature, edges: &[crate::Position], idx: &
} else if let Ok(q) = resolve_edges(&mb.body, &[p], &refs, &mut w) {
out.extend(q);
notes.push(match stored {
Some(n) => format!("edge `{n}` no longer exists; using the edge at the picked point"),
Some(n) => format!("edge `{}` no longer exists; using the edge at the picked point", crate::name_lookup::quoted(n)),
None => "an edge reference without a name was found by its point".into(),
});
} else {
@@ -4897,6 +5071,29 @@ pub fn revolve_extent_with_convention(
symmetric: bool,
to: Option<Vec3>,
convention: crate::RevolveConvention,
) -> Result<RevolveExtent> {
let extent = revolve_extent_for_handle(st, plane, regions, (origin, axis), (angle, angle2), symmetric, to, convention)?;
// A solid needs an open sweep inside one full turn. Zero and a turn past 360° keep a
// handle frame (`revolve_extent_for_handle`) and still fail here, with the same message.
if !(extent.sweep.abs() > 1e-9 && extent.sweep.abs() <= std::f64::consts::TAU + 1e-9) {
return Err(DocError::Invalid("the revolve angle must be more than 0 and at most 360°".into()));
}
Ok(extent)
}
/// Where the angle handle sits. A sweep of zero, or past a full turn, builds no solid and
/// [`revolve_extent_with_convention`] refuses it. The boundary is still a point on the circle,
/// and the grab point stays there so the angle can be dragged back into range.
#[allow(clippy::too_many_arguments)]
pub fn revolve_extent_for_handle(
st: &ModelState,
plane: &Plane,
regions: &[Region2],
(origin, axis): (Vec3, Vec3),
(angle, angle2): (f64, Option<f64>),
symmetric: bool,
to: Option<Vec3>,
convention: crate::RevolveConvention,
) -> Result<RevolveExtent> {
let (start, sweep) = match (to, angle2, symmetric) {
(Some(point), _, _) => {
@@ -4936,7 +5133,7 @@ pub fn revolve_extent_with_convention(
(None, None, true) => (-angle / 2.0, angle),
(None, None, false) => (0.0, angle),
};
if !(sweep.is_finite() && start.is_finite() && sweep.abs() > 1e-9 && sweep.abs() <= std::f64::consts::TAU + 1e-9) {
if !(sweep.is_finite() && start.is_finite()) {
return Err(DocError::Invalid("the revolve angle must be more than 0 and at most 360°".into()));
}
let points: Vec<Vec2> = regions.iter().flat_map(|r| r.outer.polyline(1e-2)).collect();
@@ -5137,11 +5334,15 @@ const APART_GAP: f64 = 1e-3;
/// The tool bodies a feature adds or removes (extrude, revolve, primitives), in the current state.
/// `to_body` is the body a To Object extent runs to (an extrude), when it found one.
/// `copy` builds the tool of a pattern's or mirror's source: the state already holds the source
/// feature's own material, so a join end that reads as buried in the target there may be bare
/// material where the copy lands — its ends are left as drawn (#6452).
fn feature_tools_with_constructor(
doc: &Document,
vals: &BTreeMap<String, Value>,
f: &Feature,
st: &ModelState,
copy: bool,
constructor: &mut Option<ExtrudeConstructor>,
to_body: &mut Option<String>,
) -> Result<Vec<Traced>> {
@@ -5328,7 +5529,26 @@ fn feature_tools_with_constructor(
let (mut lo, mut hi) = if a <= b { (a, b) } else { (b, a) };
// To a curved (or slanted) face: run right through the target body, then keep what
// lies before its surface (the tool less the body, the piece at the start).
let curved = curved_face(&tst, to_face, Some(n));
let mut curved = curved_face(&tst, to_face, Some(n));
// A Cut to the far side of a curved wall (a window through a duct's side): ending the
// tool on that face leaves it lying in the wall's face, where the boolean fails, and
// the tool less the body keeps only the air before the wall, which cuts nothing. Where
// the target's material ends at the face under the whole profile, the tool ends flat
// in the gap beyond it instead: it takes what lies before the face and meets no face.
if *operation == Operation::Cut
&& off == 0.0
&& let (Some(bi), Some((_, fi))) = (curved, to_face)
&& let Some(tb) = tst.bodies.get(bi)
&& let Some(end) = cut_end_past_face(tb, fi, &ss.plane, &regions, if hi > 0.0 { 1.0 } else { -1.0 })
&& (if hi > 0.0 { end > lo } else { end < hi })
{
if hi > 0.0 {
hi = end;
} else {
lo = end;
}
curved = None;
}
if let Some(bi) = curved
&& let Some(tb) = tst.bodies.get(bi)
{
@@ -5383,7 +5603,7 @@ fn feature_tools_with_constructor(
}
}
let surroundings = if exact_planar_cut || touching_batch { None } else { Surroundings::of(st, *operation, &plain_targets) };
let join_surroundings = if *operation == Operation::Join && !touching_batch {
let join_surroundings = if *operation == Operation::Join && !touching_batch && !copy {
// Trimming may discard a tool's material, so only bodies this feature
// actually joins can establish that the discarded end is already filled.
let participants = if plain_targets.is_empty() { &f.participants } else { &plain_targets };
@@ -6381,11 +6601,19 @@ fn pattern_transforms(doc: &Document, vals: &BTreeMap<String, Value>, st: &Model
}
}
crate::PatternKind::Circular { origin, axis, count: c, angle, alignment, reversed, axis_ref, axis_feature } => {
// On an edge or round face: about it as it is now; on a construction axis: where it is.
// On an edge or round face: about it as it is now; on a construction axis: where it is;
// else through its origin, whose expressions follow the parameters (#10852).
let (origin, axis) = match (axis_ref, axis_feature) {
(Some(g), _) => crate::construct::model_axis(&construction_state(doc, st, component), g)?,
(None, Some(name)) => construction_axis_in(Some(doc), st, name, component)?,
(None, None) => (origin.0, axis.0),
(None, None) => {
let [x, y, z] = origin;
let o = Vec3::new(val(vals, x, Kind::Length)?, val(vals, y, Kind::Length)?, val(vals, z, Kind::Length)?);
if ![o.x, o.y, o.z].iter().all(|c| c.is_finite() && c.abs() < 1e9) {
return Err(DocError::Invalid("the pattern's axis origin is out of range".into()));
}
(o, axis.0)
}
};
let n = count(c)?;
let total = val(vals, angle, Kind::Angle)?;
@@ -6460,6 +6688,35 @@ fn path_at(segs: &[kernel::PathSeg], d: f64) -> (Vec3, Vec3) {
}
/// Whether a copy has no material to cut in any of its eligible targets.
/// The bodies a hole cuts: those its feature names, else its component's and its own (as
/// `apply_traced` picks a cut's targets).
fn hole_targets<'a>(st: &'a ModelState, f: &Feature) -> Vec<&'a ModelBody> {
let all = f.participants.len() == 1 && f.participants.first().is_some_and(|t| t == "*");
st.bodies
.iter()
.filter(|b| all || if f.participants.is_empty() { b.component == f.component || b.feature == f.id } else { f.participants.contains(&b.name) })
.collect()
}
/// Whether hole tool `tool` is clear of every body the hole cuts, by their boxes (#10701). Boxes
/// only: a probe boolean for every hole of a large hole pattern would double its work, and the
/// bodies' meshes are there already (the hole found its face on them). A body of another
/// component is in another frame: the hole is then not known to miss.
fn hole_misses(st: &ModelState, f: &Feature, tool: &Body) -> bool {
let targets = hole_targets(st, f);
let bounds = tool.outline_bounds();
if targets.is_empty()
|| targets.iter().any(|b| b.component != f.component)
|| bounds.is_empty()
|| !bounds.min.is_finite()
|| !bounds.max.is_finite()
{
return false;
}
let tool_box = Some(bounds.inflated(1e-6));
targets.iter().all(|b| more::boxes_apart(&tool_box, &more::mesh_box(b)))
}
fn copy_misses(st: &ModelState, f: &Feature, targets: &[String], tools: &[Traced]) -> bool {
let targets = if targets.is_empty() { &f.participants } else { targets };
let all = targets.len() == 1 && targets.first().is_some_and(|t| t == "*");
@@ -6536,7 +6793,17 @@ fn with_named_face_points(doc: &Document, state: &ModelState, f: &Feature) -> Op
if n.is_empty() {
continue;
}
match crate::name_lookup::locate_face_on_body(&fst, f.kind.face_body(), n, *p) {
let hit = crate::name_lookup::locate_face_on_body(&fst, f.kind.face_body(), n, *p);
// A hole on one of two bodies with the same face name (two bodies of one extrude, the
// pieces of a split body) is on that body's face, whole: the name is not split there
// unless that body has pieces of it too (#10699).
let hit = match hit {
Some(h) if hole_axis.is_some() && h.split && h.on_point => {
fst.bodies.get(h.body).and_then(|b| crate::name_lookup::locate_face_on_body(&fst, Some(&b.name), n, *p)).or(Some(h))
}
other => other,
};
match hit {
Some(hit) => {
if let Some(note) = hit.note(n) {
notes.push(note);
@@ -6750,7 +7017,7 @@ fn replay(
// points follow their named faces, as the feature's own did (#2121): a To Object
// extrude's copies reach the face where it is now, not where it was picked.
let moved = with_named_face_points(doc, st, src).map(|(g, _)| g);
let tools = feature_tools_with_constructor(doc, vals, moved.as_ref().unwrap_or(src), st, &mut None, &mut None)?;
let tools = feature_tools_with_constructor(doc, vals, moved.as_ref().unwrap_or(src), st, true, &mut None, &mut None)?;
for (k, m) in mats.iter().enumerate() {
let start = copies.len();
for t in &tools {
@@ -6957,6 +7224,73 @@ fn trim_to_body(tools: Vec<Traced>, target: &ModelBody, dist_to_start: impl Fn(V
Ok(out)
}
/// For a Cut To the curved face `fi` of `target`: the height above `plane` (along its normal) at
/// which a flat-ended tool over `regions`, travelling along `travel` × the normal (±1), stops in
/// the gap beyond that face. Rays over the profiles must each cross the face once, with material
/// just before it and none just after; the end is halfway across the narrowest gap to the next
/// material (at most the usual run past a curved target). `None` when that does not hold.
fn cut_end_past_face(target: &ModelBody, fi: usize, plane: &Plane, regions: &[Region2], travel: f64) -> Option<f64> {
let m = target.mesh();
let n = plane.normal();
let dir = n * travel;
let size = target.body.size();
let eps = (size * 1e-4).max(1e-6);
let margin = size * 0.05 + 1.0;
// Sample points: the outline of each profile and a grid over its inside.
let mut samples: Vec<Vec2> = Vec::new();
for r in regions {
let ring: Vec<Vec2> = r.outer.polyline(1e-2).into_iter().collect();
let step = (ring.len() / 64).max(1);
samples.extend(ring.iter().step_by(step).copied());
let (mut lo, mut hi) = (Vec2::new(f64::MAX, f64::MAX), Vec2::new(f64::MIN, f64::MIN));
for q in &ring {
lo = Vec2::new(lo.x.min(q.x), lo.y.min(q.y));
hi = Vec2::new(hi.x.max(q.x), hi.y.max(q.y));
}
for i in 0..12 {
for j in 0..12 {
let q = Vec2::new(lo.x + (hi.x - lo.x) * (i as f64 + 0.5) / 12.0, lo.y + (hi.y - lo.y) * (j as f64 + 0.5) / 12.0);
if r.contains(q) {
samples.push(q);
}
}
}
}
let height = |q: Vec3| (q - plane.origin).dot(n);
let reach = size * 4.0 + 1.0;
let (mut face_end, mut gap_end) = (f64::MIN, f64::MAX);
for q2 in samples {
let q = plane.to_world(q2);
// Every crossing of the line through `q`, in travel order, from well before the body.
let back = travel * height(q) + reach + size;
let mut o = q - dir * back;
let mut hits: Vec<(f64, usize)> = Vec::new();
while let Some((t, tri)) = m.raycast(o, dir) {
let at = o + dir * t;
hits.push((travel * height(at), *m.tri_face.get(tri)? as usize));
o = at + dir * eps;
if hits.len() > 256 {
return None;
}
}
let on_face: Vec<usize> = (0..hits.len()).filter(|&k| hits[k].1 == fi).collect();
let [k] = on_face[..] else { return None };
let e = hits[k].0;
let at = |t: f64| plane.origin + n * (travel * t) + (q - plane.origin - n * height(q));
if !m.contains(at(e - eps * 10.0)) || m.contains(at(e + eps * 10.0)) {
return None;
}
face_end = face_end.max(e);
let next = hits.iter().map(|h| h.0).filter(|&t| t > e + eps * 10.0).fold(f64::MAX, f64::min);
gap_end = gap_end.min(next);
}
if face_end == f64::MIN || gap_end - face_end < eps * 100.0 {
return None;
}
let end = face_end + ((gap_end - face_end) * 0.5).min(margin);
Some(travel * end)
}
/// One point on each face of a body (a triangle centre, so it lies on the face).
fn face_samples(b: &ModelBody) -> Vec<Vec3> {
let m = b.mesh();
@@ -7099,10 +7433,10 @@ fn face_plane(st: &ModelState, picked: &Plane, at: Vec3, name: Option<&str>) ->
let note = if crate::naming::legacy_name(name).is_some() {
None
} else if !crate::naming::face_has_name(b, f.index, name) {
Some(format!("face `{name}` was split; using the piece nearest the picked point"))
Some(format!("face `{}` was split; using the piece nearest the picked point", crate::name_lookup::quoted(name)))
} else if !crate::naming::face_name_exact(b, f.index) && (f.centroid - at).dot(n).abs() > tol {
// Named from geometry and moved off the picked plane: geometry alone decided.
Some(format!("face `{name}` was found by geometry, not exact history; check it"))
Some(format!("face `{}` was found by geometry, not exact history; check it", crate::name_lookup::quoted(name)))
} else {
None
};
@@ -7282,7 +7616,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
| FeatureKind::Sweep { operation, targets, .. } => {
let mut constructor = None;
let mut to_body = None;
let tools = feature_tools_with_constructor(doc, vals, f, st, &mut constructor, &mut to_body)?;
let tools = feature_tools_with_constructor(doc, vals, f, st, false, &mut constructor, &mut to_body)?;
// Cuts through sheet metal become cut-outs of its flat pattern.
if matches!(operation, Operation::Cut | Operation::Intersect) && matches!(f.kind, FeatureKind::Extrude { .. }) && !st.sheets.is_empty() {
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
@@ -7309,7 +7643,23 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
let handled = sheet_hole_cut(st, f, &tools, warning)?;
let tools: Vec<Traced> = tools.into_iter().map(|(tool, _)| tool).collect();
if handled.is_empty() {
// A hole moved off the material (a changed position, parameter or sketch point)
// cuts nothing: it says so, as a pattern does of its copies (#10701).
let missed = tools.iter().filter(|tool| hole_misses(st, f, &tool.body)).count();
let total = tools.len();
let targets: Vec<String> = hole_targets(st, f).iter().map(|b| b.name.clone()).collect();
apply_traced(doc, st, f, tools, Operation::Cut, &[])?;
match (missed, total) {
(0, _) => {}
(_, 1) => append_warning(warning, "the hole removes no material".into()),
_ => append_warning(warning, format!("{missed} of {total} holes remove no material")),
}
// A hole wider than the part takes all of it away (#10702): almost never what a
// hole means, so it says which bodies are gone.
let gone: Vec<&str> = targets.iter().map(String::as_str).filter(|n| st.body(n).is_none()).collect();
if !gone.is_empty() {
append_warning(warning, format!("the hole removes all of {}", gone.join(", ")));
}
} else {
apply_beside_sheet_cut(doc, st, f, tools, Operation::Cut, &[], &handled)?;
}
@@ -7470,23 +7820,38 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
// Picks on bodies of other (moved) components are where those bodies are shown.
None => doc.resolve_plane_in(vals, plane, 0, Some(&construction_state(doc, st, f.component)))?,
};
st.construct.push(crate::construct::Construct { feature: f.id, name: f.name.clone(), geom: crate::construct::ConstructGeom::Plane(pl) });
st.construct.push(crate::construct::Construct {
feature: f.id,
name: f.name.clone(),
geom: crate::construct::ConstructGeom::Plane(pl),
bodies: Vec::new(),
});
Ok(())
}
FeatureKind::ConstructionAxis { def } => {
let (origin, dir) =
crate::construct::resolve_axis(&crate::construct::Ctx { doc, vals, st: &construction_state(doc, st, f.component), depth: 0 }, def)?;
let cst = construction_state(doc, st, f.component);
let ctx = crate::construct::Ctx { doc, vals, st: &cst, depth: 0 };
let (origin, dir) = crate::construct::resolve_axis(&ctx, def)?;
let bodies = crate::construct::source_bodies(&ctx, &def.refs());
st.construct.push(crate::construct::Construct {
feature: f.id,
name: f.name.clone(),
geom: crate::construct::ConstructGeom::Axis { origin, dir },
bodies,
});
Ok(())
}
FeatureKind::ConstructionPoint { def } => {
let p =
crate::construct::resolve_point(&crate::construct::Ctx { doc, vals, st: &construction_state(doc, st, f.component), depth: 0 }, def)?;
st.construct.push(crate::construct::Construct { feature: f.id, name: f.name.clone(), geom: crate::construct::ConstructGeom::Point(p) });
let cst = construction_state(doc, st, f.component);
let ctx = crate::construct::Ctx { doc, vals, st: &cst, depth: 0 };
let p = crate::construct::resolve_point(&ctx, def)?;
let bodies = crate::construct::source_bodies(&ctx, &def.refs());
st.construct.push(crate::construct::Construct {
feature: f.id,
name: f.name.clone(),
geom: crate::construct::ConstructGeom::Point(p),
bodies,
});
Ok(())
}
FeatureKind::BoundaryFill { tools, cells, operation, remove_tools } => {
@@ -7659,6 +8024,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
}
let m = mirror_matrix(&pl);
let mut output = 0;
// Copies evaluated on each frame a placement is dragged, which nothing cuts or joins:
// searching their revolved faces again was nearly all that took.
let shown_only = !*combine && !placements.is_empty() && placement_reuse::copies_only_shown(doc, f);
for (k, n) in bodies.iter().enumerate() {
let i = st.bodies.iter().position(|b| &b.name == n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
let Some(mb) = st.bodies.get(i).cloned() else { continue };
@@ -7675,7 +8043,12 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
}
None => (None, m, m),
};
let mirrored = moved_copy(&mb, kernel::transform_matrix(&mb.body, if *combine { own } else { copy })?, 1);
let copied = if shown_only {
kernel::place_matrix(&mb.body, copy)
} else {
kernel::transform_matrix(&mb.body, if *combine { own } else { copy })
};
let mirrored = moved_copy(&mb, copied?, 1);
if *combine {
let joined = kernel::boolean(&mb.body, &mirrored.body, BoolOp::Union)?
.ok_or_else(|| DocError::Invalid("the mirror join produced nothing".into()))?;
@@ -7697,6 +8070,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
let name = copy_body_name(st, f, output, &mb.name);
copy_sheet(st, &mb.name, &name, &copy);
let mut body = ModelBody::traced_new(name, mirrored, f.id);
if !placements.is_empty() {
body.mirrored_mesh = placed_mesh::PlacedMesh::mirrored(&mb, &body.body, &copy).map(Arc::new);
}
if let Some(c) = component.filter(|c| *c != f.component) {
// Kept in that component as a moved body is (and through later features).
body.component = c;
@@ -7955,6 +8331,9 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
*slot = ModelBody::traced_new(mb.name.clone(), Traced::step(nb, &[&mb.traced()]), mb.feature);
}
}
if !*copy {
move_constructions_with(doc, st, bodies, &crate::rigid(t, Vec3::ZERO, axis, ang));
}
Ok(())
}
FeatureKind::MeshConvert { body, tolerance } => {
@@ -8090,6 +8469,36 @@ fn construction_state<'a>(doc: &Document, st: &'a ModelState, comp: u64) -> std:
state_in_frame(doc, st, comp)
}
/// Construction axes and points built on bodies a Move moves go with them (#10127): an axis
/// through a cylinder turns with the cylinder, as its faces do. Only those built from the moved
/// bodies alone (`Construct::bodies`) follow; one also held by a fixed point, a plane or another
/// body stays. `m` is the move in the bodies' own frame; each construction is kept in its
/// component's frame (`construction_state`), so the move is carried over into that frame.
fn move_constructions_with(doc: &Document, st: &mut ModelState, moved: &[crate::BodyName], m: &Mat) {
let frame_of = |feature: u64| {
let comp = doc.feature(feature).map(|f| f.component).unwrap_or(0);
if comp == 0 && !doc.occurrences.is_empty() { crate::IDENTITY } else { doc.component_transform(comp) }
};
let world: BTreeMap<&str, Mat> =
st.bodies.iter().filter(|b| moved.iter().any(|n| *n == b.name)).map(|b| (b.name.as_str(), body_world(doc, b))).collect();
let mut placed = Vec::new();
for (k, c) in st.construct.iter().enumerate() {
let Some(body) = c.bodies.first().and_then(|b| world.get(b.as_str())) else { continue };
if !c.bodies.iter().all(|b| world.contains_key(b.as_str())) {
continue;
}
let frame = frame_of(c.feature);
let (Some(body_inv), Some(frame_inv)) = (crate::mat_inverse(body), crate::mat_inverse(&frame)) else { continue };
let in_frame = crate::mat_mul(&crate::mat_mul(&frame_inv, body), &crate::mat_mul(m, &crate::mat_mul(&body_inv, &frame)));
placed.push((k, c.geom.placed(&in_frame)));
}
for (k, geom) in placed {
if let Some(c) = st.construct.get_mut(k) {
c.geom = geom;
}
}
}
/// Whether `plane` is given by the origin planes and numbers alone.
fn plane_is_fixed(plane: &PlaneRef) -> bool {
match plane {
@@ -8110,6 +8519,7 @@ fn plane_reads_geometry(plane: &PlaneRef) -> bool {
PlaneRef::ThroughPoint { .. }
| PlaneRef::Midplane { .. }
| PlaneRef::TwoEdges { .. }
| PlaneRef::AxisPoint { .. }
| PlaneRef::ThreePoints { .. }
| PlaneRef::Tangent { .. }
| PlaneRef::AlongPath { .. }
@@ -8374,7 +8784,8 @@ impl PlacedBodies {
if let Some(placed) = self.taken(b, m, &name) {
return placed;
}
match kernel::place_matrix(&b.body, *m) {
let body = if crate::is_identity(m) { Ok(b.body.clone()) } else { kernel::place_matrix(&b.body, *m) };
match body {
Ok(body) => {
let mut fresh = ModelBody::new(name, body, b.feature);
fresh.mesh_placement = placed_mesh::PlacedMesh::new(b, m).map(Arc::new);
@@ -8480,6 +8891,12 @@ fn placed_key(b: &ModelBody, m: &crate::Mat) -> (usize, [u64; 16]) {
(Arc::as_ptr(&b.mesh) as usize, bits)
}
/// Whether a world state shows `b`, placed by `m`, apart from the model's body. A mirrored copy
/// always is: it is drawn from its source's opposite hand, and the model keeps its own triangles.
fn placed_apart(b: &ModelBody, m: &crate::Mat) -> bool {
!crate::is_identity(m) || b.mirrored_mesh.is_some()
}
/// [`world_state`], reusing the bodies `placed` holds from an earlier call and leaving it holding
/// this call's.
pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut PlacedBodies) -> ModelState {
@@ -8487,7 +8904,10 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
return st.clone();
}
let mut placed_now = PlacedBodies::default();
let comp_of_feature = |id: u64| doc.feature(id).map(|f| f.component).unwrap_or(0);
// Looked up once: every sketch, construction and tap asked the timeline, and every placement
// went through all bodies, each time searching (a large assembly paid this every command).
let feature_components: std::collections::HashMap<u64, u64> = doc.features.iter().rev().map(|f| (f.id, f.component)).collect();
let comp_of_feature = |id: u64| feature_components.get(&id).copied().unwrap_or(0);
let first_path = |comp: u64| -> Vec<u64> {
let mut path = Vec::new();
let mut c = comp;
@@ -8510,11 +8930,28 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
let mut jobs = Vec::new();
let owners = doc.model_body_components(st);
let names = crate::WorldNames::new(doc, st);
let bodies_of = |comp: u64| st.bodies.iter().zip(&owners).filter(move |(_, owner)| **owner == comp).map(|(b, _)| b);
let mut by_component: std::collections::HashMap<u64, Vec<&ModelBody>> = std::collections::HashMap::new();
for (b, owner) in st.bodies.iter().zip(&owners) {
by_component.entry(*owner).or_default().push(b);
}
let bodies_of = |comp: u64| by_component.get(&comp).into_iter().flatten().copied();
// Each tap's component and the offset of the body it is on (the first body of that name).
let mut first_body: std::collections::HashMap<&str, (&ModelBody, u64)> = std::collections::HashMap::new();
for (b, owner) in st.bodies.iter().zip(&owners).rev() {
first_body.insert(b.name.as_str(), (b, *owner));
}
let tap_frames: Vec<(u64, Option<&crate::Mat>)> = st
.threads
.iter()
.map(|t| match first_body.get(t.body.as_str()) {
Some((body, owner)) => (*owner, doc.model_body_offset(body)),
None => (comp_of_feature(t.feature), None),
})
.collect();
for (comp, path, m) in &placements {
for b in bodies_of(*comp) {
let bm = body_matrix(b, m);
if !crate::is_identity(&bm) {
if placed_apart(b, &bm) {
jobs.push((b, bm, names.name(&b.name, *comp, path)));
}
}
@@ -8532,7 +8969,7 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
let bm = body_matrix(b, &m);
// Identical geometry also shares its display mesh cell with the definition.
// A changed placement keeps a distinct mesh, at that placement's coordinates.
let mut placed = if crate::is_identity(&bm) {
let mut placed = if !placed_apart(b, &bm) {
let mut placed = b.clone();
placed.name = names.name(&b.name, comp, &path);
placed
@@ -8590,14 +9027,10 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
}
}
// Unlike editable sketch guides, cosmetic surfaces belong to every occurrence.
for t in st
.threads
.iter()
.filter(|t| st.body(&t.body).map(|body| doc.model_body_component(body)).unwrap_or_else(|| comp_of_feature(t.feature)) == comp)
{
for (t, (_, offset)) in st.threads.iter().zip(&tap_frames).filter(|(_, (owner, _))| *owner == comp) {
let mut t2 = t.clone();
t2.body = names.name(&t.body, comp, &path);
let frame = st.body(&t.body).and_then(|body| doc.model_body_offset(body)).map(|offset| crate::mat_mul(&m, offset)).unwrap_or(m);
let frame = offset.map(|offset| crate::mat_mul(&m, offset)).unwrap_or(m);
t2.axis_point = crate::apply_point(&frame, t.axis_point);
t2.axis = crate::apply_vector(&frame, *t.axis).unit().unwrap_or(t.axis);
out.threads.push(t2);
@@ -8782,7 +9215,7 @@ mod chain_tests {
#[cfg(test)]
#[path = "eval_cost_tests.rs"]
mod cost_tests;
pub(crate) mod cost_tests;
#[cfg(test)]
#[path = "eval_whole_face_tests.rs"]
+699 -2
View File
@@ -12,20 +12,37 @@ use solidcraft_geom::{Plane, Vec2, Vec3};
use solidcraft_kernel as kernel;
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
/// measured.
/// measured, bodies searched for by name, features searched for by id, bodies a cut (or
/// intersect) ran its booleans on, and bodies searched for the cylinder a tap marking lies on.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(super) struct Counts {
pub moved: usize,
pub digested: usize,
pub joint_faces: usize,
pub measures: usize,
pub body_searches: usize,
pub feature_searches: usize,
pub cut: usize,
pub cylinder_search: usize,
}
thread_local! {
static COUNTS: Cell<Counts> = const { Cell::new(Counts { moved: 0, digested: 0, joint_faces: 0, measures: 0 }) };
static COUNTS: Cell<Counts> = const {
Cell::new(Counts {
moved: 0,
digested: 0,
joint_faces: 0,
measures: 0,
body_searches: 0,
feature_searches: 0,
cut: 0,
cylinder_search: 0,
})
};
}
pub(super) fn count(f: impl FnOnce(&mut Counts)) {
@@ -36,6 +53,33 @@ pub(super) fn count(f: impl FnOnce(&mut Counts)) {
});
}
thread_local! {
static WRITTEN: Cell<usize> = const { Cell::new(0) };
}
/// A feature written out on this thread to be fingerprinted.
pub(super) fn count_written() {
WRITTEN.with(|c| c.set(c.get() + 1));
}
/// The features `f` writes out on this thread to fingerprint them.
fn written(f: impl FnOnce()) -> usize {
WRITTEN.with(|c| c.set(0));
f();
WRITTEN.with(Cell::get)
}
/// A search on this thread through all of a design's features for one.
pub(crate) fn count_feature_search() {
count(|c| c.feature_searches += 1);
}
/// The searches `f` makes on this thread through all features or all bodies.
fn searched(f: impl FnOnce()) -> usize {
let n = counted(f);
n.feature_searches + n.body_searches
}
/// The work `f` does on this thread.
fn counted(f: impl FnOnce()) -> Counts {
COUNTS.with(|c| c.set(Counts::default()));
@@ -143,6 +187,25 @@ fn joint_face_measurements_never_follow_a_replaced_body_or_stale_offset() {
assert_eq!(work.joint_faces, 0, "offsets are applied after the shared face measurements");
}
/// Every solve of a drag resolves its edge origins again: they share the joint faces' one
/// measurement, as a face origin does, instead of tessellating the whole body each time.
#[test]
fn an_edge_origin_measures_its_faces_once() {
use crate::joints::{JointOrigin, Snap};
let doc = Document::new("edge origin");
let mut state = ModelState::default();
state.bodies.push(ModelBody::new("Block".into(), kernel::box_solid(Vec3::ZERO, Vec3::new(10.0, 10.0, 10.0)).unwrap(), 0));
let pick = Vec3::new(5.0, 0.0, 10.0);
let origin = JointOrigin { occurrence: 0, snap: Snap::Edge { pick, edge: None, face: None, corner: None, axis: None } };
let work = counted(|| {
for _ in 0..8 {
let frame = joints::resolve_origin(&doc, &state.clone(), &origin).unwrap();
assert!(apply_point(&frame, Vec3::ZERO).dist(pick) < 1e-9);
}
});
assert_eq!(work.joint_faces, 1, "a solve must not measure the faces again: {work:?}");
}
/// `parts` imported files of two boxes each, every one in its own component placed away from
/// the origin (as inserted parts are).
fn placed_imports(parts: usize) -> Document {
@@ -358,6 +421,132 @@ fn an_imported_file_is_fingerprinted_by_its_text() {
assert_eq!(model.state().bodies.len(), 3);
}
/// Pinning body names fingerprints the timeline again only from the first renamed feature on
/// (`Model::pinned_prefix`), and gives the fingerprints the full pass gives: random pins
/// on a timeline of boxes, cuts, patterns of features (one placed before its source, as one made
/// with the timeline rolled back is), a suppressed feature and a history marker, after an
/// evaluation and after earlier pins.
#[test]
fn pinning_body_names_fingerprints_as_the_full_pass_does() {
let kind = |v: serde_json::Value| serde_json::from_value::<FeatureKind>(v).unwrap();
let mut doc = Document::new("pins");
let mut ids = Vec::new();
for i in 0..10 {
let x = 20.0 * i as f64;
let b =
kind(serde_json::json!({"type": "box", "corner": [x, 0.0, 0.0], "length": "10", "width": "10", "height": "10", "operation": "new_body"}));
ids.push(doc.add_feature(b, None).unwrap());
if i % 3 == 1 {
let c = kind(
serde_json::json!({"type": "cylinder", "axis": [0.0, 0.0, 1.0], "base": [x + 5.0, 5.0, -1.0], "height": "12", "radius": "2", "operation": "cut"}),
);
ids.push(doc.add_feature(c, None).unwrap());
}
}
let circular = |features: Vec<u64>| {
kind(serde_json::json!({"type": "pattern", "features": features, "bodies": [],
"pattern": {"type": "circular", "angle": "360 deg", "axis": [0.0, 0.0, 1.0], "count": "3", "origin": [0.0, -40.0, 0.0]}}))
};
ids.push(doc.add_feature(circular(vec![ids[2]]), None).unwrap());
// A pattern of a later feature, placed early in the timeline.
let early = doc.add_feature(circular(vec![ids[9]]), None).unwrap();
let at = doc.features.iter().position(|f| f.id == early).unwrap();
let f = doc.features.remove(at);
doc.features.insert(3, f);
ids.push(early);
doc.feature_mut(ids[5]).unwrap().suppressed = true;
let mut seed = 0x2545_f491_4f6c_dd1du64;
let mut next = |n: usize| {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
(seed % n as u64) as usize
};
let fps = |m: &Model| m.results.iter().map(|r| (r.id, r.fingerprint)).collect::<Vec<_>>();
let mut old = Arc::new(doc);
let mut model = Model::new();
model.evaluate_shared(&old);
let (mut proven, mut kept) = (0, 0);
for round in 0..60 {
if round % 20 == 10 {
// From an evaluation's fingerprints again, with the marker in some rounds.
let mut d = (*old).clone();
d.marker = (round == 30).then_some(d.features.len() - 2);
old = Arc::new(d);
model.evaluate_shared(&old);
}
let mut new = (*old).clone();
let mut renamed = Vec::new();
for _ in 0..1 + next(3) {
let f = &mut new.features[next(old.features.len())];
let slot = next(3);
f.body_names.resize(f.body_names.len().max(slot + 1), String::new());
f.body_names[slot] = format!("Pinned {round} {slot}");
renamed.push(f.id);
}
new.reserved_body_names.insert(format!("Body{}", 100 + round));
let new = Arc::new(new);
let start = model.pinned_prefix(&old, &renamed);
let first = new.features.iter().position(|f| renamed.contains(&f.id)).unwrap();
assert!(start <= first, "round {round}: kept {start} past the first renamed feature {first}");
proven += usize::from(start > 0);
kept += start;
let mut partial = model.clone();
partial.refingerprint_shared(&new, start);
let mut full = model.clone();
full.refingerprint_shared(&new, 0);
assert_eq!(fps(&partial), fps(&full), "round {round}");
assert_ne!(fps(&partial), fps(&model), "round {round}: the names are fingerprinted");
// The pinned fingerprints prove themselves for the next pin, as the full pass's do.
assert_eq!(partial.fingerprinted.as_ref().map(|(_, d)| *d), full.fingerprinted.as_ref().map(|(_, d)| *d));
model = partial;
old = new;
}
assert!(proven > 40 && kept > 0, "the stored fingerprints were reused: {proven} rounds, {kept} results");
// No proof, no reuse: results changed since, another design, or the design about to change
// in place.
let last = [*ids.last().unwrap()];
assert!(model.pinned_prefix(&old, &last) > 0);
let mut changed = model.clone();
changed.results[0].fingerprint ^= 1;
assert_eq!(changed.pinned_prefix(&old, &last), 0);
assert_eq!(model.pinned_prefix(&Arc::new((*old).clone()), &last), 0);
let mut forgot = model.clone();
forgot.forget_fingerprints();
assert_eq!(forgot.pinned_prefix(&old, &last), 0);
}
/// A design evaluated ahead, on a worker thread of its own, fingerprints its features from the
/// texts the window's evaluation wrote: none is written out again there (the store was per
/// thread, and every ahead run started it empty), and the fingerprints are the same.
#[test]
fn an_evaluation_on_another_thread_reuses_the_written_features() {
let mut doc = Document::new("ahead");
// Ids no other test's design uses: the store is shared by the tests running beside this one.
doc.next_id = 0x0007_e57a_4ead_0000;
for i in 0..40 {
let x = 20.0 * i as f64;
let b = serde_json::json!({"type": "box", "corner": [x, 0.0, 0.0], "length": "10", "width": "10", "height": "10", "operation": "new_body"});
doc.add_feature(serde_json::from_value(b).unwrap(), None).unwrap();
}
let mut window = Model::new();
let first = written(|| window.evaluate(&doc));
assert!(first >= doc.features.len(), "{first} written");
let fps = |m: &Model| m.results.iter().map(|r| r.fingerprint).collect::<Vec<_>>();
let (doc2, mut ahead) = (doc.clone(), window.clone());
ahead.no_cache = true;
let (work, ahead_fps) = std::thread::spawn(move || {
let work = written(|| ahead.evaluate(&doc2));
(work, fps(&ahead))
})
.join()
.unwrap();
assert_eq!(work, 0, "features written out again");
assert_eq!(ahead_fps, fps(&window));
}
/// A 10 mm square plate per hole along X, 4 mm thick, in component `comp`, with `holes` square
/// holes cut through it by boxes drawn in component `tools` (the plate's own, usually). The
/// plate's body name.
@@ -647,3 +836,511 @@ fn shared_evaluation_refreshes_live_sketches_and_keeps_unknown_features_conserva
assert!(model.results.iter().all(|r| r.error.is_none()));
assert!(model.evaluate_shared(&doc), "an unclassified feature must keep the ordinary evaluation path");
}
/// A saved model writes each set of sketch keys once, as the keys it sets and drops relative to
/// the set written before it (every state repeating its whole set made a 5,484-feature design's
/// model too large for its file, which then kept only some states and evaluated the rest on
/// opening). Every state reads back the keys it was evaluated with, also in a model thinned to
/// fewer states; a set that names no set before it is refused.
#[test]
fn saved_states_read_back_their_sketch_keys() {
let mut doc = Document::new("Sketch keys");
for i in 0..4 {
doc.add_feature(line_sketch(PlaneRef::Origin { name: "XY".into() }, Vec2::new(f64::from(i) + 1.0, 1.0)), None).unwrap();
for j in 0..3 {
let corner = point_expr(Vec3::new(f64::from(i) * 20.0, f64::from(j) * 20.0, 0.0));
let kind = FeatureKind::Box { corner, length: "5".into(), width: "5".into(), height: "5".into(), operation: Operation::NewBody };
doc.add_feature(kind, None).unwrap();
}
}
let mut model = Model::new();
model.evaluate(&doc);
let keys = |m: &Model| m.results.iter().map(|r| r.state.sketch_keys.clone()).collect::<Vec<_>>();
assert_eq!(model.state().sketch_keys.len(), 4);
let saved = model.saved(&doc).unwrap();
assert_eq!(saved.records["sketch_key_sets"].as_array().unwrap().len(), 4, "one set per sketch");
assert_eq!(saved.records["states"].as_array().unwrap().len(), doc.features.len());
assert_eq!(keys(&Model::adopt(&doc, &saved).unwrap()), keys(&model));
// The final state alone (every body costs more than the budget): the sets are all kept.
let pieces: Vec<Vec<(usize, usize)>> = (0..saved.bodies.len()).map(|i| vec![(i, 1 << 20)]).collect();
let (thinned, _, _) = saved.thinned(&pieces, 0).unwrap();
assert_eq!(thinned.records["states"].as_array().unwrap().len(), 1);
assert_eq!(thinned.final_state().unwrap().sketch_keys, model.state().sketch_keys);
assert_eq!(keys(&Model::adopt(&doc, &thinned).unwrap()), keys(&model));
// Sets and drops read as written; a base that is not an earlier set is refused.
let with_sets = |sets: serde_json::Value| SavedModel {
records: {
let mut r = saved.records.clone();
r["sketch_key_sets"] = sets;
r
},
bodies: saved.bodies.clone(),
};
let first = *model.state().sketch_keys.keys().next().unwrap();
let dropped = with_sets(serde_json::json!([
{"set": [[first, "0000000000000001"], [7, "0000000000000002"]], "drop": []},
{"base": 0, "set": [], "drop": []},
{"base": 1, "set": [[8, "0000000000000003"]], "drop": [first]},
{"base": 0, "set": [[7, "00000000000000ff"]], "drop": []},
]));
let want: BTreeMap<u64, u64> = [(7, 0xff), (first, 1)].into();
assert_eq!(dropped.final_state().unwrap().sketch_keys, want);
let dropped = with_sets(serde_json::json!([
{"set": [[first, "0000000000000001"], [7, "0000000000000002"]], "drop": []},
{"base": 0, "set": [], "drop": []},
{"base": 0, "set": [], "drop": []},
{"base": 1, "set": [[8, "0000000000000003"]], "drop": [first]},
]));
assert_eq!(dropped.final_state().unwrap().sketch_keys, [(7, 2), (8, 3)].into());
for base in [3, 4] {
let sets =
serde_json::json!([{"set": [], "drop": []}, {"set": [], "drop": []}, {"set": [], "drop": []}, {"base": base, "set": [], "drop": []}]);
assert!(with_sets(sets).final_state().is_err(), "base {base}");
}
}
/// The dirty check and autosave compare the whole document after every edit; a scanned
/// part's shared triangles must not be read for that. A coordinate that equals nothing (not a
/// number) shows whether the shared arrays were read: only reading them makes a mesh differ
/// from its own copy.
#[test]
fn comparing_a_copied_mesh_does_not_read_its_shared_arrays() {
let mesh = MeshData {
name: "Scan".into(),
positions: Arc::from([0., 0., 0., 1., 0., 0., 0., 1., f64::NAN]),
triangles: Arc::from([0, 1, 2]),
color: None,
};
let copy = mesh.clone();
assert!(copy == mesh, "a copy shares the arrays and is the same mesh without reading them");
let mut renamed = mesh.clone();
renamed.name = "Other".into();
assert!(renamed != mesh);
let mut coloured = mesh.clone();
coloured.color = Some([0.2, 0.4, 0.6]);
assert!(coloured != mesh);
let unshared = MeshData { positions: Arc::from(mesh.positions.to_vec()), ..mesh.clone() };
assert!(unshared != mesh, "arrays that are not shared still compare value by value");
let finite = MeshData { positions: Arc::from([0., 0., 0., 1., 0., 0., 0., 1., 0.]), ..mesh.clone() };
let equal = MeshData { positions: Arc::from(finite.positions.to_vec()), triangles: Arc::from(finite.triangles.to_vec()), ..finite.clone() };
assert!(equal == finite, "equal values in other arrays are the same mesh");
let flipped = MeshData { triangles: Arc::from([0, 2, 1]), ..finite.clone() };
assert!(flipped != finite);
}
/// `blocks` boxes, each its own body, in component `comp`, then a cut box without targets
/// through each of the first `cuts` (it cuts every body of its component).
fn blocks_with_cuts(doc: &mut Document, comp: u64, blocks: usize, cuts: usize) {
let y = 100.0 * comp as f64;
for k in 0..blocks {
let corner = point_expr(Vec3::new(20.0 * k as f64, y, 0.0));
let kind = FeatureKind::Box { corner, length: "10".into(), width: "10".into(), height: "4".into(), operation: Operation::NewBody };
let id = doc.add_feature(kind, None).unwrap();
doc.feature_mut(id).unwrap().component = comp;
}
for k in 0..cuts {
let corner = point_expr(Vec3::new(20.0 * k as f64 + 4.0, y + 4.0, -1.0));
let kind = FeatureKind::Box { corner, length: "2".into(), width: "2".into(), height: "6".into(), operation: Operation::Cut };
let id = doc.add_feature(kind, None).unwrap();
doc.feature_mut(id).unwrap().component = comp;
}
}
#[test]
fn an_evaluation_from_the_cache_does_not_search_the_bodies_for_each_cut() {
// Each cut reads where every body of its component is placed; looking each one up by name
// made an evaluation quadratic in the bodies, even with every feature taken from the cache.
let searches = |blocks: usize| {
let mut doc = Document::new("blocks");
blocks_with_cuts(&mut doc, 0, blocks, 3);
let mut model = Model::new();
model.evaluate(&doc);
assert!(model.results.iter().all(|r| r.error.is_none()), "{:?}", model.results.iter().find(|r| r.error.is_some()));
let work = counted(|| model.evaluate(&doc));
assert_eq!(model.last_recomputed, 0);
work.body_searches
};
let (few, many) = (searches(4), searches(24));
assert_eq!(few, many, "searches grew with the bodies: {few} for 4 blocks, {many} for 24");
}
/// The bodies part of the placement digest as it was before the bodies were looked up once:
/// each body searched by name, placed through [`body_world`].
fn digest_searching_each_body(doc: &Document, f: &Feature, st: &ModelState) -> u64 {
use std::hash::{Hash, Hasher};
let mut reads = PlacementReads::default();
reads.drawn_targets(Some(st), f);
let inverse = mat_inverse(&doc.component_transform(f.component)).unwrap();
let mut h = std::collections::hash_map::DefaultHasher::new();
for name in &reads.bodies {
name.hash(&mut h);
match st.body(name) {
Some(b) => hash_placement(&mat_mul(&inverse, &body_world(doc, b)), &mut h),
None => u8::MAX.hash(&mut h),
}
}
h.finish() | 1
}
#[test]
fn looking_bodies_up_once_keeps_every_placement_digest() {
// Two parts placed apart, a body moved into the other part and one moved within its part:
// each cut's digest is the one searching each body gave, bit for bit, so saved caches stay
// valid.
let mut doc = Document::new("digests");
let a = doc.add_component(Some("A"), 0).unwrap();
let b = doc.add_component(Some("B"), 0).unwrap();
blocks_with_cuts(&mut doc, a, 5, 2);
blocks_with_cuts(&mut doc, b, 4, 2);
blocks_with_cuts(&mut doc, 0, 3, 1);
place(&mut doc, a, rigid(Vec3::new(3.0, -7.0, 1.5), Vec3::ZERO, Vec3::Z, 0.4));
place(&mut doc, b, rigid(Vec3::new(-20.0, 5.0, 0.0), Vec3::ZERO, Vec3::X, 0.25));
let mut model = Model::new();
model.evaluate(&doc);
let names: Vec<String> = model.state().bodies.iter().map(|b| b.name.clone()).collect();
assert_eq!(names.len(), 12);
doc.body_components.insert(names[1].clone(), b);
doc.body_offsets.insert(names[2].clone(), rigid(Vec3::new(0.0, 0.0, 2.0), Vec3::ZERO, Vec3::Y, 0.1));
doc.body_offsets.insert(names[6].clone(), rigid(Vec3::new(1.0, 1.0, 0.0), Vec3::ZERO, Vec3::Z, -0.3));
model.evaluate(&doc);
let st = model.state();
let cuts: Vec<&Feature> = doc.features.iter().filter(|f| matches!(f.kind, FeatureKind::Box { operation: Operation::Cut, .. })).collect();
assert_eq!(cuts.len(), 5);
for f in cuts {
let digest = placement_digest(&doc, f, Some(&st));
assert_ne!(digest, 0);
assert_eq!(digest, digest_searching_each_body(&doc, f, &st), "cut {}", f.id);
}
}
/// `comps` parts, each with `bodies` boxes, `sketches` sketches and `taps` taps, placed `copies`
/// times: a design and its state, made without evaluating it.
fn placed_parts(comps: usize, bodies: usize, sketches: usize, taps: usize, copies: usize) -> (Document, ModelState) {
let mut doc = Document::new("placed parts");
let mut one = Document::new("one sketch");
one.add_feature(line_sketch(PlaneRef::Origin { name: "XY".into() }, Vec2::new(3.0, 4.0)), None).unwrap();
let mut m = Model::new();
m.evaluate(&one);
let solved = m.state().sketches.first().unwrap().clone();
let mut st = ModelState::default();
let solid = kernel::box_solid(Vec3::ZERO, Vec3::new(2.0, 3.0, 4.0)).unwrap();
for c in 0..comps {
let comp = doc.add_component(Some(&format!("C{c}")), 0).unwrap();
let o = doc.occurrences.iter_mut().find(|o| o.component == comp).unwrap();
o.transform = rigid(Vec3::new(10.0 * c as f64, 0.0, 0.0), Vec3::ZERO, Vec3::Z, 0.01 * c as f64);
for k in 1..copies {
doc.add_occurrence(comp, 0, rigid(Vec3::new(10.0 * c as f64, 20.0 * k as f64, 0.0), Vec3::ZERO, Vec3::Z, 0.02 * k as f64)).unwrap();
}
let mut names = Vec::new();
for b in 0..bodies {
let kind =
serde_json::from_value(serde_json::json!({"type": "box", "corner": [0, 0, 0], "length": "2", "width": "3", "height": "4"})).unwrap();
let name = format!("B{c}_{b}");
let id = doc.add_feature(kind, Some(&name)).unwrap();
doc.feature_mut(id).unwrap().component = comp;
st.bodies.push(ModelBody::new(name.clone(), solid.clone(), id));
names.push((name, id));
}
for s in 0..sketches {
let id = doc.add_feature(line_sketch(PlaneRef::Origin { name: "XY".into() }, Vec2::new(1.0 + s as f64, 2.0)), None).unwrap();
doc.feature_mut(id).unwrap().component = comp;
let mut s2 = SolvedSketch::clone(&solved);
s2.feature = id;
st.sketches.push(Arc::new(s2));
}
for t in 0..taps {
let (body, feature) = names[t % names.len()].clone();
st.threads.push(ThreadInfo {
feature,
body,
designation: "M6".into(),
major_diameter: 6.0,
radius: 3.0,
pitch: 1.0,
internal: true,
handedness: Default::default(),
axis_point: Vec3::new(1.0, 1.0, 0.0),
axis: solidcraft_geom::UnitVec3::Z,
start: 0.0,
end: 4.0,
});
}
}
(doc, st)
}
#[test]
fn placing_parts_looks_each_sketch_and_tap_up_once() {
// A large assembly searched all features for every sketch and construction, and all bodies
// twice for every tap and placement, each time its world state was made (every command).
let searches = |sketches: usize, taps: usize| {
let (doc, state) = placed_parts(4, 2, sketches, taps, 3);
let mut world = ModelState::default();
let n = searched(|| world = world_state(&doc, &state));
assert_eq!(world.bodies.len(), 4 * 2 * 3);
assert_eq!(world.sketches.len(), 4 * sketches);
assert_eq!(world.threads.len(), 4 * taps * 3);
n
};
assert_eq!(searches(8, 6), searches(1, 1));
}
#[test]
fn a_placed_tap_follows_its_body_offset() {
let (mut doc, state) = placed_parts(1, 1, 0, 1, 1);
let comp = doc.components[0].id;
doc.body_offsets.insert("B0_0".into(), rigid(Vec3::new(0.0, 0.0, 7.0), Vec3::ZERO, Vec3::Z, 0.0));
let world = world_state(&doc, &state);
let tap = world.threads.first().unwrap();
let expected =
apply_point(&mat_mul(&doc.component_transform(comp), &rigid(Vec3::new(0.0, 0.0, 7.0), Vec3::ZERO, Vec3::Z, 0.0)), Vec3::new(1.0, 1.0, 0.0));
assert!(tap.axis_point.dist(expected) < 1e-9, "{:?} {expected:?}", tap.axis_point);
}
/// What adding a feature of `kind` at the end of `doc` did to the bodies already there: how many
/// its cut ran booleans on, and which it left exactly as they were (the same geometry, its name,
/// key and face names carried over). A cut that cannot reach a body hands it back as it was, and
/// must not pay a whole boolean to find that out (#10225); the sheet metal hole (#10226) and the
/// cosmetic thread (#10227) check their own shortcuts with it too.
pub(super) struct CutWork {
pub cut: usize,
pub kept: Vec<String>,
pub model: Model,
}
pub(super) fn cut_work(doc: &mut Document, kind: FeatureKind) -> CutWork {
let mut model = Model::new();
model.evaluate(doc);
let id = doc.add_feature(kind, None).unwrap();
let work = counted(|| model.evaluate(doc));
let result = model.result(id).unwrap();
assert_eq!(result.error, None, "the feature builds");
let before = model.state_before(id);
let kept = result
.state
.bodies
.iter()
.filter(|b| {
before
.body(&b.name)
.is_some_and(|old| old.body.same(&b.body) && old.key == b.key && crate::naming::face_names(old) == crate::naming::face_names(b))
})
.map(|b| b.name.clone())
.collect();
CutWork { cut: work.cut, kept, model }
}
/// A box body named `name`, `size` long on each side from `corner`.
fn block(doc: &mut Document, name: &str, corner: Vec3, size: [f64; 3]) {
let id = doc
.add_feature(
FeatureKind::Box {
corner: point_expr(corner),
length: size[0].to_string().into(),
width: size[1].to_string().into(),
height: size[2].to_string().into(),
operation: Operation::NewBody,
},
None,
)
.unwrap();
doc.feature_mut(id).unwrap().body_names = vec![name.into()];
}
/// A cylinder that cuts, standing on `base` along Z.
fn cutter(base: Vec3, radius: f64, height: f64) -> FeatureKind {
FeatureKind::Cylinder {
base: point_expr(base),
axis: Dir(Vec3::Z),
radius: radius.to_string().into(),
height: height.to_string().into(),
operation: Operation::Cut,
references: Vec::new(),
}
}
/// A cut leaves the bodies its tool cannot reach as they were, without a boolean on each: in a
/// design of many bodies those booleans were most of a hole's time (#10225).
#[test]
fn a_cut_leaves_bodies_it_cannot_reach_as_they_were() {
let mut doc = Document::new("far");
block(&mut doc, "Near", Vec3::ZERO, [50.0, 40.0, 30.0]);
// 0.5 mm clear of the tool, and far off.
block(&mut doc, "Beside", Vec3::new(13.5, 0.0, 0.0), [20.0, 40.0, 30.0]);
for i in 0..5 {
block(&mut doc, &format!("Far{i}"), Vec3::new(200.0 * (i + 1) as f64, 0.0, 0.0), [50.0, 40.0, 30.0]);
}
// Near and Beside overlap; the hole goes through Near only, beside Beside.
let work = cut_work(&mut doc, cutter(Vec3::new(10.0, 20.0, -5.0), 3.0, 40.0));
assert_eq!(work.cut, 1, "only the body the tool reaches goes through the boolean");
assert_eq!(work.kept, ["Beside", "Far0", "Far1", "Far2", "Far3", "Far4"]);
assert!(volume(&work.model, "Near") < 60_000.0 - 800.0, "and it is cut");
}
/// A cut still goes through the boolean wherever its tool reaches a body or comes close: on a
/// face, along a line, a hair beside it, into a dome above its edge circle. (Bodies its tool
/// cannot reach are left out of it: `a_cut_leaves_bodies_it_cannot_reach_as_they_were`.)
#[test]
fn a_cut_still_runs_where_its_tool_touches_grazes_or_meets_a_curved_face() {
// A cylinder whose side is tangent to the face x = 50, one a hair clear of it, one 0.1 mm in.
for (x, cuts) in [(53.0, false), (53.0005, false), (52.9, true)] {
let mut doc = Document::new("graze");
block(&mut doc, "A", Vec3::ZERO, [50.0, 40.0, 30.0]);
let work = cut_work(&mut doc, cutter(Vec3::new(x, 20.0, 0.0), 3.0, 30.0));
assert_eq!(work.cut, 1, "the tool at x = {x} goes through the boolean");
let v = volume(&work.model, "A");
assert_eq!(v < 60_000.0 - 1.0, cuts, "the tool at x = {x} cuts material: {v}");
assert!(!cuts || work.kept.is_empty(), "a body cut is a new body");
}
// A box meeting the block only along its top edge.
let mut doc = Document::new("edge");
block(&mut doc, "A", Vec3::ZERO, [50.0, 40.0, 30.0]);
let along = FeatureKind::Box {
corner: point_expr(Vec3::new(50.0, 0.0, 30.0)),
length: "10".into(),
width: "40".into(),
height: "10".into(),
operation: Operation::Cut,
};
assert_eq!(cut_work(&mut doc, along).cut, 1);
// A dome: a sphere's cap rising 10 mm above the circle it stands on. A thin tool drilled into
// its top near the apex, well above that circle, cuts it.
let mut doc = Document::new("dome");
doc.add_feature(FeatureKind::Sphere { center: point_expr(Vec3::new(0.0, 0.0, -10.0)), radius: "20".into(), operation: Operation::NewBody }, None)
.unwrap();
let below = FeatureKind::Box {
corner: point_expr(Vec3::new(-30.0, -30.0, -40.0)),
length: "60".into(),
width: "60".into(),
height: "40".into(),
operation: Operation::Cut,
};
doc.add_feature(below, None).unwrap();
let mut model = Model::new();
model.evaluate(&doc);
let dome = model.state().bodies[0].name.clone();
let whole = volume(&model, &dome);
let work = cut_work(&mut doc, cutter(Vec3::new(2.0, 1.0, 8.0), 1.0, 5.0));
assert_eq!(work.cut, 1);
assert!(volume(&work.model, &dome) < whole - 1.0, "the drill cuts into the dome");
assert!(work.kept.is_empty());
}
/// A cylinder body named `name` standing on `base` along Z.
fn shaft(doc: &mut Document, name: &str, base: Vec3, radius: f64, height: f64) {
let id = doc
.add_feature(
FeatureKind::Cylinder {
base: point_expr(base),
axis: Dir(Vec3::Z),
radius: radius.to_string().into(),
height: height.to_string().into(),
operation: Operation::NewBody,
references: Vec::new(),
},
None,
)
.unwrap();
doc.feature_mut(id).unwrap().body_names = vec![name.into()];
}
/// The work of adding `kind` at the end of `doc`, and the tap markings the design then has.
fn marking_work(doc: &mut Document, kind: FeatureKind) -> (Counts, Vec<ThreadInfo>) {
let mut model = Model::new();
model.evaluate(doc);
let id = doc.add_feature(kind, None).unwrap();
let work = counted(|| model.evaluate(doc));
assert_eq!(model.result(id).unwrap().error, None, "the feature builds");
(work, model.state().threads.clone())
}
/// A cosmetic tap marking at `p`.
fn marking(p: Vec3) -> FeatureKind {
FeatureKind::Thread { face: FacePick(p), designation: Some("M6".into()), length: None, modeled: false, handedness: ThreadHandedness::Right }
}
/// A tap marking looks for its cylinder in the bodies near it only: each body searched was
/// tessellated again, so many markings in a design of many bodies took quadratic time (#10227).
#[test]
fn a_tap_marking_searches_only_the_bodies_near_it() {
let mut doc = Document::new("far");
shaft(&mut doc, "Shaft", Vec3::ZERO, 3.0, 6.0);
for i in 0..5 {
block(&mut doc, &format!("Far{i}"), Vec3::new(200.0 * (i + 1) as f64, 0.0, 0.0), [50.0, 40.0, 30.0]);
}
let (work, threads) = marking_work(&mut doc, marking(Vec3::new(3.0, 0.0, 3.0)));
assert_eq!(work.cylinder_search, 1, "only the shaft is searched");
assert_eq!(threads.len(), 1);
assert_eq!(threads[0].body, "Shaft");
assert_eq!(threads[0].designation, "M6");
assert!((threads[0].radius - 3.0).abs() < 1e-6 && !threads[0].internal);
// A tapped hole in a plate among far bodies: only the plate is searched for its wall.
let mut doc = Document::new("tapped");
block(&mut doc, "Plate", Vec3::ZERO, [60.0, 60.0, 12.0]);
for i in 0..5 {
block(&mut doc, &format!("Far{i}"), Vec3::new(200.0 * (i + 1) as f64, 0.0, 0.0), [50.0, 40.0, 30.0]);
}
let hole = FeatureKind::Hole {
position: FacePick(Vec3::new(30.0, 30.0, 12.0)),
at: None,
direction: Dir(-Vec3::Z),
diameter: "5".into(),
depth: Some("10".into()),
hole: HoleKind::default(),
points: None,
thread: Some("M6".into()),
thread_depth: None,
handedness: ThreadHandedness::Right,
to: None,
references: Vec::new(),
};
let (work, threads) = marking_work(&mut doc, hole);
assert_eq!(work.cylinder_search, 1, "only the plate is searched");
assert_eq!(threads.len(), 1);
assert_eq!(threads[0].body, "Plate");
assert!(threads[0].internal && (threads[0].radius - 2.5).abs() < 1e-6);
}
/// Bodies within reach of a tap marking are still searched: the shaft it lies on, a block 3 mm
/// off (within a twentieth of the block's size, where the cylinder search itself looks), and one
/// 3.6 mm off (just past it, but within the margin the boxes keep); a block 4.5 mm off is not.
/// The marking stays on the shaft.
#[test]
fn a_tap_marking_still_searches_the_bodies_within_its_reach() {
let mut doc = Document::new("near");
shaft(&mut doc, "Shaft", Vec3::ZERO, 3.0, 6.0);
// Each block is 50 x 40 x 30 (a twentieth of its size is 3.54 mm), its face x = 3 + gap.
for (name, gap) in [("Close", 3.0), ("Edge", 3.6), ("Clear", 4.5)] {
block(&mut doc, name, Vec3::new(3.0 + gap, -20.0, 0.0), [50.0, 40.0, 30.0]);
}
let (work, threads) = marking_work(&mut doc, marking(Vec3::new(3.0, 0.0, 3.0)));
assert_eq!(work.cylinder_search, 3, "the shaft, Close and Edge are searched");
assert_eq!(threads.len(), 1);
assert_eq!(threads[0].body, "Shaft");
// A pin in a hole a hair wider: a marking on the pin, and one on the hole's wall.
for (p, body, internal) in [(Vec3::new(2.0, 0.0, 5.0), "Pin", false), (Vec3::new(2.5, 0.0, 5.0), "Plate", true)] {
let mut doc = Document::new("pin");
block(&mut doc, "Plate", Vec3::new(-20.0, -20.0, 0.0), [40.0, 40.0, 10.0]);
let bore = FeatureKind::Cylinder {
base: point_expr(Vec3::new(0.0, 0.0, -1.0)),
axis: Dir(Vec3::Z),
radius: "2.5".into(),
height: "12".into(),
operation: Operation::Cut,
references: Vec::new(),
};
doc.add_feature(bore, None).unwrap();
shaft(&mut doc, "Pin", Vec3::ZERO, 2.0, 10.0);
let mut kind = marking(p);
if let FeatureKind::Thread { designation, .. } = &mut kind {
*designation = None;
}
let (work, threads) = marking_work(&mut doc, kind);
assert_eq!(work.cylinder_search, 2, "both bodies are searched for {body}");
assert_eq!(threads.len(), 1);
assert_eq!(threads[0].body, body);
assert_eq!(threads[0].internal, internal);
}
}
+241 -169
View File
@@ -97,7 +97,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
}
Ok(())
}
FeatureKind::Align { bodies, from, to, from_normal, to_normal, flip, from_plane, to_plane } => {
FeatureKind::Align { bodies, from, to, from_normal, to_normal, flip, from_plane, to_plane, axes } => {
// A plane on either side is worked out now, so the bodies follow it when it moves.
let (mut from, mut from_normal) = (from.0, from_normal.and_then(|n| n.normalized()));
if let Some(p) = from_plane {
@@ -126,6 +126,28 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
(Some(_), Some(n)) => n * (to - from).dot(n),
_ => to - from,
};
// Point-to-point alignment can keep only the chosen world X/Y/Z directions,
// stored in the bodies' frame. A missing list means all three (the full offset).
let d = match axes {
None => d,
Some(dirs) if dirs.is_empty() => {
return Err(DocError::Invalid("the align axes enable none of X, Y and Z".into()));
}
Some(dirs) => {
let mut kept = Vec3::ZERO;
let mut used = false;
for a in dirs {
if let Some(a) = a.0.normalized() {
kept += a * d.dot(a);
used = true;
}
}
if !used {
return Err(DocError::Invalid("the align axes have no direction".into()));
}
kept
}
};
m[3][0] += d.x;
m[3][1] += d.y;
m[3][2] += d.z;
@@ -662,7 +684,7 @@ pub(super) fn sheet_eval(
tag_flanges(sh, n0, f.id);
rebuild_sheet(st, i)
}
FeatureKind::SheetPerforate { diameter, pitch, margin, pattern, body } => {
FeatureKind::SheetPerforate { diameter, pitch, margin, pattern, body, sketch, profiles } => {
// Without a body name the field goes on the newest sheet, as the command's own body lookup does.
let i = match body {
Some(_) => sheet_for(st, body, None)?,
@@ -684,8 +706,18 @@ pub(super) fn sheet_eval(
"perforation needs diameter > 0, pitch > diameter and margin >= 0 (diameter {d}, pitch {pitch}, margin {margin})"
)));
}
// The outline sketch's profiles, where they lie on the sheet now.
let outline = match sketch {
Some(sk) => {
let ss = st.sketch(sk.0).ok_or_else(|| DocError::Unknown(format!("sketch {sk}")))?;
let regions = select_profiles(ss, profiles.as_ref().unwrap_or(&ProfileSel::All))?;
let sh = st.sheets.get(i).ok_or_else(|| DocError::Invalid("sheet".into()))?;
Some(sh.perforation_outline(&ss.plane, &regions)?)
}
None => None,
};
let sh = st.sheets.get_mut(i).ok_or_else(|| DocError::Invalid("sheet".into()))?;
sh.perforations.push(crate::sheet::SheetPerforation { feature: f.id, diameter: d, pitch, margin, square, from_right: false });
sh.perforations.push(crate::sheet::SheetPerforation { feature: f.id, diameter: d, pitch, margin, square, from_right: false, outline });
Ok(())
}
FeatureKind::SheetHem { edges, length, gap, flip, body } => {
@@ -780,7 +812,10 @@ pub(super) fn sheet_eval(
hit.point
}
Some((n, Err(_))) => {
append_warning(warning, format!("edge `{n}` no longer exists; using the edge at the picked point"));
append_warning(
warning,
format!("edge `{}` no longer exists; using the edge at the picked point", crate::name_lookup::quoted(n)),
);
p.0
}
None => p.0,
@@ -821,12 +856,12 @@ pub(super) fn sheet_eval(
// (its outer level, `hi`) with z into the material, as with bend edges, so the sheet
// takes the plate's place: its thickness runs from `hi` back to `lo`.
let plane = Plane::from_normal(face.0 + n * hi, n * -1.0).ok_or_else(|| DocError::Invalid("face".into()))?;
let slice = FaceSlice { mesh: &mesh, plane, at: face.0, n, tol };
// Recesses in one face (an engraved label) become pockets; the outline is the other face's.
let recesses = plate_recesses(&mb.body, &mesh, &plane, face.0, n, (lo, hi), tol)?;
let recesses = slice.recesses(&mb.body, (lo, hi))?;
let mut walls = std::collections::HashMap::new();
let mut wall = |f: u32| *walls.entry(f).or_insert_with(|| sheet::round_wall(&mb.body, &plane, n, f));
let mut loops: Vec<Loop2> =
face_outline(&mesh, &plane, face.0, n, recesses.level, tol).iter().map(|l| sheet::outline_loop(l, &mut wall, tol * 1e-2)).collect();
let mut loops: Vec<Loop2> = slice.outline(recesses.level).iter().map(|l| sheet::outline_loop(l, &mut wall, tol * 1e-2)).collect();
loops.sort_by(|a, b| b.signed_area().abs().total_cmp(&a.signed_area().abs()));
let mut it = loops.into_iter();
let outer = it.next().ok_or_else(|| DocError::Invalid("no outline on that face".into()))?.ccw();
@@ -1534,13 +1569,13 @@ fn sheet_prism_cut(
/// A box around `body`, grown past its mesh's deflection from the exact faces (and a hair
/// more): bodies whose boxes are apart do not meet. None when it can't be worked out.
fn body_box(body: &Body) -> Option<Aabb3> {
pub(super) fn body_box(body: &Body) -> Option<Aabb3> {
let tol = (body.size() * 1e-3).max(1e-4);
padded_box(&body.tessellate(tol).ok()?, tol)
}
/// [`body_box`] of a model body, from the mesh it keeps.
fn mesh_box(mb: &ModelBody) -> Option<Aabb3> {
pub(super) fn mesh_box(mb: &ModelBody) -> Option<Aabb3> {
padded_box(&mb.mesh(), mb.display_tol())
}
@@ -1549,8 +1584,25 @@ fn padded_box(mesh: &solidcraft_geom::Mesh, tol: f64) -> Option<Aabb3> {
(!b.is_empty() && b.min.is_finite() && b.max.is_finite()).then(|| b.inflated(2.0 * tol + 1e-6))
}
/// Whether [`kernel::cylinder_face_at`] is known to find no face of `mb` at `p`. It takes a
/// face only when its own mesh of the body comes within a twentieth of the body's size of the
/// point. That mesh lies in the body's box ([`mesh_box`]) give or take its own deflection, which
/// the reach allows for, so a box farther away than that holds no face for it. Working that out from the mesh the body keeps
/// spares a tessellation of every body in the design for each tap marking (#10227). A mesh
/// body, and a body whose box is unknown, are searched as before.
pub(super) fn beyond_cylinder_reach(mb: &ModelBody, p: Vec3) -> bool {
if mb.body.is_mesh() {
return false;
}
let Some(b) = mesh_box(mb) else { return false };
let size = mb.body.size();
let reach = size * 0.05 + 2.0 * (size * 1e-3).max(1e-3);
let off = (b.min - p).max(p - b.max).max(Vec3::ZERO);
off.len() > reach
}
/// Whether two boxes are known to be apart (an unknown box is never apart).
fn boxes_apart(a: &Option<Aabb3>, b: &Option<Aabb3>) -> bool {
pub(super) fn boxes_apart(a: &Option<Aabb3>, b: &Option<Aabb3>) -> bool {
let (Some(a), Some(b)) = (a, b) else { return false };
a.max.x < b.min.x || b.max.x < a.min.x || a.max.y < b.min.y || b.max.y < a.min.y || a.max.z < b.min.z || b.max.z < a.min.z
}
@@ -1584,43 +1636,58 @@ fn parted_note(sh: &sheet::SheetBody, material: &[Region2], before: &sheet::Shee
})
}
/// Outline loops (in `plane` coordinates) of the mesh triangles lying in the plane at distance
/// `level` from `at` along `n`: the edges used by one of them only, chained.
fn face_loops(mesh: &solidcraft_geom::Mesh, plane: &Plane, at: Vec3, n: Vec3, level: f64, tol: f64) -> Vec<Ring<Vec2>> {
face_outline(mesh, plane, at, n, level, tol).into_iter().map(|l| l.into_iter().map(|(p, _)| p).collect()).collect()
/// The face that [`FaceSlice::loops`], [`FaceSlice::outline`] and [`FaceSlice::recesses`] measure:
/// the mesh, the plane the outline's coordinates live in, the point `at` and normal `n` that levels
/// are measured from, and the tolerance. Callers build it once and pass it on unchanged.
/// (`n` stays a `Vec3` until #2675 lands `UnitVec3` upstream.)
struct FaceSlice<'a> {
mesh: &'a Mesh,
plane: Plane,
at: Vec3,
n: Vec3,
tol: f64,
}
/// [`face_loops`] with each point's B-rep face across the outline from it to the next point (the
/// wall the outline runs along there): the face off the plane that meets both points.
fn face_outline(mesh: &solidcraft_geom::Mesh, plane: &Plane, at: Vec3, n: Vec3, level: f64, tol: f64) -> Vec<Vec<(Vec2, Option<u32>)>> {
let on_plane = |t: &[u32; 3]| t.iter().all(|k| mesh.positions.get(*k as usize).is_some_and(|p| ((*p - at).dot(n) - level).abs() < tol));
let on: Vec<[u32; 3]> = mesh.triangles.iter().copied().filter(on_plane).collect();
let pos = |k: u32| mesh.positions.get(k as usize).copied().unwrap_or_default();
let q = |p: Vec3| ((p.x * 1e6).round() as i64, (p.y * 1e6).round() as i64, (p.z * 1e6).round() as i64);
// The faces off the plane at each point on it, by position to a millionth of a millimetre
// either way (a neighbouring face's mesh may round a shared point differently).
let mut touch: std::collections::HashMap<(i64, i64, i64), Vec<u32>> = Default::default();
for (t, f) in mesh.triangles.iter().zip(&mesh.tri_face).filter(|(t, _)| !on_plane(t)) {
for p in t.iter().map(|k| pos(*k)).filter(|p| ((*p - at).dot(n) - level).abs() < tol) {
let faces = touch.entry(q(p)).or_default();
if !faces.contains(f) {
faces.push(*f);
impl FaceSlice<'_> {
/// Outline loops (in `plane` coordinates) of the mesh triangles lying in the plane at distance
/// `level` from `at` along `n`: the edges used by one of them only, chained.
fn loops(&self, level: f64) -> Vec<Ring<Vec2>> {
self.outline(level).into_iter().map(|l| l.into_iter().map(|(p, _)| p).collect()).collect()
}
/// [`FaceSlice::loops`] with each point's B-rep face across the outline from it to the next
/// point (the wall the outline runs along there): the face off the plane that meets both points.
fn outline(&self, level: f64) -> Vec<Vec<(Vec2, Option<u32>)>> {
let (mesh, plane, at, n, tol) = (self.mesh, &self.plane, self.at, self.n, self.tol);
let on_plane = |t: &[u32; 3]| t.iter().all(|k| mesh.positions.get(*k as usize).is_some_and(|p| ((*p - at).dot(n) - level).abs() < tol));
let on: Vec<[u32; 3]> = mesh.triangles.iter().copied().filter(on_plane).collect();
let pos = |k: u32| mesh.positions.get(k as usize).copied().unwrap_or_default();
let q = |p: Vec3| ((p.x * 1e6).round() as i64, (p.y * 1e6).round() as i64, (p.z * 1e6).round() as i64);
// The faces off the plane at each point on it, by position to a millionth of a millimetre
// either way (a neighbouring face's mesh may round a shared point differently).
let mut touch: std::collections::HashMap<(i64, i64, i64), Vec<u32>> = Default::default();
for (t, f) in mesh.triangles.iter().zip(&mesh.tri_face).filter(|(t, _)| !on_plane(t)) {
for p in t.iter().map(|k| pos(*k)).filter(|p| ((*p - at).dot(n) - level).abs() < tol) {
let faces = touch.entry(q(p)).or_default();
if !faces.contains(f) {
faces.push(*f);
}
}
}
let near = |p: Vec3| {
let (x, y, z) = q(p);
let cells = (-1..=1).flat_map(|i| (-1..=1).flat_map(move |j| (-1..=1).map(move |k| (x + i, y + j, z + k))));
let mut faces: Vec<u32> = cells.filter_map(|c| touch.get(&c)).flatten().copied().collect();
faces.sort_unstable();
faces.dedup();
faces
};
let across = |a: Vec3, b: Vec3| {
let at_b = near(b);
near(a).into_iter().find(|f| at_b.contains(f))
};
chain_loops(mesh, plane, &on, across)
}
let near = |p: Vec3| {
let (x, y, z) = q(p);
let cells = (-1..=1).flat_map(|i| (-1..=1).flat_map(move |j| (-1..=1).map(move |k| (x + i, y + j, z + k))));
let mut faces: Vec<u32> = cells.filter_map(|c| touch.get(&c)).flatten().copied().collect();
faces.sort_unstable();
faces.dedup();
faces
};
let across = |a: Vec3, b: Vec3| {
let at_b = near(b);
near(a).into_iter().find(|f| at_b.contains(f))
};
chain_loops(mesh, plane, &on, across)
}
/// The loops (in `plane` coordinates) that bound the triangles `on` of the mesh.
@@ -1710,138 +1777,142 @@ struct LevelFace {
tris: Vec<(Vec3, f64)>,
}
/// The recesses of a plate lying between levels `lo` and `hi` along `n` (measured from `at`),
/// whose sheet is built in `plane`. A recess's floor is a face parallel to the plate inside its
/// thickness, lying in an opening of the face it is cut into, and its walls are square to the
/// plate (walls that are not show as material the plate's volume doesn't account for). A body
/// that is no plate once its recesses are filled has none, so the plate check refuses it as
/// before: a bent part, whose legs' faces lie inside its extent. Recesses are taken in one face
/// only, up to half the thickness deep, and no larger than the face they are cut into (a plate
/// raised in places is no plate of the raised thickness).
fn plate_recesses(body: &Body, mesh: &Mesh, plane: &Plane, at: Vec3, n: Vec3, (lo, hi): (f64, f64), tol: f64) -> Result<Recesses> {
let t = hi - lo;
let eps = t * 1e-3;
let level = |p: Vec3| (p - at).dot(n);
let mut faces: BTreeMap<u32, Option<LevelFace>> = BTreeMap::new();
for (tri, f) in mesh.triangles.iter().zip(&mesh.tri_face) {
let Some([a, b, c]) = mesh.tri(tri) else { continue };
let cr = (b - a).cross(c - a);
let len = cr.len();
if !(len > 1e-12) {
continue;
}
let up = cr.dot(n) > 0.0;
let entry = faces.entry(*f).or_insert(Some(LevelFace { lo: f64::MAX, hi: f64::MIN, up, area: 0.0, at: a, tris: Vec::new() }));
match entry {
Some(face) if face.up == up && cr.dot(n).abs() >= len * (1.0 - 1e-6) => {
for p in [a, b, c] {
face.lo = face.lo.min(level(p));
face.hi = face.hi.max(level(p));
}
face.area += len / 2.0;
face.tris.push(((a + b + c) / 3.0, len / 2.0));
impl FaceSlice<'_> {
/// The recesses of a plate lying between levels `lo` and `hi` along `n` (measured from `at`),
/// whose sheet is built in `plane`. A recess's floor is a face parallel to the plate inside its
/// thickness, lying in an opening of the face it is cut into, and its walls are square to the
/// plate (walls that are not show as material the plate's volume doesn't account for). A body
/// that is no plate once its recesses are filled has none, so the plate check refuses it as
/// before: a bent part, whose legs' faces lie inside its extent. Recesses are taken in one face
/// only, up to half the thickness deep, and no larger than the face they are cut into (a plate
/// raised in places is no plate of the raised thickness).
fn recesses(&self, body: &Body, (lo, hi): (f64, f64)) -> Result<Recesses> {
let (mesh, plane, at, n, tol) = (self.mesh, &self.plane, self.at, self.n, self.tol);
let t = hi - lo;
let eps = t * 1e-3;
let level = |p: Vec3| (p - at).dot(n);
let mut faces: BTreeMap<u32, Option<LevelFace>> = BTreeMap::new();
for (tri, f) in mesh.triangles.iter().zip(&mesh.tri_face) {
let Some([a, b, c]) = mesh.tri(tri) else { continue };
let cr = (b - a).cross(c - a);
let len = cr.len();
if !(len > 1e-12) {
continue;
}
let up = cr.dot(n) > 0.0;
let entry = faces.entry(*f).or_insert(Some(LevelFace { lo: f64::MAX, hi: f64::MIN, up, area: 0.0, at: a, tris: Vec::new() }));
match entry {
Some(face) if face.up == up && cr.dot(n).abs() >= len * (1.0 - 1e-6) => {
for p in [a, b, c] {
face.lo = face.lo.min(level(p));
face.hi = face.hi.max(level(p));
}
face.area += len / 2.0;
face.tris.push(((a + b + c) / 3.0, len / 2.0));
}
_ => *entry = None,
}
_ => *entry = None,
}
}
// The floors (face index, level, the face), and the areas of the plate's two faces (at `lo`
// and at `hi`).
let mut floors: Vec<(usize, f64, &LevelFace)> = Vec::new();
let (mut bottom, mut top) = (0.0, 0.0);
for (f, face) in faces.iter().filter_map(|(f, face)| Some((*f as usize, face.as_ref()?))) {
let d = (face.lo + face.hi) / 2.0;
if face.hi - face.lo > eps {
continue;
} else if (d - hi).abs() <= eps && face.up {
top += face.area;
} else if (d - lo).abs() <= eps && !face.up {
bottom += face.area;
} else if d > lo + eps && d < hi - eps {
floors.push((f, d, face));
// The floors (face index, level, the face), and the areas of the plate's two faces (at `lo`
// and at `hi`).
let mut floors: Vec<(usize, f64, &LevelFace)> = Vec::new();
let (mut bottom, mut top) = (0.0, 0.0);
for (f, face) in faces.iter().filter_map(|(f, face)| Some((*f as usize, face.as_ref()?))) {
let d = (face.lo + face.hi) / 2.0;
if face.hi - face.lo > eps {
continue;
} else if (d - hi).abs() <= eps && face.up {
top += face.area;
} else if (d - lo).abs() <= eps && !face.up {
bottom += face.area;
} else if d > lo + eps && d < hi - eps {
floors.push((f, d, face));
}
}
}
let none = || Recesses { level: lo, pockets: Vec::new(), volume: 0.0 };
if floors.is_empty() {
return Ok(none());
}
// A floor is a recess's where (most of) it lies in an opening of the face it is cut into:
// inside that face's outline and in a hole of it, not on an island in the hole. (Its
// triangles are those at its level within `eps`: `tol` may exceed a recess's depth.)
let loops_at = |level: f64| -> Vec<Loop2> { face_loops(mesh, plane, at, n, level, eps).into_iter().map(|l| Loop2::polygon(&l)).collect() };
let (top_loops, bottom_loops) = (loops_at(hi), loops_at(lo));
floors.retain(|(_, _, face)| {
let loops = if face.up { &top_loops } else { &bottom_loops };
let open = |p: Vec2| {
let k = loops.iter().filter(|l| l.contains(p)).count();
k >= 2 && k.is_multiple_of(2)
};
face.tris.iter().filter(|(c, _)| open(plane.to_local(*c))).map(|(_, a)| a).sum::<f64>() > face.area / 2.0
});
let Some(&(_, _, first)) = floors.first() else { return Ok(none()) };
// Filled, the recesses leave a plate: its outline as large at `lo` as at `hi` once their
// openings count, and its volume with theirs the outline times the thickness. Else (a bent
// part) they are none.
let depth_in = |d: f64, up: bool| if up { hi - d } else { d - lo };
let opened = |up: bool| floors.iter().filter(|f| f.2.up == up).map(|f| f.2.area).sum::<f64>();
let outline = bottom + opened(false);
let removed: f64 = floors.iter().map(|f| f.2.area * depth_in(f.1, f.2.up)).sum();
let volume = kernel::measure(body)?.volume;
if (outline - top - opened(true)).abs() > 1e-3 * outline || (outline * t - volume - removed).abs() > 1e-3 * volume.max(1.0) {
return Ok(none());
}
let pt = |p: Vec3| format!("({:.3}, {:.3}, {:.3})", p.x, p.y, p.z);
let up = first.up;
if let Some(other) = floors.iter().find(|f| f.2.up != up) {
return Err(DocError::Invalid(format!(
"not supported yet: converting a plate with recesses in both faces (at {} and {}); recesses in one face become pockets",
pt(first.at),
pt(other.2.at)
)));
}
let depth_of = |d: f64| depth_in(d, up);
if let Some(deep) = floors.iter().find(|f| depth_of(f.1) > t / 2.0 + eps) {
return Err(DocError::Invalid(format!(
"not supported yet: the recess at {} is {:.3} mm deep, more than half the plate's {:.3} mm; shallower recesses (an engraving, a label) become pockets",
pt(deep.2.at),
depth_of(deep.1),
t
)));
}
let face_area = if up { top } else { bottom };
if floors.iter().map(|f| f.2.area).sum::<f64>() > face_area {
return Err(DocError::Invalid(format!(
"not supported yet: converting a plate whose recesses are larger than the face they are in (a plate thinner than {t:.3} mm, raised in places)"
)));
}
// The floors' outlines, from a mesh fine enough to mark them by.
let fine = body.tessellate((t * 1e-2).min(tol).max(1e-4))?;
floors.sort_by(|a, b| depth_of(a.1).total_cmp(&depth_of(b.1)));
// Floors at one depth are one pocket where they meet.
let mut groups: Vec<(f64, Vec<Region2>)> = Vec::new();
for (f, d, ..) in &floors {
let on: Vec<[u32; 3]> = fine.triangles.iter().zip(&fine.tri_face).filter(|(_, g)| **g as usize == *f).map(|(tri, _)| *tri).collect();
let mut loops = boundary_loops(&fine, plane, &on);
loops.sort_by(|a, b| b.signed_area().abs().total_cmp(&a.signed_area().abs()));
let mut it = loops.into_iter();
let Some(outline) = it.next() else { continue };
let region = Region2 { outer: Loop2::polygon(&outline).ccw(), holes: it.map(|l| Loop2::polygon(&l).ccw().reversed()).collect() };
match groups.last_mut() {
Some((depth, regions)) if depth_of(*d) - *depth <= eps => regions.push(region),
_ => groups.push((depth_of(*d), vec![region])),
let none = || Recesses { level: lo, pockets: Vec::new(), volume: 0.0 };
if floors.is_empty() {
return Ok(none());
}
}
// The sheet's z starts from the picked face when its plane faces away from that face.
let from_zero = up == (plane.normal().dot(n) < 0.0);
let mut work = sheet::FlatWork::new();
let (mut pockets, mut volume) = (Vec::new(), 0.0);
for (depth, regions) in groups {
let (z0, z1) = if from_zero { (0.0, depth) } else { (t - depth, t) };
for region in sheet::material_union(regions, &mut work)? {
volume += region.area().abs() * depth;
pockets.push((region, z0, z1));
// A floor is a recess's where (most of) it lies in an opening of the face it is cut into:
// inside that face's outline and in a hole of it, not on an island in the hole. (Its
// triangles are those at its level within `eps`: `tol` may exceed a recess's depth.)
let probe = FaceSlice { mesh, plane: self.plane, at, n, tol: eps };
let loops_at = |level: f64| -> Vec<Loop2> { probe.loops(level).into_iter().map(|l| Loop2::polygon(&l)).collect() };
let (top_loops, bottom_loops) = (loops_at(hi), loops_at(lo));
floors.retain(|(_, _, face)| {
let loops = if face.up { &top_loops } else { &bottom_loops };
let open = |p: Vec2| {
let k = loops.iter().filter(|l| l.contains(p)).count();
k >= 2 && k.is_multiple_of(2)
};
face.tris.iter().filter(|(c, _)| open(plane.to_local(*c))).map(|(_, a)| a).sum::<f64>() > face.area / 2.0
});
let Some(&(_, _, first)) = floors.first() else { return Ok(none()) };
// Filled, the recesses leave a plate: its outline as large at `lo` as at `hi` once their
// openings count, and its volume with theirs the outline times the thickness. Else (a bent
// part) they are none.
let depth_in = |d: f64, up: bool| if up { hi - d } else { d - lo };
let opened = |up: bool| floors.iter().filter(|f| f.2.up == up).map(|f| f.2.area).sum::<f64>();
let outline = bottom + opened(false);
let removed: f64 = floors.iter().map(|f| f.2.area * depth_in(f.1, f.2.up)).sum();
let volume = kernel::measure(body)?.volume;
if (outline - top - opened(true)).abs() > 1e-3 * outline || (outline * t - volume - removed).abs() > 1e-3 * volume.max(1.0) {
return Ok(none());
}
let pt = |p: Vec3| format!("({:.3}, {:.3}, {:.3})", p.x, p.y, p.z);
let up = first.up;
if let Some(other) = floors.iter().find(|f| f.2.up != up) {
return Err(DocError::Invalid(format!(
"not supported yet: converting a plate with recesses in both faces (at {} and {}); recesses in one face become pockets",
pt(first.at),
pt(other.2.at)
)));
}
let depth_of = |d: f64| depth_in(d, up);
if let Some(deep) = floors.iter().find(|f| depth_of(f.1) > t / 2.0 + eps) {
return Err(DocError::Invalid(format!(
"not supported yet: the recess at {} is {:.3} mm deep, more than half the plate's {:.3} mm; shallower recesses (an engraving, a label) become pockets",
pt(deep.2.at),
depth_of(deep.1),
t
)));
}
let face_area = if up { top } else { bottom };
if floors.iter().map(|f| f.2.area).sum::<f64>() > face_area {
return Err(DocError::Invalid(format!(
"not supported yet: converting a plate whose recesses are larger than the face they are in (a plate thinner than {t:.3} mm, raised in places)"
)));
}
// The floors' outlines, from a mesh fine enough to mark them by.
let fine = body.tessellate((t * 1e-2).min(tol).max(1e-4))?;
floors.sort_by(|a, b| depth_of(a.1).total_cmp(&depth_of(b.1)));
// Floors at one depth are one pocket where they meet.
let mut groups: Vec<(f64, Vec<Region2>)> = Vec::new();
for (f, d, ..) in &floors {
let on: Vec<[u32; 3]> = fine.triangles.iter().zip(&fine.tri_face).filter(|(_, g)| **g as usize == *f).map(|(tri, _)| *tri).collect();
let mut loops = boundary_loops(&fine, plane, &on);
loops.sort_by(|a, b| b.signed_area().abs().total_cmp(&a.signed_area().abs()));
let mut it = loops.into_iter();
let Some(outline) = it.next() else { continue };
let region = Region2 { outer: Loop2::polygon(&outline).ccw(), holes: it.map(|l| Loop2::polygon(&l).ccw().reversed()).collect() };
match groups.last_mut() {
Some((depth, regions)) if depth_of(*d) - *depth <= eps => regions.push(region),
_ => groups.push((depth_of(*d), vec![region])),
}
}
// The sheet's z starts from the picked face when its plane faces away from that face.
let from_zero = up == (plane.normal().dot(n) < 0.0);
let mut work = sheet::FlatWork::new();
let (mut pockets, mut volume) = (Vec::new(), 0.0);
for (depth, regions) in groups {
let (z0, z1) = if from_zero { (0.0, depth) } else { (t - depth, t) };
for region in sheet::material_union(regions, &mut work)? {
volume += region.area().abs() * depth;
pockets.push((region, z0, z1));
}
}
Ok(Recesses { level: if up { lo } else { hi }, pockets, volume })
}
Ok(Recesses { level: if up { lo } else { hi }, pockets, volume })
}
/// The body a placed plastic feature joins: by name, else the one under the point.
@@ -2033,7 +2104,8 @@ pub(super) fn plastic_eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &F
let plane = Plane::from_normal(face.0, n).ok_or_else(|| DocError::Invalid("rim plane".into()))?;
let tol = (mb.body.size() * 1e-3).max(1e-3);
let mesh = mb.body.tessellate(tol)?;
let mut loops = face_loops(&mesh, &plane, face.0, n, 0.0, tol);
let slice = FaceSlice { mesh: &mesh, plane, at: face.0, n, tol };
let mut loops = slice.loops(0.0);
loops.sort_by(|a, b| b.signed_area().abs().total_cmp(&a.signed_area().abs()));
let ccw = |l: &Ring<Vec2>| if l.signed_area() < 0.0 { l.iter().rev().copied().collect::<Ring<_>>() } else { l.clone() };
// The inner edge is an opening (the cavity under a rim): not the foot of a boss or a
+110 -22
View File
@@ -304,6 +304,28 @@ pub fn value_in_unit(v: Value, unit: &str) -> f64 {
}
}
/// The number a reply reports for a value: millimetres for a length, degrees for an angle,
/// as-is for a unit-less or compound value. One meaning of `value` in every reply (#10747).
pub fn reported_value(v: Value) -> f64 {
match v.kind() {
Some(Kind::Angle) => v.v.to_degrees(),
_ => v.v,
}
}
/// The number a person would read for `v`: the shortest decimal that reads back after
/// rounding off the last few bits (12 significant digits), so `30 deg` stored as
/// 29.999999999999996 reports 30 and `0.1 + 0.2` reports 0.3. Only what is *reported*
/// is rounded; stored values and evaluation keep full precision (#10747).
pub fn round_reported(v: f64) -> f64 {
if !v.is_finite() {
return v;
}
// `:.11e` keeps 12 significant digits; parsing back gives the f64 nearest that decimal,
// which serializes as the shortest form that reads back to it.
format!("{v:.11e}").parse().unwrap_or(v)
}
fn unit(name: &str) -> Option<Value> {
UNITS.iter().find(|(n, _, _)| *n == name).map(|(_, k, s)| match k {
Kind::Angle => Value::angle(*s),
@@ -350,8 +372,9 @@ fn lex_spans(s: &str) -> Result<Vec<(Tok, usize, usize)>, DocError> {
let at = |i: usize| cs.get(i).map(|x| x.0).unwrap_or(s.len());
let mut i = 0;
let mut out = Vec::new();
// Parentheses open at this point: a comma outside them can only be a decimal comma.
let mut depth = 0usize;
// Parentheses open at this point, innermost last, true for a function call's: a comma directly
// in a call separates its arguments, and anywhere else can only be a decimal comma (#9408).
let mut parens: Vec<bool> = Vec::new();
// A single leading `=` marks the text as a formula (`=d3`, #4681) and is not part of it.
if let Some(k) = cs.iter().position(|(_, c)| !c.is_whitespace())
&& cs.get(k).is_some_and(|(_, c)| *c == '=')
@@ -369,7 +392,7 @@ fn lex_spans(s: &str) -> Result<Vec<(Tok, usize, usize)>, DocError> {
while let Some(&(_, c)) = cs.get(i) {
if c.is_whitespace() {
i += 1;
} else if c.is_ascii_digit() || c == '.' || leading_decimal_comma(&cs, i, depth, out.last()) {
} else if c.is_ascii_digit() || c == '.' || leading_decimal_comma(&cs, i, in_call(&parens), out.last()) {
let st = i;
// A leading decimal comma (`,5` for 0,5, #2966) starts the number as `.5` would.
let leading_comma = c == ',';
@@ -380,11 +403,17 @@ fn lex_spans(s: &str) -> Result<Vec<(Tok, usize, usize)>, DocError> {
i += 1;
}
// A decimal comma (`2,5 mm`, as typed with a numeric keypad in many locales, #1347):
// a comma between digits, in a number with no point, outside any parentheses (inside
// them a comma separates a function's arguments: `max(1,2)`).
// a comma between digits, in a number with no point, not directly in a function call
// (there a comma separates arguments: `max(1,2)`).
let digit = |k: usize| cs.get(k).is_some_and(|(_, c)| c.is_ascii_digit());
let has_point = cs.get(st..i).is_some_and(|ds| ds.iter().any(|(_, c)| *c == '.'));
if depth == 0 && !leading_comma && !has_point && digit(i.wrapping_sub(1)) && cs.get(i).is_some_and(|(_, c)| *c == ',') && digit(i + 1) {
if !in_call(&parens)
&& !leading_comma
&& !has_point
&& digit(i.wrapping_sub(1))
&& cs.get(i).is_some_and(|(_, c)| *c == ',')
&& digit(i + 1)
{
i += 1;
while digit(i) {
i += 1;
@@ -406,7 +435,7 @@ fn lex_spans(s: &str) -> Result<Vec<(Tok, usize, usize)>, DocError> {
// A mixed number, as imperial sizes are written (`2 1/8"` is 2.125 in, #1802): a whole
// number, then a proper fraction of whole numbers, is one value, not `2 × 1/8`.
if t.chars().all(|c| c.is_ascii_digit())
&& let Some((fraction, end)) = mixed_fraction(&cs, i, depth)
&& let Some((fraction, end)) = mixed_fraction(&cs, i, in_call(&parens))
{
out.push((Tok::Num(v + fraction), at(st), at(end)));
i = end;
@@ -444,8 +473,10 @@ fn lex_spans(s: &str) -> Result<Vec<(Tok, usize, usize)>, DocError> {
i += 1;
} else if "+-*/^(),%".contains(c) {
match c {
'(' => depth += 1,
')' => depth = depth.saturating_sub(1),
'(' => parens.push(matches!(out.last(), Some((Tok::Id(_), _, _)))),
')' => {
parens.pop();
}
_ => {}
}
out.push((Tok::Op(c), at(i), at(i + 1)));
@@ -457,27 +488,32 @@ fn lex_spans(s: &str) -> Result<Vec<(Tok, usize, usize)>, DocError> {
Ok(out)
}
/// Whether the innermost open parenthesis is a function call's.
fn in_call(parens: &[bool]) -> bool {
parens.last() == Some(&true)
}
/// Whether the comma at `i` starts a number, as a leading decimal comma (`,5` for 0,5, #2966).
/// It must be outside parentheses (inside them a comma separates arguments, as for any decimal
/// comma), be followed by a digit, and stand where an operand is expected: at the start or right
/// after `+ - * / ^`. A comma after a number, name, `)` or `%` keeps its meaning (`2 ,5` and
/// `1,5,3` stay errors).
fn leading_decimal_comma(cs: &[(usize, char)], i: usize, depth: usize, prev: Option<&(Tok, usize, usize)>) -> bool {
depth == 0
/// It must not be directly in a function call (`in_call`: there a comma separates arguments, as
/// for any decimal comma), be followed by a digit, and stand where an operand is expected: at the
/// start or right after `( + - * / ^`. A comma after a number, name, `)` or `%` keeps its meaning
/// (`2 ,5` and `1,5,3` stay errors).
fn leading_decimal_comma(cs: &[(usize, char)], i: usize, in_call: bool, prev: Option<&(Tok, usize, usize)>) -> bool {
!in_call
&& cs.get(i).is_some_and(|(_, c)| *c == ',')
&& cs.get(i + 1).is_some_and(|(_, c)| c.is_ascii_digit())
&& match prev {
None => true,
Some((Tok::Op(op), _, _)) => "+-*/^".contains(*op),
Some((Tok::Op(op), _, _)) => "(+-*/^".contains(*op),
Some(_) => false,
}
}
/// The proper fraction of a mixed number whose whole part ends at `i`: spaces, digits, `/`,
/// digits, with nothing after them that would continue the number (`2 1/8.5`, `2 1/8e3`, a
/// decimal comma outside parentheses: `2 1/8,5`). Its value and where it ends. `depth`: the
/// parentheses open there (inside them a comma separates arguments: `max(2 1/8,1)`).
fn mixed_fraction(cs: &[(usize, char)], mut i: usize, depth: usize) -> Option<(f64, usize)> {
/// decimal comma: `2 1/8,5`). Its value and where it ends. `in_call`: directly in a function call
/// there (a comma separates arguments: `max(2 1/8,1)`).
fn mixed_fraction(cs: &[(usize, char)], mut i: usize, in_call: bool) -> Option<(f64, usize)> {
let digits = |from: usize| cs.get(from..).map_or(0, |rest| rest.iter().take_while(|(_, c)| c.is_ascii_digit()).count());
let space = i;
while cs.get(i).is_some_and(|(_, c)| *c == ' ' || *c == '\t') {
@@ -491,7 +527,7 @@ fn mixed_fraction(cs: &[(usize, char)], mut i: usize, depth: usize) -> Option<(f
let end = i + numerator + 1 + denominator;
let next = |k: usize| cs.get(end + k).map(|(_, c)| *c);
let continues = matches!(next(0), Some('.'))
|| (depth == 0 && matches!(next(0), Some(',')) && next(1).is_some_and(|c| c.is_ascii_digit()))
|| (!in_call && matches!(next(0), Some(',')) && next(1).is_some_and(|c| c.is_ascii_digit()))
|| (matches!(next(0), Some('e' | 'E')) && next(1).is_some_and(|c| c.is_ascii_digit() || c == '-' || c == '+'));
if denominator == 0 || continues {
return None;
@@ -936,7 +972,11 @@ fn call(f: &str, args: &[Value], d: Defaults) -> Result<Value, DocError> {
let a = one()?;
unit_fn(a.v.abs(), a)
}
"sign" => Value::num(one()?.v.signum()),
"sign" => {
let x = one()?.v;
// `f64::signum` gives 1 for +0 and -1 for -0; the sign of zero is 0.
Value::num(if x == 0.0 { 0.0 } else { x.signum() })
}
"floor" | "ceil" | "round" | "trunc" => {
let a = one()?;
// Round in the value's own default unit (round(2.4 in) = 2 in for an inch parameter).
@@ -1240,7 +1280,13 @@ mod tests {
// Inside parentheses a comma still separates a function's arguments.
assert_eq!(ev("max(1,2)", Kind::Unitless).unwrap(), 2.0);
assert_eq!(ev("max(1 mm, 3, 2)", Kind::Length).unwrap(), 3.0);
assert_eq!(ev("(2,5)", Kind::Unitless).ok(), None, "a comma inside parentheses is not a decimal comma");
// Plain parentheses have no arguments to separate (#9408), also inside a call.
assert_eq!(ev("(2,5)", Kind::Unitless).unwrap(), 2.5);
assert_eq!(ev("-(width-0,5)/2", Kind::Length).unwrap(), -19.75);
assert_eq!(ev("(,5)", Kind::Unitless).unwrap(), 0.5);
assert_eq!(ev("(2 1/8,5)", Kind::Unitless).unwrap(), 2.0 / 8.5);
assert_eq!(ev("max((1,5), 1)", Kind::Unitless).unwrap(), 1.5);
assert_eq!(ev("(max(1,2))", Kind::Unitless).unwrap(), 2.0);
// Not between two digits, or a second comma: still an error.
for bad in ["2,", "2 ,5", "2, 5", "1,5,3", "1.5,3", ",5,3", ",5.3", "width ,5", "(1) ,5"] {
assert!(ev(bad, Kind::Unitless).is_err(), "{bad}");
@@ -1388,6 +1434,16 @@ mod tests {
}
}
/// `sign(0)` is 0, not 1 (Rust's `signum` takes the sign bit, so it calls zero positive).
#[test]
fn sign_of_zero_is_zero() {
for (e, want) in
[("sign(0)", 0.0), ("sign(-0)", 0.0), ("sign(0 mm)", 0.0), ("sign(5)", 1.0), ("sign(-2 mm)", -1.0), ("sign(width - 40)", 0.0)]
{
assert_eq!(ev(e, Kind::Unitless).unwrap(), want, "{e}");
}
}
/// A count from a length (`(L - 20) / 210`, the 210 read as unit-less) names the fix (#5901).
#[test]
fn a_length_where_a_count_is_expected_says_to_divide_by_a_length() {
@@ -1617,4 +1673,36 @@ mod tests {
defs[2].expr = "bad +".into();
assert!(eval_params(&defs, 1.0).1.contains_key("c"));
}
/// #10747: reported numbers are the shortest decimals that read back after rounding off
/// the last few bits (12 significant digits); stored values keep full precision.
#[test]
fn round_reported_reads_as_a_person_would() {
for (noisy, read) in [
(30f64.to_radians().to_degrees(), 30.0),
(0.1 + 0.2, 0.3),
(3.0 * 25.4, 76.2),
(1.2 * 25.4, 30.48),
(29.999999999999996, 30.0),
(0.30000000000000004, 0.3),
(-0.1 - 0.2, -0.3),
] {
assert_eq!(round_reported(noisy), read, "{noisy}");
}
// A number with real digits of its own keeps them up to 12 significant digits.
assert_eq!(round_reported(1.234567890123), 1.23456789012);
assert_eq!(round_reported(1.0000000000001), 1.0);
// Zero and non-finite values pass through.
assert_eq!(round_reported(0.0), 0.0);
assert!(round_reported(f64::INFINITY).is_infinite());
assert!(round_reported(f64::NAN).is_nan());
}
/// #10747: `value` means millimetres and degrees in every reply.
#[test]
fn reported_value_is_mm_and_degrees() {
assert_eq!(reported_value(Value::length(50.8)), 50.8);
assert_eq!(round_reported(reported_value(Value::angle(30f64.to_radians()))), 30.0);
assert_eq!(reported_value(Value::num(4.0)), 4.0);
}
}
+122 -36
View File
@@ -13,12 +13,30 @@ pub struct FaceAnchor {
pub component: u64,
pub path: Vec<u64>,
pub author_path: Vec<u64>,
/// Source component to author component at capture, for an independently placed part.
/// Body offsets and source geometry remain live; later assembly moves do not reshape it.
/// Older attachments without this field retain their live placement relationship.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub placement: Option<crate::Mat>,
/// Origin and x direction in the exact canonical face plane's coordinates.
pub coordinates: [f64; 2],
pub x_axis: [f64; 2],
}
impl FaceAnchor {
/// Re-express a captured context when definitions (not their occurrences) change frames.
pub fn reframe_placement(&mut self, author: &crate::Mat, source: &crate::Mat) -> Result<()> {
if let Some(placement) = self.placement {
let inverse = crate::mat_inverse(source).ok_or_else(|| DocError::Invalid("attached source frame is invalid".into()))?;
let placement = crate::mat_mul(&crate::mat_mul(author, &placement), &inverse);
if !crate::valid_transfer_matrix(&placement) {
return Err(DocError::Invalid("invalid captured face placement".into()));
}
self.placement = Some(placement);
}
Ok(())
}
pub fn remap_target(
&mut self,
features: &std::collections::BTreeMap<u64, u64>,
@@ -47,6 +65,7 @@ impl FaceAnchor {
|| self.path.iter().chain(&self.author_path).any(|id| *id == 0)
|| (self.component == 0 && !self.path.is_empty())
|| (self.component != 0 && self.path.is_empty())
|| self.placement.as_ref().is_some_and(|m| !crate::valid_transfer_matrix(m))
|| self.coordinates.iter().any(|value| !value.is_finite() || value.abs() > 1e9)
|| self.x_axis.iter().any(|value| !value.is_finite())
|| (self.x_axis[0].hypot(self.x_axis[1]) - 1.).abs() > 1e-6
@@ -70,13 +89,11 @@ impl FaceAnchor {
}
let inverse = crate::mat_inverse(&self.author_frame(doc, author)?)
.ok_or_else(|| DocError::Invalid("attached sketch author frame is invalid".into()))?;
let source = EmbossFace {
body: self.target.body.clone().into(),
at: crate::FacePick(self.target.point),
path: self.path.clone(),
author_path: self.author_path.clone(),
};
Ok(crate::mat_mul(&inverse, &source.body_frame(doc, body)?))
// Resolve both paths even with a captured placement: a missing or reparented source
// must still fail explicitly rather than silently reusing an unrelated definition.
let source = EmbossFace::component_frame(doc, self.component, &self.path)?;
let relative = self.placement.unwrap_or_else(|| crate::mat_mul(&inverse, &source));
Ok(doc.model_body_offset(body).map_or(relative, |offset| crate::mat_mul(&relative, offset)))
}
}
@@ -172,7 +189,43 @@ impl Document {
author: u64,
author_path: &[u64],
) -> Result<FaceAnchor> {
// A construction certificate that names this face alone, else its exact history name.
let target = self.flat_face_target(body, index)?;
let source = self
.anchor_face_plane(body, index, target.certificate.as_ref())
.ok_or_else(|| DocError::Invalid("this face has no exact planar source; choose a fixed plane instead".into()))?;
let mut anchor = FaceAnchor {
target,
component: self.model_body_component(body),
path: target_path.to_vec(),
author_path: author_path.to_vec(),
placement: None,
coordinates: [0., 0.],
x_axis: [1., 0.],
};
let canonical = map_plane(&anchor.source_to_author(self, author, body)?, &source)?;
if picked.normal().dot(canonical.normal()) < 1. - 1e-7 || (picked.origin - canonical.origin).dot(canonical.normal()).abs() > 1e-7 {
return Err(DocError::Invalid("picked plane does not match its exact source face".into()));
}
let delta = picked.origin - canonical.origin;
anchor.coordinates = [delta.dot(canonical.x), delta.dot(canonical.y)];
anchor.x_axis = [picked.x.dot(canonical.x), picked.x.dot(canonical.y)];
// A part built against another component uses that assembly context to define its
// own shape. Moving either occurrence later must not move the sketch inside the part.
// Root sketches and references inside the same component keep their live placements.
if author != 0 && author != anchor.component {
let inverse = crate::mat_inverse(&anchor.author_frame(self, author)?)
.ok_or_else(|| DocError::Invalid("attached sketch author frame is invalid".into()))?;
let source = EmbossFace::component_frame(self, anchor.component, &anchor.path)?;
anchor.placement = Some(crate::mat_mul(&inverse, &source));
}
anchor.validate()?;
Ok(anchor)
}
/// A target for flat face `index` of `body` that a rebuild finds again: a construction
/// certificate that names this face alone, else its exact history name. What a sketch on a
/// face and a print face hold.
pub(crate) fn flat_face_target(&self, body: &ModelBody, index: usize) -> Result<ConfigFaceTarget> {
let certified = body
.body
.face_plane(index)
@@ -185,39 +238,24 @@ impl Document {
let indices = self.configuration_face_indices(body, &target)?;
// A copy a pattern or mirror made on the same body carries its seed's certificate,
// which finds the seed's face: the copy is attached by its own history name below.
// A face that is no copy, on the plane of the pieces the certificate finds, is
// another piece of that face (a piece a later join left bordering another body,
// and named after it): the certificate stands for it too (#9465).
if !indices.contains(&index) {
return Err(DocError::Invalid("this face has no supported construction certificate; choose a fixed plane instead".into()));
let names = crate::naming::names(body);
let copy = names.faces.get(index).is_none_or(|name| crate::persistent_name::is_copy(name));
let all: Vec<usize> = indices.iter().copied().chain([index]).collect();
if copy || self.coplanar_pieces(body, &all, |_| true).is_err() {
return Err(DocError::Invalid("this face has no supported construction certificate; choose a fixed plane instead".into()));
}
}
self.coplanar_pieces(body, &indices, |_| true)?;
Ok(target)
});
let target = match certified {
Ok(target) => target,
Err(error) => match body.body.planar_face(index).and_then(|plane| self.lineage_face_target(body, index, plane.origin)) {
Some(target) => target,
None => return Err(error),
},
};
let source = self
.anchor_face_plane(body, index, target.certificate.as_ref())
.ok_or_else(|| DocError::Invalid("this face has no exact planar source; choose a fixed plane instead".into()))?;
let mut anchor = FaceAnchor {
target,
component: self.model_body_component(body),
path: target_path.to_vec(),
author_path: author_path.to_vec(),
coordinates: [0., 0.],
x_axis: [1., 0.],
};
let canonical = map_plane(&anchor.source_to_author(self, author, body)?, &source)?;
if picked.normal().dot(canonical.normal()) < 1. - 1e-7 || (picked.origin - canonical.origin).dot(canonical.normal()).abs() > 1e-7 {
return Err(DocError::Invalid("picked plane does not match its exact source face".into()));
match certified {
Ok(target) => Ok(target),
Err(error) => body.body.planar_face(index).and_then(|plane| self.lineage_face_target(body, index, plane.origin)).ok_or(error),
}
let delta = picked.origin - canonical.origin;
anchor.coordinates = [delta.dot(canonical.x), delta.dot(canonical.y)];
anchor.x_axis = [picked.x.dot(canonical.x), picked.x.dot(canonical.y)];
anchor.validate()?;
Ok(anchor)
}
/// The faces an attachment stands for, and a note when they were found by fallback.
@@ -378,7 +416,8 @@ impl Document {
pub fn validate_face_anchor_json(value: &Value) -> Result<()> {
let fields = value.as_object().ok_or_else(|| DocError::Invalid("face anchor must be an object".into()))?;
if fields.len() != 6 || fields.keys().any(|key| !["target", "component", "path", "author_path", "coordinates", "x_axis"].contains(&key.as_str()))
if !(6..=7).contains(&fields.len())
|| fields.keys().any(|key| !["target", "component", "path", "author_path", "placement", "coordinates", "x_axis"].contains(&key.as_str()))
{
return Err(DocError::Invalid("unknown or missing face anchor fields".into()));
}
@@ -412,6 +451,15 @@ pub fn validate_face_anchor_json(value: &Value) -> Result<()> {
return Err(DocError::Invalid("face anchor basis must be normalized".into()));
}
}
if let Some(placement) = fields.get("placement").filter(|v| !v.is_null()) {
if placement.as_array().is_none_or(|rows| rows.len() != 4 || rows.iter().any(|row| row.as_array().is_none_or(|row| row.len() != 4))) {
return Err(DocError::Invalid("face anchor placement requires a 4x4 matrix".into()));
}
let matrix: crate::Mat = serde_json::from_value(placement.clone()).map_err(|_| DocError::Invalid("invalid face anchor placement".into()))?;
if !crate::valid_transfer_matrix(&matrix) {
return Err(DocError::Invalid("face anchor placement must be a finite rigid transform".into()));
}
}
Ok(())
}
@@ -520,6 +568,44 @@ mod tests {
.unwrap();
assert!(doc.resolve_face_anchor(&anchor, 0, &state).is_err(), "reused name cannot replace the producer");
}
#[test]
fn captured_component_placement_is_rigid_and_legacy_anchors_stay_live() {
let (mut doc, body, index, picked) = fixture();
let author = doc.add_component(Some("Part"), 0).unwrap();
let path = doc.first_path(author);
let anchor = doc.capture_face_anchor(&body, index, &picked, &[], author, &path).unwrap();
assert_eq!(anchor.placement, Some(crate::IDENTITY));
let raw = serde_json::to_value(&anchor).unwrap();
assert!(validate_face_anchor_json(&raw).is_ok());
let restored: FaceAnchor = serde_json::from_value(raw.clone()).unwrap();
assert_eq!(restored, anchor);
for placement in [
serde_json::json!([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]]),
serde_json::json!([[2, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),
serde_json::json!([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),
serde_json::json!([[1, 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]),
serde_json::json!([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1e10, 0, 0, 1]]),
] {
let mut invalid = raw.clone();
invalid["placement"] = placement;
assert!(validate_face_anchor_json(&invalid).is_err(), "{invalid}");
if let Ok(invalid) = serde_json::from_value::<FaceAnchor>(invalid) {
assert!(invalid.validate().is_err());
}
}
let mut legacy = raw;
legacy.as_object_mut().unwrap().remove("placement");
assert!(validate_face_anchor_json(&legacy).is_ok());
let legacy: FaceAnchor = serde_json::from_value(legacy).unwrap();
doc.occurrences.iter_mut().find(|o| o.component == author).unwrap().transform[3][0] = 40.;
let mut state = ModelState::default();
state.bodies.push(body);
assert!(doc.resolve_face_anchor(&anchor, author, &state).unwrap().origin.dist(picked.origin) < 1e-9);
assert!(doc.resolve_face_anchor(&legacy, author, &state).unwrap().origin.dist(picked.origin - Vec3::X * 40.) < 1e-9);
doc.occurrences.clear();
assert!(doc.resolve_face_anchor(&anchor, author, &state).is_err(), "a captured context still validates its paths");
}
#[test]
fn nested_anchor_offset_tracks_source_and_raw_budgets_precede_decode() {
let (doc, body, index, picked) = fixture();
+1
View File
@@ -202,6 +202,7 @@ fn construction_names_follow_renames_in_every_definition() {
let planes = [
json!({"type": "midplane", "a": plane, "b": plane}),
json!({"type": "two_edges", "a": axis, "b": axis}),
json!({"type": "axis_point", "axis": axis, "point": point}),
json!({"type": "three_points", "a": point, "b": point, "c": point}),
json!({"type": "tangent", "face": plane, "at": [0, 0, 0]}),
json!({"type": "along_path", "path": axis, "t": "0.5"}),
@@ -148,7 +148,10 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
("sheet_hem", json!({"type": "sheet_hem", "edges": [o], "length": "hem.length", "gap": "hem.gap"})),
(
"sheet_perforate",
json!({"type": "sheet_perforate", "diameter": "perforate.diameter", "pitch": "perforate.pitch", "margin": "perforate.margin"}),
json!({
"type": "sheet_perforate", "diameter": "perforate.diameter", "pitch": "perforate.pitch", "margin": "perforate.margin",
"sketch": 1, "profiles": {"type": "all"}
}),
),
(
"sheet_fold",
@@ -107,7 +107,7 @@ pattern_circular
inputs:
Quantity = circ.count (Unitless)
Total angle = circ.angle (Angle)
expressions: circ.count, circ.angle
expressions: circ.count, circ.angle, 0, 0, 0
pattern_path
inputs:
Quantity = path.count (Unitless)
@@ -47,6 +47,7 @@ pattern_rectangular
moves: pattern.dir1[0], pattern.dir1[1], pattern.dir2[1], pattern.dir2[2]
pattern_circular
moves: pattern.axis[0], pattern.axis[2], pattern.origin[0], pattern.origin[1], pattern.origin[2]
placement: 0, 0, 0
pattern_path
mirror
moves: plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2]
@@ -133,6 +134,7 @@ sheet_flange
sheet_hem
moves: edges[0][0], edges[0][1], edges[0][2]
sheet_perforate
profiles: 1 all
sheet_fold
moves: a[0], a[1], a[2], b[1], b[2], fixed[0], fixed[1], fixed[2]
sheet_path

Some files were not shown because too many files have changed in this diff Show More