Compare commits

...

172 Commits

Author SHA1 Message Date
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
286 changed files with 31843 additions and 1763 deletions
+7
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 |
@@ -628,6 +632,8 @@ 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 |
## Code adapted from sibling projects
@@ -687,6 +693,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) |
Generated
+18 -19
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.28.0"
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.28.0"
[[package]]
name = "solidcraft-cli"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"image",
"log",
@@ -4262,7 +4261,7 @@ dependencies = [
[[package]]
name = "solidcraft-derive"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"proc-macro2",
"quote",
@@ -4271,7 +4270,7 @@ dependencies = [
[[package]]
name = "solidcraft-doc"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"log",
"serde",
@@ -4288,7 +4287,7 @@ dependencies = [
[[package]]
name = "solidcraft-engine"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"image",
"log",
@@ -4309,7 +4308,7 @@ dependencies = [
[[package]]
name = "solidcraft-geom"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"serde",
"serde_json",
@@ -4318,7 +4317,7 @@ dependencies = [
[[package]]
name = "solidcraft-gpu"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"image",
"log",
@@ -4338,7 +4337,7 @@ dependencies = [
[[package]]
name = "solidcraft-io"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"cadmpeg-core",
"cadmpeg-ir",
@@ -4362,7 +4361,7 @@ dependencies = [
[[package]]
name = "solidcraft-kernel"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"log",
"proptest",
@@ -4382,7 +4381,7 @@ dependencies = [
[[package]]
name = "solidcraft-mcp"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"serde_json",
"solidcraft-engine",
@@ -4391,7 +4390,7 @@ dependencies = [
[[package]]
name = "solidcraft-render"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"image",
"moxcms",
@@ -4405,7 +4404,7 @@ dependencies = [
[[package]]
name = "solidcraft-sketch"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"harfrust",
"serde",
@@ -4418,14 +4417,14 @@ dependencies = [
[[package]]
name = "solidcraft-space-mouse"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"serde",
]
[[package]]
name = "solidcraft-ui-egui"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"arboard",
"egui",
@@ -4455,7 +4454,7 @@ dependencies = [
[[package]]
name = "solidcraft-web"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"eframe",
"egui",
@@ -5998,7 +5997,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.27.1"
version = "0.28.0"
dependencies = [
"proc-macro2",
"serde",
+4 -1
View File
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
exclude = ["vendor"]
[workspace.package]
version = "0.27.1"
version = "0.28.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -165,6 +165,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]
+8
View File
@@ -16799,6 +16799,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
+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. -->
+75 -25
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,
@@ -674,32 +682,60 @@ 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(&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");
if !installed && solidcraft_ui_egui::fonts::needs_cjk_font(&self.app.preferences.language) {
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();
if self.font.tick(&mut self.app.preferences.language, ready) {
self.fetch_font(ctx, solidcraft_ui_egui::fonts::web_script_font_kind(&self.app.preferences.language).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.
@@ -708,31 +744,42 @@ mod web {
}
}
/// 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),
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(),
&self.app.preferences.language,
);
}
self.font_language.clone_from(&self.app.preferences.language);
}
/// 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 +1009,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"
+94 -8
View File
@@ -413,11 +413,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 +459,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 +482,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 +492,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 +504,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
@@ -905,6 +939,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));
}
}
+50 -7
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;
@@ -202,13 +204,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 +254,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 +269,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 +443,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.
@@ -750,6 +777,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 +858,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());
@@ -961,6 +1002,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 =
@@ -1103,12 +1145,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
+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}"),
+86
View File
@@ -292,6 +292,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
@@ -414,6 +479,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.
@@ -201,6 +201,7 @@ polygon false
polygon_circumscribed false
polygon_edge false
presspull false
print_face false
project false
project_surface false
rack_pinion false
@@ -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

+88
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": "تخصيص",
@@ -819,6 +822,7 @@
"cmd.appearance_pick_up": "التقاط المظهر",
"cmd.appearance_replace": "استبدال المظهر",
"cmd.assembly_bom": "قائمة المواد",
"cmd.body.print_face": "وجه الطباعة",
"cmd.body_rename": "إعادة تسمية الجسم",
"cmd.browser_collapse_all": "طي الكل",
"cmd.browser_expand_all": "توسيع الكل",
@@ -1412,6 +1416,7 @@
"help.sketch.construction_line.tangent_two": "اختر دائرتين أو قوسين دائريين ثم اختر مماسًا مشتركًا من المعاينة. يجب أن تقع نقطتا التماس على المنحنيين المرسومين.",
"help.sketch.construction_line.vertical": "أنشئ خطًا إنشائيًا رأسيًا لا نهائيًا يمر بنقطة. يحافظ القيد الرأسي على الاتجاه بعد التعديلات.",
"help.sketch.equation_curve": "يرسم منحنى من صيغ على مدى: x(t) و y(t)، أو y = f(x). يمكن أن تستخدم الصيغ المعلمات فيتبعها المنحنى: استخدمه لموجة جيبية أو لولب أو منحنى انفلات لسن ترس.",
"help.sketch.inspect": "يفتح تقريرًا عن الرسم المفتوح: عدد درجات الحرية المتبقية وهل هو مقيد بالكامل. استخدمه لمعرفة ما لا يزال يتحرك قبل إنهاء الرسم.",
"help.solid.align": "يحرّك الأجسام المحددة بحيث تلتقي نقطة أو وجه أو مستوى منها بنقطة أو وجه أو مستوى في مكان آخر. استخدمه لوضع قطعة ملاصقة لأخرى دون قياس المسافة.",
"help.solid.chamfer": "يقطع الحواف المحددة بشطفة مستوية بالمسافة التي تحددها. استخدمه لكسر الحواف الحادة أو لتسهيل إدخال مسمار أو برغي.",
"help.solid.combine": "يضم الأجسام، أو يقتطعها من جسم هدف، أو يُبقي الحجم المشترك بينها فقط، ويمكنه الاحتفاظ بأجسام الأداة. استخدمه لدمج القطع، أو لقطع شكل بشكل آخر، أو لمعرفة موضع تداخل جسمين.",
@@ -1614,6 +1619,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} ث",
@@ -1766,6 +1775,11 @@
"not a move": "ليس تحريكًا",
"not a number": "ليس عددًا",
"open a sketch first": "افتح رسمًا أولًا",
"parameters.apply_live": "طبّق كل تغيير عند مغادرة حقله",
"parameters.apply_live_tip": "عند الإيقاف: تنتظر التغييرات حتى «تطبيق» أو «موافق»، ثم يُعاد بناء التصميم مرة واحدة لها جميعًا. «إلغاء» أو Escape يتجاهلها.",
"parameters.held": "{count} تغييرات بانتظار «تطبيق»",
"parameters.held_hint": "عدّل التعابير والوحدات. تنتظر التغييرات «تطبيق» أو «موافق».",
"parameters.held_one": "تغيير واحد بانتظار «تطبيق»",
"parts.automatic_name": "تلقائي",
"parts.dimension.across_flats": "مقاس المفتاح",
"parts.dimension.bore": "قطر الثقب",
@@ -1788,6 +1802,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 +1856,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": "ظاهر",
@@ -1890,6 +1908,8 @@
"prefs.graphics_card_restart": "يُطبَّق بعد إعادة تشغيل 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": "يبدأ السجل على اليسار أو تحت المستعرض بمجلد الأجسام للمكوّن النشط وبالأصل، مع أيقونات الإظهار والقوائم كما في المستعرض. لا يتغير السجل في الأسفل.",
@@ -1950,12 +1970,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": "تغيّرت الإعدادات منذ حفظ ملف التعريف. «حفظ» يحفظها فيه.",
@@ -1977,6 +2001,8 @@
"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} محفوظة",
@@ -1990,6 +2016,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 يبدّل",
@@ -2068,6 +2096,8 @@
"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": "أثناء فتح مربع حوار أو رسم، تؤدي علامة الصح وعلامة الإكس أعلى يمين العرض ما يؤديه زرّا موافق وإلغاء في مربع الحوار، أو تنهي الرسم وتلغيه. وينتقل متصفح العرض إلى أسفلهما.",
@@ -2077,6 +2107,10 @@
"prefs.viewport_background": "الخلفية",
"prefs.viewport_background_colour": "لون",
"prefs.viewport_background_theme": "تدرج السمة",
"prefs.viewport_section_status": "إظهار حالة المقطع في منفذ العرض",
"prefs.viewport_section_status_help": "عرض إعداد المقطع في منفذ العرض. يُوقف Escape وزر الشريط النشط المقطع. عطّل الخيار للاحتفاظ بأدوات المقطع السابقة.",
"prefs.viewport_status_summary": "تلخيص عناصر حالة منفذ العرض",
"prefs.viewport_status_summary_help": "اجمع ثلاثة عناصر أو أكثر لحالة منفذ العرض في شارة ملخّصة واحدة. عطّل الخيار لإبقاء كل عنصر في الصف الكامل.",
"prefs.wheel": "العجلة",
"prefs.wheel_trackpad": "العجلة ولوحة اللمس",
"prefs.workspace": "مساحة العمل",
@@ -2955,7 +2989,9 @@
"render.output_ready": "جاهز للتصيير",
"render.output_run_check": "تشغيل الفحص",
"render.output_size_limit": "{method}: حتى {dimension} بكسل لكل ضلع و{pixels} بكسل. أكبر حجم مناسب: {width} × {height}.",
"render.output_size_method_profile": "لتصيير {width} × {height}، اختر {method} مع ملف موارد CPU «{profile}»، أو حجمًا أصغر.",
"render.output_size_ok": "الحجم مناسب · {width} × {height} بكسل",
"render.output_size_profile": "لتصيير {width} × {height}، اختر ملف موارد CPU «{profile}»، أو حجمًا أصغر.",
"render.output_swap_orientation": "تبديل العرض والارتفاع",
"render.output_tools_inspector": "عدّل الحجم والصيغة في مفتش المخرجات.",
"render.output_view_image": "عرض الصورة",
@@ -3168,6 +3204,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": "معاينة",
@@ -3455,6 +3492,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 معرّف أمر",
"shortcut.pin": "تثبيت في أعلى مربع S",
@@ -3491,6 +3546,7 @@
"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.on_path": "النص يتبع مساره: المنحنى يحدد موضعه",
@@ -3631,6 +3687,8 @@
"ui.3d_mouse_section": "الفأرة ثلاثية الأبعاد",
"ui.3d_mouse_show_in_view": "إظهار الفأرة ثلاثية الأبعاد في العرض ثلاثي الأبعاد",
"ui.3d_mouse_show_in_view_tip": "صورة للفأرة ثلاثية الأبعاد في الزاوية السفلية اليسرى، تتحرك كلما حرّكتها.",
"ui.3d_mouse_sketch_orbit": "تدوير الرسومات بالفأرة ثلاثية الأبعاد",
"ui.3d_mouse_sketch_orbit_tip": "تدير الفأرة ثلاثية الأبعاد العرض أيضًا أثناء فتح رسم ورؤيته من الأمام مباشرة، كما تفعل الفأرة. عند الإيقاف: تُزيح الرسم وتكبّره فقط حتى تُميل العرض.",
"ui.3d_mouse_skip": "جهازي لا يستطيع ذلك",
"ui.3d_mouse_skip_tip": "يتخطى هذه الحركة في الاتجاهين، وتستبعدها المعايرة.",
"ui.3d_mouse_speed": "سرعة الفأرة ثلاثية الأبعاد",
@@ -3659,6 +3717,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": "قراءة الفأرة ثلاثية الأبعاد مباشرة (hidraw) بدلًا من المرور عبر spacenavd.",
"ui.3d_mouse_view_axis": "محور العرض",
"ui.3d_mouse_view_off": "متوقف",
"ui.3d_mouse_view_off_tip": "ملف تعريف مشغّل الفأرة ثلاثية الأبعاد يوقف هذا المحور، فلا تحرك حركتُه العرض. اختيار حركة محور آخر هنا يبادل بينهما، فتتوقف تلك الحركة عن تحريك العرض.",
@@ -3792,6 +3852,8 @@
"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": "حفظ",
@@ -3975,6 +4037,7 @@
"ui.circle": "دائرة",
"ui.circular_pattern": "نمط دائري",
"ui.clear": "مسح",
"ui.clear_print_face": "مسح وجه الطباعة",
"ui.clear_recent": "مسح الأخيرة",
"ui.clear_the_selection": "مسح التحديد",
"ui.click_selects_whole_bodies": "النقر يحدد الأجسام كاملة",
@@ -4407,12 +4470,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": "وحدات الملف",
@@ -4421,6 +4489,11 @@
"ui.polygon": "مضلع",
"ui.preferences": "التفضيلات",
"ui.press_a_key": "اضغط مفتاحًا…",
"ui.print_face_cleared": "{body}: تم مسح وجه الطباعة",
"ui.print_face_input": "وجه الطباعة",
"ui.print_face_one_body": "يُختار وجه الطباعة لجسم واحد: أرسل ذلك الجسم وحده لتغييره",
"ui.print_face_pick": "اختر وجهًا مستويًا واحدًا لجسم صلب لجعله وجه الطباعة",
"ui.print_face_set": "{body}: تم تعيين وجه الطباعة؛ الإرسال إلى برنامج التقطيع يضع الجسم عليه",
"ui.profile": "المقطع",
"ui.profile_plane": "مستوى المقطع",
"ui.profiles": "المقاطع",
@@ -4503,6 +4576,7 @@
"ui.selectOther": "تحديد آخر…",
"ui.selectTangentChain": "تحديد سلسلة مماسية",
"ui.select_all": "تحديد الكل",
"ui.select_components_by_face": "تحديد المكوّنات (تحديد وجه يحدّد المكوّن الذي ينتمي إليه)",
"ui.selection": "التحديد",
"ui.selection_modifiers": "مُعدِّلات التحديد",
"ui.selection_shift_add_only": "Shift مع النقر يضيف دون إزالة",
@@ -4544,6 +4618,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": "تقطيع",
@@ -4653,7 +4732,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": "مظلل مع الحواف",
@@ -4690,7 +4776,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.",
+56
View File
@@ -775,6 +775,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.",
@@ -817,6 +819,7 @@
"cmd.appearance_pick_up": "Preuzmi izgled",
"cmd.appearance_replace": "Zamijeni izgled",
"cmd.assembly_bom": "Sastavnica",
"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",
@@ -1399,6 +1402,16 @@
"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.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.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.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.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.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.",
@@ -1828,6 +1841,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",
@@ -1878,6 +1893,8 @@
"prefs.graphics_card_restart": "Stupa na snagu nakon ponovnog pokretanja SolidCrafta. WGPU_ADAPTER_NAME ima prednost nad ovim izborom.",
"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.",
@@ -1916,6 +1933,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",
@@ -1935,10 +1955,14 @@
"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.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.",
@@ -1973,6 +1997,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 +2010,14 @@
"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_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",
@@ -2052,6 +2081,8 @@
"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",
@@ -2064,6 +2095,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",
@@ -3633,6 +3668,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.",
@@ -3949,6 +3986,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 +4021,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",
@@ -4387,6 +4433,11 @@
"ui.polygon": "Poligon",
"ui.preferences": "Postavke",
"ui.press_a_key": "pritisnite tipku…",
"ui.print_face_cleared": "{body}: površina za štampu uklonjena",
"ui.print_face_input": "Površina za štampu",
"ui.print_face_one_body": "Površina za štampu pripada jednom tijelu: pošaljite to tijelo samo da biste je promijenili",
"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",
@@ -4450,6 +4501,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",
@@ -4613,6 +4665,8 @@
"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}",
@@ -4653,7 +4707,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.",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Zde se zobrazí návrhy, které otevřete nebo uložíte.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Určeno volbou K objektu. Pro úpravu úhlového rozsahu zvolte Úhel.",
"Diameter": "Průměr",
"Did this assembly open the wrong way up?": "Otevřela se tato sestava špatně natočená?",
"Did this part open the wrong way up?": "Otevřel se tento díl špatně natočený?",
"Dimension new shapes": "Kótovat nové tvary",
"Direction": "Směr",
"Discard": "Zahodit",
@@ -601,6 +603,7 @@
"Square": "Čtverec",
"Stand it up": "Postavit",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Postavit: otočení o čtvrt otáčky kolem osy X jako jeden prvek Posunutí, který lze vrátit.",
"Stand this assembly up?": "Postavit tuto sestavu?",
"Stand this part up?": "Postavit tento díl?",
"Start angle": "Počáteční úhel",
"Start bulge": "Vypouklost začátku",
@@ -772,6 +775,8 @@
"at the face": "u plochy",
"bom.group_children": "Poddíly v řádku svého dílu",
"browser.body_name": "Těleso{number}",
"browser.calibrate_click_a": "Klikněte do obrázku na bod A, nebo zadejte jeho souřadnice.",
"browser.calibrate_click_b": "Klikněte do obrázku na bod B, nebo zadejte jeho souřadnice.",
"browser.calibrate_title": "Kalibrovat: {name}",
"browser.capture_many": "Byly přesunuty komponenty ({count}), ale jejich polohy nejsou zachyceny.",
"browser.capture_note": "Zachycení polohy ponechá novou polohu; vrácení obnoví původní.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Převzít vzhled",
"cmd.appearance_replace": "Nahradit vzhled",
"cmd.assembly_bom": "Kusovník",
"cmd.body.print_face": "Tisková plocha",
"cmd.body_rename": "Přejmenovat těleso",
"cmd.browser_collapse_all": "Sbalit vše",
"cmd.browser_expand_all": "Rozbalit vše",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Dvojklikem na jiný řídicí kótu vložíte jeho název",
"dialog.dimension_name": "Kóta: {name}",
"dialog.edit_title": "UPRAVIT {title}",
"dialog.no_shown_body": "Není zobrazeno žádné těleso ke změně: zobrazte nějaké nebo zvolte Nové těleso",
"dialog.select": "↖ Vybrat",
"dialog.selected_count": "↖ Vybráno: {count}",
"dialog.selection_guidance": "Vstup změníte jeho výběrem. Kliknutím na vybraný objekt jej odeberete; Ctrl/Cmd+klik výběr také přepíná. Zkontrolujte náhled a stiskněte OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Posouvá díl sestavy jen v těch stupních volnosti, které povolují jeho klouby. Vyberte plochu dílu a pak táhněte za dostupnou šipku nebo oblouk.",
"help.parameters.change": "Otevře tabulku pojmenovaných hodnot a výrazů, které řídí rozměry návrhu. Hodí se ke změně rozměru na jednom místě, kterou převezmou všechny prvky, jež jej používají.",
"help.pointcloud.insert": "Vložte sken PLY, XYZ nebo PTS jako mračno bodů vedle modelu. Použijte ho jako referenci při tvorbě modelu z naměřených dat.",
"help.sketch.construction_line": "Vytvoří nekonečnou konstrukční přímku procházející dvěma body. Použijte ji k rozvržení náčrtu bez rozdělování profilů.",
"help.sketch.construction_line.angle": "Vytvoří nekonečnou konstrukční přímku pod úhlem od osy X náčrtu. Vyberte bod a zadejte úhel, nebo vyberte jeho směr.",
"help.sketch.construction_line.bisector": "Vyberte dvě protínající se přímé úsečky náčrtu a pak jednu ze dvou os úhlu. Výsledek sleduje své zdrojové úsečky.",
"help.sketch.construction_line.horizontal": "Vytvoří nekonečnou vodorovnou konstrukční přímku procházející bodem. Její vodorovná vazba platí i při pozdějších úpravách.",
"help.sketch.construction_line.parallel": "Vyberte přímou úsečku náčrtu a pak umístěte rovnoběžnou nekonečnou konstrukční přímku. Jinou referenci vyberete kliknutím pravým tlačítkem.",
"help.sketch.construction_line.perpendicular": "Vyberte přímou úsečku náčrtu a pak umístěte kolmou nekonečnou konstrukční přímku. Jinou referenci vyberete kliknutím pravým tlačítkem.",
"help.sketch.construction_line.tangent_at": "Vytvoří tečnu v bodě kružnice nebo kruhového oblouku, případně v koncovém bodě otevřeného splajnu nebo kuželosečky. Vyberte křivku a pak bod dotyku.",
"help.sketch.construction_line.tangent_external": "Vyberte kružnici nebo kruhový oblouk, pak vnější bod a řešení tečny. Náhledy ukazují jen body dotyku na nakresleném oblouku.",
"help.sketch.construction_line.tangent_two": "Vyberte dvě kružnice nebo kruhové oblouky a pak v náhledu zvolte společnou tečnu. Body dotyku musí ležet na nakreslených křivkách.",
"help.sketch.construction_line.vertical": "Vytvoří nekonečnou svislou konstrukční přímku procházející bodem. Její svislá vazba platí i při pozdějších úpravách.",
"help.sketch.equation_curve": "Nakreslí křivku podle vzorců: x(t) a y(t), nebo y = f(x), v zadaném rozsahu. Vzorce mohou používat parametry a křivka se podle nich mění: použijte ji pro sinusovku, spirálu nebo evolventu zubu ozubeného kola.",
"help.solid.align": "Posune vybraná tělesa tak, aby se bod, plocha nebo rovina na nich setkala s bodem, plochou nebo rovinou jinde. Hodí se k přiložení dílu k jinému bez měření odsazení.",
"help.solid.chamfer": "Zkosí vybrané hrany plochým sražením o zadanou vzdálenost. Hodí se k otupení ostrých hran nebo ke snadnějšímu zasunutí kolíku či šroubu.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Přepočítá všechny prvky na časové ose od začátku. Použijte to k obnovení modelu a ke zjištění, které prvky selhávají.",
"help.timeline.delete": "Smaže vybrané prvky z časové osy; smazáním náčrtu se smažou i prvky z něj vytvořené. Použijte to k odstranění kroku, který už nechcete.",
"help.timeline.play": "Přehraje zbývající prvky jeden po druhém s půlsekundovou pauzou po každém přestavění. Spuštěním tohoto příkazu znovu nebo stisknutím Escape přehrávání zastavíte; Přejít na konec skočí okamžitě.",
"history.sketch_under_constrained": "Nedourčeno (stupňů volnosti: {count})",
"history.sketch_under_constrained_one": "Nedourčeno ({count} stupeň volnosti)",
"hole_presets.confirm_delete": "Potvrdit smazání",
"hole_presets.delete": "Smazat",
"hole_presets.delete_prompt": "Smazat tuto osobní předvolbu? Existující otvory se nezmění.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Vyhlazuje zubaté hrany.",
"prefs.applies_on_restart": "Použije se po restartu",
"prefs.assistant": "Asistent",
"prefs.auto_operation": "Automaticky vybírat operaci",
"prefs.auto_operation_note": "Okna prvků vyberou Sjednotit, Nové těleso nebo Vyjmout podle toho, čeho se nový materiál dotýká, dokud sami jednu nevyberete. Vypnuto: ponechají si operaci, se kterou se otevřou.",
"prefs.axes": "Osy",
"prefs.axis": "Osa",
"prefs.bodies_folders": "Složky těles",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Dole uprostřed",
"prefs.bottom_right": "Vpravo dole",
"prefs.browser": "Prohlížeč",
"prefs.browser_eye_left": "Oko vedle šipky sbalení",
"prefs.browser_eye_left_note": "Řádky prohlížeče zobrazují oko pro zobrazení/skrytí vedle šipky sbalení jako dříve, místo ve sloupci na pravém konci řádků.",
"prefs.browser_reveal_on_hover": "Odkrýt při najetí myší",
"prefs.browser_show_note": "Zobrazeno ponechá prohlížeč na obrazovce. Odkrýt při najetí myší jej skryje, dokud ukazatel nenajede na jeho místo nebo na tenký jazýček u jeho levého okraje, a prohlížeč zůstane otevřený, dokud je ukazatel uvnitř něj nebo je otevřená některá z jeho nabídek.",
"prefs.browser_shown": "Zobrazeno",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Projeví se po restartování aplikace SolidCraft. Tuto volbu přepíše WGPU_ADAPTER_NAME.",
"prefs.graphics_note": "Rychlost, nebo hladkost obrazu: každá volba ukazuje svůj účinek na ukázce.",
"prefs.graphics_presets_note": "Předvolby nastaví níže uvedené volby najednou.",
"prefs.hide_constraint_glyphs_zoomed_out": "Skrýt symboly vazeb při oddálení",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Symboly vazeb zmizí z pohledu, když je náčrt oddálen natolik, že by ho zahlcovaly. Vypnuto: při každém zvětšení zůstávají ve stejné velikosti na obrazovce.",
"prefs.history": "Historie",
"prefs.history_origin_bodies": "Zobrazit počátek a tělesa nahoře",
"prefs.history_origin_bodies_note": "Historie vlevo nebo pod prohlížečem začíná složkou Tělesa aktivní komponenty a počátkem, s ikonami oka a nabídkami jako v prohlížeči. Historie dole zůstává beze změny.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "Soubory STEP a importu",
"prefs.max_file_size_web": "Webová aplikace otevírá soubory STEP a importu do 512 MB a návrhy do 256 MB: karta prohlížeče větší soubory nepojme. V aplikaci pro počítač lze limity zvýšit.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Velké ikony",
"prefs.menu_list": "Seznam",
"prefs.menus": "Nabídky",
"prefs.minutes": "minut",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Myš a trackpad",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Názvy rovin počátku",
"prefs.origin_plane_names_note": "Názvy zobrazené v prohlížeči, dialozích a seznamech. {top} je XY, {front} je XZ a {right} je YZ, osa Z směřuje nahoru. Příkazy, skripty a uložené návrhy zachovávají XY, XZ a YZ.",
"prefs.overall_speed": "Celková rychlost",
"prefs.persistent_ribbon_pressed": "Zvýrazňovat aktivní příkazy na pásu karet",
"prefs.persistent_ribbon_pressed_help": "Zvýrazní příkazy na pásu karet, dokud jsou jejich účinky zapnuté. Vypnutím zachováte dřívější neutrální vzhled a nápovědu k příkazům.",
"prefs.perspective_fov": "Zorný úhel",
"prefs.placement": "Umístění",
"prefs.prefer_free_sketch_points": "Při překrytí bodů upřednostnit pohyblivé body náčrtu",
"prefs.preview_choices": "Náhled vašich voleb",
"prefs.print_face_highlight": "Zvýraznění tiskové plochy",
"prefs.print_face_highlight_tip": "Jak výrazně se tisková plocha tělesa v pohledu obarví a obtáhne. 0 % nezobrazí žádné zvýraznění.",
"prefs.profile_active": "Aktivní profil",
"prefs.profile_applied": "Profil byl použit.",
"prefs.profile_changed": "Nastavení se od uložení profilu změnila. Uložit je do něj zapíše.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Žádná odpovídající nastavení",
"prefs.select": "Vybrat",
"prefs.select_one_item": "Vybrat jednu položku",
"prefs.select_other_hidden": "Vybírat skrytou geometrii pomocí Vybrat jiné",
"prefs.select_other_hidden_help": "Zapnuto: zahrne skrytá tělesa, náčrty a zakryté hrany v blízkosti ukazatele. Vypnuto: použije původní seznam viditelné geometrie pod ukazatelem.",
"prefs.selecting": "Výběr",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift jen přidává; Ctrl / Cmd přepíná",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Při kreslení se bod zarovná vodorovně s jedním bodem náčrtu a zároveň svisle s jiným, s tečkovaným vodítkem, a kliknutí zachová obě zarovnání. Vypnuto: jen s předchozím bodem, vždy v jedné ose.",
"prefs.sketch_conflict_colour": "Konfliktní geometrie v barvě konfliktu",
"prefs.sketch_conflict_colour_note": "Dokud jsou vazby náčrtu v konfliktu, vykreslují se v barvě konfliktu i křivky a body, které selhávající vazba drží, nejen její symbol nebo kóta. Vypnuto: ponechají si obvyklé barvy.",
"prefs.sketch_construction_lines": "Rozšířené nástroje konstrukčních přímek",
"prefs.sketch_exit_view": "Po ukončení náčrtu",
"prefs.sketch_exit_view.keep": "Ponechat aktuální pohled",
"prefs.sketch_exit_view.previous": "Vrátit se k předchozímu pohledu",
"prefs.sketch_exit_view.unless_rotated": "Vrátit, pokud nebyl pohled otočen",
"prefs.sketch_exit_view_note": "Kam se pohled přesune po příkazu Dokončit náčrt, Zrušit nebo dvojitém Esc.",
"prefs.sketch_open_look_at": "Při otevření náčrtu natočit pohled k němu",
"prefs.sketch_open_look_at_note": "Vypnuto: vytvoření nebo úprava náčrtu ponechá pohled beze změny. Pohled kolmo natočí pohled k náčrtu, kdykoli ho chcete.",
"prefs.sketch_recent_snap": "Automaticky přichytávat k nedávným středům a středním bodům",
"prefs.sketch_recent_snap_note": "Při kreslení se kurzor přichytí tam, kde se kříží vodítka dvou posledních středů kružnic nebo středů rovných hran. Vypnuto: tato vodítka se jen zobrazují a kurzor se nepohne.",
"prefs.sketch_selection": "Náčrt a výběr",
"prefs.sketch_selection_heading": "Náčrt a výběr",
"prefs.sketch_selection_note": "Jak se v náčrtu přichytává a jak kliknutí vybírá.",
"prefs.sketch_state_icons": "Ikony náčrtů v barvě jejich stavu",
"prefs.sketch_state_icons_note": "V Historii a v prohlížeči má ikona náčrtu barvu jeho stavu (nedourčený, plně zavazbený nebo v konfliktu) a její bublinová nápověda uvádí název stavu. Vypnuto: všechny náčrty mají stejnou ikonu.",
"prefs.sketch_tab_always": "Vždy zobrazovat kartu Náčrt",
"prefs.sketch_tab_always_note": "Karta Náčrt zůstává na pásu karet hned za kartou Model, i když není otevřený žádný náčrt. Její kreslicí nástroje pak zahájí náčrt na vybrané rovině nebo ploše, nebo na rovině, kterou vyberete jako další, a nástroj je v něm rovnou připraven.",
"prefs.sketch_tracking_guides": "Vodítka zarovnání ke středům a středním bodům",
@@ -1995,6 +2034,11 @@
"prefs.source": "Zdroj",
"prefs.speed": "Rychlost",
"prefs.stand_up_note": "Po otevření souboru STEP nebo IGES s osou Z nahoru nabídnout otočení o 90° kolem osy X.",
"prefs.stand_up_unknown": "U souborů z neznámých programů",
"prefs.stand_up_unknown.ask": "Zeptat se v dialogu",
"prefs.stand_up_unknown.note": "Zobrazit poznámku",
"prefs.stand_up_unknown.off": "Nenabízet",
"prefs.stand_up_unknown_note": "Jak se nabídka zobrazí, když hlavička souboru neuvádí žádný program, o kterém je známo, že ukládá s osou Y nahoru. Poznámka se zobrazí jen tehdy, když soubor vypadá, že leží na zádech; volba Zeptat se v dialogu se ptá u každého takového souboru, jako dříve.",
"prefs.status_bar": "Stavový řádek",
"prefs.status_bar_note": "Tenký pruh na dolním okraji okna ukazuje, co se právě upravuje, stav otevřeného náčrtu svou barvou a jednotky návrhu; kliknutím na jednotky je změníte. Stav náčrtu se pak již nezobrazuje v rohu pohledu.",
"prefs.status_done": "Hotovo",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK a Zrušit v rohu pohledu",
"prefs.view_confirm_corner_note": "Když je otevřen dialog nebo náčrt, zaškrtnutí a křížek vpravo nahoře v pohledu provedou totéž co OK a Zrušit v dialogu, nebo náčrt dokončí a zruší. Navigátor pohledu se posune pod ně.",
"prefs.viewport": "Zobrazovací okno",
"prefs.viewport_analysis_status": "Zobrazovat stav analýz nad pohledem",
"prefs.viewport_analysis_status_help": "Zobrazí aktivní analýzy nad pohledem, z každé položky otevře její legendu a klávesou Esc vypne nejnovější analýzu. Vypnutím obnovíte legendy v rozích a předchozí chování klávesy Esc.",
"prefs.viewport_background": "Pozadí",
"prefs.viewport_background_colour": "Barva",
"prefs.viewport_background_theme": "Přechod motivu",
@@ -2049,6 +2095,10 @@
"properties.title": "Vlastnosti: {name}",
"recovery.days_ago": "před {count} dny",
"recovery.detail": "{file} · prvků: {features} · automaticky uloženo {when}",
"recovery.discard_all": "Zahodit vše",
"recovery.discard_all_detail": "Tuto akci nelze vrátit zpět.",
"recovery.discard_all_title": "Zahodit automatická uložení: {count}?",
"recovery.discard_all_title_one": "Zahodit 1 automatické uložení?",
"recovery.hours_ago": "před {count} h",
"recovery.just_now": "právě teď",
"recovery.min_ago": "před {count} min",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Přesunout prvky do složky",
"timeline.rename_group": "Přejmenovat složku prvků",
"timeline.reorder_many": "Změnit pořadí prvků",
"timeline.suppress_features": "Potlačit prvky",
"timeline.suppress_folders_tip": "Potlačí nebo zruší potlačení všech prvků ve složkách a v jejich vnořených složkách",
"timeline.ungroup": "Odebrat složky prvků",
"timeline.unsuppress_features": "Zrušit potlačení prvků",
"to the face": "k ploše",
"to the next face": "k další ploše",
"toolbar.customize_note": "Karta {tab}. Zaškrtnutím příkazu přidáte jeho tlačítko na panel nástrojů. Můžete také přetáhnout řádek z rozbalovací nabídky panelu na panel nástrojů, odstranit tlačítko přetažením mimo panel nástrojů nebo použít připínáček na řádku rozbalovací nabídky.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
"ui.3d_mouse_switch_cap": "Kalibrovat jako návrat do středu",
"ui.3d_mouse_switch_trackball": "Kalibrovat jako trackball",
"ui.3d_mouse_use_direct": "Použít přímý ovladač",
"ui.3d_mouse_use_direct_tip": "Číst 3D myš přímo (hidraw) místo přes spacenavd.",
"ui.3d_mouse_view_axis": "Osa pohledu",
"ui.3d_mouse_view_off": "Vypnuto",
"ui.3d_mouse_view_off_tip": "Profil ovladače 3D myši tuto osu vypíná, takže její pohyb s pohledem nehýbe. Když tu vyberete pohyb jiné osy, prohodí se s ní a s pohledem přestane hýbat.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Zrušit",
"ui.canvases": "Plátna",
"ui.center": "Střed",
"ui.center_arc_follows_cursor": "Oblouk ze středu sleduje směr kurzoru",
"ui.center_arc_follows_cursor_tip": "Oblouk ze středu se kreslí po směru nebo proti směru hodinových ručiček podle toho, jak kurzor obíhá jeho střed. Vypnuto: vždy proti směru hodinových ručiček od začátku.",
"ui.centerline": "Osa",
"ui.chain": "Řetězec",
"ui.chamfer": "Sražení",
@@ -3930,6 +3986,7 @@
"ui.circle": "Kružnice",
"ui.circular_pattern": "Kruhové pole",
"ui.clear": "Vymazat",
"ui.clear_print_face": "Zrušit tiskovou plochu",
"ui.clear_recent": "Vymazat nedávné",
"ui.clear_the_selection": "Zrušit výběr",
"ui.click_selects_whole_bodies": "Kliknutí vybírá celá tělesa",
@@ -3964,6 +4021,14 @@
"ui.constant": "Konstantní",
"ui.constraints_conflict": "Konflikt vazeb",
"ui.construction": "Konstrukční",
"ui.construction.angle_hint": "Konstrukční přímka: klikněte na bod, pak zadejte úhel nebo vyberte směr",
"ui.construction.bisector_hint": "Osa úhlu: vyberte dvě protínající se přímky, pak požadované řešení",
"ui.construction.common_hint": "Společná tečna: vyberte dvě kružnice/oblouky, pak požadované řešení",
"ui.construction.contact_hint": "Tečna: vyberte kružnici/oblouk nebo otevřený splajn/kuželosečku, pak bod dotyku (volné křivky: koncové body)",
"ui.construction.external_hint": "Tečna: vyberte kružnici nebo oblouk, vnější bod, pak požadované řešení",
"ui.construction.free_hint": "Konstrukční přímka: klikněte na dva body; pravým tlačítkem začnete znovu",
"ui.construction.point_hint": "Konstrukční přímka: klikněte na bod, kterým má procházet",
"ui.construction.reference_hint": "Konstrukční přímka: vyberte přímku, pak bod; pravým tlačítkem změníte referenci",
"ui.construction.ribbon_label": "Pom. přímky",
"ui.context_menu": "Místní nabídka",
"ui.continuity_g0": "G0 hrana",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Mnohoúhelník",
"ui.preferences": "Předvolby",
"ui.press_a_key": "stiskněte klávesu…",
"ui.print_face_cleared": "{body}: tisková plocha zrušena",
"ui.print_face_input": "Tisková plocha",
"ui.print_face_one_body": "Tisková plocha patří jednomu tělesu: odešlete toto těleso samostatně, abyste ji změnili",
"ui.print_face_pick": "Vyberte rovinnou plochu objemového tělesa, aby se stala tiskovou plochou",
"ui.print_face_set": "{body}: tisková plocha nastavena; Odeslat do sliceru položí těleso na ni",
"ui.profile": "Profil",
"ui.profile_plane": "Rovina profilu",
"ui.profiles": "Profily",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Rotace",
"ui.ribbon_exit": "Ukončit",
"ui.ribbon_labels": "Zobrazovat text pod ikonami pásu karet",
"ui.ribbon_menus_skip_pinned": "Vynechat připnuté příkazy v rozbalovacích nabídkách",
"ui.ribbon_pin_footer": "Připnuté příkazy se zobrazují na pásu karet. Najetím na řádek jej připnete nebo odepnete.",
"ui.ribbon_workspace": "Pracovní prostředí",
"ui.right": "Zprava",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Střed pohledu",
"ui.viewport_status_clear": "Vypnout {name}",
"ui.viewport_status_clear_hint": "Vypnout {name} · Esc",
"ui.viewport_status_edges": "Hrany: {count}",
"ui.viewport_status_from": "ze směru {direction}",
"ui.viewport_status_key": "Zobrazit legendu pro {name}",
"ui.viewport_status_minimum_radius": "Minimální poloměr",
"ui.viewport_status_pairs": "Dvojice: {count}",
@@ -4634,7 +4707,9 @@
"update.button": "Aktualizovat",
"update.button_version": "Aktualizovat na {version}",
"update.check_automatically": "Automaticky kontrolovat aktualizace",
"update.check_for_updates": "Zkontrolovat aktualizace",
"update.check_now": "Zkontrolovat nyní",
"update.check_unavailable": "Pro tuto instalaci není kontrola aktualizací dostupná.",
"update.checking": "Kontrola…",
"update.copy": "Kopírovat",
"update.dmg_opened": "Obraz disku je otevřený a ověřený: přetáhněte SolidCraft do Aplikací místo staré kopie a poté SolidCraft restartujte.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} je nainstalován. Pro použití restartujte.",
"update.installer_started": "Instalátor se spustí po zavření SolidCraftu.",
"update.later": "Připomenout později",
"update.managed": "K dispozici je aktualizace: aktualizujte SolidCraft v {manager}",
"update.no_notes": "Tato verze nemá poznámky.",
"update.package_hint": "Tato kopie pochází z {kind}. Stáhněte nový balíček a nainstalujte jej pomocí:",
"update.prefs_dev": "Toto je vývojová verze ({version}): aktualizace nekontroluje.",
"update.prefs_flatpak": "Tuto kopii ({version}) aktualizuje Flatpak: spusťte {command} nebo použijte správce softwaru. SolidCraft nekontroluje.",
"update.prefs_label": "Aktualizace",
"update.prefs_managed": "Toto je SolidCraft {version}, spravovaný aplikací {manager}. Kontrola se dotáže GitHubu na nejnovější verzi a nic dalšího neodesílá; aktualizujte SolidCraft v {manager}.",
"update.prefs_note": "Toto je SolidCraft {version} ({kind}). Kontrola se dotáže GitHubu na nejnovější verzi a nic dalšího neodesílá.",
"update.prefs_web": "Znovu načtěte stránku, abyste získali nejnovější webovou aplikaci. Pokud síť není dostupná nebo se aktualizace nestihne včas stáhnout, otevře se uložená verze; probíhající stahování pokračuje pro další načtení.",
"update.refresh_checking": "Hledání nejnovější verze…",
+2314 -1
View File
File diff suppressed because it is too large Load Diff
+469
View File
@@ -60,8 +60,10 @@
"BLEND SURFACE": "ÜBERGANGSFLÄCHE",
"BOUNDARY SURFACE": "BERANDUNGSFLÄCHE",
"BROWSER": "BROWSER",
"Back to the original order": "Zurück zur ursprünglichen Reihenfolge",
"Ball": "Kugelgelenk",
"Bar": "Flachstab",
"Base Center": "Mittelpunkt der Basis",
"Base Height": "Sockelhöhe",
"Bend": "Biegung",
"Bend Edges": "Biegekanten",
@@ -205,6 +207,7 @@
"Edges": "Kanten",
"Edit": "Bearbeiten",
"Edit Profile Sketch": "Profile-Skizze bearbeiten",
"Edit shortcuts": "Tastenkürzel bearbeiten",
"Enable rule": "Regel aktivieren",
"Enabled": "Aktiviert",
"End Radius": "Endradius",
@@ -259,6 +262,8 @@
"Flanges": "Flansche",
"Flat Pattern": "Flachmuster",
"Flat size": "Abwicklungsgröße",
"Flat size X": "Abwicklungsmaß X",
"Flat size Y": "Abwicklungsmaß Y",
"Follow camera (Look At sketches)": "Kamera folgen (Skizzen ansehen)",
"Follow the camera (Look At sketches)": "Der Kamera folgen (Skizzen ansehen)",
"Font": "Schriftart",
@@ -290,6 +295,7 @@
"Horiz./Vert.": "Horiz./Vert.",
"I-Beam": "I-Träger",
"IMAGES": "BILDER",
"If you are offline or GitHub does not open, use Copy report and keep the text to submit later.": "Wenn Sie offline sind oder GitHub nicht geöffnet werden kann, verwenden Sie „Bericht kopieren“ und bewahren Sie den Text auf, um ihn später einzureichen.",
"Import material into design": "Material ins Design importieren",
"Import…": "Importieren…",
"In This Design": "In diesem Design",
@@ -400,13 +406,16 @@
"Of": "Freiheitsgrad",
"On your computer": "Auf diesem Computer",
"Opacity": "Deckkraft",
"Open GitHub": "GitHub öffnen",
"Open GitHub draft": "GitHub-Entwurf öffnen",
"Open GitHub issue form": "GitHub-Issueformular öffnen",
"Open bodies (no volume)": "Offene Körper (ohne Volumen)",
"Open library...": "Bibliothek öffnen...",
"Open local libraries in the desktop app. You can export this design's library here.": "Öffnen Sie lokale Bibliotheken in der Desktop-App. Die Bibliothek dieses Designs können Sie hier exportieren.",
"Open surface — no volume": "Offene Oberfläche – kein Volumen",
"Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Quelloffenes parametrisches 3D-CAD in Rust. Lizenziert unter MIT OR Apache-2.0.",
"Opens your browser and copies the report text for you to paste into GitHub.": "Öffnet den Browser und kopiert den Berichtstext, den Sie anschließend auf GitHub einfügen können.",
"Opens your browser and copies the report text. Review and submit your report on GitHub.": "Öffnet den Browser und kopiert den Berichtstext. Prüfen Sie den Bericht und reichen Sie ihn anschließend auf GitHub ein.",
"Opens your browser. Review and submit your report on GitHub.": "Öffnet den Browser. Prüfen Sie den Bericht und reichen Sie ihn auf GitHub ein.",
"Operator": "Operator",
"Orbit (right drag)": "Drehen (mit rechts ziehen)",
@@ -461,6 +470,7 @@
"Radius": "Radius",
"Radius Fit": "Radiusanpassung",
"Ratio": "Verhältnis",
"Read-only list: shortcuts can't be changed here.": "Schreibgeschützte Liste: Tastenkürzel können hier nicht geändert werden.",
"Reattach selected face": "Ausgewählte Fläche neu anbinden",
"Recover": "Wiederherstellen",
"Recover unsaved design?": "Nicht gespeichertes Design wiederherstellen?",
@@ -487,6 +497,7 @@
"Reopen the designs that were open when SolidCraft was last closed": "Die Designs erneut öffnen, die beim letzten Beenden von SolidCraft geöffnet waren",
"Replace existing library file": "Vorhandene Bibliotheksdatei ersetzen",
"Report a bug — review a screenshot and open a GitHub draft": "Fehler melden — Screenshot prüfen und einen GitHub-Entwurf öffnen",
"Report copied. Keep this text to submit on GitHub when you are online.": "Bericht kopiert. Bewahren Sie diesen Text auf, um ihn auf GitHub einzureichen, sobald Sie online sind.",
"Report text copied. Paste it into GitHub and submit your report there.": "Berichtstext kopiert. Fügen Sie ihn auf GitHub ein und reichen Sie den Bericht dort ein.",
"Reports are public. A GitHub account is required.": "Berichte sind öffentlich. Ein GitHub-Konto ist erforderlich.",
"Reset Toolbar": "Symbolleiste zurücksetzen",
@@ -583,7 +594,10 @@
"Slider": "Schubgelenk",
"Slot width": "Langlochbreite",
"Snap to face and edge centres": "An Flächen- und Kantenmittelpunkten einrasten",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft kann Berichte nicht direkt einreichen. Reichen Sie Ihren Bericht auf GitHub ein. In der App wird dafür kein Token benötigt.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft wurde beendet, bevor diese Designs gespeichert wurden. Ihr letzter Autosave kann geöffnet werden.",
"Sort A to Z": "Von A bis Z sortieren",
"Sort Z to A": "Von Z bis A sortieren",
"Spacing 2 must be a non-zero number": "Abstand 2 muss eine Zahl ungleich null sein",
"Spacing must be a non-zero number": "Der Abstand muss eine Zahl ungleich null sein",
"Specialise": "Spezialisieren",
@@ -643,6 +657,7 @@
"Third point": "Dritter Punkt",
"This child is shared. Deleting it removes it from every copy of its parent.": "Dieses untergeordnete Element ist geteilt. Löschen entfernt es aus jeder Kopie des übergeordneten Elements.",
"This child is shared. Position changes apply to every copy of its parent.": "Dieses untergeordnete Element ist geteilt. Positionsänderungen gelten für jede Kopie des übergeordneten Elements.",
"This component is inside a subassembly placed more than once: aligning it moves it in every placement.": "Diese Komponente gehört zu einer Unterbaugruppe, die mehrfach platziert wurde. Beim Ausrichten wird sie an allen Platzierungen verschoben.",
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "Diese Fläche kann keine dauerhafte Anbindung bieten. Wählen Sie eine andere Fläche oder verwenden Sie ausdrücklich eine feste Ebene.",
"This image is not attached to GitHub yet. Copy or save it, then attach it on GitHub.": "Dieses Bild wurde noch nicht an GitHub angehängt. Kopieren oder speichern Sie es und fügen Sie es anschließend Ihrem GitHub-Bericht hinzu.",
"Thread depth": "Gewindetiefe",
@@ -763,6 +778,8 @@
"at the face": "an der Fläche",
"bom.group_children": "Unterteile in der Zeile ihres Teils",
"browser.body_name": "Körper {number}",
"browser.calibrate_click_a": "Klicken Sie im Bild auf die Position von Punkt A oder geben Sie dessen Koordinaten ein.",
"browser.calibrate_click_b": "Klicken Sie im Bild auf die Position von Punkt B oder geben Sie dessen Koordinaten ein.",
"browser.calibrate_title": "Kalibrieren: {name}",
"browser.capture_many": "{count} Komponenten wurden verschoben, ihre Positionen sind aber nicht erfasst.",
"browser.capture_note": "„Position erfassen“ behält die neue Position bei; „Position zurücksetzen“ stellt die vorherige wieder her.",
@@ -787,6 +804,8 @@
"camera.shot_pitch": "Nickwinkel",
"camera.shot_roll": "Rollwinkel",
"camera.shot_yaw": "Gierwinkel",
"canvas.decal_place_hint": "Klicken Sie auf eine Fläche, um das Dekal zu platzieren.",
"canvas.place_hint": "Klicken Sie auf eine Ebene oder Fläche, um die Zeichenfläche zu platzieren.",
"choose a profile": "ein Profil wählen",
"choose the cam's and the follower's joints": "die Gelenke von Nocken und Stößel wählen",
"choose the joints to couple": "die zu koppelnden Gelenke wählen",
@@ -803,6 +822,7 @@
"cmd.appearance_pick_up": "Aussehen aufnehmen",
"cmd.appearance_replace": "Erscheinungsbild ersetzen",
"cmd.assembly_bom": "Stückliste",
"cmd.body.print_face": "Auflagefläche",
"cmd.body_rename": "Körper umbenennen",
"cmd.browser_collapse_all": "Alles einklappen",
"cmd.browser_expand_all": "Alles ausklappen",
@@ -839,6 +859,7 @@
"cmd.component_move_bodies": "Körper in Komponente verschieben",
"cmd.component_move_features": "Merkmale in Komponente verschieben",
"cmd.component_move_sketches": "Skizzen in Komponente verschieben",
"cmd.component_paste_from_design": "Aus einem anderen Design einfügen",
"cmd.component_paste_new": "Neu einfügen",
"cmd.component_properties": "Physikalische Eigenschaften der Komponente",
"cmd.component_relink": "Quelle neu verknüpfen",
@@ -975,6 +996,7 @@
"cmd.motion_trace": "Bewegungsbahn",
"cmd.motion_trace_edit": "Bewegungsbahn bearbeiten",
"cmd.motion_trace_keep": "Bewegungsbahn behalten",
"cmd.occurrence_align": "Instanz ausrichten",
"cmd.occurrence_copy": "Einfügen (Instanz)",
"cmd.occurrence_drag": "Instanz ziehen",
"cmd.occurrence_ground": "Festsetzen",
@@ -1310,6 +1332,7 @@
"dialog.dimension_insert_hint": "Doppelklicken Sie auf eine andere steuernde Bemaßung, um ihren Namen einzufügen",
"dialog.dimension_name": "Bemaßung: {name}",
"dialog.edit_title": "{title} bearbeiten",
"dialog.no_shown_body": "Kein sichtbarer Körper zum Ändern vorhanden. Blenden Sie einen Körper ein oder wählen Sie „Neuer Körper“.",
"dialog.select": "↖ Auswählen",
"dialog.selected_count": "↖ {count} ausgewählt",
"dialog.selection_guidance": "Wählen Sie eine Eingabe aus, um sie zu ändern. Klicken Sie auf ausgewählte Objekte, um sie zu entfernen; Strg/Cmd-Klick schaltet sie ebenfalls um. Prüfen Sie die Vorschau und drücken Sie dann OK.",
@@ -1366,16 +1389,21 @@
"from the profile": "aus dem Profil",
"from the rule": "aus der Regel",
"help.apache": "Apache-Lizenz 2.0",
"help.appearance.assign": "Weist den ausgewählten Körpern, Flächen oder Komponenten eine Farbe oder ein Erscheinungsbild zu, ohne ihre Masse zu verändern. So können Sie Bauteile optisch unterscheiden oder das Aussehen einer Oberfläche vorab prüfen.",
"help.attributions": "Quellenangaben (ATTRIBUTION.md)",
"help.build": "Version {version} · Commit {commit} · erstellt am {date}",
"help.component.finish_edit": "Speichert das an Ort und Stelle bearbeitete Teil, kehrt zu seiner Baugruppe zurück und aktualisiert die Baugruppe daraus. Verwenden Sie es, wenn Ihre Änderungen am verknüpften Teil fertig sind.",
"help.connect_note": "Starten Sie jeden Assistenten neu, damit er den SolidCraft-Server lädt. Jede Datei wurde vor der Änderung als .bak gesichert.",
"help.connect_title": "KI-Assistenten verbinden",
"help.face.delete": "Entfernt die ausgewählten Flächen. Die angrenzenden Flächen schließen dabei die entstandene Lücke, oder sie bleibt als offene Fläche bestehen. So können Sie beispielsweise eine Rundung, eine Bohrung oder einen störenden Ansatz entfernen.",
"help.font_details": "Die Oberfläche verwendet die integrierten Schriftarten von egui (Ubuntu Light und Hack unter der Ubuntu Font Licence und der MIT/Bitstream-Vera-Lizenz; Noto Emoji und die Emoji-Symbolschrift unter OFL und MIT). Skizzentext verwendet Liberation Sans (SIL OFL 1.1). Das optionale Schriftpaket craft-fonts von The Crafting Apps (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1) ist nicht in SolidCraft-Builds enthalten.",
"help.fonts": "Schriftarten",
"help.licences": "Lizenzen",
"help.material.assign": "Legt fest, aus welchem Werkstoff die ausgewählten Körper bestehen, beispielsweise Stahl, Aluminium oder ABS. Dadurch entspricht die berechnete Masse derjenigen des realen Bauteils.",
"help.mit": "MIT-Lizenz",
"help.notice": "NOTICE",
"help.occurrence.move": "Bewegt eine Baugruppeninstanz ausschließlich entlang der Freiheitsgrade, die ihre Gelenke zulassen. Wählen Sie eine Fläche des Bauteils aus und ziehen Sie anschließend einen der verfügbaren Pfeile oder Bögen.",
"help.parameters.change": "Öffnet die Tabelle mit benannten Parametern und Ausdrücken, die die Abmessungen des Designs steuern. Ändern Sie einen Wert an einer zentralen Stelle, werden alle Features, die diesen Wert verwenden, entsprechend aktualisiert.",
"help.pointcloud.insert": "Fügt einen PLY-, XYZ- oder PTS-Scan als Punktwolke neben dem Modell ein. Verwenden Sie ihn als Referenz beim Modellieren anhand von Messdaten.",
"help.sketch.construction_line": "Erstellt eine unendliche Konstruktionslinie durch zwei Punkte, ohne Skizzenprofile zu teilen.",
"help.sketch.construction_line.angle": "Erstellt eine unendliche Konstruktionslinie in einem Winkel zur Skizzen-X-Achse. Punkt wählen und Winkel eingeben oder Richtung wählen.",
@@ -1388,19 +1416,32 @@
"help.sketch.construction_line.tangent_two": "Zwei Kreise oder Kreisbögen wählen, dann eine gemeinsame Tangente in der Vorschau. Die Berührpunkte müssen auf den gezeichneten Kurven liegen.",
"help.sketch.construction_line.vertical": "Erstellt eine unendliche vertikale Konstruktionslinie durch einen Punkt. Die Vertikalbedingung bleibt bei späteren Änderungen erhalten.",
"help.sketch.equation_curve": "Zeichnet eine Kurve aus Formeln über einen Bereich: x(t) und y(t) oder y = f(x). Die Formeln können Parameter verwenden, und die Kurve folgt ihnen: für eine Sinuswelle, eine Spirale oder die Evolvente eines Zahnrads.",
"help.sketch.inspect": "Öffnet einen Bericht zur offenen Skizze: wie viele Freiheitsgrade übrig sind und ob sie vollständig bestimmt ist. Damit sehen Sie, was sich noch bewegt, bevor Sie die Skizze abschließen.",
"help.solid.align": "Verschiebt die ausgewählten Körper so, dass ein Punkt, eine Fläche oder eine Ebene eines Körpers mit einem entsprechenden Punkt, einer Fläche oder Ebene an anderer Stelle zusammenfällt. So können Sie Bauteile aneinander ausrichten, ohne Abstände messen zu müssen.",
"help.solid.chamfer": "Schneidet die gewählten Kanten mit einer ebenen Fase im eingestellten Abstand ab. Damit brechen Sie scharfe Kanten oder erleichtern das Einführen eines Stifts oder einer Schraube.",
"help.solid.combine": "Kombiniert Körper: vereinigt sie, zieht sie von einem Zielkörper ab oder behält nur das gemeinsame Volumen; die Werkzeugkörper können erhalten bleiben. Damit fügen Sie Teile zusammen, schneiden eine Form mit einer anderen aus oder finden, wo sich zwei Körper überschneiden.",
"help.solid.draft": "Schrägt die ausgewählten Flächen um den eingestellten Winkel gegenüber einer neutralen Ebene ab. Diese Funktion wird bei Guss- und Spritzgussteilen verwendet, damit sich die Bauteile leichter aus der Form lösen lassen.",
"help.solid.extrude": "Zieht ein Skizzenprofil über eine Strecke in die dritte Dimension und fügt dabei Material hinzu oder entfernt es. Damit machen Sie aus einer flachen Kontur einen Block, eine Erhebung oder eine Tasche.",
"help.solid.fillet": "Rundet die gewählten Kanten mit einem glatten Kreisübergang im eingestellten Radius ab. Damit entschärfen Sie scharfe Kanten für Festigkeit, Griffigkeit oder Optik.",
"help.solid.hole": "Bohrt an einem gewählten Punkt auf einer Fläche ein rundes Loch im eingestellten Durchmesser. Für Durchgangsbohrungen für Schrauben, Stifte und andere einfache runde Öffnungen.",
"help.solid.loft": "Erzeugt einen glatten Übergangskörper zwischen zwei oder mehr Skizzenprofilen oder von einem Profil zu einem Punkt. Für Übergänge, Trichter, Griffe und alle Formen, deren Querschnitt sich über die Länge ändert.",
"help.solid.mirror": "Erzeugt eine gespiegelte Kopie der gewählten Körper oder Features an einer Ebene und kann sie mit dem Original vereinigen. Für symmetrische Teile: Sie modellieren eine Hälfte und erhalten beide.",
"help.solid.move": "Verschiebt oder dreht die ausgewählten Körper oder deren Kopien um den eingestellten Abstand und Winkel. So können Sie einen Körper neu positionieren oder an anderer Stelle eine Kopie erstellen.",
"help.solid.offset_face": "Versetzt die ausgewählten Flächen um den eingestellten Abstand nach innen oder außen. Die angrenzenden Flächen werden entsprechend angepasst. So können Sie beispielsweise eine Wand verdicken, eine Tasche vertiefen oder einen Ansatz erhöhen, ohne die Skizze bearbeiten zu müssen.",
"help.solid.pattern.circular": "Wiederholt die gewählten Objekte um eine Achse, mit der eingestellten Anzahl und dem Gesamtwinkel. Für Lochkreise, Speichen, Kühlrippen und Zähne.",
"help.solid.pattern.rectangular": "Wiederholt die gewählten Objekte in einer oder zwei Richtungen mit der eingestellten Anzahl und dem Abstand. Für Reihen und Raster von Bohrungen, Rippen, Stiften oder Füßen.",
"help.solid.press_pull": "Fügt Material hinzu oder trägt es ab, indem Sie eine Fläche ziehen. Durch Ziehen an einer Kante können Sie diese abrunden. So lassen sich schnelle Änderungen direkt am Modell vornehmen.",
"help.solid.remove": "Entfernt die ausgewählten Körper ab dieser Stelle der Timeline aus dem Design. In den vorherigen Schritten bleiben sie erhalten. So können Sie beispielsweise einen nicht mehr benötigten Hilfs- oder Schneidkörper entfernen.",
"help.solid.replace_face": "Verschiebt die ausgewählten ebenen Flächen auf eine Ebene oder eine parallele Fläche. Der Körper wird dabei entsprechend vergrößert oder verkleinert. So können Sie eine Fläche bündig zu einem anderen Bauteil oder einer Konstruktionsebene ausrichten.",
"help.solid.revolve": "Dreht ein Skizzenprofil um eine Achse und erzeugt so einen runden Körper. Für Wellen, Knöpfe, Schalen und alle Teile, die um eine Mittellinie rund sind.",
"help.solid.scale": "Skaliert die ausgewählten Körper mit einem festgelegten Faktor, gleichmäßig in alle Richtungen oder getrennt entlang der einzelnen Achsen. So können Sie beispielsweise importierte Geometrien anpassen oder Materialschwindung berücksichtigen.",
"help.solid.shell": "Höhlt einen Körper aus, lässt Wände in der eingestellten Dicke stehen und kann gewählte Flächen öffnen. Für Kästen, Deckel und andere dünnwandige Teile.",
"help.solid.split_body": "Teilt einen Körper mithilfe einer Ebene, der Fläche eines anderen Körpers oder einer Skizzenlinie in mehrere getrennte Körper. So können Sie Bauteile erstellen, die einzeln gedruckt, bearbeitet oder eingefärbt werden können.",
"help.solid.split_face": "Teilt die ausgewählten Flächen entlang einer Ebene, der Fläche eines anderen Körpers oder von Skizzenkurven, ohne den Körper selbst zu teilen. So können Sie Teilbereiche einer Fläche mit einer eigenen Formschräge, Farbe oder einem Versatz versehen.",
"help.solid.sweep": "Führt ein Skizzenprofil entlang eines Pfads und erzeugt so einen Körper. Für Rohre, Schienen, Griffe und alle Teile mit gleichbleibendem Querschnitt entlang einer Kurve.",
"help.third_party": "Drittanbieterlizenzen",
"help.timeline.compute_all": "Berechnet alle Features der Timeline von Anfang an neu. Dadurch wird das Modell aktualisiert und Sie sehen, bei welchen Features Berechnungsfehler auftreten.",
"help.timeline.delete": "Löscht die ausgewählten Features aus der Timeline. Beim Löschen einer Skizze werden auch alle daraus erstellten Features gelöscht. So können Sie nicht mehr benötigte Konstruktionsschritte entfernen.",
"help.timeline.play": "Die verbleibenden Features nacheinander abspielen, mit einer halben Sekunde Pause nach jedem Neuaufbau. Diesen Befehl erneut ausführen oder Escape drücken, um anzuhalten; Zum Ende springt sofort ans Ende.",
"history.sketch_under_constrained": "Unterbestimmt ({count} Freiheitsgrade)",
"history.sketch_under_constrained_one": "Unterbestimmt ({count} Freiheitsgrad)",
@@ -1504,6 +1545,21 @@
"joint.one_component": "Diese Teile sind Körper einer Komponente: {bodies}. Ein Gelenk bewegt eine Komponente gegenüber einer anderen, daher braucht jedes Teil eine eigene Komponente.",
"joint.rigid_group_one_component": "Diese {count} Körper liegen alle in einer Komponente (oder direkt im Design): Verwenden Sie zuerst {command}.",
"joint.rigid_group_select_components": "Wählen Sie zwei oder mehr Komponenten aus.",
"joint_move.blocked_hint": "Prüfen Sie den Gelenktyp (z. B. „Starr“), die Fixierung, die Gelenkgrenzen und die durch Parameter gesteuerten Werte.",
"joint_move.blocked_title": "Dieses Bauteil kann nicht bewegt werden",
"joint_move.coarse": "Drehschritt nahe der Achse (°)",
"joint_move.distance": "Abstand vom Ausgangspunkt",
"joint_move.done": "Fertig",
"joint_move.fine": "Drehschritt weit von der Achse entfernt (°)",
"joint_move.fixed": "Dieses Bauteil verfügt über keine bewegliche Gelenkachse.",
"joint_move.help": "Verschieben: Ziehen Sie einen Pfeil oder Ring. Mit Esc brechen Sie den aktuellen Ziehvorgang ab.",
"joint_move.legacy_hint": "Deaktivieren, um wieder den bisherigen Verschiebebereich zu verwenden.",
"joint_move.precision": "Ziehen Sie außerhalb der gestrichelten Führung, um die Drehung fein einzustellen.",
"joint_move.rotation": "Drehwinkel vom Ausgangspunkt",
"joint_move.select": "Wählen Sie eine Fläche oder eine Baugruppenkomponente aus und anschließend „Verschieben“.",
"joint_move.settings": "Baugruppe verschieben",
"joint_move.step": "Schrittweite",
"joint_move.use_handles": "Gelenkgriffe zum Bewegen von Baugruppen verwenden",
"keymap.command_id": "Befehls-ID",
"keymap.empty": "Keine passenden Befehle",
"keymap.use_anyway": "{key} hier trotzdem verwenden",
@@ -1563,6 +1619,10 @@
"material.selection.mixed": "Unterschiedliche Materialien",
"measure.at": "Bei",
"measure.item": "Element {number}",
"measure.quick_copied": "Messwert kopiert",
"measure.quick_copy": "Klicken, um den Messwert zu kopieren",
"measure.quick_edges": "{count} Kanten",
"measure.quick_faces": "{count} Flächen",
"messages.ago_h": "vor {n} Std.",
"messages.ago_min": "vor {n} Min.",
"messages.ago_s": "vor {n} Sek.",
@@ -1715,6 +1775,11 @@
"not a move": "keine Verschiebung",
"not a number": "keine Zahl",
"open a sketch first": "zuerst eine Skizze öffnen",
"parameters.apply_live": "Jede Änderung beim Verlassen des Feldes übernehmen",
"parameters.apply_live_tip": "Aus: Änderungen warten auf Übernehmen oder OK, dann wird das Modell einmal für alle neu aufgebaut. Abbrechen oder Esc verwirft sie.",
"parameters.held": "{count} Änderungen warten auf Übernehmen",
"parameters.held_hint": "Bearbeiten Sie Ausdrücke und Einheiten. Änderungen warten auf Übernehmen oder OK.",
"parameters.held_one": "1 Änderung wartet auf Übernehmen",
"parts.automatic_name": "automatisch",
"parts.dimension.across_flats": "Schlüsselweite",
"parts.dimension.bore": "Bohrung",
@@ -1737,6 +1802,8 @@
"parts.family.set_screw": "Gewindestift mit Innensechskant und Kegelstumpf",
"parts.family.socket_head_cap_screw": "Zylinderschraube mit Innensechskant",
"parts.family.washer": "Unterlegscheibe",
"parts.holes": "Bohrungen (optional)",
"parts.placement_hint": "Wählen Sie eine oder mehrere Bohrungen, um in jede ein Teil einzusetzen, oder OK platziert es im Ursprung",
"pattern.alignment_2": "Ausrichtung 2",
"pattern.alignment_hint": "Die Anzahl enthält das Original. Die zentrierte Anordnung verteilt Kopien auf beide Seiten; bei gerader Anzahl legt die Richtungsumkehr die Seite mit der zusätzlichen Kopie fest.",
"pattern.direction_1": "Richtung 1",
@@ -1777,6 +1844,8 @@
"prefs.anti_aliasing_note": "Glättet gezackte Kanten.",
"prefs.applies_on_restart": "Wird nach einem Neustart übernommen",
"prefs.assistant": "Assistent",
"prefs.auto_operation": "Operation automatisch auswählen",
"prefs.auto_operation_note": "Die Feature-Dialoge wählen automatisch zwischen „Verbinden“, „Neuer Körper“ und „Ausschneiden“, abhängig davon, was das neue Material berührt. Sobald Sie selbst eine Operation auswählen, wird diese Einstellung nicht mehr angewendet. Deaktiviert: Die beim Öffnen des Dialogs voreingestellte Operation wird beibehalten.",
"prefs.axes": "Achsen",
"prefs.axis": "Achse",
"prefs.bodies_folders": "Körperordner",
@@ -1787,6 +1856,8 @@
"prefs.bottom_centre": "Unten mittig",
"prefs.bottom_right": "Unten rechts",
"prefs.browser": "Browser",
"prefs.browser_eye_left": "Auge neben dem Aufklapppfeil",
"prefs.browser_eye_left_note": "Browserzeilen zeigen ihr Auge zum Ein- und Ausblenden wie bisher neben dem Aufklapppfeil statt in einer Spalte am rechten Zeilenende.",
"prefs.browser_reveal_on_hover": "Beim Überfahren einblenden",
"prefs.browser_show_note": "Angezeigt lässt den Browser auf dem Bildschirm. Beim Überfahren einblenden blendet ihn aus, bis der Zeiger über seinen Platz oder die schmale Lasche an seinem linken Rand kommt; er bleibt offen, solange der Zeiger darin ist oder ein Menü daraus offen ist.",
"prefs.browser_shown": "Angezeigt",
@@ -1837,6 +1908,8 @@
"prefs.graphics_card_restart": "Diese Einstellung wird nach einem Neustart von SolidCraft wirksam. Die Umgebungsvariable WGPU_ADAPTER_NAME hat Vorrang vor dieser Auswahl.",
"prefs.graphics_note": "Wägen Sie Leistung und Darstellungsqualität gegeneinander ab. Jede Option veranschaulicht ihre Wirkung anhand eines Beispiels.",
"prefs.graphics_presets_note": "Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
"prefs.hide_constraint_glyphs_zoomed_out": "Bindungssymbole bei starkem Herauszoomen ausblenden",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
"prefs.history": "Verlauf",
"prefs.history_origin_bodies": "Ursprung und Körper oben zeigen",
"prefs.history_origin_bodies_note": "Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
@@ -1863,6 +1936,11 @@
"prefs.line_drag_draws_one": "Drücken und Ziehen zeichnet eine Linie",
"prefs.line_drag_draws_one_note": "Im Linienwerkzeug zeichnet Drücken, Ziehen und Loslassen eine Linie vom Drückpunkt bis zum Loslassen, und dort endet sie. Klicks zeichnen weiterhin eine Kette. Aus: Ein Ziehen setzt wie ein Klick einen Punkt dort, wo losgelassen wird.",
"prefs.linked_designs": "Verknüpfte Designs",
"prefs.live_input": "Live-Eingabe",
"prefs.live_preview": "Live-Vorschau",
"prefs.logo": "Logo",
"prefs.logo_accent": "Akzentfarbe",
"prefs.logo_theme": "Designfarben",
"prefs.max_file_size": "Maximale Dateigröße zum Öffnen",
"prefs.max_file_size_design": "Designdateien",
"prefs.max_file_size_flag": "Dieser Lauf wurde mit --max-file-size {size} gestartet. Der Wert gilt, bis SolidCraft geschlossen wird. Diese Einstellungen gelten beim Start ohne diese Option.",
@@ -1873,12 +1951,33 @@
"prefs.menu_grid": "Große Symbole",
"prefs.menu_list": "Liste",
"prefs.menus": "Menüs",
"prefs.minutes": "Minuten",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Maus und Trackpad",
"prefs.mouse_heading": "Maus",
"prefs.mouse_mapping": "Mausbelegung",
"prefs.navigation": "Navigation",
"prefs.navigation_note": "Legt fest, wie Maus, Trackpad und 3D-Maus die Ansicht steuern.",
"prefs.network_host_remove": "Entfernen",
"prefs.network_locations": "Netzwerkspeicherorte in Designs zulassen",
"prefs.network_locations_note": "Deaktiviert: Verknüpfte Dateien eines Designs auf einem anderen Computer (\\\\server\\share) werden nur von den hier aufgeführten Servern geöffnet oder nachdem Sie einen Server auf Nachfrage ausdrücklich zugelassen haben.",
"prefs.network_locations_section": "Netzwerkspeicherorte",
"prefs.not_on_gpu": "Auf dieser GPU nicht verfügbar",
"prefs.on": "An",
"prefs.opaque": "Undurchsichtig",
"prefs.orbit_snap": "Halten Sie beim Drehen die Umschalttaste gedrückt, um die Ansicht an einer Achse einrasten zu lassen.",
"prefs.orientation": "Ausrichtung",
"prefs.origin_plane_names": "Namen der Ursprungsebenen",
"prefs.origin_plane_names_note": "Namen in Browser, Dialogen und Listen. {top} ist XY, {front} ist XZ und {right} ist YZ, mit Z nach oben. Befehle, Skripte und gespeicherte Konstruktionen behalten XY, XZ und YZ.",
"prefs.overall_speed": "Gesamtgeschwindigkeit",
"prefs.persistent_ribbon_pressed": "Aktive Menübandbefehle hervorheben",
"prefs.persistent_ribbon_pressed_help": "Hebt Befehle im Menüband hervor, solange ihre Effekte eingeschaltet sind. Deaktivieren Sie diese Option, um die bisherige neutrale Darstellung und Befehlshilfe beizubehalten.",
"prefs.perspective_fov": "Sichtfeld",
"prefs.placement": "Platzierung",
"prefs.prefer_free_sketch_points": "Bei überlappenden Punkten bewegliche Skizzenpunkte bevorzugen",
"prefs.preview_choices": "Vorschau Ihrer Einstellungen",
"prefs.print_face_highlight": "Hervorhebung der Auflagefläche",
"prefs.print_face_highlight_tip": "Wie stark die Auflagefläche eines Körpers in der Ansicht eingefärbt und umrandet wird. 0 % zeigt keine Hervorhebung.",
"prefs.profile_active": "Aktives Profil",
"prefs.profile_applied": "Profil angewendet.",
"prefs.profile_changed": "Die Einstellungen wurden seit dem Speichern des Profils geändert. „Speichern“ übernimmt sie ins Profil.",
@@ -1896,10 +1995,32 @@
"prefs.profiles_note": "Tastenkürzel, Werkzeugleiste, Navigation, Radialmenü und Oberflächeneinstellungen, gemeinsam unter einem Namen gespeichert und auf einmal umgeschaltet.",
"prefs.project_options_dialog": "Project-Optionendialog",
"prefs.project_options_dialog_note": "Ein: Vor dem Projizieren Geometrie oder Körper und die Projektionsverknüpfung auswählen. Aus: Den bisherigen Direktwahl-Ablauf beibehalten.",
"prefs.quality_default": "Standard",
"prefs.quality_fastest": "Am schnellsten",
"prefs.quality_slowest": "Am langsamsten",
"prefs.quality_smoother": "Stärker geglättet",
"prefs.quick_measure": "Schnellmessung in der Ecke der Ansicht",
"prefs.quick_measure_note": "Zeigt unten rechts in der Ansicht die Größe der Auswahl an: bei einer Kante die Länge, bei einer kreisförmigen Kante zusätzlich Durchmesser (Ø) und Radius (R), bei einer Fläche den Flächeninhalt, bei einem Punkt die Position, bei zwei Elementen Abstand und Winkel sowie bei mehreren Kanten oder Flächen die Gesamtlänge bzw. Gesamtfläche. Ein Klick kopiert den Messwert.",
"prefs.render_previous_geometry_budget": "Bisheriges Geometriebudget für die Render-Vorschau verwenden",
"prefs.render_previous_geometry_budget_help": "Stellt die bisherige feste Obergrenze für die Geometrie wieder her (512 MiB auf 64-Bit-Desktop-Systemen, andernfalls 128 MiB) anstelle des geräteabhängigen Budgets.",
"prefs.reports_saved": "{n} gespeichert",
"prefs.ribbon_reveal": "Suchergebnisse im Menüband zeigen",
"prefs.ribbon_reveal_note": "Steht der Zeiger über einem Ergebnis in Befehle suchen, zeigt das Menüband, wo der Befehl liegt: seine Registerkarte, seine Schaltfläche oder seine Zeile im Aufklappmenü eines Panels. Ein aus der Suche gestarteter Befehl blinkt dort, und ein laufender Befehl aus einem Aufklappmenü markiert sein Panel.",
"prefs.right_click": "Rechtsklick",
"prefs.saving": "Automatisches Speichern und Wiederherstellung",
"prefs.scale_note": "Die Skalierung lässt sich auch mit Strg + / Strg − im Fenster ändern. Bei 100 % sind die Schaltflächen 28 px groß.",
"prefs.scale_text": "Skalierung und Text",
"prefs.search": "Einstellungen suchen",
"prefs.search_empty": "Keine passenden Einstellungen",
"prefs.select": "Auswählen",
"prefs.select_one_item": "Ein Element auswählen",
"prefs.select_other_hidden": "Verborgene Geometrie mit „Andere auswählen“ auswählen",
"prefs.select_other_hidden_help": "Ein: Verborgene Körper, Skizzen und verdeckte Kanten nahe dem Zeiger einbeziehen. Aus: Die ursprüngliche Liste sichtbarer Geometrie direkt unter dem Zeiger verwenden.",
"prefs.selecting": "Auswahl",
"prefs.shift": "Umschalt",
"prefs.shift_add_ctrl_toggle": "Umschalt fügt nur hinzu; Strg / Cmd schaltet die Auswahl um",
"prefs.shift_add_note": "Aktiviert: Umschalt fügt Elemente zur Auswahl hinzu, Strg / Cmd schaltet ihre Auswahl um. Deaktiviert (Standard): Beide Tasten schalten die Auswahl um.",
"prefs.shortcuts": "Tastenkürzel",
"prefs.single_surface_analysis": "Nur eine Flächenanalyse verwenden",
"prefs.single_surface_analysis_help": "Wenn deaktiviert, können Flächenschattierung und Stetigkeitsanalyse gleichzeitig aktiv bleiben. Beim Aktivieren bleibt die zuletzt gestartete oder geänderte Analyse erhalten.",
"prefs.sketch_align_points": "Beim Zeichnen an anderen Skizzenpunkten ausrichten",
@@ -1907,16 +2028,29 @@
"prefs.sketch_conflict_colour": "Widersprüchliche Geometrie in der Konfliktfarbe",
"prefs.sketch_conflict_colour_note": "Solange die Bedingungen einer Skizze im Konflikt stehen, werden auch die Kurven und Punkte, die eine nicht erfüllte Bedingung hält, in der Konfliktfarbe gezeichnet, nicht nur ihr Symbol oder ihre Bemaßung. Aus: Sie behalten ihre üblichen Farben.",
"prefs.sketch_construction_lines": "Erweiterte Konstruktionslinien-Werkzeuge",
"prefs.sketch_exit_view": "Ansicht nach dem Beenden einer Skizze",
"prefs.sketch_exit_view.keep": "Aktuelle Ansicht beibehalten",
"prefs.sketch_exit_view.previous": "Zur vorherigen Ansicht zurückkehren",
"prefs.sketch_exit_view.unless_rotated": "Zurückkehren, sofern nicht gedreht",
"prefs.sketch_exit_view_note": "Legt fest, zu welcher Ansicht nach „Skizze beenden“, „Abbrechen“ oder zweimaligem Drücken der Esc-Taste gewechselt wird.",
"prefs.sketch_open_look_at": "Beim Öffnen einer Skizze auf sie ausrichten",
"prefs.sketch_open_look_at_note": "Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Ansehen“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
"prefs.sketch_recent_snap": "Automatisch an zuletzt verwendete Mittelpunkte fangen",
"prefs.sketch_recent_snap_note": "Fängt den Zeiger beim Zeichnen dort ein, wo sich die Hilfslinien der beiden zuletzt verwendeten Kreismittelpunkte oder Kantenmitten kreuzen. Aus: die Hilfslinien bleiben sichtbar und der Zeiger bewegt sich nicht.",
"prefs.sketch_selection": "Skizzen und Auswahl",
"prefs.sketch_selection_heading": "Skizzen und Auswahl",
"prefs.sketch_selection_note": "Legt fest, wie Skizzenelemente einrasten und wie Klicks Elemente auswählen.",
"prefs.sketch_state_icons": "Skizzensymbole in der Farbe ihres Zustands",
"prefs.sketch_state_icons_note": "Im Verlauf und im Browser nimmt das Symbol einer Skizze die Farbe ihres Zustands an (unterbestimmt, vollständig bestimmt oder im Konflikt), und ihr Tooltip nennt den Zustand. Aus: Alle Skizzen haben dasselbe Symbol.",
"prefs.sketch_tab_always": "Registerkarte Skizze immer zeigen",
"prefs.sketch_tab_always_note": "Die Registerkarte Skizze bleibt im Menüband direkt nach Modell, auch wenn keine Skizze offen ist. Ihre Zeichenwerkzeuge beginnen dann eine Skizze auf der ausgewählten Ebene oder Fläche oder auf der Ebene, die Sie als Nächstes wählen, und das Werkzeug ist darin bereit.",
"prefs.sketch_tracking_guides": "Ausrichtungshilfen für Mittelpunkte",
"prefs.sketch_tracking_guides_note": "Den Mauszeiger über einen Kreismittelpunkt oder eine Kantenmitte bewegen und dann waagerecht oder senkrecht bewegen, um eine temporäre gepunktete Hilfslinie anzuzeigen.",
"prefs.sketching": "Skizzieren",
"prefs.snap_drawing_note": "Gilt beim Zeichnen oder Verschieben eines Skizzenpunkts.",
"prefs.source": "Quelle",
"prefs.speed": "Geschwindigkeit",
"prefs.stand_up_note": "Wenn eine STEP- oder IGES-Datei mit der Z-Achse nach oben geöffnet wird, wird eine Vierteldrehung um die X-Achse angeboten.",
"prefs.stand_up_unknown": "Für Dateien aus unbekannten Programmen",
"prefs.stand_up_unknown.ask": "In einem Dialog fragen",
"prefs.stand_up_unknown.note": "Einen Hinweis zeigen",
@@ -1924,6 +2058,13 @@
"prefs.stand_up_unknown_note": "Wie das Aufrichten angeboten wird, wenn der Dateikopf kein Programm nennt, das bekanntlich mit Y nach oben speichert. Ein Hinweis erscheint nur, wenn die Datei auf dem Rücken zu liegen scheint; „In einem Dialog fragen“ fragt wie bisher bei jeder solchen Datei.",
"prefs.status_bar": "Statusleiste",
"prefs.status_bar_note": "Eine schmale Leiste am unteren Fensterrand zeigt, was gerade bearbeitet wird, den Zustand der offenen Skizze in seiner Farbe und die Einheiten der Konstruktion; ein Klick auf die Einheiten ändert sie. Der Zustand der Skizze steht dann nicht mehr in der Ecke der Ansicht.",
"prefs.status_done": "Fertig",
"prefs.status_error": "Fehler",
"prefs.status_selected": "Ausgewählt",
"prefs.status_warning": "Warnung",
"prefs.supersampling_brief": "Rendert die Ansicht größer und verkleinert sie dann: sehr scharf, aber rechenintensiver.",
"prefs.tab_accent_note": "Die Akzentfarbe richtet sich nach dem aktiven Tab.",
"prefs.theme_file": "Designdatei",
"prefs.theme_file_or_paste": "Datei oder Einfügen",
"prefs.theme_follow": "Einer Designdatei folgen",
"prefs.theme_follow_error": "Nicht angewendet: {error}. Das letzte gültige Design bleibt aktiv.",
@@ -1934,21 +2075,46 @@
"prefs.theme_import_hint": "Klicken, um eine Designdatei zu wählen, eine hierher ziehen oder mit kopiertem Design Strg+V (Cmd+V auf dem Mac) drücken",
"prefs.theme_paste": "Design einfügen",
"prefs.theme_paste_none": "Die Zwischenablage enthält kein SolidCraft-Design",
"prefs.timeline_half_hour": "−30 min",
"prefs.timeline_now": "Jetzt",
"prefs.timeline_quarter_hour": "−15",
"prefs.toast_place": "Hinweise",
"prefs.toggle_item": "Element hinzufügen oder wieder entfernen",
"prefs.toolbar": "Werkzeugleiste",
"prefs.toolbar_tabs": "Werkzeugleisten-Registerkarten",
"prefs.top": "Oben",
"prefs.trackpad_zoom_modifier": "Mit zwei Fingern zoomen bei gedrückter Taste",
"prefs.trackpad_zoom_modifier.alt": "⌥ / Alt",
"prefs.trackpad_zoom_modifier.cmd": "⌘",
"prefs.trackpad_zoom_modifier.ctrl": "Strg",
"prefs.trackpad_zoom_modifier.help": "Nur diese Taste gedrückt halten, um durch vertikales Scrollen am Zeiger zu zoomen. Aus behält das bisherige Scrollverhalten bei.",
"prefs.trackpad_zoom_modifier.off": "Aus",
"prefs.translucent": "Durchscheinend",
"prefs.under_browser": "Unter dem Browser",
"prefs.under_cursor": "Unter dem Cursor",
"prefs.units_precision": "Einheiten und Genauigkeit",
"prefs.updates": "Updates",
"prefs.value_boxes_in_view": "Werte in der Ansicht neben ihren Griffen zeigen",
"prefs.value_boxes_in_view_tip": "Der Wert eines Befehls (Abstand, Radius …) in einem Feld neben seinem Griff, über dem Modell. Aus: Sie geben ihn im Befehlsfenster ein, das nach einer Auswahl die Tastatur erhält, und nichts verdeckt das Modell.",
"prefs.view": "Ansichtsverhalten",
"prefs.view_confirm_corner": "OK und Abbrechen in der Ansichtsecke",
"prefs.view_confirm_corner_note": "Solange ein Dialog oder eine Skizze geöffnet ist, übernehmen ein Häkchen und ein Kreuz oben rechts in der Ansicht dieselben Funktionen wie „OK“ und „Abbrechen“ des Dialogs. Eine Skizze wird damit beendet oder verworfen. Der Ansichtsnavigator rückt darunter.",
"prefs.viewport": "Ansichtsfenster",
"prefs.viewport_analysis_status": "Analysestatus im Ansichtsfenster anzeigen",
"prefs.viewport_analysis_status_help": "Zeigt aktive Analysen über dem Ansichtsfenster und ihre Legenden am jeweiligen Eintrag. Esc beendet die neueste Analyse. Ausschalten stellt Ecklegenden und das bisherige Esc-Verhalten wieder her.",
"prefs.viewport_background": "Hintergrund",
"prefs.viewport_background_colour": "Farbe",
"prefs.viewport_background_theme": "Verlauf des Designs",
"prefs.viewport_section_status": "Schnittstatus im Ansichtsfenster anzeigen",
"prefs.viewport_section_status_help": "Die Schnitteinstellung im Ansichtsfenster anzeigen. Escape und die aktive Schaltfläche schalten den Schnitt aus. Deaktivieren, um die bisherigen Schnittbedienelemente zu behalten.",
"prefs.viewport_status_summary": "Status im Ansichtsfenster zusammenfassen",
"prefs.viewport_status_summary_help": "Drei oder mehr Statuselemente in einer Zusammenfassung anzeigen. Deaktivieren, um jedes Element in der vollständigen Zeile zu behalten.",
"prefs.wheel": "Mausrad",
"prefs.wheel_trackpad": "Mausrad und Trackpad",
"prefs.workspace": "Arbeitsbereich",
"prefs.zoom_stop_at_model": "Zoom am Modell stoppen",
"prefs.zoom_stop_at_model_tip": "3D-Maus und Geräte-App zoomen zur Oberfläche in der Ansichtsmitte; Zieh-Zoom hält den Punkt am Beginn des Ziehens fest. Aus: Zoom kann durch das Modell gehen. Mausrad und Pinch verwenden separat Zoom zum Mauszeiger",
"prefs.zoom_toward_cursor": "Zoomen zum Mauszeiger hin",
"properties.title": "Eigenschaften: {name}",
"recovery.days_ago": "vor {count} Tagen",
"recovery.detail": "{file} · {features} Merkmale · automatisch gespeichert {when}",
@@ -1960,7 +2126,44 @@
"recovery.just_now": "gerade eben",
"recovery.min_ago": "vor {count} Min.",
"recovery.never_saved": "nie gespeichert",
"render.adaptive_absolute": "Absolute RGB-Fehleruntergrenze",
"render.adaptive_coverage": "Zielwert für den Abdeckungsfehler",
"render.adaptive_enable": "Samples mithilfe unabhängiger Pilot-Samples zuteilen",
"render.adaptive_help": "Nur GPU. Unabhängige Pilot-Samples werden verworfen. Anschließend werden pro Pixel 16 oder mehr Produktions-Samples zugeteilt, bis zu den Samples pro Pixel. Die Zuteilung richtet sich nach der RGB- und Abdeckungsvarianz; seltene Pfade können übersehen werden, und Pixel, die ihr Limit erreicht haben, können verrauscht bleiben. Der Abschlussbericht zählt den tatsächlichen Aufwand. Keine zertifizierte Garantie für ein bestimmtes Rauschniveau oder eine bestimmte Renderzeit.",
"render.adaptive_minimum": "Minimale Produktions-Samples",
"render.adaptive_pending": "Die adaptive Zuteilung mit unabhängigen GPU-Pilot-Samples und bedingte Fresnel-Schichten sind verfügbar. Zertifizierte Konvergenz, Garantien für seltene Ereignisse und adaptiver Lichttransport auf der CPU bleiben nicht verfügbar.",
"render.adaptive_pilots": "Pilot-Samples pro Pixel",
"render.adaptive_relative": "Zielwert für den relativen RGB-Fehler",
"render.add_ground": "Add fitted ground to setup",
"render.advanced_material_scope": "Ein endliches, homogenes GPU-Medium ist innerhalb ausdrücklich festgelegter Grenzen verfügbar. Dichtegitter, verschachtelte Glas-/Volumen-Kombinationen, Subsurface-Oberflächen, Displacement, spektraler Lichttransport und nicht zugeordnete erweiterte Materialkonzepte bleiben nicht verfügbar.",
"render.aging_add": "Alterungsschicht hinzufügen",
"render.aging_airborne": "Luftgetragener Staub / teilnehmendes Medium",
"render.aging_airborne_help": "Luftgetragener Staub bildet ein separates, endliches und homogenes Streuvolumen. Er beeinflusst den Lichttransport und das unbearbeitete EXR-Bild, nicht jedoch die Oberflächenalterungsschichten. Die bestehenden Einschränkungen für Volumen, Glas und Entrauschen gelten weiterhin. Eine Atmosphäre wird nicht automatisch hinzugefügt.",
"render.aging_all_bodies": "Alle geeigneten undurchsichtigen Körper",
"render.aging_chips": "Abgeplatzter Lack",
"render.aging_class_any": "Alle undurchsichtigen Materialklassen",
"render.aging_class_dielectric": "Nur nichtmetallische Oberflächen",
"render.aging_class_metal": "Nur metallische Oberflächen",
"render.aging_color": "Schichtfarbe · sRGB",
"render.aging_coverage": "Abdeckung",
"render.aging_dust": "Abgelagerter Staub",
"render.aging_empty": "Keine Alterungsschichten: Die ursprünglichen Oberflächen bleiben unverändert.",
"render.aging_fading": "Ausgeblichene Beschichtung",
"render.aging_grime": "Schmutz und Verfärbungen",
"render.aging_help": "Bis zu acht optische Schichten der Reihe nach kombinieren. Rost und Patina sind gestaltete Erscheinungsbilder, keine Simulationen von Alterung oder Korrosion. Die Mustergröße wird in Weltmillimetern angegeben. Geometrie, Masse und technische Eigenschaften bleiben unverändert; Glas und passive Bühnenflächen sind ausgenommen.",
"render.aging_metallic": "Metallanteil der Schicht",
"render.aging_origin": "Feldursprung · Weltkoordinaten (mm)",
"render.aging_patina": "Kupfergrüne Patina",
"render.aging_refresh_targets": "Aktuelle Körper prüfen",
"render.aging_roughness": "Rauheit der Schicht",
"render.aging_rust": "Oxidiertes Eisen / Rost",
"render.aging_scale": "Mustergröße",
"render.aging_scratches": "Feine Kratzer",
"render.aging_target": "Ziel und Koordinaten",
"render.aging_target_help": "Ein benanntes Ziel muss weiterhin zu seinem erfassten Index und Namen passen. Globale Masken sind im Weltkoordinatensystem verankert und folgen bewegten Teilen nicht. Die Funktion erkennt weder Hohlräume noch echten Kantenverschleiß. Filtern Sie die Liste, um weitere Körper zu erreichen.",
"render.aging_target_search": "Körpernamen filtern",
"render.aging_upward": "Ablagerung auf nach oben zeigenden Flächen",
"render.all_controls_help": "Kompakte Übersicht aller aktuellen Einstellungen. Die Abschnittsseiten erlauben eine gezielte Bearbeitung, ohne die zugrunde liegende Anfrage zu ändern.",
"render.all_shots_label": "Alle Aufnahmen",
"render.appearance_editor.absorption": "Absorption",
"render.appearance_editor.base_colour": "Grundfarbe",
@@ -2001,18 +2204,64 @@
"render.appearance_editor.v_repeat": "V-Wiederholung",
"render.appearance_editor.v_up": "Textur-V zeigt nach oben",
"render.appearance_editor.weight": "Gewicht",
"render.appearance_physics_separate": "Das visuelle Aussehen weist weder Dichte noch thermische Daten oder andere technische Eigenschaften zu.",
"render.appearances": "Erscheinungsbilder",
"render.assign_help": "Klicken wendet den Look auf die Auswahl an. Ziehen zeigt eine Vorschau; ohne Auswahl startet Malen.",
"render.availability_native": "Native Zusammenstellung; vor dem Anwenden prüfen",
"render.availability_projection": "Unterstützte Projektion; Umfang und Auslassungen beachten",
"render.availability_reference": "Referenzdaten; keine automatische Zuweisung",
"render.backdrop_alpha_conflict": "Wählen Sie entweder diesen Hintergrund oder die Ausgabe als transparentes PNG; der Renderer ignoriert keine der beiden Einstellungen stillschweigend.",
"render.backdrop_enable": "Verlaufshintergrund nur für die Kamera verwenden",
"render.backdrop_first": "Start-/Mittelfarbe",
"render.backdrop_help": "Dieser Hintergrund im Bildschirmraum beleuchtet nicht, wirft keine Schatten, spiegelt nicht und stellt kein Gelände dar. Er ist im unbearbeiteten EXR-Bild nicht enthalten. Geometrie, Lichter und die HDR-Umgebung bleiben unabhängig.",
"render.backdrop_horizontal": "Horizontaler Verlauf",
"render.backdrop_radial": "Radialer Verlauf",
"render.backdrop_radius": "Radius",
"render.backdrop_second": "End-/Randfarbe",
"render.backdrop_vertical": "Vertikaler Verlauf",
"render.backdrop_x": "Mittelpunkt X",
"render.backdrop_y": "Mittelpunkt Y",
"render.background": "Szenenhintergrund",
"render.background_camera_section": "Kamerahintergrund · PNG-Darstellung",
"render.background_completed_frozen": "Das angezeigte Ergebnis ist ein eingefrorenes, fertiges Bild. Eine Änderung des Hintergrunds zeichnet dieses Ergebnis nicht automatisch neu. Verwenden Sie „Neu graden“ für reine Kameraänderungen oder „Rendern“ für eine neue physische Bühne.",
"render.background_hdr_separate": "Die aktuellen HDR-Ressourcen beleuchten Oberflächen und spiegeln sich darin; sie werden nicht automatisch als Panorama hinter dem Motiv angezeigt. Das Ändern eines Verlaufs dreht oder verfärbt die Beleuchtung nicht.",
"render.background_lighting_section": "HDR-Beleuchtung · getrennt vom Kamerahintergrund",
"render.background_no_alpha_override": "Ein Kamerahintergrund wird einer transparenten Ausgabe nicht stillschweigend hinzugefügt. Beim Neu-Graden des linearen Rohbilds wird die erfasste Abdeckung verwendet und das Entrauschen nicht erneut angewendet; das Quell-EXR bleibt unverändert.",
"render.background_not_light": "Die Hintergrundfarbe dient der Darstellung und ist keine zusätzliche Lichtquelle.",
"render.background_regrade": "Auf das behaltene lineare Rohbild anwenden",
"render.background_stage_occlusion": "Diese Aufnahme enthält physische Bühnengeometrie. Ein großer Boden oder eine große Wand kann das Bild füllen und einen reinen Kamerahintergrund verdecken. Entfernen oder verkleinern Sie die Bühne gezielt oder ändern Sie ihren physikalischen Reflexionsgrad. Neu graden kann bereits erfasste Geometrie nicht entfernen.",
"render.background_workflow": "Wählen Sie den gewünschten Hintergrund: eine reine Kamerafarbe bzw. einen Verlauf, eine physische Bühne, die Licht empfängt, oder HDR-Beleuchtung. Sie sind voneinander unabhängig. Die Live-Vorschau ist eine Modellieransicht; rendern Sie ein neues Bild oder graden Sie ein behaltenes lineares Rohbild neu, um die Nachbearbeitung der Kamera zu sehen.",
"render.base_appearance": "Bearbeitbares Aussehen des Körpers",
"render.base_apply": "Aussehen des Körpers anwenden",
"render.base_apply_help": "Eine rückgängig machbare visuelle Zuweisung an genau den geprüften Körper. Bestehende flächenspezifische Looks und technische Materialien bleiben davon getrennt.",
"render.base_color": "Grundfarbe · sRGB",
"render.base_metallic": "Metallanteil",
"render.base_opaque_only": "Dieser Editor lässt Körper undurchsichtig. Für optische Transmission verwenden Sie die gesonderten Glaseinstellungen.",
"render.bloom_enable": "Darstellungs-Bloom",
"render.bloom_help": "Kreativer Ausgabeglanz aus linearen Glanzlichtern mit drei normierten Durchläufen. Radius in Ausgabepixeln; Addition vor sRGB. Keine Linsen-, Volumen- oder Konvergenzsimulation. Das rohe EXR bleibt unverändert.",
"render.bloom_opaque": "Bloom benötigt eine deckende Ausgabe. Transparentes PNG wird abgelehnt, nicht still abgeflacht.",
"render.bloom_radius": "Bloom-Radius",
"render.bloom_strength": "Bloom-Stärke",
"render.bloom_threshold": "Linearer Glanzlichtschwellwert",
"render.browse_capabilities": "Vollständigen Funktionskatalog ansehen",
"render.browse_environments": "Originale HDR-Umgebungen durchsuchen",
"render.browse_looks": "Durchsuchen…",
"render.browse_native_scenes": "Native Studios durchsuchen",
"render.browse_physical": "Physikalische Referenzen ansehen",
"render.browse_quality": "Qualitätsspezifikationen ansehen",
"render.browse_rigs": "Erstellte Lichtaufbauten durchsuchen",
"render.browse_scene_specs": "Erweiterte Szenenspezifikationen durchsuchen",
"render.browse_surfaces": "Erstellte Oberflächen durchsuchen",
"render.browser_library": "Bibliothek",
"render.browser_model": "Modell",
"render.camera_distance": "Abstand zwischen Kamera und Ziel",
"render.camera_exact_help": "Weltmillimeter; Winkel werden in Grad angezeigt und als Bogenmaß gespeichert. Die Navigation verwendet dieselbe Kamera wie die Ansicht.",
"render.camera_fov": "Vertikaler Bildwinkel",
"render.camera_pitch": "Kameraneigung",
"render.camera_roll": "Rollwinkel",
"render.camera_set": "Render-Kamera einstellen",
"render.camera_target": "Zielposition · mm",
"render.camera_yaw": "Gierwinkel",
"render.category_metal": "Metall",
"render.category_paint": "Lack",
"render.category_plastic": "Kunststoff",
@@ -2096,7 +2345,22 @@
"render.children.vignette": "Vignette",
"render.children.workstation": "Workstation",
"render.choose_look": "Zuerst einen Look in der Bibliothek wählen",
"render.chromatic_offset": "Chromatischer Kantenversatz",
"render.clear_stage": "Remove saved stage",
"render.copy_request": "JSON kopieren",
"render.cpu_aa_help": "Räumliche Samples: Mehr Samples verbessern die Kantenglättung, verlängern jedoch die Renderzeit.",
"render.cpu_area_add": "Rechteckiges CPU-Flächenlicht hinzufügen",
"render.cpu_area_editor": "Rechteckige CPU-Flächenlichter",
"render.cpu_area_editor_help": "Vollständige Seitenvektoren in Millimetern; das Kreuzprodukt U × V zeigt zur abstrahlenden Seite. Das unterscheidet sich von den halben Kantenvektoren der GPU.",
"render.cpu_area_full_u": "Volle Seite U · mm",
"render.cpu_area_full_v": "Volle Seite V · mm",
"render.cpu_area_samples": "Flächenlicht-Samples",
"render.cpu_binding_confirm": "Diesen Körper bestätigen",
"render.cpu_binding_stale": "Die Szene hat sich geändert. Bestätigen Sie dieses Reflexionsziel vor dem Rendern erneut.",
"render.cpu_body_choose": "Körper wählen",
"render.cpu_body_filter": "Körper nach Namen filtern",
"render.cpu_body_filter_more": "Verfeinern Sie den Filter, um weitere Körper zu sehen.",
"render.cpu_body_missing": "Ein Reflexionsziel fehlt, ist ausgeblendet, mehrdeutig oder hat einen Namen von mehr als 128 Byte. Wählen Sie es erneut.",
"render.cpu_budget": "CPU-Ressourcenprofil",
"render.cpu_budget_bodies": "Körperobergrenze",
"render.cpu_budget_compact": "Kompakt (bisherige Grenzen)",
@@ -2108,7 +2372,77 @@
"render.cpu_budget_studio": "Studio (große Bilder)",
"render.cpu_budget_triangles": "Dreiecksobergrenze",
"render.cpu_budget_workstation": "Workstation (komplexe Szenen)",
"render.cpu_captured_hdr": "Für dieses Bild erfasstes HDR",
"render.cpu_check_capture_help": "Prüfen und Starten verifizieren dieselbe Datei-Prüfsumme oder verwenden das eingebettete HDR. Jedes Bild behält seine erfasste Quelle.",
"render.cpu_check_changed": "Die Einstellungen oder das Modell haben sich nach dieser Prüfung geändert. Prüfen Sie erneut, bevor Sie rendern.",
"render.cpu_constant_help": "Konstante Strahldichte ist für reflektierte oder durchgelassene Strahlen sichtbar. Sie liefert keine direkte diffuse Beleuchtung.",
"render.cpu_custom_directions": "Vorgabe durch eigene Richtungslichter ersetzen",
"render.cpu_direction": "Richtung zum Licht (X, Y, Z)",
"render.cpu_directional": "Richtungslicht",
"render.cpu_directional_add": "Richtungslicht hinzufügen",
"render.cpu_environment": "CPU-Umgebung",
"render.cpu_environment_background": "Der Bildhintergrund bleibt von der Umgebungsbeleuchtung getrennt.",
"render.cpu_environment_bounds": "Die CPU-Intensität muss 0–64 betragen, die HDR-Drehung endlich innerhalb von ±1.000.000 Radiant liegen, und die HDR-Samples müssen 1, 4, 16 oder 64 sein.",
"render.cpu_environment_constant": "Konstante Umgebung für Sekundärstrahlen",
"render.cpu_environment_none": "Keine CPU-Umgebung",
"render.cpu_environment_samples": "HDR-Beleuchtungs-Samples",
"render.cpu_exposure_help": "Die CPU-Belichtung muss zwischen −16 und 16 EV liegen. Eine GPU-Belichtung außerhalb dieses Bereichs bleibt erhalten, bis Sie sie ändern.",
"render.cpu_fixed": "Einzelbild",
"render.cpu_hdr_help": "Radiance-HDR-Datei (.hdr), Panorama im Format 2:1, bis zu 2048 × 1024 Pixel und 16 MiB. Für die CPU müssen die sRGB/D65-Primärfarben ausdrücklich in der Datei angegeben sein. Dateien ohne Angabe der Primärfarben sowie EXR-Dateien werden abgelehnt.",
"render.cpu_hdr_reflection_help": "Raue HDR-Reflexionen erfordern vollständig metallische Körper- und Flächenoberflächen. Die Prüfung meldet nicht unterstützte Kombinationen.",
"render.cpu_key_rim": "Haupt- und Konturlicht",
"render.cpu_last_check": "Letzte CPU-Prüfung",
"render.cpu_light_help": "Bis zu acht Richtungslichter. Eine leere eigene Liste schaltet sie aus; die Wahl einer Vorgabe stellt deren Lichter wieder her. Farben sind lineares RGB, und die Intensität ist ein relativer Wert, keine Leistungsangabe in Watt.",
"render.cpu_light_intensity": "Relative Intensität (0–64)",
"render.cpu_light_minimum": "Mindestabstand für die Abschwächung",
"render.cpu_light_position": "Weltposition (X, Y, Z)",
"render.cpu_light_reference": "Referenzabstand für die Intensität",
"render.cpu_light_remove": "Licht entfernen",
"render.cpu_lighting": "Beleuchtungsvorgabe",
"render.cpu_lights": "CPU-Lichter",
"render.cpu_linear_color": "Lineare Farbe (R, G, B)",
"render.cpu_neutral": "Neutral",
"render.cpu_opaque_required": "CPU-Raytracing benötigt einen undurchsichtigen Hintergrund. Entfernen Sie das Häkchen bei „Transparenter Hintergrund“, um fortzufahren.",
"render.cpu_options_pending": "Dieses CPU-Bedienfeld kann Emission, Oberflächeneffekte, Glas oder Anisotropie noch nicht bearbeiten. Entfernen Sie diese API-Optionen, bevor Sie es verwenden.",
"render.cpu_point": "Punktlicht",
"render.cpu_point_add": "Punktlicht hinzufügen",
"render.cpu_point_help": "Bis zu acht Punktlichter, zusätzlich zur gewählten Richtungsbeleuchtung. Abstände in mm; der Mindestabstand darf den Referenzabstand nicht überschreiten.",
"render.cpu_primary_samples": "Kanten-Samples pro Pixel",
"render.cpu_progressive": "CPU progressiv",
"render.cpu_progressive_help": "Den vorhandenen CPU-Integrator in Durchgängen mit je einem Primär-Sample akkumulieren. Ausgewählte Reflexionen erfordern ein Sample; Flächenlichter, Dielektrika und Anisotropie erfordern „Einzelbild“.",
"render.cpu_progressive_limits": "Höchstens 1.048.576 Pixel und 16.777.216 Auswertungen von Pixel × Sample. Das Ändern der Samples passt die Bildgröße nie automatisch an.",
"render.cpu_progressive_seed": "Startwert für das CPU-Sampling",
"render.cpu_progressive_spp": "Samples pro Pixel",
"render.cpu_reflection_add": "Raue Reflexion hinzufügen",
"render.cpu_reflection_depth": "Reflexionstiefe (1–8)",
"render.cpu_reflection_empty": "Keine Körper ausgewählt: Szenenreflexionen sind aus.",
"render.cpu_reflection_help": "Wählen Sie Körper und stellen Sie „Aus“, „Spiegelnd“ oder „Rau“ ein. „Rau“ verwendet die zugewiesene Oberflächenrauheit. Mehr Samples und Reflexionsstufen verlängern die Rechenzeit; dies ist begrenzter spiegelnder Transport, keine globale Beleuchtung.",
"render.cpu_reflection_limit": "Bis zu 64 reflektierte Körper werden unterstützt.",
"render.cpu_reflection_mirror": "Spiegelnd",
"render.cpu_reflection_off": "Aus",
"render.cpu_reflection_rough": "Rau",
"render.cpu_reflection_samples": "Reflexions-Samples (1–64)",
"render.cpu_reflections": "CPU-Reflexionen",
"render.cpu_runtime_help": "Numerische Licht- und Umgebungseinstellungen werden mit dem Setup gespeichert. Körperreflexionen und HDR-Dateien bleiben vorübergehend. Vorhandene Werte für Flächenlicht und Spotkegel bleiben erhalten.",
"render.cpu_sampling_mode": "CPU-Sampling-Modus",
"render.cpu_shadows": "Schatten werfen",
"render.cpu_summary": "CPU-Raytracing · Reflexionen · HDR · Richtungs- und Punktlichter",
"render.cpu_warm_cool": "Warm und kühl",
"render.diag_bodies": "Sichtbare Körper",
"render.diag_job_details": "Erfasste Auftragsdetails",
"render.diag_last_check": "Letzte Vorabprüfung",
"render.diag_memory": "Geschätzter logischer Speicherbedarf",
"render.diag_not_implemented": "Ausdrückliche Funktionsgrenzen",
"render.diag_preflight_details": "Details der Vorabprüfung",
"render.diag_request": "Aktuelle Anfrage im Wortlaut",
"render.diag_triangles": "Dreiecke",
"render.discard_continue": "Entwurf verwerfen und fortfahren",
"render.draft_confirm_help": "Der aktuelle Render-Entwurf enthält nicht angewendete Änderungen. Speichern Sie sie vor dem Wechsel, verwerfen Sie sie ausdrücklich oder bearbeiten Sie weiter.",
"render.draft_confirm_title": "Bearbeitete Rendereinstellungen beibehalten",
"render.effects_gpu_only": "Diese Effekte erfordern GPU-Pfadverfolgung. Ein Seitenwechsel schaltet den Renderer niemals automatisch um.",
"render.effects_help": "Geordnete Farb-Looks und Objektiveffekte im Bildraum kombinieren. Das Rendern und das Neu-Graden des linearen Rohbilds verwenden dieselben gespeicherten Einstellungen. Das unbearbeitete EXR bleibt unverändert. Dies sind eigene künstlerische Regler, keine kalibrierten Objektiv- oder Filmemulationen bestimmter Marken.",
"render.effects_reset_lens": "Objektiveffekte zurücksetzen",
"render.effects_seed": "Startwert des Musters",
"render.emitted_axis": "Abstrahlachse",
"render.env_advanced": "Erweitert",
"render.env_backdrop": "Hintergrund",
@@ -2248,11 +2582,19 @@
"render.error_vertex_packing": "Produktvorschau stimmt nicht mit den zugelassenen Scheitelpunktdaten überein",
"render.error_visible_solid": "In Design einen sichtbaren Volumenkörper erstellen, um ihn hier anzuzeigen.",
"render.exit_focus": "Fokus beenden",
"render.export_display": "Export: Anzeige-PNG",
"render.export_image_label": "Bild exportieren",
"render.export_linear": "Export: lineares Roh-EXR",
"render.exr_pending": "Die GPU kann rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
"render.film_grain": "Fotografisches Korn",
"render.fit": "Produkt einpassen",
"render.fixed_camera": "Kamera",
"render.fixed_world": "Welt",
"render.flare_ghosts": "Geisterringe pro Quelle",
"render.flare_halo": "Reflex-Lichthof",
"render.flare_streak": "Streifen im anamorphen Stil",
"render.flare_strength": "Lichtreflex-Stärke an Glanzlichtern",
"render.flare_threshold": "Szenenlinearer Lichtreflex-Schwellwert",
"render.fold_browser": "Browser einklappen",
"render.frame_aspect": "Seitenverhältnis",
"render.frame_centre_mark": "Mittelpunkt",
@@ -2281,6 +2623,27 @@
"render.frame_tall": "9:16 · Vertikal",
"render.frame_thirds": "Drittellinien",
"render.frame_wide": "16:9 · Breitbild",
"render.gpu_depth": "Maximale Pfadtiefe",
"render.gpu_options_help": "Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
"render.gpu_path_failed": "GPU-Pathtracer nicht verfügbar",
"render.gpu_path_first_setup": "Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
"render.gpu_path_preparing": "GPU-Pathtracer: Shader werden kompiliert…",
"render.gpu_seed": "Zufallsstartwert",
"render.gpu_unavailable": "GPU-Pathtracing ist auf diesem Gerät nicht bereit.",
"render.ground_apply": "Boden anpassen und speichern",
"render.ground_gap": "Definierter Abstand",
"render.ground_linear_rgb": "Diffuser Reflexionsgrad · lineares RGB",
"render.ground_margin": "Randzugabe der Bodenfläche",
"render.hdr_details": "Details zur HDR-Quelle",
"render.hdr_embedded": "In diesem Design gespeichertes HDR",
"render.hdr_embedded_help": "Das Rendern verwendet die im Design enthaltenen Bytes; die Originaldatei wird nicht mehr benötigt.",
"render.hdr_file_captured": "HDR-Datei geprüft. Beim Speichern werden diese Bytes eingebettet; ändert sich die Datei, prüfen Sie sie erneut ausdrücklich.",
"render.hdr_inspect": "HDR-Datei prüfen",
"render.hdr_inspect_required": "Wählen und prüfen Sie eine HDR-Datei, bevor Sie speichern oder rendern.",
"render.hdr_primaries_declared": "Die Quelle gibt ihre Primärfarben an.",
"render.hdr_primaries_srgb": "Primärfarben: sRGB/D65",
"render.hdr_primaries_undeclared": "Die Quelle gibt keine Primärfarben an; prüfen Sie die Farbanforderungen des Renderers.",
"render.hdr_resource_missing": "In diesem Design fehlt die gespeicherte HDR-Ressource.",
"render.heading_batch": "Stapel",
"render.heading_frame": "Bildrahmen",
"render.heading_lens": "Objektiv",
@@ -2288,19 +2651,26 @@
"render.heading_shots": "Aufnahmen",
"render.hide_shots_label": "Aufnahmenleiste ausblenden",
"render.hover_look": "{object} · {look} · Zum Auswählen klicken",
"render.image_advanced": "Erweiterte GPU-Einstellungen",
"render.image_cancelled": "Rendering abgebrochen",
"render.image_cancelling": "Wird abgebrochen",
"render.image_completed": "Bild bereit zum Export",
"render.image_encoding": "PNG wird kodiert",
"render.image_export": "Fertiges Bild exportieren",
"render.image_exposure": "Belichtung",
"render.image_failed": "Rendering fehlgeschlagen",
"render.image_height": "Höhe",
"render.image_help": "CPU-Vorschaubild · PNG · angenäherte Transparenz",
"render.image_host_missing": "Es ist kein Bild-Host installiert.",
"render.image_method": "Bildmethode",
"render.image_overwrite": "Vorhandene Datei ersetzen",
"render.image_preparing": "Bild wird vorbereitet",
"render.image_progressive": "Progressives Rendering",
"render.image_queued": "Warten auf Rendering",
"render.image_reduce": "Kleineres Bild oder einfachere Szene versuchen.",
"render.image_running": "Bild wird gerendert",
"render.image_samples": "Samples pro Pixel",
"render.image_scene_stale": "Schließen Sie die Modellierungsgeste ab oder aktualisieren Sie die Szene, bevor Sie ein Bild prüfen oder starten.",
"render.image_stale": "Dieses Bild verwendet die Szene vor den nachfolgenden Änderungen.",
"render.image_start": "Rendering starten",
"render.image_supported": "Renderanfrage unterstützt; Warteschlange und Speicher werden beim Start erneut geprüft.",
@@ -2312,6 +2682,7 @@
"render.image_width": "Breite",
"render.include_rig_cards": "Include passive cards and flags",
"render.inner_half_angle": "Innerer Halbwinkel",
"render.inspect_scene": "Erfasste Szenenübersicht aktualisieren",
"render.inspector": "Inspektor",
"render.inspector_advanced": "Erweitert",
"render.inspector_all_users": "Alle {count} Nutzer",
@@ -2343,7 +2714,17 @@
"render.inspector_this_face": "Diese Fläche",
"render.inspector_this_target": "Dieses Ziel",
"render.inspector_used_by": "Verwendet von {count}",
"render.keep_editing": "Weiter bearbeiten",
"render.layer_down": "Nach unten",
"render.layer_enabled": "Schicht aktiviert",
"render.layer_off_suffix": " (aus)",
"render.layer_remove": "Schicht entfernen",
"render.layer_strength": "Schichtstärke",
"render.layer_up": "Nach oben",
"render.lens_effects_help": "Lichtreflexe nutzen bis zu vier getrennte helle Quellen im tatsächlichen linearen Bild. Geisterringe, Lichthof, Streifen, chromatische Versätze und radiale Verzeichnung sind künstlerische Näherungen im Bildraum. Räumliche Effekte benötigen eine undurchsichtige Ausgabe; Samples außerhalb setzen den Bildrand fort. Das Korn ist deterministisch. Physik, EXR und Geometrie bleiben unverändert.",
"render.lens_effects_title": "Objektivinspiriert und fotografisch",
"render.library_all": "Katalog",
"render.library_all_states": "Alle Verfügbarkeitszustände",
"render.library_appearance_available": "Verfügbares Erscheinungsbild",
"render.library_applicable": "Passende Ressourcen",
"render.library_applied": "Ressourcenvariante angewendet. Vollständigen Renderauftrag vor dem Start prüfen.",
@@ -2355,10 +2736,16 @@
"render.library_apply_uniform": "Einheitliches opakes Aussehen anwenden",
"render.library_authored_help": "Original erstellte Ressourcen dienen der Darstellung, nicht als gemessene technische Daten. Jede Aktion wendet nur ihre angegebene Variante an.",
"render.library_blocked": "Diese Ressource benötigt noch Kalibrierung oder eine externe Sonne und kann noch nicht angewendet werden.",
"render.library_capabilities": "Funktionsspezifikationen",
"render.library_center": "Mittelpunkt des Lichtaufbaus",
"render.library_clear_search": "Leeren",
"render.library_details": "Spezifikation und Herkunft",
"render.library_emitters_only_ack": "Nur Strahler verwenden; dokumentierte passive Karten auslassen",
"render.library_empty": "Keine passenden Ressourcen. Versuchen Sie eine andere Suche oder einen anderen Verfügbarkeitsfilter.",
"render.library_environment_concepts": "Umgebungskonzepte",
"render.library_extent": "Objektausdehnung",
"render.library_favorite": "Zu Favoriten hinzufügen",
"render.library_favorites": "Favoriten",
"render.library_filters": "Katalog und Verfügbarkeit",
"render.library_grid": "Raster",
"render.library_hdr": "Integrierte HDR-Umgebungen",
@@ -2371,12 +2758,15 @@
"render.library_maps_help": "Built-in maps can be saved with pinned sources and local occurrence mapping. Temporary optical overrides remain a separate mode.",
"render.library_material_concepts": "Materialkonzepte",
"render.library_metallic": "Metallisch",
"render.library_native_scenes": "Native Studios",
"render.library_next": "Nächste Seite",
"render.library_omitted": "Nicht enthalten",
"render.library_opacity": "Deckkraft",
"render.library_other": "Weitere Erscheinungsbilder",
"render.library_physical_profiles": "Physical reference profiles",
"render.library_previous": "Vorherige Seite",
"render.library_quality_concepts": "Qualitätsspezifikationen",
"render.library_recent": "Zuletzt",
"render.library_replace_environment": "Vorhandene Umgebung ausdrücklich ersetzen",
"render.library_replace_studio": "Replace saved lighting, environment and stage",
"render.library_results": "Passende Ressourcen",
@@ -2387,12 +2777,25 @@
"render.library_scene_bounds": "Erfasste Szenengrenzen verwenden",
"render.library_scenes": "Szenenspezifikationen",
"render.library_search": "Bibliothek durchsuchen",
"render.library_select_hint": "Wählen Sie eine Ressource in der Bibliothek, um ihre unterstützten Aktionen und Einschränkungen zu sehen.",
"render.library_selene": "SELENE: opake Oberflächen",
"render.library_selene_shots": "SELENE-Aufnahmespezifikationen",
"render.library_short_blocked": "Nicht verfügbar",
"render.library_short_native": "Native Zusammenstellung",
"render.library_short_projection": "Unterstützte Projektion",
"render.library_short_reference": "Nur Referenz",
"render.library_short_specification": "Nur Spezifikation",
"render.library_source_preview": "Vorschau der Quellmap laden",
"render.library_source_preview_help": "Erstellte Quellmap der Grundfarbe. Dies ist keine gerenderte oder gemessene Materialvorschau.",
"render.library_specification_only": "Nur Spezifikation: keine vollständig ausführbare Voreinstellung. Erforderliche Effekte, Ressourcen und Objektrollen müssen noch implementiert und gebunden werden.",
"render.library_specs": "Nur Spezifikationen",
"render.library_status": "Verfügbarkeit",
"render.library_surfaces": "Erstellte PBR-Oberflächen",
"render.library_target": "Zielkörper",
"render.library_title": "Render-Ressourcenbibliothek",
"render.library_unfavorite": "Aus Favoriten entfernen",
"render.library_uniform_ack": "Einheitliche Näherung ohne räumliche oder erweiterte Effekte verwenden",
"render.library_usable": "Ausführbare Projektionen",
"render.library_users": "Verwendungen",
"render.light_aim": "Ausrichten auf",
"render.light_anchor_pending": "Das Koordinatensystem der Lichter kann noch nicht geändert werden.",
@@ -2471,9 +2874,22 @@
"render.lighting_width": "Breite U",
"render.lighting_world_position": "Welt X Y Z",
"render.lights_fixed_to": "Lichter gebunden an",
"render.linear_help": "Behält ein lineares 32-Bit-Gleitkomma-RGBA-Rohbild vor Entrauschen, Belichtung, Tone-Mapping, Filtern und Hintergrund. Lineares sRGB/D65; Abdeckung vormultipliziert. Zusätzlicher Speicher wird ausdrücklich zugelassen. Keine Komprimierung und keine zusätzlichen Durchgänge.",
"render.linear_retain": "Lineares Roh-EXR behalten",
"render.linear_unavailable": "Für das gewählte fertige Bild wurde kein EXR behalten. Aktivieren Sie das Behalten vor dem Rendern; ein älteres PNG kann die rohe Strahldichte nicht wiederherstellen.",
"render.look_add": "Farblook hinzufügen",
"render.look_applied": "{name} angewendet",
"render.look_bleach": "Von Bleach-Bypass inspiriert",
"render.look_cool": "Kühl",
"render.look_cyanotype": "Von Cyanotypie inspiriert",
"render.look_faded": "Verblasster Abzug",
"render.look_material": "Material",
"render.look_mono": "Monochrom",
"render.look_pasted": "Look eingefügt",
"render.look_sepia": "Sepia",
"render.look_strength": "Look-Stärke",
"render.look_teal_orange": "Türkis / Orange",
"render.look_title": "Stapel kreativer Looks",
"render.material_color_replace": "Basismap ersetzt Körperfarbe",
"render.material_limits": "Höchstens 128 Körperüberschreibungen, 32 getrennte Glaskörper, 64 Beschichtungen und 128 Texturbindungen. Identische Maps teilen Speicher; Geräte- und Gesamtspeichergrenzen bleiben aktiv.",
"render.material_metallic_map": "Metallanteil-Map (linear)",
@@ -2509,7 +2925,9 @@
"render.output_ready": "Bereit zum Rendern",
"render.output_run_check": "Prüfung starten",
"render.output_size_limit": "{method}: bis zu {dimension} px pro Seite und {pixels} Pixel. Größte passende Größe: {width} × {height}.",
"render.output_size_method_profile": "Für {width} × {height} {method} mit dem CPU-Ressourcenprofil {profile} wählen oder eine kleinere Größe.",
"render.output_size_ok": "Größe OK · {width} × {height} px",
"render.output_size_profile": "Für {width} × {height} das CPU-Ressourcenprofil {profile} wählen oder eine kleinere Größe.",
"render.output_tools_inspector": "Größe und Format im Ausgabe-Inspektor bearbeiten.",
"render.output_view_image": "Bild anzeigen",
"render.paint_label": "Aufmalen",
@@ -2641,6 +3059,7 @@
"render.size_custom": "Benutzerdefiniert…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · erfordert {profile}",
"render.size_presets": "Größenvorlagen",
"render.size_presets_pending": "Kommt mit dem Ausgabeeditor",
"render.size_preview": "Vorschau",
@@ -2863,6 +3282,24 @@
"sheet.flange_type_base": "Basislasche",
"sheet.flange_type_contour": "Konturlasche",
"sheet.flange_type_edge": "Kantenlasche",
"sheet.personal_rules.confirm_delete": "Löschen bestätigen",
"sheet.personal_rules.delete": "Löschen",
"sheet.personal_rules.delete_prompt": "Möchten Sie diese persönliche Regel wirklich löschen? Regeln, die bereits in Entwürfen sind, bleiben unverändert.",
"sheet.personal_rules.full": "Die persönliche Bibliothek enthält bereits 64 Regeln.",
"sheet.personal_rules.invalid_name": "Geben Sie einen kurzen Namen ohne Zeilenumbrüche oder Steuerzeichen ein.",
"sheet.personal_rules.invalid_values": "Vervollständigen Sie die Werte der Regel: Jeder muss sich in diesem Entwurf berechnen lassen, mit positiver Dicke und einem K-Faktor von 0 bis 1.",
"sheet.personal_rules.missing": "Die ausgewählte persönliche Regel ist nicht mehr verfügbar.",
"sheet.personal_rules.name": "Regelname",
"sheet.personal_rules.name_exists": "Eine persönliche Regel mit diesem Namen existiert bereits. Wählen Sie einen anderen Namen.",
"sheet.personal_rules.name_hint": "Eindeutigen Namen eingeben",
"sheet.personal_rules.overwrite": "Änderungen in der ausgewählten persönlichen Regel speichern",
"sheet.personal_rules.rename": "Umbenennen",
"sheet.personal_rules.rule": "Persönliche Regel",
"sheet.personal_rules.save": "Als neue Regel speichern",
"sheet.personal_rules.scope": "In Ihrem Profil gespeichert und für alle Entwürfe verfügbar.",
"sheet.personal_rules.select": "Regel auswählen",
"sheet.personal_rules.title": "Persönliche Regeln",
"sheet.personal_rules.unsupported": "Diese Regel enthält Werte, die diese Version nicht verwenden kann.",
"shortcut.json_error": "Parameter müssen JSON sein: {error}",
"shortcut.no_command": "kein Befehl `{id}`; der Text vor dem JSON muss eine Befehls-ID sein",
"shortcut.pin": "Oben im S-Feld anheften",
@@ -3025,6 +3462,8 @@
"ui.3d_mouse_section": "3D-Maus",
"ui.3d_mouse_show_in_view": "3D-Maus in der 3D-Ansicht anzeigen",
"ui.3d_mouse_show_in_view_tip": "Ein Bild Ihrer 3D-Maus unten links, das sich mit ihr bewegt.",
"ui.3d_mouse_sketch_orbit": "Skizzen mit der 3D-Maus drehen",
"ui.3d_mouse_sketch_orbit_tip": "Die 3D-Maus dreht die Ansicht auch, solange eine Skizze offen ist und frontal gesehen wird, wie es die Maus kann. Aus: Sie verschiebt und zoomt die Skizze, bis Sie die Ansicht neigen.",
"ui.3d_mouse_skip": "Mein Gerät kann das nicht",
"ui.3d_mouse_skip_tip": "Überspringt diese Bewegung in beide Richtungen; die Kalibrierung lässt sie aus.",
"ui.3d_mouse_speed": "Geschwindigkeit der 3D-Maus",
@@ -3053,6 +3492,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Kappe gegen den Uhrzeigersinn drehen, halten, dann loslassen.",
"ui.3d_mouse_switch_cap": "Als Zurück zur Mitte kalibrieren",
"ui.3d_mouse_switch_trackball": "Als Trackball kalibrieren",
"ui.3d_mouse_use_direct": "Direkten Treiber verwenden",
"ui.3d_mouse_use_direct_tip": "Die 3D-Maus direkt lesen (hidraw) statt über spacenavd.",
"ui.3d_mouse_view_axis": "Ansichtsachse",
"ui.3d_mouse_view_off": "Aus",
"ui.3d_mouse_view_off_tip": "Das Profil des 3D-Maus-Treibers schaltet diese Achse ab, daher bewegt ihre Bewegung die Ansicht nicht. Wählen Sie hier die Bewegung einer anderen Achse, werden die beiden getauscht und sie bewegt die Ansicht nicht mehr.",
@@ -3168,6 +3609,8 @@
"ui.agent_chat_remove": "Aus der Warteschlange entfernen",
"ui.agent_chat_remove_file": "Von dieser Nachricht entfernen",
"ui.agent_chat_remove_imported": "Von dieser Nachricht entfernen; das Teil bleibt im Design (Rückgängig entfernt es)",
"ui.agent_chat_reply_cut_off": "Die Antwort hat ihre Längengrenze erreicht, bevor sie fertig war, und ist daher vielleicht unvollständig. Bitten Sie den Agenten weiterzumachen oder kürzer zu antworten.",
"ui.agent_chat_reply_declined": "Das Modell hat abgelehnt, auf diese Nachricht zu antworten.",
"ui.agent_chat_right": "Rechts",
"ui.agent_chat_save": "Speichern",
"ui.agent_chat_send": "Senden",
@@ -3319,6 +3762,7 @@
"ui.circle": "Kreis",
"ui.circular_pattern": "Radiales Muster",
"ui.clear": "Leeren",
"ui.clear_print_face": "Auflagefläche entfernen",
"ui.clear_recent": "Zuletzt verwendet leeren",
"ui.clear_the_selection": "Auswahl aufheben",
"ui.click_selects_whole_bodies": "Klick wählt ganze Körper aus",
@@ -3721,12 +4165,17 @@
"ui.plugins_skipped": "{count} Ordner konnte(n) nicht gelesen werden.",
"ui.plugins_version": "{version}",
"ui.point": "Punkt",
"ui.point_cloud_add_filter": "Filter hinzufügen…",
"ui.point_cloud_auto_range": "Bereich automatisch",
"ui.point_cloud_color_by": "Färben nach",
"ui.point_cloud_file_colors": "Farben der Datei",
"ui.point_cloud_filter_by": "Filtern nach",
"ui.point_cloud_height_high": "Oberste Höhe",
"ui.point_cloud_height_low": "Unterste Höhe",
"ui.point_cloud_legend_filter": "In der Legende oben ein- und ausgeblendet",
"ui.point_cloud_no_file_colors": "Die Datei hat keine Farben",
"ui.point_cloud_pause": "Pause",
"ui.point_cloud_play": "Abspielen",
"ui.point_cloud_single_color": "Einheitliche Farbe",
"ui.point_cloud_size": "Punktgröße",
"ui.point_cloud_units": "Einheit der Datei",
@@ -3735,6 +4184,11 @@
"ui.polygon": "Polygon",
"ui.preferences": "Einstellungen",
"ui.press_a_key": "Taste drücken…",
"ui.print_face_cleared": "{body}: Auflagefläche entfernt",
"ui.print_face_input": "Auflagefläche",
"ui.print_face_one_body": "Eine Auflagefläche gehört zu einem Körper: Sende diesen Körper allein, um sie zu ändern",
"ui.print_face_pick": "Eine plane Fläche eines Volumenkörpers auswählen, um sie zur Auflagefläche zu machen",
"ui.print_face_set": "{body}: Auflagefläche gesetzt; An Slicer senden legt den Körper darauf",
"ui.profile": "Profil",
"ui.profile_plane": "Profilebene",
"ui.profiles": "Profile",
@@ -3813,6 +4267,7 @@
"ui.selectOther": "Andere auswählen…",
"ui.selectTangentChain": "Tangentialkette auswählen",
"ui.select_all": "Alles auswählen",
"ui.select_components_by_face": "Komponenten auswählen (eine Fläche wählt ihre Komponente)",
"ui.selection": "Auswahl",
"ui.selection_modifiers": "Auswahlmodifikatoren",
"ui.selection_shift_add_only": "Umschalt+Klick fügt hinzu, ohne zu entfernen",
@@ -3850,6 +4305,11 @@
"ui.sketch_intersect_selection_filter": "Auswahlfilter",
"ui.sketch_modifiers": "Skizzenmodifikatoren",
"ui.sketch_project_geometry": "Geometrie",
"ui.sketch_report.all_constrained": "Alles ist vollständig bestimmt.",
"ui.sketch_report.dof": "Freiheitsgrade",
"ui.sketch_report.loose": "Nicht vollständig bestimmt",
"ui.sketch_report.status": "Status",
"ui.sketch_report.title": "Skizzenbericht",
"ui.sketch_scale": "Skizzenskalierung",
"ui.sketches": "Skizzen",
"ui.slice": "Scheibe",
@@ -3946,7 +4406,14 @@
"ui.viewport_status_key": "Legende für {name} anzeigen",
"ui.viewport_status_minimum_radius": "Mindestradius",
"ui.viewport_status_pairs": "{count} Paare",
"ui.viewport_status_section_hatch": "{setting} · Schraffur",
"ui.viewport_status_section_plane": "Aktuelle Ebene",
"ui.viewport_status_section_tilted": "geneigt · {offset}",
"ui.viewport_status_stripes": "{count} Streifen",
"ui.viewport_status_summary_clear": "Alles ausschalten",
"ui.viewport_status_summary_heading": "In dieser Ansicht",
"ui.viewport_status_summary_label": "{name} und {count} weitere Elemente",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Dem Design folgen",
"ui.visual_shaded_hidden_edges": "Schattiert mit verdeckten Kanten",
"ui.visual_shaded_with_edges": "Schattiert mit Kanten",
@@ -3979,7 +4446,9 @@
"update.button": "Update",
"update.button_version": "Auf {version} aktualisieren",
"update.check_automatically": "Automatisch nach Updates suchen",
"update.check_for_updates": "Nach Updates suchen",
"update.check_now": "Jetzt suchen",
"update.check_unavailable": "Für diese Installation ist keine Update-Prüfung verfügbar.",
"update.checking": "Suche läuft…",
"update.copy": "Kopieren",
"update.dmg_opened": "Das Disk-Image ist geöffnet und geprüft: SolidCraft in „Programme“ ziehen, die alte Version ersetzen und SolidCraft neu starten.",
+88
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "BLEND SURFACE",
"BOUNDARY SURFACE": "BOUNDARY SURFACE",
"BROWSER": "BROWSER",
"Back to the original order": "Back to the original order",
"Ball": "Ball",
"Bar": "Bar",
"Base Center": "Base Center",
@@ -595,6 +596,8 @@
"Snap to face and edge centres": "Snap to face and edge centers",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft closed before these designs were saved. Their last autosave can be opened.",
"Sort A to Z": "Sort A to Z",
"Sort Z to A": "Sort Z to A",
"Spacing 2 must be a non-zero number": "Spacing 2 must be a non-zero number",
"Spacing must be a non-zero number": "Spacing must be a non-zero number",
"Specialise": "Specialize",
@@ -819,6 +822,7 @@
"cmd.appearance_pick_up": "Pick Up Appearance",
"cmd.appearance_replace": "Replace Appearance",
"cmd.assembly_bom": "Bill of Materials",
"cmd.body.print_face": "Print Face",
"cmd.body_rename": "Rename Body",
"cmd.browser_collapse_all": "Collapse All",
"cmd.browser_expand_all": "Expand All",
@@ -1412,6 +1416,7 @@
"help.sketch.construction_line.tangent_two": "Pick two circles or circular arcs, then choose a common tangent in the preview. Contacts must lie on the drawn curves.",
"help.sketch.construction_line.vertical": "Create an infinite vertical construction line through a point. Its vertical constraint follows later edits.",
"help.sketch.equation_curve": "Draws a curve from formulas: x(t) and y(t), or y = f(x), over a range. The formulas can use parameters and the curve follows them: use it for a sine wave, a spiral or a gear tooth's involute.",
"help.sketch.inspect": "Opens a report on the open sketch: how many degrees of freedom are left and whether it is fully constrained. Use it to find out what still moves before you finish the sketch.",
"help.solid.align": "Moves the selected bodies so that a point, face or plane on them meets a point, face or plane elsewhere. Use it to place a part against another one without measuring the offset.",
"help.solid.chamfer": "Cuts the selected edges with a flat bevel of the distance you set. Use it to break sharp edges or to ease the entry of a pin or screw.",
"help.solid.combine": "Joins bodies, cuts them out of a target body, or keeps only the volume they share, and can keep the tool bodies. Use it to merge parts, to cut one shape with another or to find where two bodies overlap.",
@@ -1614,6 +1619,10 @@
"material.selection.mixed": "Different materials",
"measure.at": "At",
"measure.item": "Item {number}",
"measure.quick_copied": "Measurement copied",
"measure.quick_copy": "Click to copy the measurement",
"measure.quick_edges": "{count} edges",
"measure.quick_faces": "{count} faces",
"messages.ago_h": "{n} h ago",
"messages.ago_min": "{n} min ago",
"messages.ago_s": "{n} s ago",
@@ -1766,6 +1775,11 @@
"not a move": "not a move",
"not a number": "not a number",
"open a sketch first": "open a sketch first",
"parameters.apply_live": "Apply each change as you leave its field",
"parameters.apply_live_tip": "Off: changes wait until Apply or OK, then the design rebuilds once for all of them. Cancel or Escape drops them.",
"parameters.held": "{count} changes waiting for Apply",
"parameters.held_hint": "Edit expressions and units. Changes wait for Apply or OK.",
"parameters.held_one": "1 change waiting for Apply",
"parts.automatic_name": "automatic",
"parts.dimension.across_flats": "Across flats",
"parts.dimension.bore": "Bore",
@@ -1788,6 +1802,8 @@
"parts.family.set_screw": "Hex socket set screw, flat point",
"parts.family.socket_head_cap_screw": "Socket head cap screw",
"parts.family.washer": "Plain washer",
"parts.holes": "Holes (optional)",
"parts.placement_hint": "pick one or more holes to seat a part in each, or OK places it at the origin",
"pattern.alignment_2": "Alignment 2",
"pattern.alignment_hint": "Counts include the original. Center balances copies; Flip chooses the extra side for even totals.",
"pattern.direction_1": "Direction 1",
@@ -1840,6 +1856,8 @@
"prefs.bottom_centre": "Bottom center",
"prefs.bottom_right": "Bottom right",
"prefs.browser": "Browser",
"prefs.browser_eye_left": "Eye next to the fold arrow",
"prefs.browser_eye_left_note": "Browser rows show their show/hide eye next to the fold arrow, as before, instead of in a column at the right end of the rows.",
"prefs.browser_reveal_on_hover": "Reveal on hover",
"prefs.browser_show_note": "Shown keeps the browser on screen. Reveal on hover keeps it out of sight until the pointer comes over its place or the thin tab at its left edge, and it stays open while the pointer is inside it or a menu from it is open.",
"prefs.browser_shown": "Shown",
@@ -1890,6 +1908,8 @@
"prefs.graphics_card_restart": "Takes effect after restarting SolidCraft. WGPU_ADAPTER_NAME overrides this choice.",
"prefs.graphics_note": "Trade speed for smoothness. Each choice shows its effect on a sample.",
"prefs.graphics_presets_note": "Presets set the choices below together.",
"prefs.hide_constraint_glyphs_zoomed_out": "Hide constraint glyphs when zoomed out",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Constraint glyphs leave the view when the sketch is zoomed out far enough that they would clutter it. Off: they stay at a fixed screen size at every zoom.",
"prefs.history": "History",
"prefs.history_origin_bodies": "Show origin and bodies at the top",
"prefs.history_origin_bodies_note": "The History on the left or under the browser starts with the active component's Bodies folder and the Origin, with their eyes and menus as in the browser. The History at the bottom is unchanged.",
@@ -1950,12 +1970,16 @@
"prefs.origin_plane_names": "Origin plane names",
"prefs.origin_plane_names_note": "Names shown in the browser, dialogs and lists. {top} is XY, {front} is XZ and {right} is YZ, with Z up. Commands, scripts and saved designs keep XY, XZ and YZ.",
"prefs.overall_speed": "Overall speed",
"prefs.parameter_list_rows": "Parameter list rows",
"prefs.parameter_list_rows_tip": "Down arrow in an empty value field lists the parameters, favourites first, up to this many. Fewer keeps the list light in a large design.",
"prefs.persistent_ribbon_pressed": "Highlight active ribbon commands",
"prefs.persistent_ribbon_pressed_help": "Highlight ribbon commands while their effects are on. Turn off to keep the previous neutral appearance and command help.",
"prefs.perspective_fov": "Field of view",
"prefs.placement": "Placement",
"prefs.prefer_free_sketch_points": "Prefer movable sketch points when points overlap",
"prefs.preview_choices": "Preview of your choices",
"prefs.print_face_highlight": "Print face highlight",
"prefs.print_face_highlight_tip": "How strongly the print face of a body is tinted and outlined in the view. 0 % shows no highlight.",
"prefs.profile_active": "Active profile",
"prefs.profile_applied": "Profile applied.",
"prefs.profile_changed": "Settings changed since the profile was saved. Save keeps them in it.",
@@ -1977,6 +2001,8 @@
"prefs.quality_fastest": "fastest",
"prefs.quality_slowest": "slowest",
"prefs.quality_smoother": "smoother",
"prefs.quick_measure": "Quick measurement in the view's corner",
"prefs.quick_measure_note": "The selection's size in the view's bottom right corner: an edge's length, or Ø and R for a round one, a face's area, a point's position, two items' distance and angle, several edges' or faces' total. A click copies it.",
"prefs.render_previous_geometry_budget": "Use previous Render preview geometry budget",
"prefs.render_previous_geometry_budget_help": "Restore the previous fixed geometry limit (512 MiB on 64-bit desktop, 128 MiB elsewhere) instead of the device-based budget.",
"prefs.reports_saved": "{n} saved",
@@ -1990,6 +2016,8 @@
"prefs.search_empty": "No matching preferences",
"prefs.select": "Select",
"prefs.select_one_item": "Select one item",
"prefs.select_other_hidden": "Select hidden geometry with Select Other",
"prefs.select_other_hidden_help": "On: include hidden bodies, sketches and occluded edges near the pointer. Off: use the original list of visible geometry under the pointer.",
"prefs.selecting": "Selecting",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift adds only; Ctrl / Cmd toggles",
@@ -2068,6 +2096,8 @@
"prefs.under_cursor": "Under cursor",
"prefs.units_precision": "Units and precision",
"prefs.updates": "Updates",
"prefs.value_boxes_in_view": "Show values beside their handles in the view",
"prefs.value_boxes_in_view_tip": "A command's value (Distance, Radius…) in a box next to its handle, over the model. Off: you type it in the command window, which takes the keyboard after a pick, and nothing covers the model.",
"prefs.view": "View behavior",
"prefs.view_confirm_corner": "OK and Cancel in the view's corner",
"prefs.view_confirm_corner_note": "While a dialog or a sketch is open, a check and a cross at the top right of the view do what the dialog's OK and Cancel do, or finish and cancel the sketch. The view navigator moves down below them.",
@@ -2077,6 +2107,10 @@
"prefs.viewport_background": "Background",
"prefs.viewport_background_colour": "Color",
"prefs.viewport_background_theme": "Theme's gradient",
"prefs.viewport_section_status": "Show viewport section status",
"prefs.viewport_section_status_help": "Show the section setting in the viewport. Escape and the active ribbon button turn it off. Disable to keep the previous section controls.",
"prefs.viewport_status_summary": "Fold viewport status items",
"prefs.viewport_status_summary_help": "Fold three or more viewport status items into one summary badge. Disable to keep every item in the full row.",
"prefs.wheel": "Wheel",
"prefs.wheel_trackpad": "Wheel and trackpad",
"prefs.workspace": "Workspace",
@@ -2955,7 +2989,9 @@
"render.output_ready": "Ready to render",
"render.output_run_check": "Run check",
"render.output_size_limit": "{method}: up to {dimension} px per side and {pixels} pixels. Largest fitting size: {width} × {height}.",
"render.output_size_method_profile": "To render {width} × {height}, choose {method} with the {profile} CPU resource profile, or a smaller size.",
"render.output_size_ok": "Size OK · {width} × {height} px",
"render.output_size_profile": "To render {width} × {height}, choose the {profile} CPU resource profile, or a smaller size.",
"render.output_swap_orientation": "Swap width and height",
"render.output_tools_inspector": "Edit size and format in the Output inspector.",
"render.output_view_image": "View image",
@@ -3168,6 +3204,7 @@
"render.size_custom": "Custom…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · needs {profile}",
"render.size_presets": "Size presets",
"render.size_presets_pending": "Arrives with the Output editor",
"render.size_preview": "Preview",
@@ -3455,6 +3492,24 @@
"sheet.flange_type_contour": "Contour",
"sheet.flange_type_edge": "Edge",
"sheet.no_body": "there is no sheet metal body: start one with Flange > Base, or turn a plate into sheet metal with Convert to Sheet Metal (pick its large flat face)",
"sheet.personal_rules.confirm_delete": "Confirm delete",
"sheet.personal_rules.delete": "Delete",
"sheet.personal_rules.delete_prompt": "Delete this personal rule? Rules already in designs are unchanged.",
"sheet.personal_rules.full": "The personal library already contains 64 rules.",
"sheet.personal_rules.invalid_name": "Enter a short name without line breaks or control characters.",
"sheet.personal_rules.invalid_values": "Complete the rule's values: each must work out in this design, with a positive thickness and a K factor from 0 to 1.",
"sheet.personal_rules.missing": "The selected personal rule is no longer available.",
"sheet.personal_rules.name": "Rule name",
"sheet.personal_rules.name_exists": "A personal rule with this name already exists. Use a different name.",
"sheet.personal_rules.name_hint": "A unique name",
"sheet.personal_rules.overwrite": "Save changes to selected personal rule",
"sheet.personal_rules.rename": "Rename",
"sheet.personal_rules.rule": "Personal rule",
"sheet.personal_rules.save": "Save new",
"sheet.personal_rules.scope": "Saved in your profile for all designs.",
"sheet.personal_rules.select": "Select a rule",
"sheet.personal_rules.title": "Personal rules",
"sheet.personal_rules.unsupported": "This rule has values that this version can't use.",
"shortcut.json_error": "parameters must be JSON: {error}",
"shortcut.no_command": "no command `{id}`; the text before the JSON must be a command id",
"shortcut.pin": "Pin to the top of the S box",
@@ -3491,6 +3546,7 @@
"sketch_text.align_right": "Right",
"sketch_text.bold": "Bold",
"sketch_text.builtin_font": "{family} (built-in)",
"sketch_text.formula": "Start with = to build the words from parameters: ='W' + number(Width)",
"sketch_text.italic": "Italic",
"sketch_text.loading_fonts": "Looking for installed fonts…",
"sketch_text.on_path": "The text follows its path: the curve places it",
@@ -3631,6 +3687,8 @@
"ui.3d_mouse_section": "3D mouse",
"ui.3d_mouse_show_in_view": "Show the 3D mouse in the 3D view",
"ui.3d_mouse_show_in_view_tip": "A picture of your 3D mouse in the lower left corner, moving as you move it.",
"ui.3d_mouse_sketch_orbit": "Turn sketches with the 3D mouse",
"ui.3d_mouse_sketch_orbit_tip": "The 3D mouse also turns the view while a sketch is open and seen square on, as the mouse can. Off: it pans and zooms the sketch until you tilt the view.",
"ui.3d_mouse_skip": "My device can't do this",
"ui.3d_mouse_skip_tip": "Skips this motion both ways, and the calibration leaves it out.",
"ui.3d_mouse_speed": "3D mouse speed",
@@ -3659,6 +3717,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Twist the cap anticlockwise, hold it, then let go.",
"ui.3d_mouse_switch_cap": "Calibrate as Return to Center",
"ui.3d_mouse_switch_trackball": "Calibrate as a trackball",
"ui.3d_mouse_use_direct": "Use Direct Driver",
"ui.3d_mouse_use_direct_tip": "Read the 3D mouse directly (hidraw) instead of through spacenavd.",
"ui.3d_mouse_view_axis": "View axis",
"ui.3d_mouse_view_off": "Off",
"ui.3d_mouse_view_off_tip": "The 3D mouse driver's profile turns this axis off, so its motion doesn't move the view. Picking another axis's motion here swaps the two, and that motion stops moving the view.",
@@ -3792,6 +3852,8 @@
"ui.agent_chat_remove": "Remove from the queue",
"ui.agent_chat_remove_file": "Take it off this message",
"ui.agent_chat_remove_imported": "Take it off this message; the part stays in the design (Undo removes it)",
"ui.agent_chat_reply_cut_off": "The reply reached its length limit before it finished, so it may be incomplete. Ask the agent to go on, or for a shorter answer.",
"ui.agent_chat_reply_declined": "The model declined to answer this message.",
"ui.agent_chat_retry": "Retry",
"ui.agent_chat_right": "Right",
"ui.agent_chat_save": "Save",
@@ -3975,6 +4037,7 @@
"ui.circle": "Circle",
"ui.circular_pattern": "Circular Pattern",
"ui.clear": "Clear",
"ui.clear_print_face": "Clear Print Face",
"ui.clear_recent": "Clear Recent",
"ui.clear_the_selection": "Clear the selection",
"ui.click_selects_whole_bodies": "Click selects whole bodies",
@@ -4407,12 +4470,17 @@
"ui.plugins_skipped": "{count} folder(s) could not be read.",
"ui.plugins_version": "{version}",
"ui.point": "Point",
"ui.point_cloud_add_filter": "Add filter…",
"ui.point_cloud_auto_range": "Automatic Range",
"ui.point_cloud_color_by": "Color By",
"ui.point_cloud_file_colors": "File Colors",
"ui.point_cloud_filter_by": "Filter by",
"ui.point_cloud_height_high": "Highest",
"ui.point_cloud_height_low": "Lowest",
"ui.point_cloud_legend_filter": "Shown and hidden in the legend above",
"ui.point_cloud_no_file_colors": "The file has no colors",
"ui.point_cloud_pause": "Pause",
"ui.point_cloud_play": "Play",
"ui.point_cloud_single_color": "Single Color",
"ui.point_cloud_size": "Point Size",
"ui.point_cloud_units": "File Units",
@@ -4421,6 +4489,11 @@
"ui.polygon": "Polygon",
"ui.preferences": "Preferences",
"ui.press_a_key": "press a key…",
"ui.print_face_cleared": "{body}: print face cleared",
"ui.print_face_input": "Print face",
"ui.print_face_one_body": "A print face is picked for one body: send that body on its own to change it",
"ui.print_face_pick": "Select one flat face of a solid body to make it the print face",
"ui.print_face_set": "{body}: print face set; Send to Slicer lays the body on it",
"ui.profile": "Profile",
"ui.profile_plane": "Profile Plane",
"ui.profiles": "Profiles",
@@ -4503,6 +4576,7 @@
"ui.selectOther": "Select Other…",
"ui.selectTangentChain": "Select Tangent Chain",
"ui.select_all": "Select All",
"ui.select_components_by_face": "Select components (a face picks its component)",
"ui.selection": "Selection",
"ui.selection_modifiers": "Selection modifiers",
"ui.selection_shift_add_only": "Shift+click adds without removing",
@@ -4544,6 +4618,11 @@
"ui.sketch_intersect_selection_filter": "Selection Filter",
"ui.sketch_modifiers": "Sketch modifiers",
"ui.sketch_project_geometry": "Geometry",
"ui.sketch_report.all_constrained": "Everything is fully constrained.",
"ui.sketch_report.dof": "Degrees of freedom",
"ui.sketch_report.loose": "Not fully constrained",
"ui.sketch_report.status": "Status",
"ui.sketch_report.title": "Sketch Report",
"ui.sketch_scale": "Sketch Scale",
"ui.sketches": "Sketches",
"ui.slice": "Slice",
@@ -4653,7 +4732,14 @@
"ui.viewport_status_key": "Show key for {name}",
"ui.viewport_status_minimum_radius": "Minimum radius",
"ui.viewport_status_pairs": "{count} pairs",
"ui.viewport_status_section_hatch": "{setting} · hatch",
"ui.viewport_status_section_plane": "Current plane",
"ui.viewport_status_section_tilted": "tilted · {offset}",
"ui.viewport_status_stripes": "{count} stripes",
"ui.viewport_status_summary_clear": "Turn everything off",
"ui.viewport_status_summary_heading": "In this view",
"ui.viewport_status_summary_label": "{name} and {count} more items",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Follow theme",
"ui.visual_shaded_hidden_edges": "Shaded with hidden edges",
"ui.visual_shaded_with_edges": "Shaded with edges",
@@ -4690,7 +4776,9 @@
"update.button": "Update",
"update.button_version": "Update to {version}",
"update.check_automatically": "Check for updates automatically",
"update.check_for_updates": "Check for updates",
"update.check_now": "Check now",
"update.check_unavailable": "Update checks are not available for this installation.",
"update.checking": "Checking…",
"update.copy": "Copy",
"update.dmg_opened": "The disk image is open and verified: drag SolidCraft to Applications, replacing the old copy, then restart SolidCraft.",
+1123 -10
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+93
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,7 @@
"cmd.appearance_pick_up": "Prélever l'apparence",
"cmd.appearance_replace": "Remplacer l'apparence",
"cmd.assembly_bom": "Nomenclature",
"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",
@@ -1323,6 +1332,7 @@
"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.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.",
@@ -1395,6 +1405,16 @@
"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.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.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.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.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.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.",
@@ -1422,6 +1442,8 @@
"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.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.",
"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.",
@@ -1748,6 +1770,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",
@@ -1770,6 +1797,8 @@
"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.holes": "Trous (facultatif)",
"parts.placement_hint": "choisissez un ou plusieurs trous pour y loger une pièce chacun, ou OK la place à l'origine",
"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 +1839,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 +1851,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 +1903,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.",
@@ -1908,6 +1943,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 +1965,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.",
@@ -1965,6 +2009,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,16 +2022,21 @@
"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",
@@ -1995,6 +2046,11 @@
"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é",
@@ -2037,6 +2093,8 @@
"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",
@@ -3454,6 +3512,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 +3566,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 +3653,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 +3683,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.",
@@ -3926,6 +3991,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 +4001,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 +4036,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",
@@ -4358,12 +4434,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 +4453,11 @@
"ui.polygon": "Polygone",
"ui.preferences": "Préférences",
"ui.press_a_key": "appuyez sur une touche…",
"ui.print_face_cleared": "{body} : face d’impression effacée",
"ui.print_face_input": "Face d’impression",
"ui.print_face_one_body": "Une face d’impression appartient à un seul corps : envoyez ce corps seul pour la modifier",
"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",
@@ -4435,6 +4521,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",
@@ -4598,6 +4685,8 @@
"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",
@@ -4638,7 +4727,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 +4743,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…",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Os deseños que abras ou gardes aparecen aquí.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por Ata o obxecto. Escolle Ángulo para editar a extensión angular.",
"Diameter": "Diámetro",
"Did this assembly open the wrong way up?": "Abriuse esta ensamblaxe mal orientada?",
"Did this part open the wrong way up?": "Abriuse esta peza mal orientada?",
"Dimension new shapes": "Cotar as formas novas",
"Direction": "Dirección",
"Discard": "Descartar",
@@ -601,6 +603,7 @@
"Square": "Cadrado",
"Stand it up": "Poñela de pé",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Poñela de pé: un cuarto de volta arredor de X, como unha operación Mover que podes desfacer.",
"Stand this assembly up?": "Poñer esta ensamblaxe de pé?",
"Stand this part up?": "Poñer esta peza de pé?",
"Start angle": "Ángulo inicial",
"Start bulge": "Abaulamento inicial",
@@ -772,6 +775,8 @@
"at the face": "na cara",
"bom.group_children": "Subpezas na fila da súa peza",
"browser.body_name": "Corpo{number}",
"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.",
"browser.calibrate_title": "Calibrar: {name}",
"browser.capture_many": "Movéronse {count} compoñentes, pero as súas posicións non se capturaron.",
"browser.capture_note": "Capturar a posición conserva a nova posición; Reverter a posición restaura a anterior.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Tomar aparencia",
"cmd.appearance_replace": "Substituír aparencia",
"cmd.assembly_bom": "Lista de materiais",
"cmd.body.print_face": "Cara de impresión",
"cmd.body_rename": "Renomear o corpo",
"cmd.browser_collapse_all": "Contraer todo",
"cmd.browser_expand_all": "Expandir todo",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Faga dobre clic noutra cota de control para inserir o seu nome",
"dialog.dimension_name": "Cota: {name}",
"dialog.edit_title": "EDITAR {title}",
"dialog.no_shown_body": "Non hai ningún corpo visible para modificar: mostra un ou escolle Corpo novo",
"dialog.select": "↖ Seleccionar",
"dialog.selected_count": "↖ {count} seleccionados",
"dialog.selection_guidance": "Selecciona unha entrada para cambiala. Fai clic nos obxectos seleccionados para quitalos; Ctrl/Cmd-clic tamén os alterna. Revisa a previsualización e preme Aceptar.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Move unha peza da ensamblaxe só segundo os graos de liberdade que permiten as súas unións. Selecciona unha cara da peza e despois arrastra unha frecha ou un arco dispoñible.",
"help.parameters.change": "Abre a táboa de valores con nome e expresións que controlan as cotas do deseño. Úsao para cambiar un tamaño nun só lugar e que o sigan todas as operacións que o usan.",
"help.pointcloud.insert": "Insire unha dixitalización PLY, XYZ ou PTS como nube de puntos xunto ao modelo. Úsaa como referencia ao construír un modelo a partir de datos medidos.",
"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.",
"help.sketch.equation_curve": "Debuxa unha curva a partir de fórmulas: x(t) e y(t), ou y = f(x), nun intervalo. As fórmulas poden usar parámetros e a curva segue os seus cambios: úsaa para unha onda senoidal, unha espiral ou a evolvente do dente dunha engrenaxe.",
"help.solid.align": "Move os corpos seleccionados para que un punto, cara ou plano deles coincida cun punto, cara ou plano doutro lugar. Úsao para colocar unha peza contra outra sen medir o desprazamento.",
"help.solid.chamfer": "Corta as arestas seleccionadas cun bisel plano da distancia que indiques. Úsao para romper arestas vivas ou para facilitar a entrada dun pasador ou dun parafuso.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Reconstrúe todas as operacións da liña de tempo desde o principio. Úsao para actualizar o modelo e ver que operacións fallan.",
"help.timeline.delete": "Elimina as operacións seleccionadas da liña de tempo; ao eliminar un bosquexo tamén se eliminan as operacións construídas a partir del. Úsao para quitar un paso que xa non se quere.",
"help.timeline.play": "Reproduce as operacións restantes unha a unha, cunha pausa de medio segundo despois de cada reconstrución. Executa este comando de novo ou preme Escape para deter a reprodución; Ir ao final salta de inmediato.",
"history.sketch_under_constrained": "Subrestrinxido ({count} graos de liberdade)",
"history.sketch_under_constrained_one": "Subrestrinxido ({count} grao de liberdade)",
"hole_presets.confirm_delete": "Confirmar a eliminación",
"hole_presets.delete": "Eliminar",
"hole_presets.delete_prompt": "Eliminar este axuste predefinido persoal? Os furados existentes non cambian.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Suaviza as arestas dentadas.",
"prefs.applies_on_restart": "Aplícase ao reiniciar",
"prefs.assistant": "Asistente",
"prefs.auto_operation": "Escoller a operación automaticamente",
"prefs.auto_operation_note": "Os diálogos das operacións escollen Unir, Corpo novo ou Cortar segundo o que toque o material novo, ata que escollas unha. Desactivado: manteñen a operación coa que se abren.",
"prefs.axes": "Eixes",
"prefs.axis": "Eixe",
"prefs.bodies_folders": "Cartafoles de corpos",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Inferior centro",
"prefs.bottom_right": "Inferior dereita",
"prefs.browser": "Navegador",
"prefs.browser_eye_left": "Ollo xunto á frecha de expansión",
"prefs.browser_eye_left_note": "As filas do navegador mostran o seu ollo de mostrar/ocultar xunto á frecha de expansión, como antes, en vez de nunha columna no extremo dereito das filas.",
"prefs.browser_reveal_on_hover": "Mostrar ao pasar o punteiro",
"prefs.browser_show_note": "Visible mantén o navegador na pantalla. Con Mostrar ao pasar o punteiro, queda oculto ata que o punteiro pasa polo seu lugar ou pola lapela estreita do seu bordo esquerdo, e segue aberto mentres o punteiro está dentro del ou hai aberto un menú del.",
"prefs.browser_shown": "Visible",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Aplícase despois de reiniciar SolidCraft. WGPU_ADAPTER_NAME anula esta opción.",
"prefs.graphics_note": "Troca velocidade por suavidade. Cada opción mostra o seu efecto nunha mostra.",
"prefs.graphics_presets_note": "Os axustes predefinidos configuran xuntas as opcións de abaixo.",
"prefs.hide_constraint_glyphs_zoomed_out": "Ocultar as iconas de restricións ao reducir o zoom",
"prefs.hide_constraint_glyphs_zoomed_out_note": "As iconas de restricións desaparecen da vista cando o zoom do bosquexo está tan afastado que o sobrecargarían. Desactivado: manteñen un tamaño fixo na pantalla con calquera zoom.",
"prefs.history": "Historial",
"prefs.history_origin_bodies": "Mostrar a orixe e os corpos na parte superior",
"prefs.history_origin_bodies_note": "O Historial da esquerda ou baixo o navegador comeza co cartafol Corpos do compoñente activo e coa Orixe, cos seus ollos e menús coma no navegador. O Historial da parte inferior non cambia.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "Ficheiros STEP e de importación",
"prefs.max_file_size_web": "A aplicación web abre ficheiros STEP e de importación de ata 512 MB e deseños de ata 256 MB: unha lapela do navegador non admite ficheiros maiores. A aplicación de escritorio permite aumentar estes límites.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Iconas grandes",
"prefs.menu_list": "Lista",
"prefs.menus": "Menús",
"prefs.minutes": "minutos",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Rato e panel táctil",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Nomes dos planos de orixe",
"prefs.origin_plane_names_note": "Nomes que se mostran no navegador, nos diálogos e nas listas. {top} é XY, {front} é XZ e {right} é YZ, co eixe Z cara arriba. Os comandos, os scripts e os deseños gardados manteñen XY, XZ e YZ.",
"prefs.overall_speed": "Velocidade xeral",
"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.perspective_fov": "Campo de visión",
"prefs.placement": "Colocación",
"prefs.prefer_free_sketch_points": "Preferir os puntos de bosquexo móbiles cando os puntos se solapan",
"prefs.preview_choices": "Vista previa das túas opcións",
"prefs.print_face_highlight": "Resaltado da cara de impresión",
"prefs.print_face_highlight_tip": "Intensidade da tinguidura e do contorno da cara de impresión dun corpo na vista. 0 % non mostra resaltado.",
"prefs.profile_active": "Perfil activo",
"prefs.profile_applied": "Perfil aplicado.",
"prefs.profile_changed": "Os axustes cambiaron desde que se gardou o perfil. Gardar consérvaos nel.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Non hai axustes que coincidan",
"prefs.select": "Seleccionar",
"prefs.select_one_item": "Seleccionar un elemento",
"prefs.select_other_hidden": "Seleccionar a xeometría oculta con Seleccionar outro",
"prefs.select_other_hidden_help": "Activado: inclúe os corpos e os bosquexos ocultos e as arestas ocluídas preto do punteiro. Desactivado: usa a lista orixinal de xeometría visible baixo o punteiro.",
"prefs.selecting": "Selección",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift só engade; Ctrl / Cmd alterna",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Ao debuxar, un punto alíñase en horizontal cun punto do bosquexo e en vertical con outro á vez, cunha guía punteada, e o clic conserva os dous aliñamentos. Desactivado: só co punto anterior, un eixe de cada vez.",
"prefs.sketch_conflict_colour": "Xeometría en conflito na cor de conflito",
"prefs.sketch_conflict_colour_note": "Mentres as restricións dun bosquexo están en conflito, as curvas e os puntos que suxeita unha restrición que falla tamén se debuxan na cor de conflito, non só o seu símbolo ou a súa cota. Desactivado: manteñen as súas cores habituais.",
"prefs.sketch_construction_lines": "Ferramentas ampliadas de rectas de construción",
"prefs.sketch_exit_view": "Ao rematar un bosquexo",
"prefs.sketch_exit_view.keep": "Manter a vista actual",
"prefs.sketch_exit_view.previous": "Volver á vista anterior",
"prefs.sketch_exit_view.unless_rotated": "Volver, salvo que se xirase",
"prefs.sketch_exit_view_note": "A onde vai a vista despois de Finalizar o bosquexo, Cancelar ou un dobre Esc.",
"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.",
"prefs.sketch_recent_snap": "Axustar automaticamente aos centros e puntos medios recentes",
"prefs.sketch_recent_snap_note": "Ao debuxar, o cursor axústase onde se cruzan as guías dos dous últimos centros de círculo ou puntos medios de liña recta. Desactivado: esas guías só se mostran e o cursor non se move.",
"prefs.sketch_selection": "Bosquexo e selección",
"prefs.sketch_selection_heading": "Bosquexo e selección",
"prefs.sketch_selection_note": "Como se axustan os bosquexos e como seleccionan os clics.",
"prefs.sketch_state_icons": "Iconas dos bosquexos na cor do seu estado",
"prefs.sketch_state_icons_note": "No Historial e no navegador, a icona dun bosquexo toma a cor do seu estado (subrestrinxido, totalmente restrinxido ou en conflito) e a súa información emerxente nomea o estado. Desactivado: todos os bosquexos teñen a mesma icona.",
"prefs.sketch_tab_always": "Manter visible a lapela Bosquexo",
"prefs.sketch_tab_always_note": "A lapela Bosquexo permanece na cinta de opcións, xusto despois de Modelo, mentres non haxa ningún bosquexo aberto. As súas ferramentas de debuxo inician entón un bosquexo no plano ou na cara seleccionados, ou no plano que escollas a continuación, coa ferramenta lista nel.",
"prefs.sketch_tracking_guides": "Guías de aliñamento de centro e punto medio",
@@ -1995,6 +2034,11 @@
"prefs.source": "Fonte",
"prefs.speed": "Velocidade",
"prefs.stand_up_note": "Despois de abrir un ficheiro STEP ou IGES con Z cara arriba, ofrece un cuarto de volta arredor de X.",
"prefs.stand_up_unknown": "Para ficheiros de programas descoñecidos",
"prefs.stand_up_unknown.ask": "Preguntar nun diálogo",
"prefs.stand_up_unknown.note": "Mostrar unha nota",
"prefs.stand_up_unknown.off": "Non ofrecer",
"prefs.stand_up_unknown_note": "Como se fai a oferta cando a cabeceira do ficheiro non nomea ningún programa que se saiba que garda con Y cara arriba. Só se mostra unha nota cando o ficheiro parece estar deitado; Preguntar nun diálogo pregunta por cada ficheiro así, como antes.",
"prefs.status_bar": "Barra de estado",
"prefs.status_bar_note": "Unha barra fina ao longo do bordo inferior da xanela mostra o que se está a editar, o estado do bosquexo aberto na súa cor e as unidades do deseño; un clic nas unidades cámbiaas. O estado do bosquexo xa non aparece entón na esquina da vista.",
"prefs.status_done": "Feito",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "Aceptar e Cancelar na esquina da vista",
"prefs.view_confirm_corner_note": "Mentres hai un diálogo ou un bosquexo aberto, unha marca de verificación e unha cruz na parte superior dereita da vista fan o mesmo que Aceptar e Cancelar do diálogo, ou finalizan e cancelan o bosquexo. O navegador da vista desprázase cara abaixo, debaixo delas.",
"prefs.viewport": "Ventá gráfica",
"prefs.viewport_analysis_status": "Mostrar o estado de análise da ventá gráfica",
"prefs.viewport_analysis_status_help": "Mostra as análises activas sobre a ventá gráfica, abre as súas lendas desde cada elemento e usa Esc para desactivar a análise máis recente. Desactiva para restaurar as lendas nas esquinas e o comportamento anterior de Esc.",
"prefs.viewport_background": "Fondo",
"prefs.viewport_background_colour": "Cor",
"prefs.viewport_background_theme": "Degradado do tema",
@@ -2049,6 +2095,10 @@
"properties.title": "Propiedades: {name}",
"recovery.days_ago": "hai {count} días",
"recovery.detail": "{file} · {features} operacións · gardado automaticamente {when}",
"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?",
"recovery.hours_ago": "hai {count} h",
"recovery.just_now": "agora mesmo",
"recovery.min_ago": "hai {count} min",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Mover operacións ao cartafol",
"timeline.rename_group": "Renomear o cartafol de operacións",
"timeline.reorder_many": "Reordenar operacións",
"timeline.suppress_features": "Suprimir as operacións",
"timeline.suppress_folders_tip": "Suprime ou deixa de suprimir todas as operacións dos cartafoles e dos seus cartafoles aniñados",
"timeline.ungroup": "Quitar os cartafoles de operacións",
"timeline.unsuppress_features": "Deixar de suprimir as operacións",
"to the face": "á cara",
"to the next face": "ata a seguinte cara",
"toolbar.customize_note": "Lapela {tab}. Marque un comando para engadir o seu botón á barra de ferramentas. Tamén pode arrastrar unha fila do despregable dun panel á barra, arrastrar un botón fóra da barra para retiralo ou usar a chincheta dunha fila do despregable.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantén a posición e despois sóltao.",
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
"ui.3d_mouse_use_direct": "Usar controlador directo",
"ui.3d_mouse_use_direct_tip": "Ler o rato 3D directamente (hidraw) en vez de a través de spacenavd.",
"ui.3d_mouse_view_axis": "Eixe da vista",
"ui.3d_mouse_view_off": "Desactivado",
"ui.3d_mouse_view_off_tip": "O perfil do controlador do rato 3D desactiva este eixe, así que o seu movemento non move a vista. Se escolles aquí o movemento doutro eixe, intercámbianse e ese movemento deixa de mover a vista.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Cancelar",
"ui.canvases": "Lenzos",
"ui.center": "Centro",
"ui.center_arc_follows_cursor": "O arco por punto central segue a dirección do cursor",
"ui.center_arc_follows_cursor_tip": "Un arco por punto central vai no sentido horario ou antihorario segundo como o cursor xira arredor do seu centro. Desactivado: sempre no sentido antihorario desde o inicio.",
"ui.centerline": "Liña central",
"ui.chain": "Cadea",
"ui.chamfer": "Chafrán",
@@ -3930,6 +3986,7 @@
"ui.circle": "Círculo",
"ui.circular_pattern": "Patrón circular",
"ui.clear": "Borrar",
"ui.clear_print_face": "Quitar cara de impresión",
"ui.clear_recent": "Borrar recentes",
"ui.clear_the_selection": "Borrar a selección",
"ui.click_selects_whole_bodies": "Un clic selecciona corpos enteiros",
@@ -3964,6 +4021,14 @@
"ui.constant": "Constante",
"ui.constraints_conflict": "Conflito de restricións",
"ui.construction": "Construción",
"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",
"ui.construction.ribbon_label": "Liñas aux.",
"ui.context_menu": "Menú contextual",
"ui.continuity_g0": "G0 aresta",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Polígono",
"ui.preferences": "Preferencias",
"ui.press_a_key": "preme unha tecla…",
"ui.print_face_cleared": "{body}: cara de impresión quitada",
"ui.print_face_input": "Cara de impresión",
"ui.print_face_one_body": "Unha cara de impresión pertence a un só corpo: envía ese corpo só para cambiala",
"ui.print_face_pick": "Selecciona unha cara plana dun corpo sólido para facela a cara de impresión",
"ui.print_face_set": "{body}: cara de impresión definida; Enviar ao laminador apoia o corpo sobre ela",
"ui.profile": "Perfil",
"ui.profile_plane": "Plano do perfil",
"ui.profiles": "Perfís",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Revolución",
"ui.ribbon_exit": "Saír",
"ui.ribbon_labels": "Mostrar texto baixo as iconas da cinta",
"ui.ribbon_menus_skip_pinned": "Deixar as ordes fixadas fóra dos menús despregables",
"ui.ribbon_pin_footer": "As ordes fixadas móstranse na cinta. Pasa o cursor sobre unha fila para fixala ou soltala.",
"ui.ribbon_workspace": "Espazo de traballo",
"ui.right": "Dereita",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Centro da vista",
"ui.viewport_status_clear": "Desactivar {name}",
"ui.viewport_status_clear_hint": "Desactivar {name} · Esc",
"ui.viewport_status_edges": "{count} arestas",
"ui.viewport_status_from": "desde {direction}",
"ui.viewport_status_key": "Mostrar a lenda de {name}",
"ui.viewport_status_minimum_radius": "Raio mínimo",
"ui.viewport_status_pairs": "{count} pares",
@@ -4634,7 +4707,9 @@
"update.button": "Actualizar",
"update.button_version": "Actualizar a {version}",
"update.check_automatically": "Buscar actualizacións automaticamente",
"update.check_for_updates": "Buscar actualizacións",
"update.check_now": "Comprobar agora",
"update.check_unavailable": "A busca de actualizacións non está dispoñible para esta instalación.",
"update.checking": "Comprobando…",
"update.copy": "Copiar",
"update.dmg_opened": "A imaxe de disco está aberta e verificada: arrastra SolidCraft a Aplicacións, substituíndo a copia antiga, e despois reinicia SolidCraft.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} está instalado. Reinicia para usalo.",
"update.installer_started": "O instalador iníciase cando se pecha SolidCraft.",
"update.later": "Lembrarmo máis tarde",
"update.managed": "Actualización dispoñible: actualiza SolidCraft en {manager}",
"update.no_notes": "Esta versión non ten notas.",
"update.package_hint": "Esta copia veu dun {kind}. Descarga o paquete novo e despois instálao con:",
"update.prefs_dev": "Esta é unha compilación de desenvolvemento ({version}): non busca actualizacións.",
"update.prefs_flatpak": "Flatpak actualiza esta copia ({version}): executa {command} ou usa o teu centro de software. SolidCraft non comproba.",
"update.prefs_label": "Actualizacións",
"update.prefs_managed": "Esta é a versión {version} de SolidCraft, xestionada por {manager}. Unha comprobación pídelle a GitHub a última versión e non envía nada máis; actualiza SolidCraft en {manager}.",
"update.prefs_note": "Esta é a versión {version} de SolidCraft ({kind}). Unha comprobación pídelle a GitHub a última versión e non envía nada máis.",
"update.prefs_web": "Recarga a páxina para obter a última versión da aplicación web. Se a rede non está dispoñible ou a actualización non pode rematar de descargarse a tempo, ábrese a versión gardada; unha descarga en curso continúa para a seguinte recarga.",
"update.refresh_checking": "Buscando a versión máis recente…",
+88
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": "התמחות",
@@ -819,6 +822,7 @@
"cmd.appearance_pick_up": "בחירת מראה",
"cmd.appearance_replace": "החלפת מראה",
"cmd.assembly_bom": "רשימת חלקים",
"cmd.body.print_face": "פאת הדפסה",
"cmd.body_rename": "שינוי שם גוף",
"cmd.browser_collapse_all": "כיווץ הכול",
"cmd.browser_expand_all": "הרחבת הכול",
@@ -1412,6 +1416,7 @@
"help.sketch.construction_line.tangent_two": "יש לבחור שני מעגלים או קשתות מעגליות ואז משיק משותף בתצוגה המקדימה. נקודות ההשקה חייבות להיות על העקומות המצוירות.",
"help.sketch.construction_line.vertical": "יצירת ישר עזר אנכי אינסופי דרך נקודה. האילוץ האנכי נשמר בעריכות מאוחרות יותר.",
"help.sketch.equation_curve": "משרטט עקומה מנוסחאות על פני טווח: x(t) ו־y(t), או y = f(x). הנוסחאות יכולות להשתמש בפרמטרים והעקומה עוקבת אחריהם: לגל סינוס, לספירלה או לאבולבנטה של שן גלגל שיניים.",
"help.sketch.inspect": "פותח דוח על הסקיצה הפתוחה: כמה דרגות חופש נותרו והאם היא מאולצת במלואה. השתמשו בו כדי לגלות מה עדיין זז לפני שמסיימים את הסקיצה.",
"help.solid.align": "הזזת הגופים שנבחרו כך שנקודה, פאה או מישור שלהם יפגשו נקודה, פאה או מישור במקום אחר. מתאים להצמדת חלק לחלק אחר בלי למדוד את המרחק.",
"help.solid.chamfer": "חיתוך המקצועות שנבחרו בשיפוע ישר במרחק שנקבע. כך שוברים מקצועות חדים או מקלים על הכנסה של פין או בורג.",
"help.solid.combine": "איחוד גופים, חיסורם מגוף יעד או השארת הנפח המשותף להם בלבד, עם אפשרות לשמור את גופי הכלי. מתאים למיזוג חלקים, לחיתוך צורה אחת בעזרת אחרת או למציאת החפיפה בין שני גופים.",
@@ -1614,6 +1619,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} שנ'",
@@ -1766,6 +1775,11 @@
"not a move": "זו אינה הזזה",
"not a number": "לא מספר",
"open a sketch first": "יש לפתוח סקיצה תחילה",
"parameters.apply_live": "להחיל כל שינוי ביציאה מהשדה שלו",
"parameters.apply_live_tip": "כבוי: השינויים ממתינים עד ל„החל” או „אישור”, ואז התכן נבנה מחדש פעם אחת עבור כולם. „ביטול” או Escape משליכים אותם.",
"parameters.held": "{count} שינויים ממתינים ל„החל”",
"parameters.held_hint": "עריכת ביטויים ויחידות. השינויים ממתינים ל„החל” או ל„אישור”.",
"parameters.held_one": "שינוי אחד ממתין ל„החל”",
"parts.automatic_name": "אוטומטי",
"parts.dimension.across_flats": "מידת מפתח",
"parts.dimension.bore": "קוטר קדח",
@@ -1788,6 +1802,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 +1856,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": "מוצג",
@@ -1890,6 +1908,8 @@
"prefs.graphics_card_restart": "ייכנס לתוקף אחרי הפעלה מחדש של 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": "ההיסטוריה משמאל או מתחת לדפדפן מתחילה בתיקיית הגופים של הרכיב הפעיל ובראשית, עם סמלי העין והתפריטים כמו בדפדפן. ההיסטוריה למטה לא משתנה.",
@@ -1950,12 +1970,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": "ההגדרות השתנו מאז שהפרופיל נשמר. „שמירה“ מכניסה אותן לפרופיל.",
@@ -1977,6 +2001,8 @@
"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} שמורים",
@@ -1990,6 +2016,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 מחליף מצב",
@@ -2068,6 +2096,8 @@
"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": "כשתיבת דו־שיח או סקיצה פתוחות, סימן וי וסימן איקס בפינה הימנית העליונה של התצוגה עושים את מה שעושים אישור וביטול של תיבת הדו־שיח, או מסיימים ומבטלים את הסקיצה. ניווט התצוגה זז אל מתחתיהם.",
@@ -2077,6 +2107,10 @@
"prefs.viewport_background": "רקע",
"prefs.viewport_background_colour": "צבע",
"prefs.viewport_background_theme": "מעבר הצבעים של ערכת הנושא",
"prefs.viewport_section_status": "הצגת מצב חתך בחלון התצוגה",
"prefs.viewport_section_status_help": "הצגת הגדרת החתך בחלון התצוגה. Escape ולחצן הסרט הפעיל מכבים את החתך. כיבוי האפשרות שומר על בקרי החתך הקודמים.",
"prefs.viewport_status_summary": "סיכום פריטי מצב בחלון התצוגה",
"prefs.viewport_status_summary_help": "קיבוץ שלושה פריטי מצב או יותר לתג סיכום אחד. כיבוי האפשרות משאיר כל פריט בשורה המלאה.",
"prefs.wheel": "גלגלת",
"prefs.wheel_trackpad": "גלגלת ומשטח מגע",
"prefs.workspace": "סביבת עבודה",
@@ -2955,7 +2989,9 @@
"render.output_ready": "מוכן לרינדור",
"render.output_run_check": "הפעל בדיקה",
"render.output_size_limit": "{method}: עד {dimension} פיקסלים לצלע ו-{pixels} פיקסלים. הגודל הגדול ביותר שמתאים: {width} × {height}.",
"render.output_size_method_profile": "כדי לרנדר {width} × {height}, יש לבחור ב-{method} עם פרופיל משאבי CPU {profile} או בגודל קטן יותר.",
"render.output_size_ok": "הגודל תקין · {width} × {height} פיקסלים",
"render.output_size_profile": "כדי לרנדר {width} × {height}, יש לבחור בפרופיל משאבי CPU {profile} או בגודל קטן יותר.",
"render.output_swap_orientation": "החלפת רוחב וגובה",
"render.output_tools_inspector": "ערוך גודל ופורמט במפקח הפלט.",
"render.output_view_image": "הצג תמונה",
@@ -3168,6 +3204,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": "תצוגה מקדימה",
@@ -3455,6 +3492,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 חייב להיות מזהה פקודה",
"shortcut.pin": "הצמדה לראש תיבת S",
@@ -3491,6 +3546,7 @@
"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.on_path": "הטקסט עוקב אחר המסלול שלו: העקומה קובעת את מיקומו",
@@ -3631,6 +3687,8 @@
"ui.3d_mouse_section": "עכבר תלת־ממדי",
"ui.3d_mouse_show_in_view": "הצגת העכבר התלת־ממדי בתצוגה התלת־ממדית",
"ui.3d_mouse_show_in_view_tip": "תמונה של העכבר התלת־ממדי בפינה השמאלית התחתונה, שזזה כשמזיזים אותו.",
"ui.3d_mouse_sketch_orbit": "סיבוב סקיצות בעכבר התלת־ממדי",
"ui.3d_mouse_sketch_orbit_tip": "העכבר התלת־ממדי מסובב את התצוגה גם כשסקיצה פתוחה ונראית חזיתית, כמו שהעכבר יכול. כבוי: הוא מזיז ומגדיל את הסקיצה עד שתטה את התצוגה.",
"ui.3d_mouse_skip": "ההתקן שלי לא יכול לעשות את זה",
"ui.3d_mouse_skip_tip": "מדלג על התנועה הזאת בשני הכיוונים, והכיול משמיט אותה.",
"ui.3d_mouse_speed": "מהירות עכבר תלת־ממדי",
@@ -3659,6 +3717,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": "קריאת העכבר התלת־ממדי ישירות (hidraw) במקום דרך spacenavd.",
"ui.3d_mouse_view_axis": "ציר מבט",
"ui.3d_mouse_view_off": "כבוי",
"ui.3d_mouse_view_off_tip": "הפרופיל של מנהל ההתקן לעכבר התלת־ממדי מכבה את הציר הזה, ולכן התנועה שלו לא מזיזה את המבט. בחירה כאן בתנועה של ציר אחר מחליפה בין השניים, והתנועה ההיא מפסיקה להזיז את המבט.",
@@ -3792,6 +3852,8 @@
"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": "שמירה",
@@ -3975,6 +4037,7 @@
"ui.circle": "מעגל",
"ui.circular_pattern": "מערך מעגלי",
"ui.clear": "ניקוי",
"ui.clear_print_face": "נקה פאת הדפסה",
"ui.clear_recent": "ניקוי האחרונים",
"ui.clear_the_selection": "ניקוי הבחירה",
"ui.click_selects_whole_bodies": "לחיצה בוחרת גופים שלמים",
@@ -4407,12 +4470,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": "יחידות הקובץ",
@@ -4421,6 +4489,11 @@
"ui.polygon": "מצולע",
"ui.preferences": "העדפות",
"ui.press_a_key": "יש ללחוץ על מקש…",
"ui.print_face_cleared": "{body}: פאת ההדפסה נוקתה",
"ui.print_face_input": "פאת הדפסה",
"ui.print_face_one_body": "פאת הדפסה נבחרת לגוף אחד: שלח את הגוף הזה לבדו כדי לשנות אותה",
"ui.print_face_pick": "בחר פאה שטוחה אחת של גוף מוצק כדי להפוך אותה לפאת ההדפסה",
"ui.print_face_set": "{body}: פאת ההדפסה הוגדרה; שליחה לסלייסר מניחה את הגוף עליה",
"ui.profile": "פרופיל",
"ui.profile_plane": "מישור הפרופיל",
"ui.profiles": "פרופילים",
@@ -4503,6 +4576,7 @@
"ui.selectOther": "בחירת אחר…",
"ui.selectTangentChain": "בחירת שרשרת משיקה",
"ui.select_all": "בחירת הכול",
"ui.select_components_by_face": "בחירת רכיבים (בחירת פאה בוחרת את הרכיב שלה)",
"ui.selection": "בחירה",
"ui.selection_modifiers": "מקשי שינוי לבחירה",
"ui.selection_shift_add_only": "Shift+לחיצה מוסיף בלי להסיר",
@@ -4544,6 +4618,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": "חתך",
@@ -4653,7 +4732,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": "מוצלל עם מקצועות",
@@ -4690,7 +4776,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.",
+27
View File
@@ -819,6 +819,7 @@
"cmd.appearance_pick_up": "Preuzmi izgled",
"cmd.appearance_replace": "Zamijeni izgled",
"cmd.assembly_bom": "Sastavnica",
"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",
@@ -1840,6 +1841,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",
@@ -1890,6 +1893,8 @@
"prefs.graphics_card_restart": "Stupa na snagu nakon ponovnog pokretanja SolidCrafta. WGPU_ADAPTER_NAME ima prednost nad ovim odabirom.",
"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.",
@@ -1928,6 +1933,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",
@@ -1953,6 +1961,8 @@
"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.",
@@ -1987,6 +1997,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",
@@ -2069,6 +2081,8 @@
"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",
@@ -3654,6 +3668,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.",
@@ -3970,6 +3986,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",
@@ -4416,6 +4433,11 @@
"ui.polygon": "Mnogokut",
"ui.preferences": "Postavke",
"ui.press_a_key": "pritisnite tipku…",
"ui.print_face_cleared": "{body}: ploha ispisa uklonjena",
"ui.print_face_input": "Ploha ispisa",
"ui.print_face_one_body": "Ploha ispisa pripada jednom tijelu: pošaljite to tijelo samo da biste je promijenili",
"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",
@@ -4479,6 +4501,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",
@@ -4642,6 +4665,8 @@
"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}",
@@ -4682,7 +4707,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.",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Itt jelennek meg a megnyitott vagy mentett tervek.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Az Objektumig érték határozza meg. A szögtartomány szerkesztéséhez válassza a Szög lehetőséget.",
"Diameter": "Átmérő",
"Did this assembly open the wrong way up?": "Rossz tájolással nyílt meg ez az összeállítás?",
"Did this part open the wrong way up?": "Rossz tájolással nyílt meg ez az alkatrész?",
"Dimension new shapes": "Új alakzatok méretezése",
"Direction": "Irány",
"Discard": "Elvetés",
@@ -601,6 +603,7 @@
"Square": "Négyzet",
"Stand it up": "Felállítás",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Felállítás: negyedfordulat az X körül, egyetlen visszavonható Mozgatás elemként.",
"Stand this assembly up?": "Felállítja ezt az összeállítást?",
"Stand this part up?": "Felállítja ezt az alkatrészt?",
"Start angle": "Kezdőszög",
"Start bulge": "Kezdő ívelés",
@@ -772,6 +775,8 @@
"at the face": "a lapnál",
"bom.group_children": "Részalkatrészek az alkatrészük sorában",
"browser.body_name": "Test{number}",
"browser.calibrate_click_a": "Kattintson a képen az A pontra, vagy adja meg a koordinátáit.",
"browser.calibrate_click_b": "Kattintson a képen a B pontra, vagy adja meg a koordinátáit.",
"browser.calibrate_title": "Kalibrálás: {name}",
"browser.capture_many": "{count} komponens el lett mozgatva, de a pozíciójuk nincs rögzítve.",
"browser.capture_note": "A rögzítés megtartja az új pozíciót; a visszaállítás visszateszi.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Megjelenés felvétele",
"cmd.appearance_replace": "Megjelenés cseréje",
"cmd.assembly_bom": "Anyagjegyzék",
"cmd.body.print_face": "Nyomtatási lap",
"cmd.body_rename": "Test átnevezése",
"cmd.browser_collapse_all": "Összes összecsukása",
"cmd.browser_expand_all": "Összes kibontása",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Kattintson egy másik hajtó méretre a neve beszúrásához",
"dialog.dimension_name": "Méret: {name}",
"dialog.edit_title": "{title} SZERKESZTÉSE",
"dialog.no_shown_body": "Nincs módosítható megjelenített test: jelenítsen meg egyet, vagy válassza az Új test lehetőséget",
"dialog.select": "↖ Kijelölés",
"dialog.selected_count": "↖ {count} kijelölve",
"dialog.selection_guidance": "Válasszon ki egy bemenetet a módosításhoz. A kijelölt objektumokra kattintva eltávolíthatja őket; a Ctrl/Cmd-kattintás is váltja a kijelölést. Tekintse át az előnézetet, majd nyomja meg az OK gombot.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Az összeállítás egy alkatrészét csak a csuklói által megengedett szabadságfokok mentén mozgatja. Jelölje ki az alkatrész egy lapját, majd húzza az egyik elérhető nyilat vagy ívet.",
"help.parameters.change": "Megnyitja a terv méreteit vezérlő elnevezett értékek és kifejezések táblázatát. Használja egy méret egy helyen való módosítására, hogy az azt használó összes elem kövesse.",
"help.pointcloud.insert": "PLY, XYZ vagy PTS szkennelést szúr be pontfelhőként a modell mellé. Használja referenciaként, amikor mért adatokból épít modellt.",
"help.sketch.construction_line": "Végtelen szerkesztőegyenest hoz létre két ponton át. Használja a vázlat megtervezéséhez a profilok szétvágása nélkül.",
"help.sketch.construction_line.angle": "Végtelen szerkesztőegyenest hoz létre a vázlat X tengelyéhez képest adott szögben. Válassza ki a pontot, és adja meg a szöget, vagy válassza ki az irányát.",
"help.sketch.construction_line.bisector": "Válasszon ki két egymást metsző egyenes vázlatvonalat, majd bármelyik szögfelezőt. Az eredmény követi a forrásvonalait.",
"help.sketch.construction_line.horizontal": "Végtelen vízszintes szerkesztőegyenest hoz létre egy ponton át. Vízszintes kényszere a későbbi módosítások után is megmarad.",
"help.sketch.construction_line.parallel": "Válasszon ki egy egyenes vázlatvonalat, majd helyezzen el egy vele párhuzamos, végtelen szerkesztőegyenest. Másik referencia kiválasztásához kattintson jobb gombbal.",
"help.sketch.construction_line.perpendicular": "Válasszon ki egy egyenes vázlatvonalat, majd helyezzen el egy rá merőleges, végtelen szerkesztőegyenest. Másik referencia kiválasztásához kattintson jobb gombbal.",
"help.sketch.construction_line.tangent_at": "Érintőt hoz létre egy kör vagy körív pontjában, illetve egy nyitott spline vagy kúpgörbe végpontjában. Válassza ki a görbét, majd az érintési pontját.",
"help.sketch.construction_line.tangent_external": "Válasszon ki egy kört vagy körívet, majd egy külső pontot és egy érintőmegoldást. Az előnézetek csak a megrajzolt íven lévő érintési pontokat mutatják.",
"help.sketch.construction_line.tangent_two": "Válasszon ki két kört vagy körívet, majd az előnézetben válasszon közös érintőt. Az érintési pontoknak a megrajzolt görbéken kell lenniük.",
"help.sketch.construction_line.vertical": "Végtelen függőleges szerkesztőegyenest hoz létre egy ponton át. Függőleges kényszere a későbbi módosítások után is megmarad.",
"help.sketch.equation_curve": "Görbét rajzol képletekből: x(t) és y(t), vagy y = f(x) alapján, egy tartományon. A képletek paramétereket is használhatnak, és a görbe követi őket: használja szinuszhullámhoz, spirálhoz vagy fogaskerékfog evolvenséhez.",
"help.solid.align": "Úgy mozgatja a kijelölt testeket, hogy egy rajtuk lévő pont, lap vagy sík találkozzon máshol lévő ponttal, lappal vagy síkkal. Használja egy alkatrész másikhoz illesztésére az eltolás lemérése nélkül.",
"help.solid.chamfer": "A kijelölt éleket lapos letöréssel vágja le a megadott távolságban. Használja éles élek tompítására, illetve egy csap vagy csavar bevezetésének megkönnyítésére.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Az idővonal minden elemét az elejétől újraépíti. A modell frissítésére és a sikertelen elemek megtekintésére használja.",
"help.timeline.delete": "Törli a kijelölt elemeket az idővonalról; egy vázlat törlése a belőle épített elemeket is törli. Egy már nem kívánt lépés eltávolítására használja.",
"help.timeline.play": "Egyenként újrajátssza a hátralévő elemeket, minden újraépítés után fél másodperc szünettel. A leállításhoz futtassa újra ezt a parancsot, vagy nyomja meg az Esc billentyűt; az Ugrás a végére azonnal a végére ugrik.",
"history.sketch_under_constrained": "Alulkényszerezett ({count} szabadságfok)",
"history.sketch_under_constrained_one": "Alulkényszerezett ({count} szabadságfok)",
"hole_presets.confirm_delete": "Törlés megerősítése",
"hole_presets.delete": "Törlés",
"hole_presets.delete_prompt": "Törli ezt a személyes előbeállítást? A meglévő furatok nem változnak.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Elsimítja a recés éleket.",
"prefs.applies_on_restart": "Újraindításkor lép érvénybe",
"prefs.assistant": "Asszisztens",
"prefs.auto_operation": "A művelet automatikus kiválasztása",
"prefs.auto_operation_note": "Az elemablakok aszerint választják az Egyesítés, Új test vagy Kivágás műveletet, hogy mit érint az új anyag, amíg Ön nem választ egyet. Kikapcsolva: megtartják a megnyitáskor kapott műveletet.",
"prefs.axes": "Tengelyek",
"prefs.axis": "Tengely",
"prefs.bodies_folders": "Testmappák",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Alsó közép",
"prefs.bottom_right": "Jobb alsó",
"prefs.browser": "Böngésző",
"prefs.browser_eye_left": "Szem az összecsukó nyíl mellett",
"prefs.browser_eye_left_note": "A böngésző sorai a megjelenítés/elrejtés szemét az összecsukó nyíl mellett mutatják, mint korábban, nem a sorok jobb végén lévő oszlopban.",
"prefs.browser_reveal_on_hover": "Megjelenítés rámutatáskor",
"prefs.browser_show_note": "A Megjelenítve beállítás a képernyőn tartja a böngészőt. A Megjelenítés rámutatáskor elrejti, amíg a mutató a helye vagy a bal szélén lévő keskeny fül fölé nem kerül, és nyitva marad, amíg a mutató rajta van, vagy amíg egy belőle nyitott menü nyitva van.",
"prefs.browser_shown": "Megjelenítve",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "A SolidCraft újraindítása után lép életbe. A WGPU_ADAPTER_NAME felülbírálja ezt a választást.",
"prefs.graphics_note": "Cserélje a sebességet simaságra. Minden választás hatását egy minta mutatja.",
"prefs.graphics_presets_note": "Az előbeállítások az alábbi beállításokat együtt állítják be.",
"prefs.hide_constraint_glyphs_zoomed_out": "Kényszerjelek elrejtése kicsinyítéskor",
"prefs.hide_constraint_glyphs_zoomed_out_note": "A kényszerjelek eltűnnek a nézetből, ha a vázlat annyira ki van kicsinyítve, hogy zsúfolttá tennék. Kikapcsolva: minden nagyításnál változatlan képernyőméretűek maradnak.",
"prefs.history": "Előzmények",
"prefs.history_origin_bodies": "Az origó és a testek megjelenítése felül",
"prefs.history_origin_bodies_note": "A bal oldali vagy a Böngésző alatti Előzmények az aktív komponens Testek mappájával és az Origóval kezdődik, szem ikonjaikkal és menüikkel, ahogy a Böngészőben. Az alul lévő Előzmények nem változik.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "STEP- és importfájlok",
"prefs.max_file_size_web": "A webalkalmazás legfeljebb 512 MB-os STEP- és importfájlokat, valamint 256 MB-os terveket nyit meg: egy böngészőlap nem tud nagyobb fájlokat tárolni. Az asztali alkalmazásban e korlátok növelhetők.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Nagy ikonok",
"prefs.menu_list": "Lista",
"prefs.menus": "Menük",
"prefs.minutes": "perc",
"prefs.minutes_suffix": " perc",
"prefs.mouse": "Egér és érintőpad",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Origósíkok nevei",
"prefs.origin_plane_names_note": "A Böngészőben, a párbeszédablakokban és a listákban megjelenő nevek. A {top} az XY, a {front} az XZ, a {right} pedig az YZ, a Z felfelé mutat. A parancsok, szkriptek és mentett tervek az XY, XZ és YZ jelölést tartják meg.",
"prefs.overall_speed": "Összsebesség",
"prefs.persistent_ribbon_pressed": "Az aktív menüszalag-parancsok kiemelése",
"prefs.persistent_ribbon_pressed_help": "Kiemeli a menüszalag parancsait, amíg a hatásuk be van kapcsolva. Kapcsolja ki a korábbi semleges megjelenés és parancssúgó megtartásához.",
"prefs.perspective_fov": "Látószög",
"prefs.placement": "Elhelyezés",
"prefs.prefer_free_sketch_points": "Átfedő pontoknál a mozgatható vázlatpontok előnyben részesítése",
"prefs.preview_choices": "A választások előnézete",
"prefs.print_face_highlight": "Nyomtatási lap kiemelése",
"prefs.print_face_highlight_tip": "Milyen erősen színezi és körvonalazza a nézet a test nyomtatási lapját. 0 % esetén nincs kiemelés.",
"prefs.profile_active": "Aktív profil",
"prefs.profile_applied": "Profil alkalmazva.",
"prefs.profile_changed": "A beállítások a profil mentése óta megváltoztak. A Mentés eltárolja őket a profilban.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Nincs egyező beállítás",
"prefs.select": "Kijelölés",
"prefs.select_one_item": "Egy elem kijelölése",
"prefs.select_other_hidden": "Rejtett geometria kijelölése a Más kijelölése paranccsal",
"prefs.select_other_hidden_help": "Bekapcsolva: a mutató közelében lévő rejtett testeket, vázlatokat és takart éleket is tartalmazza. Kikapcsolva: a mutató alatti látható geometria eredeti listáját használja.",
"prefs.selecting": "Kijelölés",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "A Shift csak hozzáad; a Ctrl / Cmd átkapcsol",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Rajzolás közben egy pont egyszerre igazodhat vízszintesen az egyik vázlatponthoz és függőlegesen egy másikhoz, pontozott segédvonallal, és a kattintás mindkét igazítást megtartja. Ki: csak az előző ponthoz, egyszerre egy tengely mentén.",
"prefs.sketch_conflict_colour": "Ütköző geometria az ütközés színével",
"prefs.sketch_conflict_colour_note": "Amíg egy vázlat kényszerei ütköznek, a hibás kényszer által megfogott görbék és pontok is az ütközés színével jelennek meg, nem csak a kényszer szimbóluma vagy mérete. Kikapcsolva: megtartják szokásos színüket.",
"prefs.sketch_construction_lines": "Bővített szerkesztőegyenes-eszközök",
"prefs.sketch_exit_view": "Vázlat befejezésekor",
"prefs.sketch_exit_view.keep": "A jelenlegi nézet megtartása",
"prefs.sketch_exit_view.previous": "Visszatérés az előző nézethez",
"prefs.sketch_exit_view.unless_rotated": "Visszatérés, ha nem lett forgatva",
"prefs.sketch_exit_view_note": "Hová kerül a nézet a Vázlat befejezése, a Mégse vagy a dupla Esc után.",
"prefs.sketch_open_look_at": "Vázlat megnyitásakor a nézet a vázlatra fordul",
"prefs.sketch_open_look_at_note": "Ki: vázlat létrehozása vagy szerkesztése nem változtatja a nézetet. A Ránézés a vázlatra fordítja a nézetet, amikor szeretné.",
"prefs.sketch_recent_snap": "Automatikus illesztés a legutóbbi középpontokhoz és felezőpontokhoz",
"prefs.sketch_recent_snap_note": "Rajzolás közben a kurzor oda illeszkedik, ahol a két legutóbbi pont (kör középpontja vagy egyenes él felezőpontja) segédvonalai keresztezik egymást. Kikapcsolva: ezek a segédvonalak csak megjelennek, és a kurzor nem mozdul el.",
"prefs.sketch_selection": "Vázlat és kijelölés",
"prefs.sketch_selection_heading": "Vázlat és kijelölés",
"prefs.sketch_selection_note": "Hogyan illeszkednek a vázlatok, és hogyan jelölnek ki a kattintások.",
"prefs.sketch_state_icons": "A vázlatok ikonjai az állapotuk színével",
"prefs.sketch_state_icons_note": "Az Előzményekben és a böngészőben a vázlat ikonja az állapotának színét veszi fel (alulkényszerezett, teljesen kényszerezett vagy ütköző), a buboréksúgója pedig megnevezi az állapotot. Kikapcsolva: minden vázlat ikonja egyforma.",
"prefs.sketch_tab_always": "A Vázlat fül mindig látható",
"prefs.sketch_tab_always_note": "A Vázlat fül a szalagon marad, közvetlenül a Modell után, akkor is, ha nincs megnyitott vázlat. Rajzszerszámai ilyenkor a kijelölt síkon vagy lapon, illetve a következőként kiválasztott síkon indítanak vázlatot, benne a kiválasztott szerszámmal.",
"prefs.sketch_tracking_guides": "Középpont- és felezőpont-igazítási segédvonalak",
@@ -1995,6 +2034,11 @@
"prefs.source": "Forrás",
"prefs.speed": "Sebesség",
"prefs.stand_up_note": "Miután egy STEP- vagy IGES-fájl Z felfelé tájolással megnyílik, negyedfordulat felajánlása az X körül.",
"prefs.stand_up_unknown": "Ismeretlen programokból származó fájloknál",
"prefs.stand_up_unknown.ask": "Rákérdezés párbeszédpanelben",
"prefs.stand_up_unknown.note": "Megjegyzés megjelenítése",
"prefs.stand_up_unknown.off": "Ne ajánlja fel",
"prefs.stand_up_unknown_note": "Az ajánlat módja, ha a fájl fejléce nem nevez meg olyan programot, amelyről ismert, hogy Y felfelé tájolással ment. Megjegyzés csak akkor jelenik meg, ha a fájl hanyatt fekvőnek látszik; a Rákérdezés párbeszédpanelben minden ilyen fájlnál rákérdez, mint korábban.",
"prefs.status_bar": "Állapotsor",
"prefs.status_bar_note": "Az ablak alsó szélén lévő vékony sáv megmutatja, hogy mit szerkeszt, a megnyitott vázlat állapotát a színével, valamint a terv egységeit; az egységekre kattintva módosíthatók. A vázlat állapota ilyenkor már nem jelenik meg a nézet sarkában.",
"prefs.status_done": "Kész",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK és Mégse a nézet sarkában",
"prefs.view_confirm_corner_note": "Amíg egy párbeszédablak vagy vázlat nyitva van, a nézet jobb felső részén egy pipa és egy kereszt a párbeszédablak OK és Mégse gombját működteti, illetve befejezi vagy megszakítja a vázlatot. A nézetnavigátor lejjebb kerül alattuk.",
"prefs.viewport": "Nézetablak",
"prefs.viewport_analysis_status": "Elemzési állapot megjelenítése a nézetablak fölött",
"prefs.viewport_analysis_status_help": "A nézetablak fölött megjeleníti az aktív elemzéseket, minden elemből megnyitja a jelmagyarázatát, az Esc billentyűvel pedig kikapcsolja a legutóbbi elemzést. Kikapcsolva: visszaáll a sarokban lévő jelmagyarázat és az Esc korábbi működése.",
"prefs.viewport_background": "Háttér",
"prefs.viewport_background_colour": "Szín",
"prefs.viewport_background_theme": "A téma színátmenete",
@@ -2049,6 +2095,10 @@
"properties.title": "Tulajdonságok: {name}",
"recovery.days_ago": "{count} napja",
"recovery.detail": "{file} · {features} elem · automatikusan mentve: {when}",
"recovery.discard_all": "Összes elvetése",
"recovery.discard_all_detail": "Ez a művelet nem vonható vissza.",
"recovery.discard_all_title": "{count} automatikus mentés elvetése?",
"recovery.discard_all_title_one": "1 automatikus mentés elvetése?",
"recovery.hours_ago": "{count} órája",
"recovery.just_now": "épp most",
"recovery.min_ago": "{count} perce",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Elemek áthelyezése mappába",
"timeline.rename_group": "Elemmappa átnevezése",
"timeline.reorder_many": "Elemek átrendezése",
"timeline.suppress_features": "Elemek elnyomása",
"timeline.suppress_folders_tip": "A mappákban és a beágyazott mappákban lévő összes elem elnyomása vagy elnyomásának megszüntetése",
"timeline.ungroup": "Elemmappák eltávolítása",
"timeline.unsuppress_features": "Elemek elnyomásának megszüntetése",
"to the face": "a laphoz",
"to the next face": "a következő laphoz",
"toolbar.customize_note": "{tab} lap. Jelöljön be egy parancsot, hogy eszköztárgombot kapjon. Húzhat egy panel legördülő menüjének sorát is az eszköztárra, húzhat egy gombot le az eszköztárról az eltávolításhoz, vagy használhatja a legördülő sor rögzítő tűjét.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
"ui.3d_mouse_switch_cap": "Kalibrálás középre visszatérőként",
"ui.3d_mouse_switch_trackball": "Kalibrálás trackballként",
"ui.3d_mouse_use_direct": "Közvetlen illesztőprogram",
"ui.3d_mouse_use_direct_tip": "A 3D-egér közvetlen olvasása (hidraw) a spacenavd helyett.",
"ui.3d_mouse_view_axis": "Nézettengely",
"ui.3d_mouse_view_off": "Ki",
"ui.3d_mouse_view_off_tip": "A 3D-egér illesztőprogramjának profilja kikapcsolja ezt a tengelyt, ezért a mozgása nem mozgatja a nézetet. Ha itt egy másik tengely mozgását választja, a kettő felcserélődik, és az a mozgás már nem mozgatja a nézetet.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Mégse",
"ui.canvases": "Vásznak",
"ui.center": "Középpont",
"ui.center_arc_follows_cursor": "A középpontos ív követi a kurzor irányát",
"ui.center_arc_follows_cursor_tip": "A középpontos ív az óramutató járásával megegyező vagy azzal ellentétes irányban halad, aszerint, hogy a kurzor merre kerüli meg a középpontját. Kikapcsolva: mindig az óramutató járásával ellentétes irányban, a kezdőponttól.",
"ui.centerline": "Középvonal",
"ui.chain": "Lánc",
"ui.chamfer": "Letörés",
@@ -3930,6 +3986,7 @@
"ui.circle": "Kör",
"ui.circular_pattern": "Körminta",
"ui.clear": "Törlés",
"ui.clear_print_face": "Nyomtatási lap törlése",
"ui.clear_recent": "Legutóbbiak törlése",
"ui.clear_the_selection": "Kijelölés törlése",
"ui.click_selects_whole_bodies": "A kattintás teljes testeket jelöl ki",
@@ -3964,6 +4021,14 @@
"ui.constant": "Állandó",
"ui.constraints_conflict": "Kényszerek ütköznek",
"ui.construction": "Szerkesztő",
"ui.construction.angle_hint": "Szerkesztőegyenes: kattintson egy pontra, majd adjon meg szöget, vagy válasszon irányt",
"ui.construction.bisector_hint": "Szögfelező: válasszon két egymást metsző egyenest, majd a kívánt megoldást",
"ui.construction.common_hint": "Közös érintő: válasszon két kört/ívet, majd a kívánt megoldást",
"ui.construction.contact_hint": "Érintő: válasszon egy kört/ívet vagy nyitott spline-t/kúpgörbét, majd az érintési pontot (szabadformájú görbék: végpontok)",
"ui.construction.external_hint": "Érintő: válasszon egy kört vagy ívet, egy külső pontot, majd a kívánt megoldást",
"ui.construction.free_hint": "Szerkesztőegyenes: kattintson két pontra; jobb kattintással újrakezdheti",
"ui.construction.point_hint": "Szerkesztőegyenes: kattintson arra a pontra, amelyen át kell haladnia",
"ui.construction.reference_hint": "Szerkesztőegyenes: válasszon egy egyenest, majd egy pontot; jobb kattintással módosíthatja a referenciát",
"ui.construction.ribbon_label": "Segédvonalak",
"ui.context_menu": "Helyi menü",
"ui.continuity_g0": "G0 törés",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Sokszög",
"ui.preferences": "Beállítások",
"ui.press_a_key": "nyomjon meg egy billentyűt…",
"ui.print_face_cleared": "{body}: nyomtatási lap törölve",
"ui.print_face_input": "Nyomtatási lap",
"ui.print_face_one_body": "A nyomtatási lap egy testhez tartozik: a módosításhoz küldd el azt a testet egyedül",
"ui.print_face_pick": "Válassz ki egy tömör test egy sík lapját, hogy az legyen a nyomtatási lap",
"ui.print_face_set": "{body}: nyomtatási lap beállítva; a Küldés szeletelőnek erre a lapra fekteti a testet",
"ui.profile": "Profil",
"ui.profile_plane": "Profilsík",
"ui.profiles": "Profilok",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Forgatás",
"ui.ribbon_exit": "Kilépés",
"ui.ribbon_labels": "Szöveg megjelenítése a menüszalag ikonjai alatt",
"ui.ribbon_menus_skip_pinned": "A rögzített parancsok kihagyása a legördülő menükből",
"ui.ribbon_pin_footer": "A rögzített parancsok megjelennek a menüszalagon. Egy sor fölé víve rögzíthető vagy feloldható.",
"ui.ribbon_workspace": "Munkaterület",
"ui.right": "Jobb",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Nézet középpontja",
"ui.viewport_status_clear": "Kikapcsolás: {name}",
"ui.viewport_status_clear_hint": "Kikapcsolás: {name} · Esc",
"ui.viewport_status_edges": "{count} él",
"ui.viewport_status_from": "{direction} felől",
"ui.viewport_status_key": "Jelmagyarázat megjelenítése: {name}",
"ui.viewport_status_minimum_radius": "Minimális sugár",
"ui.viewport_status_pairs": "{count} pár",
@@ -4634,7 +4707,9 @@
"update.button": "Frissítés",
"update.button_version": "Frissítés erre: {version}",
"update.check_automatically": "Frissítések automatikus keresése",
"update.check_for_updates": "Frissítések keresése",
"update.check_now": "Keresés most",
"update.check_unavailable": "Ehhez a telepítéshez nem érhető el a frissítések keresése.",
"update.checking": "Keresés…",
"update.copy": "Másolás",
"update.dmg_opened": "A lemezkép meg van nyitva és ellenőrizve: húzza a SolidCraftot az Alkalmazások mappába, lecserélve a régi példányt, majd indítsa újra a SolidCraftot.",
@@ -4648,11 +4723,13 @@
"update.installed": "A SolidCraft {version} telepítve. Indítsa újra a használatához.",
"update.installer_started": "A telepítő a SolidCraft bezárásakor indul.",
"update.later": "Emlékeztessen később",
"update.managed": "Frissítés érhető el: frissítse a SolidCraftot itt: {manager}",
"update.no_notes": "Ehhez a kiadáshoz nincs megjegyzés.",
"update.package_hint": "Ez a példány egy {kind} csomagból származik. Töltse le az új csomagot, majd telepítse ezzel:",
"update.prefs_dev": "Ez fejlesztői build ({version}): nem keres frissítéseket.",
"update.prefs_flatpak": "A Flatpak frissíti ezt a példányt ({version}): futtassa a(z) {command} parancsot, vagy használja a szoftverközpontot. A SolidCraft nem keres.",
"update.prefs_label": "Frissítések",
"update.prefs_managed": "Ez a SolidCraft {version}, kezelője: {manager}. A keresés a GitHubtól kéri a legújabb kiadást, és semmi mást nem küld; a SolidCraftot itt frissítse: {manager}.",
"update.prefs_note": "Ez a SolidCraft {version} ({kind}). A keresés a GitHubtól kéri a legújabb kiadást, és semmi mást nem küld.",
"update.prefs_web": "Töltse újra az oldalt a legújabb webalkalmazás megszerzéséhez. Ha a hálózat nem érhető el, vagy a frissítés időben nem tud letöltődni, a mentett verzió nyílik meg; a folyamatban lévő letöltés a következő újratöltéshez folytatódik.",
"update.refresh_checking": "A legújabb kiadás keresése…",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Desain yang Anda buka atau simpan muncul di sini.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Ditentukan oleh To Object. Pilih Angle untuk mengedit rentang sudut.",
"Diameter": "Diameter",
"Did this assembly open the wrong way up?": "Apakah rakitan ini terbuka dengan orientasi yang salah?",
"Did this part open the wrong way up?": "Apakah part ini terbuka dengan orientasi yang salah?",
"Dimension new shapes": "Beri dimensi bentuk baru",
"Direction": "Arah",
"Discard": "Buang",
@@ -601,6 +603,7 @@
"Square": "Persegi",
"Stand it up": "Tegakkan",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Tegakkan: putaran seperempat terhadap X, sebagai satu fitur Move yang dapat dibatalkan.",
"Stand this assembly up?": "Tegakkan rakitan ini?",
"Stand this part up?": "Tegakkan part ini?",
"Start angle": "Sudut awal",
"Start bulge": "Bulge awal",
@@ -772,6 +775,8 @@
"at the face": "di muka",
"bom.group_children": "Sub-part dalam baris part induknya",
"browser.body_name": "Bodi{number}",
"browser.calibrate_click_a": "Klik gambar untuk Titik A, atau ketik koordinatnya.",
"browser.calibrate_click_b": "Klik gambar untuk Titik B, atau ketik koordinatnya.",
"browser.calibrate_title": "Kalibrasi: {name}",
"browser.capture_many": "{count} komponen dipindahkan tetapi posisinya belum ditangkap.",
"browser.capture_note": "Tangkap menyimpan posisi baru; Kembalikan memulihkannya.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Ambil Tampilan",
"cmd.appearance_replace": "Ganti Tampilan",
"cmd.assembly_bom": "Daftar Material (BOM)",
"cmd.body.print_face": "Permukaan cetak",
"cmd.body_rename": "Ganti Nama Bodi",
"cmd.browser_collapse_all": "Ciutkan Semua",
"cmd.browser_expand_all": "Perluas Semua",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Klik dimensi penggerak lain untuk menyisipkan namanya",
"dialog.dimension_name": "Dimensi: {name}",
"dialog.edit_title": "EDIT {title}",
"dialog.no_shown_body": "Tidak ada bodi yang ditampilkan untuk diubah: tampilkan satu, atau pilih Bodi Baru",
"dialog.select": "↖ Pilih",
"dialog.selected_count": "↖ {count} terpilih",
"dialog.selection_guidance": "Pilih input untuk mengubahnya. Klik objek terpilih untuk menghapusnya; Ctrl/Cmd-klik juga mengalihkannya. Tinjau pratinjau, lalu tekan OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Memindahkan part rakitan hanya sepanjang kebebasan yang diizinkan oleh sambungannya. Pilih sebuah muka part, lalu seret panah atau busur yang tersedia.",
"help.parameters.change": "Membuka tabel nilai dan ekspresi bernama yang menggerakkan dimensi desain. Gunakan untuk mengubah ukuran di satu tempat dan membuat semua fitur yang memakainya mengikuti.",
"help.pointcloud.insert": "Sisipkan hasil pindai PLY, XYZ, atau PTS sebagai awan titik di samping model. Gunakan sebagai referensi saat membangun model dari data hasil pengukuran.",
"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.",
"help.sketch.equation_curve": "Menggambar kurva dari rumus: x(t) dan y(t), atau y = f(x), pada suatu rentang. Rumusnya dapat memakai parameter dan kurva mengikutinya: gunakan untuk gelombang sinus, spiral, atau involut gigi roda gigi.",
"help.solid.align": "Memindahkan bodi terpilih sehingga titik, muka, atau bidang padanya bertemu titik, muka, atau bidang di tempat lain. Gunakan untuk menempatkan part pada part lain tanpa mengukur offset.",
"help.solid.chamfer": "Memotong tepi terpilih dengan bevel datar sejauh jarak yang Anda atur. Gunakan untuk menumpulkan tepi tajam atau mempermudah masuknya pin atau sekrup.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Membangun ulang setiap fitur di timeline dari awal. Gunakan untuk menyegarkan model dan melihat fitur mana yang gagal.",
"help.timeline.delete": "Menghapus fitur terpilih dari timeline; menghapus sketsa juga menghapus fitur yang dibangun darinya. Gunakan untuk membuang langkah yang tidak diinginkan lagi.",
"help.timeline.play": "Putar ulang fitur yang tersisa satu per satu, dengan jeda setengah detik setelah setiap pembangunan ulang. Jalankan perintah ini lagi atau tekan Escape untuk berhenti; Ke akhir langsung melompat.",
"history.sketch_under_constrained": "Kurang dibatasi ({count} derajat kebebasan)",
"history.sketch_under_constrained_one": "Kurang dibatasi ({count} derajat kebebasan)",
"hole_presets.confirm_delete": "Konfirmasi hapus",
"hole_presets.delete": "Hapus",
"hole_presets.delete_prompt": "Hapus preset pribadi ini? Lubang yang sudah ada tidak berubah.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Menghaluskan tepi bergerigi.",
"prefs.applies_on_restart": "Berlaku setelah dimulai ulang",
"prefs.assistant": "Asisten",
"prefs.auto_operation": "Pilih operasi secara otomatis",
"prefs.auto_operation_note": "Jendela fitur memilih Gabung, Bodi Baru, atau Potong berdasarkan apa yang disentuh material baru, sampai Anda memilih salah satu. Mati: jendela mempertahankan operasi yang dipakai saat dibuka.",
"prefs.axes": "Sumbu",
"prefs.axis": "Sumbu",
"prefs.bodies_folders": "Folder bodi",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Tengah bawah",
"prefs.bottom_right": "Kanan bawah",
"prefs.browser": "Penjelajah",
"prefs.browser_eye_left": "Ikon mata di sebelah panah perluas/ciutkan",
"prefs.browser_eye_left_note": "Baris penjelajah menampilkan ikon mata tampilkan/sembunyikan di sebelah panah perluas/ciutkan, seperti sebelumnya, bukan dalam satu kolom di ujung kanan baris.",
"prefs.browser_reveal_on_hover": "Tampilkan saat disorot",
"prefs.browser_show_note": "Ditampilkan menjaga penjelajah tetap di layar. Tampilkan saat disorot menyembunyikannya sampai penunjuk berada di atas tempatnya atau di atas tab tipis di tepi kirinya, dan penjelajah tetap terbuka selama penunjuk ada di dalamnya atau ada menu darinya yang terbuka.",
"prefs.browser_shown": "Ditampilkan",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Berlaku setelah SolidCraft dimulai ulang. WGPU_ADAPTER_NAME menggantikan pilihan ini.",
"prefs.graphics_note": "Tukar kecepatan dengan kehalusan. Setiap pilihan menampilkan efeknya pada sebuah contoh.",
"prefs.graphics_presets_note": "Preset mengatur pilihan di bawah secara bersamaan.",
"prefs.hide_constraint_glyphs_zoomed_out": "Sembunyikan glif batasan saat zoom out",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Glif batasan menghilang dari tampilan saat sketsa di-zoom out cukup jauh sehingga glif itu membuat tampilan berantakan. Mati: glif tetap berukuran sama di layar pada setiap tingkat zoom.",
"prefs.history": "Riwayat",
"prefs.history_origin_bodies": "Tampilkan origin dan bodi di bagian atas",
"prefs.history_origin_bodies_note": "Riwayat di sisi kiri atau di bawah penjelajah dimulai dengan folder Bodi komponen aktif dan Origin, lengkap dengan ikon mata dan menunya seperti di penjelajah. Riwayat di bagian bawah tidak berubah.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "File STEP dan impor",
"prefs.max_file_size_web": "Aplikasi web membuka file STEP dan impor hingga 512 MB serta desain hingga 256 MB: tab peramban tidak dapat menampung file yang lebih besar. Aplikasi desktop dapat menaikkan batas ini.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Ikon besar",
"prefs.menu_list": "Daftar",
"prefs.menus": "Menu",
"prefs.minutes": "menit",
"prefs.minutes_suffix": " mnt",
"prefs.mouse": "Mouse dan trackpad",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Nama bidang origin",
"prefs.origin_plane_names_note": "Nama yang ditampilkan di penjelajah, dialog, dan daftar. {top} adalah XY, {front} adalah XZ, dan {right} adalah YZ, dengan Z ke atas. Perintah, skrip, dan desain tersimpan tetap memakai XY, XZ, dan YZ.",
"prefs.overall_speed": "Kecepatan keseluruhan",
"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.perspective_fov": "Bidang pandang",
"prefs.placement": "Penempatan",
"prefs.prefer_free_sketch_points": "Utamakan titik sketsa yang dapat digerakkan saat titik saling tumpang tindih",
"prefs.preview_choices": "Pratinjau pilihan Anda",
"prefs.print_face_highlight": "Sorotan permukaan cetak",
"prefs.print_face_highlight_tip": "Seberapa kuat permukaan cetak sebuah bodi diwarnai dan diberi garis tepi di tampilan. 0 % tidak menampilkan sorotan.",
"prefs.profile_active": "Profil aktif",
"prefs.profile_applied": "Profil diterapkan.",
"prefs.profile_changed": "Pengaturan berubah sejak profil disimpan. Simpan akan menyimpannya ke profil.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Tidak ada pengaturan yang cocok",
"prefs.select": "Pilih",
"prefs.select_one_item": "Pilih satu item",
"prefs.select_other_hidden": "Pilih geometri tersembunyi dengan Pilih Lainnya",
"prefs.select_other_hidden_help": "Aktif: sertakan bodi, sketsa, dan tepi tersembunyi di dekat penunjuk. Mati: pakai daftar asli geometri terlihat di bawah penunjuk.",
"prefs.selecting": "Pemilihan",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift hanya menambah; Ctrl / Cmd mengalihkan",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Saat menggambar, sebuah titik sejajar secara horizontal dengan satu titik sketsa dan secara vertikal dengan titik lain sekaligus, dengan pemandu titik-titik, dan klik mempertahankan kedua perataan itu. Mati: hanya dengan titik sebelumnya, satu sumbu setiap kali.",
"prefs.sketch_conflict_colour": "Geometri yang konflik dengan warna konflik",
"prefs.sketch_conflict_colour_note": "Saat batasan sketsa saling konflik, kurva dan titik yang ditahan oleh batasan yang gagal juga digambar dengan warna konflik, bukan hanya glif atau dimensinya. Mati: warnanya tetap seperti biasa.",
"prefs.sketch_construction_lines": "Alat garis bantu lanjutan",
"prefs.sketch_exit_view": "Saat sketsa selesai",
"prefs.sketch_exit_view.keep": "Pertahankan tampilan saat ini",
"prefs.sketch_exit_view.previous": "Kembali ke tampilan sebelumnya",
"prefs.sketch_exit_view.unless_rotated": "Kembali kecuali diputar",
"prefs.sketch_exit_view_note": "Ke mana tampilan beralih setelah Selesai Sketsa, Batal, atau Esc dua kali.",
"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.",
"prefs.sketch_recent_snap": "Snap otomatis ke pusat dan titik tengah terbaru",
"prefs.sketch_recent_snap_note": "Saat menggambar, kursor melakukan snap ke titik persilangan pemandu dari dua pusat lingkaran atau titik tengah tepi lurus terbaru. Mati: pemandu tersebut hanya bersifat visual dan kursor tidak bergerak.",
"prefs.sketch_selection": "Sketsa & pilihan",
"prefs.sketch_selection_heading": "Sketsa dan pilihan",
"prefs.sketch_selection_note": "Cara sketsa melakukan snap dan cara klik memilih.",
"prefs.sketch_state_icons": "Ikon sketsa dengan warna statusnya",
"prefs.sketch_state_icons_note": "Di Riwayat dan penjelajah, ikon sketsa mengambil warna statusnya (kurang dibatasi, terbatasi penuh, atau bertentangan) dan tooltip-nya menyebut statusnya. Mati: setiap sketsa memakai ikon yang sama.",
"prefs.sketch_tab_always": "Selalu tampilkan tab Sketsa",
"prefs.sketch_tab_always_note": "Tab Sketsa tetap ada di ribbon, tepat setelah Model, selama tidak ada sketsa yang terbuka. Alat gambarnya lalu memulai sketsa pada bidang atau muka yang dipilih, atau pada bidang yang Anda pilih berikutnya, dengan alat yang sudah siap di dalamnya.",
"prefs.sketch_tracking_guides": "Pemandu perataan pusat dan titik tengah",
@@ -1995,6 +2034,11 @@
"prefs.source": "Sumber",
"prefs.speed": "Kecepatan",
"prefs.stand_up_note": "Setelah file STEP atau IGES terbuka dengan Z ke atas, tawarkan seperempat putaran mengelilingi X.",
"prefs.stand_up_unknown": "Untuk file dari program yang tidak dikenal",
"prefs.stand_up_unknown.ask": "Tanya di dialog",
"prefs.stand_up_unknown.note": "Tampilkan catatan",
"prefs.stand_up_unknown.off": "Jangan tawarkan",
"prefs.stand_up_unknown_note": "Cara tawaran diberikan saat header file tidak menyebut program yang dikenal menyimpan dengan Y ke atas. Catatan hanya ditampilkan saat file tampak telentang; Tanya di dialog menanyakan setiap file semacam itu, seperti sebelumnya.",
"prefs.status_bar": "Bilah status",
"prefs.status_bar_note": "Bilah tipis di sepanjang tepi bawah jendela menampilkan apa yang sedang diedit, status sketsa yang terbuka dengan warnanya, dan satuan desain; klik pada satuan akan mengubahnya. Status sketsa lalu tidak lagi tampil di sudut tampilan.",
"prefs.status_done": "Selesai",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK dan Batal di sudut tampilan",
"prefs.view_confirm_corner_note": "Saat dialog atau sketsa terbuka, tanda centang dan tanda silang di kanan atas tampilan melakukan hal yang sama seperti OK dan Batal pada dialog, atau menyelesaikan dan membatalkan sketsa. Navigator tampilan bergeser ke bawahnya.",
"prefs.viewport": "Viewport",
"prefs.viewport_analysis_status": "Tampilkan status analisis viewport",
"prefs.viewport_analysis_status_help": "Tampilkan analisis yang aktif di atas viewport, buka legendanya dari setiap item, dan gunakan Esc untuk mematikan analisis terbaru. Matikan untuk mengembalikan legenda di sudut dan perilaku Esc sebelumnya.",
"prefs.viewport_background": "Latar belakang",
"prefs.viewport_background_colour": "Warna",
"prefs.viewport_background_theme": "Gradasi tema",
@@ -2049,6 +2095,10 @@
"properties.title": "Properti: {name}",
"recovery.days_ago": "{count} hari lalu",
"recovery.detail": "{file} · {features} fitur · disimpan otomatis {when}",
"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?",
"recovery.hours_ago": "{count} jam lalu",
"recovery.just_now": "baru saja",
"recovery.min_ago": "{count} mnt lalu",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Pindahkan Fitur ke Folder",
"timeline.rename_group": "Ganti Nama Folder Fitur",
"timeline.reorder_many": "Urutkan Ulang Fitur",
"timeline.suppress_features": "Tekan Fitur",
"timeline.suppress_folders_tip": "Tekan atau lepas penekanan setiap fitur di folder dan folder bersarangnya",
"timeline.ungroup": "Hapus Folder Fitur",
"timeline.unsuppress_features": "Lepas Tekan Fitur",
"to the face": "ke muka",
"to the next face": "ke muka berikutnya",
"toolbar.customize_note": "Tab {tab}. Centang perintah untuk memberinya tombol toolbar. Anda juga dapat menyeret baris dropdown panel ke toolbar, menyeret tombol keluar dari toolbar untuk menghapusnya, atau memakai pin pada baris dropdown.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
"ui.3d_mouse_switch_cap": "Kalibrasi sebagai Kembali ke Tengah",
"ui.3d_mouse_switch_trackball": "Kalibrasi sebagai trackball",
"ui.3d_mouse_use_direct": "Gunakan driver langsung",
"ui.3d_mouse_use_direct_tip": "Baca mouse 3D secara langsung (hidraw), bukan melalui spacenavd.",
"ui.3d_mouse_view_axis": "Sumbu tampilan",
"ui.3d_mouse_view_off": "Mati",
"ui.3d_mouse_view_off_tip": "Profil driver mouse 3D mematikan sumbu ini, sehingga gerakannya tidak menggerakkan tampilan. Memilih gerakan sumbu lain di sini akan menukar keduanya, dan gerakan itu berhenti menggerakkan tampilan.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Batal",
"ui.canvases": "Kanvas",
"ui.center": "Pusat",
"ui.center_arc_follows_cursor": "Busur titik pusat mengikuti arah kursor",
"ui.center_arc_follows_cursor_tip": "Busur titik pusat berjalan searah atau berlawanan arah jarum jam sesuai arah kursor mengitari pusatnya. Mati: selalu berlawanan arah jarum jam dari awal.",
"ui.centerline": "Garis tengah",
"ui.chain": "Rantai",
"ui.chamfer": "Chamfer",
@@ -3930,6 +3986,7 @@
"ui.circle": "Lingkaran",
"ui.circular_pattern": "Pola Melingkar",
"ui.clear": "Bersihkan",
"ui.clear_print_face": "Hapus permukaan cetak",
"ui.clear_recent": "Bersihkan Terbaru",
"ui.clear_the_selection": "Hapus pilihan",
"ui.click_selects_whole_bodies": "Klik memilih seluruh bodi",
@@ -3964,6 +4021,14 @@
"ui.constant": "Konstan",
"ui.constraints_conflict": "Batasan bertentangan",
"ui.construction": "Konstruksi",
"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",
"ui.construction.ribbon_label": "Garis bantu",
"ui.context_menu": "Menu konteks",
"ui.continuity_g0": "Lipatan G0",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Poligon",
"ui.preferences": "Preferensi",
"ui.press_a_key": "tekan tombol…",
"ui.print_face_cleared": "{body}: permukaan cetak dihapus",
"ui.print_face_input": "Permukaan cetak",
"ui.print_face_one_body": "Permukaan cetak milik satu bodi: kirim bodi itu sendiri untuk mengubahnya",
"ui.print_face_pick": "Pilih satu permukaan datar dari bodi padat untuk dijadikan permukaan cetak",
"ui.print_face_set": "{body}: permukaan cetak diatur; Kirim ke slicer meletakkan bodi di atasnya",
"ui.profile": "Profil",
"ui.profile_plane": "Bidang Profil",
"ui.profiles": "Profil",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Revolve",
"ui.ribbon_exit": "Keluar",
"ui.ribbon_labels": "Tampilkan teks di bawah ikon pita",
"ui.ribbon_menus_skip_pinned": "Kecualikan perintah yang disematkan dari dropdown",
"ui.ribbon_pin_footer": "Perintah yang disematkan tampil di ribbon. Arahkan kursor ke baris untuk menyematkan atau melepas sematan.",
"ui.ribbon_workspace": "Workspace",
"ui.right": "Kanan",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Pusat tampilan",
"ui.viewport_status_clear": "Matikan {name}",
"ui.viewport_status_clear_hint": "Matikan {name} · Esc",
"ui.viewport_status_edges": "{count} tepi",
"ui.viewport_status_from": "dari {direction}",
"ui.viewport_status_key": "Tampilkan legenda untuk {name}",
"ui.viewport_status_minimum_radius": "Radius minimum",
"ui.viewport_status_pairs": "{count} pasang",
@@ -4634,7 +4707,9 @@
"update.button": "Perbarui",
"update.button_version": "Perbarui ke {version}",
"update.check_automatically": "Periksa pembaruan secara otomatis",
"update.check_for_updates": "Periksa pembaruan",
"update.check_now": "Periksa sekarang",
"update.check_unavailable": "Pemeriksaan pembaruan tidak tersedia untuk instalasi ini.",
"update.checking": "Memeriksa…",
"update.copy": "Salin",
"update.dmg_opened": "Disk image terbuka dan terverifikasi: seret SolidCraft ke Applications, ganti salinan lama, lalu mulai ulang SolidCraft.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} terpasang. Mulai ulang untuk memakainya.",
"update.installer_started": "Penginstal dimulai saat SolidCraft ditutup.",
"update.later": "Ingatkan saya nanti",
"update.managed": "Pembaruan tersedia: perbarui SolidCraft di {manager}",
"update.no_notes": "Rilis ini tidak memiliki catatan.",
"update.package_hint": "Salinan ini berasal dari {kind}. Unduh paket baru, lalu pasang dengan:",
"update.prefs_dev": "Ini adalah build pengembangan ({version}): tidak memeriksa pembaruan.",
"update.prefs_flatpak": "Flatpak memperbarui salinan ini ({version}): jalankan {command} atau gunakan software center Anda. SolidCraft tidak memeriksa.",
"update.prefs_label": "Pembaruan",
"update.prefs_managed": "Ini adalah SolidCraft {version}, dikelola oleh {manager}. Pemeriksaan meminta rilis terbaru ke GitHub dan tidak mengirim apa pun lagi; perbarui SolidCraft di {manager}.",
"update.prefs_note": "Ini adalah SolidCraft {version} ({kind}). Pemeriksaan meminta rilis terbaru ke GitHub dan tidak mengirim apa pun lagi.",
"update.prefs_web": "Muat ulang halaman untuk mendapatkan aplikasi web terbaru. Jika jaringan tidak tersedia atau pembaruan tidak selesai diunduh tepat waktu, versi tersimpan akan terbuka; unduhan yang sedang berjalan berlanjut untuk muat ulang berikutnya.",
"update.refresh_checking": "Memeriksa rilis terbaru…",
+79
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "I progetti aperti o salvati vengono visualizzati qui.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determinato da Fino all'oggetto. Scegli Angolo per modificare l'estensione angolare.",
"Diameter": "Diametro",
"Did this assembly open the wrong way up?": "Questo assieme si è aperto nel verso sbagliato?",
"Did this part open the wrong way up?": "Questa parte si è aperta nel verso sbagliato?",
"Dimension new shapes": "Quota le nuove forme",
"Direction": "Direzione",
"Discard": "Scarta",
@@ -601,6 +603,7 @@
"Square": "Quadrato",
"Stand it up": "Raddrizza",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Raddrizza: un quarto di giro attorno a X, come singola lavorazione Sposta annullabile.",
"Stand this assembly up?": "Raddrizzare questo assieme?",
"Stand this part up?": "Raddrizzare questa parte?",
"Start angle": "Angolo iniziale",
"Start bulge": "Rigonfiamento iniziale",
@@ -772,6 +775,8 @@
"at the face": "sulla faccia",
"bom.group_children": "Sottoparti nella riga della loro parte",
"browser.body_name": "Corpo{number}",
"browser.calibrate_click_a": "Fai clic sull'immagine per il punto A, oppure digitane le coordinate.",
"browser.calibrate_click_b": "Fai clic sull'immagine per il punto B, oppure digitane le coordinate.",
"browser.calibrate_title": "Calibra: {name}",
"browser.capture_many": "Sono stati spostati {count} componenti, ma le loro posizioni non sono state acquisite.",
"browser.capture_note": "Acquisisci posizione mantiene la nuova posizione; Ripristina posizione riporta a quella precedente.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Preleva aspetto",
"cmd.appearance_replace": "Sostituisci aspetto",
"cmd.assembly_bom": "Distinta base",
"cmd.body.print_face": "Faccia di stampa",
"cmd.body_rename": "Rinomina corpo",
"cmd.browser_collapse_all": "Comprimi tutto",
"cmd.browser_expand_all": "Espandi tutto",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Fai doppio clic su un’altra quota di controllo per inserirne il nome",
"dialog.dimension_name": "Quota: {name}",
"dialog.edit_title": "MODIFICA {title}",
"dialog.no_shown_body": "Nessun corpo visibile da modificare: mostrane uno o scegli Nuovo corpo",
"dialog.select": "↖ Seleziona",
"dialog.selected_count": "↖ Selezionati: {count}",
"dialog.selection_guidance": "Seleziona un dato di ingresso per modificarlo. Fai clic sugli oggetti selezionati per rimuoverli; anche Ctrl/Cmd-clic ne alterna la selezione. Controlla l'anteprima, poi premi OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Sposta una parte dell'assieme solo lungo i gradi di libertà consentiti dai suoi giunti. Seleziona una faccia della parte, poi trascina una freccia o un arco disponibile.",
"help.parameters.change": "Apre la tabella dei valori con nome e delle espressioni che guidano le quote della progettazione. Usala per cambiare una misura in un solo punto e far seguire tutte le lavorazioni che la usano.",
"help.pointcloud.insert": "Inserisci una scansione PLY, XYZ o PTS come nuvola di punti accanto al modello. Usala come riferimento quando costruisci un modello a partire da dati misurati.",
"help.sketch.construction_line": "Crea una retta di costruzione infinita passante per due punti. Usala per impostare uno schizzo senza dividere i profili.",
"help.sketch.construction_line.angle": "Crea una retta di costruzione infinita con un angolo rispetto all'asse X dello schizzo. Seleziona il punto e digita l'angolo, oppure scegli la direzione.",
"help.sketch.construction_line.bisector": "Seleziona due linee rette dello schizzo che si intersecano, poi scegli una delle due bisettrici. Il risultato segue le linee di origine.",
"help.sketch.construction_line.horizontal": "Crea una retta di costruzione orizzontale infinita passante per un punto. Il suo vincolo orizzontale resta valido anche nelle modifiche successive.",
"help.sketch.construction_line.parallel": "Seleziona una linea retta dello schizzo, poi posiziona una retta di costruzione infinita parallela. Fai clic destro per scegliere un altro riferimento.",
"help.sketch.construction_line.perpendicular": "Seleziona una linea retta dello schizzo, poi posiziona una retta di costruzione infinita perpendicolare. Fai clic destro per scegliere un altro riferimento.",
"help.sketch.construction_line.tangent_at": "Crea una tangente in un punto di un cerchio o di un arco di cerchio, oppure in un estremo di una spline aperta o di una conica. Seleziona la curva, poi il suo punto di contatto.",
"help.sketch.construction_line.tangent_external": "Seleziona un cerchio o un arco di cerchio, poi un punto esterno e una soluzione di tangente. Le anteprime mostrano solo i contatti sull'arco disegnato.",
"help.sketch.construction_line.tangent_two": "Seleziona due cerchi o archi di cerchio, poi scegli una tangente comune nell'anteprima. I contatti devono trovarsi sulle curve disegnate.",
"help.sketch.construction_line.vertical": "Crea una retta di costruzione verticale infinita passante per un punto. Il suo vincolo verticale resta valido anche nelle modifiche successive.",
"help.sketch.equation_curve": "Disegna una curva da formule: x(t) e y(t), oppure y = f(x), su un intervallo. Le formule possono usare parametri e la curva li segue: usala per un'onda sinusoidale, una spirale o l'evolvente del dente di un ingranaggio.",
"help.solid.align": "Sposta i corpi selezionati in modo che un punto, una faccia o un piano su di essi coincida con un punto, una faccia o un piano altrove. Usalo per posizionare un pezzo contro un altro senza misurare lo scostamento.",
"help.solid.chamfer": "Taglia gli spigoli selezionati con uno smusso piano della distanza impostata. Usalo per eliminare gli spigoli vivi o per facilitare l'ingresso di un perno o di una vite.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Ricostruisce ogni lavorazione della cronologia dall'inizio. Usalo per aggiornare il modello e vedere quali lavorazioni falliscono.",
"help.timeline.delete": "Elimina le lavorazioni selezionate dalla cronologia; eliminando uno schizzo si eliminano anche le lavorazioni costruite su di esso. Usalo per togliere un passaggio che non serve più.",
"help.timeline.play": "Riproduce le lavorazioni rimanenti una alla volta, con una pausa di mezzo secondo dopo ogni ricostruzione. Esegui di nuovo questo comando o premi Escape per arrestare; Vai alla fine salta subito alla fine.",
"history.sketch_under_constrained": "Sottovincolato ({count} gradi di libertà)",
"history.sketch_under_constrained_one": "Sottovincolato ({count} grado di libertà)",
"hole_presets.confirm_delete": "Conferma eliminazione",
"hole_presets.delete": "Elimina",
"hole_presets.delete_prompt": "Eliminare questo preset personale? I fori esistenti restano invariati.",
@@ -1770,6 +1789,8 @@
"parts.family.set_screw": "Grano a esagono incassato, punta piana",
"parts.family.socket_head_cap_screw": "Vite a testa cilindrica con esagono incassato",
"parts.family.washer": "Rondella piana",
"parts.holes": "Fori (facoltativo)",
"parts.placement_hint": "scegli uno o più fori per alloggiare una parte in ciascuno, oppure OK la colloca nell'origine",
"pattern.alignment_2": "Allineamento 2",
"pattern.alignment_hint": "Il conteggio include l'originale. Centro distribuisce le copie sui due lati; Inverti sceglie il lato con la copia in più per totali pari.",
"pattern.direction_1": "Direzione 1",
@@ -1810,6 +1831,8 @@
"prefs.anti_aliasing_note": "Ammorbidisce i bordi seghettati.",
"prefs.applies_on_restart": "Si applica al riavvio",
"prefs.assistant": "Assistente",
"prefs.auto_operation": "Scegli l'operazione automaticamente",
"prefs.auto_operation_note": "Le finestre delle lavorazioni scelgono Unisci, Nuovo corpo o Taglia in base a ciò che tocca il nuovo materiale, finché non ne scegli una tu. Disattivato: mantengono l'operazione con cui si aprono.",
"prefs.axes": "Assi",
"prefs.axis": "Asse",
"prefs.bodies_folders": "Cartelle dei corpi",
@@ -1820,6 +1843,8 @@
"prefs.bottom_centre": "In basso al centro",
"prefs.bottom_right": "In basso a destra",
"prefs.browser": "Struttura",
"prefs.browser_eye_left": "Occhio accanto alla freccia di espansione",
"prefs.browser_eye_left_note": "Le righe della Struttura mostrano l'occhio mostra/nascondi accanto alla freccia di espansione, come prima, invece che in una colonna all'estremità destra delle righe.",
"prefs.browser_reveal_on_hover": "Mostra al passaggio del puntatore",
"prefs.browser_show_note": "Visibile mantiene la Struttura sullo schermo. Mostra al passaggio del puntatore la tiene nascosta finché il puntatore non passa sulla sua posizione o sulla sottile linguetta al suo bordo sinistro, e resta aperta finché il puntatore è al suo interno o un suo menu è aperto.",
"prefs.browser_shown": "Visibile",
@@ -1870,6 +1895,8 @@
"prefs.graphics_card_restart": "Ha effetto dopo il riavvio di SolidCraft. WGPU_ADAPTER_NAME prevale su questa scelta.",
"prefs.graphics_note": "Scegli tra velocità e fluidità dell'immagine. Ogni scelta mostra il suo effetto su un esempio.",
"prefs.graphics_presets_note": "Una preimpostazione regola insieme le scelte qui sotto.",
"prefs.hide_constraint_glyphs_zoomed_out": "Nascondi i simboli dei vincoli quando lo zoom è ridotto",
"prefs.hide_constraint_glyphs_zoomed_out_note": "I simboli dei vincoli scompaiono dalla vista quando lo zoom sullo schizzo è ridotto al punto da affollarlo. Disattivato: restano a una dimensione fissa sullo schermo a qualsiasi zoom.",
"prefs.history": "Cronologia",
"prefs.history_origin_bodies": "Mostra origine e corpi in alto",
"prefs.history_origin_bodies_note": "La Cronologia a sinistra o sotto la Struttura inizia con la cartella Corpi del componente attivo e con l'Origine, con occhi e menu come nella Struttura. La Cronologia in basso non cambia.",
@@ -1908,6 +1935,9 @@
"prefs.max_file_size_step": "File STEP e di importazione",
"prefs.max_file_size_web": "L’app web apre file STEP e di importazione fino a 512 MB e progetti fino a 256 MB: una scheda del browser non può contenere file più grandi. L’app desktop permette di aumentare questi limiti.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Icone grandi",
"prefs.menu_list": "Elenco",
"prefs.menus": "Menu",
"prefs.minutes": "minuti",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Mouse e trackpad",
@@ -1927,10 +1957,14 @@
"prefs.origin_plane_names": "Nomi dei piani di origine",
"prefs.origin_plane_names_note": "Nomi mostrati nella Struttura, nelle finestre di dialogo e negli elenchi. {top} è XY, {front} è XZ e {right} è YZ, con Z verso l'alto. Comandi, script e progetti salvati mantengono XY, XZ e YZ.",
"prefs.overall_speed": "Velocità complessiva",
"prefs.persistent_ribbon_pressed": "Evidenzia i comandi attivi della barra multifunzione",
"prefs.persistent_ribbon_pressed_help": "Evidenzia i comandi della barra multifunzione finché i loro effetti sono attivi. Disattiva per mantenere l'aspetto neutro precedente e la guida dei comandi.",
"prefs.perspective_fov": "Campo visivo",
"prefs.placement": "Posizione",
"prefs.prefer_free_sketch_points": "Preferisci i punti dello schizzo mobili quando i punti si sovrappongono",
"prefs.preview_choices": "Anteprima delle tue scelte",
"prefs.print_face_highlight": "Evidenziazione della faccia di stampa",
"prefs.print_face_highlight_tip": "Intensità della tinta e del contorno della faccia di stampa di un corpo nella vista. 0 % non mostra evidenziazione.",
"prefs.profile_active": "Profilo attivo",
"prefs.profile_applied": "Profilo applicato.",
"prefs.profile_changed": "Le impostazioni sono cambiate da quando il profilo è stato salvato. Salva le conserva nel profilo.",
@@ -1965,6 +1999,8 @@
"prefs.search_empty": "Nessuna impostazione corrispondente",
"prefs.select": "Seleziona",
"prefs.select_one_item": "Seleziona un elemento",
"prefs.select_other_hidden": "Seleziona la geometria nascosta con Seleziona altro",
"prefs.select_other_hidden_help": "Attivo: include i corpi e gli schizzi nascosti e gli spigoli occlusi vicino al puntatore. Disattivato: usa l'elenco originale della geometria visibile sotto il puntatore.",
"prefs.selecting": "Selezione",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift solo aggiunge; Ctrl / Cmd alterna",
@@ -1976,16 +2012,21 @@
"prefs.sketch_align_points_note": "Durante il disegno, un punto si allinea contemporaneamente in orizzontale con un punto dello schizzo e in verticale con un altro, con una guida punteggiata, e il clic mantiene entrambi gli allineamenti. Disattivato: solo con il punto precedente, un asse alla volta.",
"prefs.sketch_conflict_colour": "Geometria in conflitto nel colore di conflitto",
"prefs.sketch_conflict_colour_note": "Quando i vincoli di uno schizzo sono in conflitto, anche le curve e i punti interessati da un vincolo in errore vengono disegnati con il colore di conflitto, non solo il suo simbolo o la sua quota. Disattivato: mantengono i colori abituali.",
"prefs.sketch_construction_lines": "Strumenti estesi per le rette di costruzione",
"prefs.sketch_exit_view": "Al termine di uno schizzo",
"prefs.sketch_exit_view.keep": "Mantieni la vista attuale",
"prefs.sketch_exit_view.previous": "Torna alla vista precedente",
"prefs.sketch_exit_view.unless_rotated": "Torna, se non ruotata",
"prefs.sketch_exit_view_note": "Dove va la vista dopo Termina schizzo, Annulla o un doppio Esc.",
"prefs.sketch_open_look_at": "Ruota la vista verso lo schizzo quando si apre",
"prefs.sketch_open_look_at_note": "Disattivato: creando o modificando uno schizzo la vista resta com'è. Vista normale orienta la vista sullo schizzo quando vuoi.",
"prefs.sketch_recent_snap": "Aggancia automaticamente ai centri e punti medi recenti",
"prefs.sketch_recent_snap_note": "Durante il disegno, il cursore si aggancia dove si incrociano le guide degli ultimi due centri di cerchio o punti medi di segmento. Disattivato: queste guide restano solo visive e il cursore non si sposta.",
"prefs.sketch_selection": "Schizzo e selezione",
"prefs.sketch_selection_heading": "Schizzo e selezione",
"prefs.sketch_selection_note": "Come si agganciano gli schizzi e come selezionano i clic.",
"prefs.sketch_state_icons": "Icone degli schizzi nel colore del loro stato",
"prefs.sketch_state_icons_note": "Nella Cronologia e nella Struttura, l'icona di uno schizzo assume il colore del suo stato (sottovincolato, completamente vincolato o in conflitto) e il suo suggerimento indica lo stato. Disattivato: ogni schizzo ha la stessa icona.",
"prefs.sketch_tab_always": "Mantieni visibile la scheda Schizzo",
"prefs.sketch_tab_always_note": "La scheda Schizzo resta nella barra multifunzione, subito dopo Modella, finché nessuno schizzo è aperto. I suoi strumenti di disegno avviano allora uno schizzo sul piano o sulla faccia selezionati, oppure sul piano che selezioni subito dopo, con lo strumento già attivo.",
"prefs.sketch_tracking_guides": "Guide di allineamento di centro e punto medio",
@@ -1995,6 +2036,11 @@
"prefs.source": "Fonte",
"prefs.speed": "Velocità",
"prefs.stand_up_note": "Dopo l'apertura di un file STEP o IGES con Z verso l'alto, proponi un quarto di giro attorno a X.",
"prefs.stand_up_unknown": "Per i file di programmi sconosciuti",
"prefs.stand_up_unknown.ask": "Chiedi in una finestra di dialogo",
"prefs.stand_up_unknown.note": "Mostra una nota",
"prefs.stand_up_unknown.off": "Non proporre",
"prefs.stand_up_unknown_note": "Come viene fatta la proposta quando l'intestazione del file non indica alcun programma noto per salvare con Y verso l'alto. Una nota viene mostrata solo quando il file sembra coricato; Chiedi in una finestra di dialogo chiede per ogni file di questo tipo, come prima.",
"prefs.status_bar": "Barra di stato",
"prefs.status_bar_note": "Una barra sottile lungo il bordo inferiore della finestra mostra cosa si sta modificando, lo stato dello schizzo aperto nel suo colore e le unità del progetto; un clic sulle unità le cambia. Lo stato dello schizzo non compare più nell'angolo della vista.",
"prefs.status_done": "Fatto",
@@ -2037,6 +2083,8 @@
"prefs.view_confirm_corner": "OK e Annulla nell'angolo della vista",
"prefs.view_confirm_corner_note": "Mentre è aperta una finestra di dialogo o uno schizzo, una spunta e una croce in alto a destra nella vista fanno ciò che fanno OK e Annulla della finestra, oppure terminano e annullano lo schizzo. Il navigatore della vista scende sotto di esse.",
"prefs.viewport": "Vista",
"prefs.viewport_analysis_status": "Mostra lo stato di analisi della vista",
"prefs.viewport_analysis_status_help": "Mostra le analisi attive sopra la vista, apri le loro legende da ogni elemento e usa Esc per disattivare l'analisi più recente. Disattiva per ripristinare le legende negli angoli e il comportamento precedente di Esc.",
"prefs.viewport_background": "Sfondo",
"prefs.viewport_background_colour": "Colore",
"prefs.viewport_background_theme": "Sfumatura del tema",
@@ -2049,6 +2097,10 @@
"properties.title": "Proprietà: {name}",
"recovery.days_ago": "{count} giorni fa",
"recovery.detail": "{file} · {features} feature · salvato automaticamente {when}",
"recovery.discard_all": "Scarta tutto",
"recovery.discard_all_detail": "Questa azione non può essere annullata.",
"recovery.discard_all_title": "Scartare {count} salvataggi automatici?",
"recovery.discard_all_title_one": "Scartare 1 salvataggio automatico?",
"recovery.hours_ago": "{count} h fa",
"recovery.just_now": "proprio ora",
"recovery.min_ago": "{count} min fa",
@@ -3503,8 +3555,10 @@
"timeline.move_to_group": "Sposta lavorazioni nella cartella",
"timeline.rename_group": "Rinomina cartella di lavorazioni",
"timeline.reorder_many": "Riordina lavorazioni",
"timeline.suppress_features": "Sospendi lavorazioni",
"timeline.suppress_folders_tip": "Sospendi o riattiva ogni lavorazione nelle cartelle e nelle cartelle annidate",
"timeline.ungroup": "Rimuovi cartelle di lavorazioni",
"timeline.unsuppress_features": "Riattiva lavorazioni",
"to the face": "alla faccia",
"to the next face": "fino alla faccia successiva",
"toolbar.customize_note": "Scheda {tab}. Seleziona un comando per assegnargli un pulsante nella barra degli strumenti. Puoi anche trascinare una riga dal menu a discesa di un pannello sulla barra, trascinare un pulsante fuori dalla barra per rimuoverlo o usare la puntina di una riga del menu a discesa.",
@@ -3616,6 +3670,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
"ui.3d_mouse_switch_cap": "Calibra come ritorno al centro",
"ui.3d_mouse_switch_trackball": "Calibra come trackball",
"ui.3d_mouse_use_direct": "Usa driver diretto",
"ui.3d_mouse_use_direct_tip": "Leggi il mouse 3D direttamente (hidraw) invece che tramite spacenavd.",
"ui.3d_mouse_view_axis": "Asse della vista",
"ui.3d_mouse_view_off": "Disattivato",
"ui.3d_mouse_view_off_tip": "Il profilo del driver del mouse 3D disattiva questo asse, quindi il suo movimento non muove la vista. Se qui scegli il movimento di un altro asse, i due si scambiano e quel movimento smette di muovere la vista.",
@@ -3922,6 +3978,8 @@
"ui.cancel": "Annulla",
"ui.canvases": "Immagini di riferimento",
"ui.center": "Centro",
"ui.center_arc_follows_cursor": "L'arco per centro segue la direzione del cursore",
"ui.center_arc_follows_cursor_tip": "Un arco per centro procede in senso orario o antiorario a seconda di come il cursore gira attorno al suo centro. Disattivato: sempre in senso antiorario dall'inizio.",
"ui.centerline": "Linea d'asse",
"ui.chain": "Catena",
"ui.chamfer": "Smusso",
@@ -3930,6 +3988,7 @@
"ui.circle": "Cerchio",
"ui.circular_pattern": "Ripetizione circolare",
"ui.clear": "Cancella",
"ui.clear_print_face": "Rimuovi faccia di stampa",
"ui.clear_recent": "Cancella recenti",
"ui.clear_the_selection": "Deseleziona tutto",
"ui.click_selects_whole_bodies": "Il clic seleziona corpi interi",
@@ -3964,6 +4023,14 @@
"ui.constant": "Costante",
"ui.constraints_conflict": "Vincoli in conflitto",
"ui.construction": "Costruzione",
"ui.construction.angle_hint": "Retta di costruzione: fai clic su un punto, poi digita un angolo o seleziona una direzione",
"ui.construction.bisector_hint": "Bisettrice: seleziona due linee rette che si intersecano, poi la soluzione desiderata",
"ui.construction.common_hint": "Tangente comune: seleziona due cerchi/archi, poi la soluzione desiderata",
"ui.construction.contact_hint": "Tangente: seleziona un cerchio/arco o una spline/conica aperta, poi il punto di contatto (curve a forma libera: estremi)",
"ui.construction.external_hint": "Tangente: seleziona un cerchio o un arco, un punto esterno, poi la soluzione desiderata",
"ui.construction.free_hint": "Retta di costruzione: fai clic su due punti; clic destro per ricominciare",
"ui.construction.point_hint": "Retta di costruzione: fai clic sul punto di passaggio",
"ui.construction.reference_hint": "Retta di costruzione: seleziona una linea retta, poi un punto; clic destro per cambiare il riferimento",
"ui.construction.ribbon_label": "Linee aus.",
"ui.context_menu": "Menu contestuale",
"ui.continuity_g0": "G0 spigolo vivo",
@@ -4368,6 +4435,11 @@
"ui.polygon": "Poligono",
"ui.preferences": "Preferenze",
"ui.press_a_key": "premi un tasto…",
"ui.print_face_cleared": "{body}: faccia di stampa rimossa",
"ui.print_face_input": "Faccia di stampa",
"ui.print_face_one_body": "Una faccia di stampa appartiene a un solo corpo: invia quel corpo da solo per cambiarla",
"ui.print_face_pick": "Seleziona una faccia piana di un corpo solido per renderla la faccia di stampa",
"ui.print_face_set": "{body}: faccia di stampa impostata; Invia allo slicer appoggia il corpo su di essa",
"ui.profile": "Profilo",
"ui.profile_plane": "Piano del profilo",
"ui.profiles": "Profili",
@@ -4431,6 +4503,7 @@
"ui.revolve": "Rivoluzione",
"ui.ribbon_exit": "Esci",
"ui.ribbon_labels": "Mostra il testo sotto le icone della barra multifunzione",
"ui.ribbon_menus_skip_pinned": "Escludi i comandi fissati dai menu a discesa",
"ui.ribbon_pin_footer": "I comandi fissati compaiono sulla barra multifunzione. Passa sopra una riga per fissarla o rimuoverla.",
"ui.ribbon_workspace": "Area di lavoro",
"ui.right": "Destra",
@@ -4594,6 +4667,8 @@
"ui.view_centre": "Centro della vista",
"ui.viewport_status_clear": "Disattiva {name}",
"ui.viewport_status_clear_hint": "Disattiva {name} · Esc",
"ui.viewport_status_edges": "{count} spigoli",
"ui.viewport_status_from": "da {direction}",
"ui.viewport_status_key": "Mostra la legenda di {name}",
"ui.viewport_status_minimum_radius": "Raggio minimo",
"ui.viewport_status_pairs": "{count} coppie",
@@ -4634,7 +4709,9 @@
"update.button": "Aggiorna",
"update.button_version": "Aggiorna a {version}",
"update.check_automatically": "Controlla automaticamente gli aggiornamenti",
"update.check_for_updates": "Verifica aggiornamenti",
"update.check_now": "Controlla ora",
"update.check_unavailable": "La verifica degli aggiornamenti non è disponibile per questa installazione.",
"update.checking": "Controllo…",
"update.copy": "Copia",
"update.dmg_opened": "L'immagine disco è aperta e verificata: trascina SolidCraft in Applicazioni sostituendo la copia precedente, poi riavvia SolidCraft.",
@@ -4648,11 +4725,13 @@
"update.installed": "SolidCraft {version} è installato. Riavvia per usarlo.",
"update.installer_started": "Il programma di installazione si avvia alla chiusura di SolidCraft.",
"update.later": "Ricordamelo più tardi",
"update.managed": "Aggiornamento disponibile: aggiorna SolidCraft in {manager}",
"update.no_notes": "Questa versione non contiene note.",
"update.package_hint": "Questa copia proviene da un {kind}. Scarica il nuovo pacchetto, poi installalo con:",
"update.prefs_dev": "Questa è una build di sviluppo ({version}): non controlla gli aggiornamenti.",
"update.prefs_flatpak": "Questa copia ({version}) viene aggiornata da Flatpak: esegui {command} o usa il centro software. SolidCraft non effettua controlli.",
"update.prefs_label": "Aggiornamenti",
"update.prefs_managed": "Questa è la versione SolidCraft {version}, gestita da {manager}. Il controllo richiede a GitHub l'ultima versione e non invia altri dati; aggiorna SolidCraft in {manager}.",
"update.prefs_note": "Questa è la versione SolidCraft {version} ({kind}). Il controllo richiede a GitHub l'ultima versione e non invia altri dati.",
"update.prefs_web": "Ricarica la pagina per ottenere l'ultima versione dell'app web. Se la rete non è disponibile o l'aggiornamento non riesce a finire il download in tempo, si apre invece la versione salvata; un download in corso continua per il prossimo ricaricamento.",
"update.refresh_checking": "Ricerca della versione più recente…",
+88
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は0以外の数値にしてください",
"Spacing must be a non-zero number": "間隔は0以外の数値にしてください",
"Specialise": "特殊操作",
@@ -819,6 +822,7 @@
"cmd.appearance_pick_up": "外観を取得",
"cmd.appearance_replace": "外観を置換",
"cmd.assembly_bom": "部品表",
"cmd.body.print_face": "造形面",
"cmd.body_rename": "ボディの名前を変更",
"cmd.browser_collapse_all": "すべて折りたたむ",
"cmd.browser_expand_all": "すべて展開",
@@ -1412,6 +1416,7 @@
"help.sketch.construction_line.tangent_two": "2つの円または円弧を選び、プレビューから共通接線を選びます。接点は描かれた曲線上にある必要があります。",
"help.sketch.construction_line.vertical": "指定点を通る無限の垂直補助直線を作成します。後で編集しても垂直拘束が維持されます。",
"help.sketch.equation_curve": "x(t) と y(t)、または y = f(x) の式から、範囲を指定して曲線を描きます。式にはパラメーターを使え、曲線はその変更に追従します。正弦波、らせん、歯車のインボリュートなどに使います。",
"help.sketch.inspect": "開いているスケッチのレポートを開きます。残りの自由度と、完全に拘束されているかどうかを表示します。スケッチを終える前に、まだ動く部分を確認するときに使います。",
"help.solid.align": "選択した立体を動かし、その点、面、平面を別の場所の点、面、平面に合わせます。距離を測らずに部品を別の部品に当てて配置するときに使います。",
"help.solid.chamfer": "選択した辺を、指定した距離で平らに斜めに削ります。鋭い角を落とすときや、ピンやねじを入れやすくするときに使います。",
"help.solid.combine": "複数の立体を結合したり、対象の立体から切り取ったり、共通する体積だけを残したりでき、工具の立体を残すこともできます。部品の結合、ある形での切り抜き、2つの立体が重なる範囲の確認に使います。",
@@ -1614,6 +1619,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} 秒前",
@@ -1766,6 +1775,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": "内径",
@@ -1788,6 +1802,8 @@
"parts.family.set_screw": "六角穴付き止めねじ 平先",
"parts.family.socket_head_cap_screw": "六角穴付きボルト",
"parts.family.washer": "平座金",
"parts.holes": "穴 (省略可)",
"parts.placement_hint": "1 つ以上の穴を選ぶとそれぞれに部品を取り付けます。選ばずに OK を押すと原点に配置します",
"pattern.alignment_2": "配置 2",
"pattern.alignment_hint": "個数には元の図形が含まれます。中央配置ではコピーを両側に配置し、反転で偶数個の場合に多く配置する側を切り替えます。",
"pattern.direction_1": "方向 1",
@@ -1840,6 +1856,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": "表示",
@@ -1890,6 +1908,8 @@
"prefs.graphics_card_restart": "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": "左側またはブラウザー下の履歴の先頭に、アクティブなコンポーネントのボディフォルダーと原点を、ブラウザーと同じ表示切り替えとメニュー付きで表示します。下部の履歴は変わりません。",
@@ -1950,12 +1970,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": "プロファイルの保存後に設定が変更されています。「保存」でプロファイルに反映されます。",
@@ -1977,6 +2001,8 @@
"prefs.quality_fastest": "最速",
"prefs.quality_slowest": "最も低速",
"prefs.quality_smoother": "より滑らか",
"prefs.quick_measure": "ビューの隅にクイック測定",
"prefs.quick_measure_note": "選択したものの寸法をビューの右下、履歴の上に表示します: エッジの長さ(円形のものは Ø と R)、面の面積、点の位置、2 つの項目の距離と角度、複数のエッジや面の合計。クリックでコピーします。",
"prefs.render_previous_geometry_budget": "レンダープレビューで以前のジオメトリ予算を使用",
"prefs.render_previous_geometry_budget_help": "デバイスに基づく予算の代わりに、以前の固定ジオメトリ上限(64ビットデスクトップでは512 MiB、その他では128 MiB)に戻します。",
"prefs.reports_saved": "{n} 件保存済み",
@@ -1990,6 +2016,8 @@
"prefs.search_empty": "一致する設定がありません",
"prefs.select": "選択",
"prefs.select_one_item": "項目を 1 つ選択",
"prefs.select_other_hidden": "「他を選択」で非表示のジオメトリを選択",
"prefs.select_other_hidden_help": "オン: ポインター付近の非表示のボディやスケッチ、遮られたエッジを含めます。オフ: ポインター直下の表示中のジオメトリだけを元のリストに表示します。",
"prefs.selecting": "選択",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift は追加のみ、Ctrl / Cmd は選択を切り替え",
@@ -2068,6 +2096,8 @@
"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 とキャンセルと同じ操作を行うか、スケッチを終了またはキャンセルします。ビューナビゲーターはその下に移動します。",
@@ -2077,6 +2107,10 @@
"prefs.viewport_background": "背景",
"prefs.viewport_background_colour": "色",
"prefs.viewport_background_theme": "テーマのグラデーション",
"prefs.viewport_section_status": "ビューポートの断面状態を表示",
"prefs.viewport_section_status_help": "ビューポートに断面設定を表示します。Escape または有効なリボンボタンで断面を解除します。無効にすると従来の断面操作を維持します。",
"prefs.viewport_status_summary": "ビューポートの状態項目をまとめる",
"prefs.viewport_status_summary_help": "3 個以上の状態項目を 1 つのサマリーバッジにまとめます。無効にすると、すべての項目を従来の行に表示します。",
"prefs.wheel": "ホイール",
"prefs.wheel_trackpad": "ホイールとトラックパッド",
"prefs.workspace": "ワークスペース",
@@ -2955,7 +2989,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": "サイズOK · {width} × {height} px",
"render.output_size_profile": "{width} × {height} をレンダリングするには、CPUリソースプロファイル「{profile}」を選ぶか、より小さいサイズを選んでください。",
"render.output_swap_orientation": "幅と高さを交換",
"render.output_tools_inspector": "サイズと形式は出力インスペクターで編集してください。",
"render.output_view_image": "画像を表示",
@@ -3168,6 +3204,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": "プレビュー",
@@ -3455,6 +3492,24 @@
"sheet.flange_type_contour": "輪郭",
"sheet.flange_type_edge": "エッジ",
"sheet.no_body": "板金ボディがありません。フランジ > Base で作成するか、板金に変換でプレートを板金に変換してください(広い平面を選択)。",
"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ボックスの先頭にピン留め",
@@ -3491,6 +3546,7 @@
"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.on_path": "文字はパスに沿います: 位置は曲線で決まります",
@@ -3631,6 +3687,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 マウス速度",
@@ -3659,6 +3717,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 マウスのドライバーのプロファイルがこの軸をオフにしているため、この軸の動きではビューは動きません。ここで他の軸の動きを選ぶと 2 つが入れ替わり、その動きではビューが動かなくなります。",
@@ -3792,6 +3852,8 @@
"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": "保存",
@@ -3975,6 +4037,7 @@
"ui.circle": "円",
"ui.circular_pattern": "円形パターン",
"ui.clear": "クリア",
"ui.clear_print_face": "造形面を解除",
"ui.clear_recent": "最近使った項目をクリア",
"ui.clear_the_selection": "選択をクリア",
"ui.click_selects_whole_bodies": "クリックでボディ全体を選択",
@@ -4407,12 +4470,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": "ファイルの単位",
@@ -4421,6 +4489,11 @@
"ui.polygon": "ポリゴン",
"ui.preferences": "環境設定",
"ui.press_a_key": "キーを押してください…",
"ui.print_face_cleared": "{body}: 造形面を解除しました",
"ui.print_face_input": "造形面",
"ui.print_face_one_body": "造形面はボディごとに選びます。変更するにはそのボディだけを送信してください",
"ui.print_face_pick": "ソリッドボディの平面を1つ選択すると造形面になります",
"ui.print_face_set": "{body}: 造形面を設定しました。スライサーへ送るとこの面を下にして置きます",
"ui.profile": "プロファイル",
"ui.profile_plane": "プロファイル平面",
"ui.profiles": "プロファイル",
@@ -4503,6 +4576,7 @@
"ui.selectOther": "その他を選択…",
"ui.selectTangentChain": "接線チェーンを選択",
"ui.select_all": "すべて選択",
"ui.select_components_by_face": "コンポーネントを選択(面をクリックするとそのコンポーネントが選択されます)",
"ui.selection": "選択",
"ui.selection_modifiers": "選択修飾キー",
"ui.selection_shift_add_only": "Shift+クリックで追加のみ(解除なし)",
@@ -4544,6 +4618,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": "スライス",
@@ -4653,7 +4732,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": "エッジ付きシェーディング",
@@ -4690,7 +4776,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 を再起動してください。",
+56
View File
@@ -711,6 +711,7 @@
"cmd.appearance_pick_up": "모양 가져오기",
"cmd.appearance_replace": "모양 바꾸기",
"cmd.assembly_bom": "자재 명세서",
"cmd.body.print_face": "출력 바닥면",
"cmd.body_rename": "바디 이름 변경",
"cmd.browser_group": "새 그룹",
"cmd.browser_move": "브라우저 항목 이동",
@@ -1525,6 +1526,8 @@
"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": "가운데 드래그",
@@ -1568,6 +1571,8 @@
"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": "왼쪽 또는 브라우저 아래의 히스토리가 활성 구성 요소의 바디 폴더와 원점으로 시작하며, 브라우저처럼 표시 아이콘과 메뉴가 있습니다. 아래쪽 히스토리는 바뀌지 않습니다.",
@@ -1616,6 +1621,8 @@
"prefs.persistent_ribbon_pressed_help": "효과가 켜져 있는 동안 리본 명령을 강조 표시합니다. 끄면 이전의 중립적인 모양과 명령 도움말을 유지합니다.",
"prefs.perspective_fov": "시야각",
"prefs.preview_choices": "선택 항목 미리보기",
"prefs.print_face_highlight": "출력 바닥면 강조",
"prefs.print_face_highlight_tip": "뷰에서 바디의 출력 바닥면을 색칠하고 윤곽을 그리는 강도입니다. 0 %이면 강조하지 않습니다.",
"prefs.project_options_dialog": "Project 옵션 대화상자",
"prefs.project_options_dialog_note": "켜기: 투영 전에 지오메트리 또는 바디와 투영 링크를 선택합니다. 끄기: 기존 직접 선택 작업 흐름을 유지합니다.",
"prefs.quality_default": "기본값",
@@ -1631,6 +1638,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는 토글",
@@ -1695,12 +1704,18 @@
"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_summary": "뷰포트 상태 항목 요약",
"prefs.viewport_status_summary_help": "상태 항목이 3개 이상이면 하나의 요약 배지로 표시합니다. 비활성화하면 모든 항목을 기존 행에 표시합니다.",
"prefs.wheel": "휠",
"prefs.wheel_trackpad": "휠 및 트랙패드",
"prefs.workspace": "작업 공간",
@@ -2265,7 +2280,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": "칠하기",
@@ -2397,6 +2414,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 +2636,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 +2838,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 마우스 드라이버 프로필이 이 축을 끄므로, 이 축의 동작으로는 뷰가 움직이지 않습니다. 여기서 다른 축의 동작을 고르면 둘이 바뀌고, 그 동작은 더 이상 뷰를 움직이지 않습니다.",
@@ -2917,6 +2955,8 @@
"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": "보내기",
@@ -3065,6 +3105,7 @@
"ui.circle": "원",
"ui.circular_pattern": "원형 패턴",
"ui.clear": "지우기",
"ui.clear_print_face": "출력 바닥면 해제",
"ui.clear_recent": "최근 항목 지우기",
"ui.clear_the_selection": "선택 해제",
"ui.click_selects_whole_bodies": "클릭하면 바디 전체 선택",
@@ -3479,6 +3520,11 @@
"ui.polygon": "다각형",
"ui.preferences": "환경 설정",
"ui.press_a_key": "키를 누르세요…",
"ui.print_face_cleared": "{body}: 출력 바닥면을 해제했습니다",
"ui.print_face_input": "출력 바닥면",
"ui.print_face_one_body": "출력 바닥면은 바디마다 하나입니다. 변경하려면 그 바디만 보내십시오",
"ui.print_face_pick": "솔리드 바디의 평면 하나를 선택하면 출력 바닥면이 됩니다",
"ui.print_face_set": "{body}: 출력 바닥면을 설정했습니다. 슬라이서로 보내면 이 면이 아래로 놓입니다",
"ui.profile": "프로파일",
"ui.profile_plane": "프로파일 평면",
"ui.profiles": "프로파일",
@@ -3555,6 +3601,7 @@
"ui.selectOther": "기타 선택…",
"ui.selectTangentChain": "접선 체인 선택",
"ui.select_all": "모두 선택",
"ui.select_components_by_face": "구성요소 선택(면을 클릭하면 해당 구성요소 선택)",
"ui.selection": "선택",
"ui.selection_modifiers": "선택 보조 키",
"ui.selection_shift_add_only": "Shift+클릭은 추가만 함",
@@ -3688,7 +3735,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": "음영 처리 및 모서리",
@@ -3720,7 +3774,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를 다시 시작하세요.",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Reka bentuk dibuka atau disimpan ada di sini.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Ditentukan oleh tetapan Ke Objek. Pilih Sudut untuk mengubah julat sudut.",
"Diameter": "Diameter",
"Did this assembly open the wrong way up?": "Adakah pemasangan ini dibuka dengan orientasi yang salah?",
"Did this part open the wrong way up?": "Adakah bahagian ini dibuka dengan orientasi yang salah?",
"Dimension new shapes": "Dimensikan bentuk baharu",
"Direction": "Arah",
"Discard": "Buang",
@@ -601,6 +603,7 @@
"Square": "Segi empat sama",
"Stand it up": "Tegakkan",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Tegakkan: suku putaran pada X, satu ciri Alih yang boleh dibatalkan.",
"Stand this assembly up?": "Tegakkan pemasangan ini?",
"Stand this part up?": "Tegakkan bahagian ini?",
"Start angle": "Sudut mula",
"Start bulge": "Bonjolan permulaan",
@@ -772,6 +775,8 @@
"at the face": "pada muka",
"bom.group_children": "Sub-bahagian dalam baris bahagiannya",
"browser.body_name": "Jasad{number}",
"browser.calibrate_click_a": "Klik imej untuk Titik A, atau taip koordinatnya.",
"browser.calibrate_click_b": "Klik imej untuk Titik B, atau taip koordinatnya.",
"browser.calibrate_title": "Tentukur: {name}",
"browser.capture_many": "{count} komponen telah dialihkan tetapi kedudukannya belum dirakam.",
"browser.capture_note": "Rakam mengekalkan kedudukan baharu; Kembalikan memulihkannya.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Ambil Penampilan",
"cmd.appearance_replace": "Gantikan Penampilan",
"cmd.assembly_bom": "Senarai Bahan",
"cmd.body.print_face": "Muka cetakan",
"cmd.body_rename": "Tukar Nama Jasad",
"cmd.browser_collapse_all": "Ringkaskan Semua",
"cmd.browser_expand_all": "Kembangkan Semua",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Klik dimensi pemacu lain untuk menyisip namanya",
"dialog.dimension_name": "Dimensi: {name}",
"dialog.edit_title": "SUNTING {title}",
"dialog.no_shown_body": "Tiada jasad dipaparkan untuk diubah: paparkan satu, atau pilih Jasad Baharu",
"dialog.select": "↖ Pilih",
"dialog.selected_count": "↖ {count} dipilih",
"dialog.selection_guidance": "Pilih input untuk diubah. Klik objek dipilih untuk nyahpilih; Ctrl/Cmd+klik juga menogol pilihan. Semak pratonton, kemudian tekan OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Mengalihkan bahagian pemasangan hanya mengikut kebebasan yang dibenarkan oleh sendinya. Pilih muka bahagian, kemudian seret anak panah atau lengkok yang tersedia.",
"help.parameters.change": "Membuka jadual nilai dan ungkapan bernama yang memacu dimensi reka bentuk. Gunakan untuk menukar saiz di satu tempat dan membiarkan setiap ciri yang menggunakannya mengikut.",
"help.pointcloud.insert": "Sisipkan imbasan PLY, XYZ atau PTS sebagai awan titik di samping model. Gunakannya sebagai rujukan semasa membina model daripada data yang diukur.",
"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.",
"help.sketch.equation_curve": "Melukis lengkung daripada formula: x(t) dan y(t), atau y = f(x), dalam satu julat. Formula boleh menggunakan parameter dan lengkung akan mengikutinya: gunakannya untuk gelombang sinus, lingkaran atau involut gigi gear.",
"help.solid.align": "Mengalihkan jasad yang dipilih supaya satu titik, muka atau satah padanya bertemu satu titik, muka atau satah di tempat lain. Gunakan untuk meletakkan satu bahagian pada bahagian lain tanpa mengukur ofset.",
"help.solid.chamfer": "Memotong tepi yang dipilih dengan potongan serong rata sejauh jarak yang anda tetapkan. Gunakan untuk menumpulkan tepi tajam atau memudahkan kemasukan pin atau skru.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Membina semula setiap ciri dalam garis masa dari awal. Gunakan untuk menyegarkan model dan melihat ciri yang gagal.",
"help.timeline.delete": "Memadam ciri yang dipilih daripada garis masa; memadam lakaran turut memadam ciri yang dibina daripadanya. Gunakan untuk membuang langkah yang tidak lagi diperlukan.",
"help.timeline.play": "Main semula ciri yang tinggal satu demi satu, dengan jeda setengah saat selepas setiap pembinaan semula. Jalankan perintah ini sekali lagi atau tekan Escape untuk berhenti; Pergi ke penghujung melompat serta-merta.",
"history.sketch_under_constrained": "Kurang terkekang ({count} darjah kebebasan)",
"history.sketch_under_constrained_one": "Kurang terkekang ({count} darjah kebebasan)",
"hole_presets.confirm_delete": "Sahkan pemadaman",
"hole_presets.delete": "Padam",
"hole_presets.delete_prompt": "Padam pratetap peribadi ini? Lubang sedia ada tidak berubah.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Melicinkan tepi bergerigi.",
"prefs.applies_on_restart": "Berkuat kuasa selepas dimulakan semula",
"prefs.assistant": "Pembantu",
"prefs.auto_operation": "Pilih operasi secara automatik",
"prefs.auto_operation_note": "Tetingkap ciri memilih Cantum, Jasad Baharu atau Potong berdasarkan apa yang disentuh oleh bahan baharu, sehingga anda memilih satu. Mati: ia mengekalkan operasi yang dibukanya.",
"prefs.axes": "Paksi",
"prefs.axis": "Paksi",
"prefs.bodies_folders": "Folder jasad",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Bawah tengah",
"prefs.bottom_right": "Bawah kanan",
"prefs.browser": "Pelayar",
"prefs.browser_eye_left": "Ikon mata di sebelah anak panah kembang/ringkas",
"prefs.browser_eye_left_note": "Baris pelayar memaparkan ikon mata papar/sembunyi di sebelah anak panah kembang/ringkas, seperti sebelum ini, bukannya dalam lajur di hujung kanan baris.",
"prefs.browser_reveal_on_hover": "Muncul apabila dilalui kursor",
"prefs.browser_show_note": "Dipaparkan mengekalkan pelayar pada skrin. Muncul apabila dilalui kursor menyembunyikannya sehingga penuding berada di atas tempatnya atau tab nipis di tepi kirinya, dan ia kekal terbuka selagi penuding berada di dalamnya atau menu daripadanya terbuka.",
"prefs.browser_shown": "Dipaparkan",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Berkuat kuasa selepas SolidCraft dimulakan semula. WGPU_ADAPTER_NAME mengatasi pilihan ini.",
"prefs.graphics_note": "Tukar kelajuan dengan kelicinan. Setiap pilihan menunjukkan kesannya pada contoh.",
"prefs.graphics_presets_note": "Pratetap menetapkan pilihan di bawah sekali gus.",
"prefs.hide_constraint_glyphs_zoomed_out": "Sembunyikan glif kekangan apabila dizum keluar",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Glif kekangan hilang daripada pandangan apabila lakaran dizum keluar sehingga glif itu menyesakkan paparan. Mati: ia kekal pada saiz skrin tetap pada setiap tahap zum.",
"prefs.history": "Sejarah",
"prefs.history_origin_bodies": "Papar titik asal dan jasad di bahagian atas",
"prefs.history_origin_bodies_note": "Sejarah di sebelah kiri atau di bawah pelayar bermula dengan folder Jasad bagi komponen aktif dan Titik Asal, lengkap dengan ikon mata dan menunya seperti dalam pelayar. Sejarah di bahagian bawah tidak berubah.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "Fail STEP dan import",
"prefs.max_file_size_web": "Aplikasi web membuka fail STEP dan import sehingga 512 MB serta reka bentuk sehingga 256 MB: tab pelayar tidak dapat memuatkan fail lebih besar. Aplikasi desktop boleh menaikkan had ini.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Ikon besar",
"prefs.menu_list": "Senarai",
"prefs.menus": "Menu",
"prefs.minutes": "minit",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Tetikus dan pad jejak",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Nama satah asal",
"prefs.origin_plane_names_note": "Nama yang dipaparkan dalam pelayar, dialog dan senarai. {top} ialah XY, {front} ialah XZ dan {right} ialah YZ, dengan Z ke atas. Arahan, skrip dan reka bentuk yang disimpan kekal menggunakan XY, XZ dan YZ.",
"prefs.overall_speed": "Kelajuan keseluruhan",
"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.perspective_fov": "Medan pandangan",
"prefs.placement": "Penempatan",
"prefs.prefer_free_sketch_points": "Utamakan titik lakaran yang boleh digerakkan apabila titik bertindih",
"prefs.preview_choices": "Pratonton pilihan anda",
"prefs.print_face_highlight": "Serlahan muka cetakan",
"prefs.print_face_highlight_tip": "Sejauh mana muka cetakan sesuatu jasad diwarnakan dan digaris dalam paparan. 0 % tidak menunjukkan serlahan.",
"prefs.profile_active": "Profil aktif",
"prefs.profile_applied": "Profil digunakan.",
"prefs.profile_changed": "Tetapan telah berubah sejak profil disimpan. Simpan mengekalkannya dalam profil.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Tiada tetapan yang sepadan",
"prefs.select": "Pilih",
"prefs.select_one_item": "Pilih satu item",
"prefs.select_other_hidden": "Pilih geometri tersembunyi dengan Pilih Lain",
"prefs.select_other_hidden_help": "Hidup: sertakan jasad, lakaran dan tepi terlindung yang tersembunyi berhampiran penuding. Mati: guna senarai asal geometri kelihatan di bawah penuding.",
"prefs.selecting": "Pemilihan",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift hanya menambah; Ctrl / Cmd menogol",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Semasa melukis, titik diselaraskan secara mendatar dengan satu titik lakaran dan secara menegak dengan titik lain pada masa yang sama, dengan panduan bertitik, dan klik mengekalkan kedua-dua penjajaran. Mati: hanya dengan titik sebelumnya, satu paksi pada satu masa.",
"prefs.sketch_conflict_colour": "Geometri bercanggah dalam warna percanggahan",
"prefs.sketch_conflict_colour_note": "Apabila kekangan lakaran bercanggah, lengkung dan titik yang dipegang oleh kekangan yang gagal turut dilukis dalam warna percanggahan, bukan sahaja glifnya atau dimensinya. Mati: ia mengekalkan warna biasa.",
"prefs.sketch_construction_lines": "Alat garis binaan lanjutan",
"prefs.sketch_exit_view": "Apabila lakaran tamat",
"prefs.sketch_exit_view.keep": "Kekalkan pandangan semasa",
"prefs.sketch_exit_view.previous": "Kembali ke pandangan sebelumnya",
"prefs.sketch_exit_view.unless_rotated": "Kembali melainkan diputar",
"prefs.sketch_exit_view_note": "Ke mana pandangan beralih selepas Siap Lakaran, Batal atau Esc dua kali.",
"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.",
"prefs.sketch_recent_snap": "Lekap auto pada pusat dan titik tengah terkini",
"prefs.sketch_recent_snap_note": "Semasa melukis, kursor melekap pada titik persilangan panduan daripada dua pusat bulatan atau titik tengah tepi lurus yang terkini. Mati: panduan itu hanya untuk paparan dan kursor tidak bergerak.",
"prefs.sketch_selection": "Lakaran & pilihan",
"prefs.sketch_selection_heading": "Lakaran dan pilihan",
"prefs.sketch_selection_note": "Cara lakaran melekap dan cara klik memilih.",
"prefs.sketch_state_icons": "Ikon lakaran dalam warna keadaannya",
"prefs.sketch_state_icons_note": "Dalam Sejarah dan pelayar, ikon lakaran mengambil warna keadaannya (kurang terkekang, terkekang sepenuhnya atau bercanggah) dan petua alatnya menamakan keadaan itu. Mati: setiap lakaran mempunyai ikon yang sama.",
"prefs.sketch_tab_always": "Kekalkan tab Lakaran dipaparkan",
"prefs.sketch_tab_always_note": "Tab Lakaran kekal dalam reben, sejurus selepas Model, selagi tiada lakaran dibuka. Alat lukisannya kemudian memulakan lakaran pada satah atau muka yang dipilih, atau pada satah yang anda pilih seterusnya, dengan alat itu sedia di dalamnya.",
"prefs.sketch_tracking_guides": "Panduan penjajaran pusat dan titik tengah",
@@ -1995,6 +2034,11 @@
"prefs.source": "Sumber",
"prefs.speed": "Kelajuan",
"prefs.stand_up_note": "Selepas fail STEP atau IGES dibuka dengan Z ke atas, tawarkan putaran suku pusingan pada paksi X.",
"prefs.stand_up_unknown": "Untuk fail daripada program yang tidak dikenali",
"prefs.stand_up_unknown.ask": "Tanya dalam dialog",
"prefs.stand_up_unknown.note": "Papar nota",
"prefs.stand_up_unknown.off": "Jangan tawarkan",
"prefs.stand_up_unknown_note": "Cara tawaran dibuat apabila pengepala fail tidak menamakan program yang diketahui menyimpan dengan Y ke atas. Nota dipaparkan hanya apabila fail kelihatan terbaring; Tanya dalam dialog bertanya tentang setiap fail sedemikian, seperti sebelum ini.",
"prefs.status_bar": "Bar status",
"prefs.status_bar_note": "Bar nipis di sepanjang tepi bawah tetingkap memaparkan apa yang sedang disunting, keadaan lakaran yang dibuka dalam warnanya dan unit reka bentuk; klik pada unit untuk menukarnya. Keadaan lakaran kemudian tidak lagi dipaparkan di sudut paparan.",
"prefs.status_done": "Selesai",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK dan Batal di sudut pandangan",
"prefs.view_confirm_corner_note": "Semasa dialog atau lakaran dibuka, tanda semak dan tanda silang di bahagian kanan atas pandangan melakukan apa yang dilakukan OK dan Batal dialog, atau menyiapkan dan membatalkan lakaran. Navigator pandangan bergerak ke bawah di bawahnya.",
"prefs.viewport": "Tetingkap pandangan",
"prefs.viewport_analysis_status": "Papar status analisis tetingkap pandangan",
"prefs.viewport_analysis_status_help": "Papar analisis aktif di atas tetingkap pandangan, buka petunjuknya daripada setiap item, dan gunakan Esc untuk mematikan analisis terbaharu. Matikan untuk memulihkan petunjuk sudut dan tingkah laku Esc yang terdahulu.",
"prefs.viewport_background": "Latar belakang",
"prefs.viewport_background_colour": "Warna",
"prefs.viewport_background_theme": "Kecerunan tema",
@@ -2049,6 +2095,10 @@
"properties.title": "Sifat: {name}",
"recovery.days_ago": "{count} hari lalu",
"recovery.detail": "{file} · {features} ciri · disimpan automatik {when}",
"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?",
"recovery.hours_ago": "{count} j lalu",
"recovery.just_now": "sebentar tadi",
"recovery.min_ago": "{count} min lalu",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Alih Ciri ke Folder",
"timeline.rename_group": "Tukar Nama Folder Ciri",
"timeline.reorder_many": "Susun Semula Ciri",
"timeline.suppress_features": "Nyahaktifkan Ciri",
"timeline.suppress_folders_tip": "Nyahaktifkan atau aktifkan semula setiap ciri dalam folder dan folder bersarangnya",
"timeline.ungroup": "Buang Folder Ciri",
"timeline.unsuppress_features": "Aktifkan Semula Ciri",
"to the face": "ke muka",
"to the next face": "ke muka seterusnya",
"toolbar.customize_note": "Tab {tab}. Tandakan arahan untuk memberinya butang bar alat. Anda juga boleh menyeret baris menu lungsur panel ke bar alat, menyeret butang keluar dari bar alat untuk membuangnya, atau guna semat pada baris menu lungsur.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
"ui.3d_mouse_switch_cap": "Tentukur sebagai kembali ke tengah",
"ui.3d_mouse_switch_trackball": "Tentukur sebagai bola jejak",
"ui.3d_mouse_use_direct": "Guna pemacu terus",
"ui.3d_mouse_use_direct_tip": "Baca tetikus 3D secara terus (hidraw) dan bukan melalui spacenavd.",
"ui.3d_mouse_view_axis": "Paksi paparan",
"ui.3d_mouse_view_off": "Mati",
"ui.3d_mouse_view_off_tip": "Profil pemacu tetikus 3D mematikan paksi ini, jadi gerakannya tidak menggerakkan paparan. Memilih gerakan paksi lain di sini menukar kedua-duanya, dan gerakan itu tidak lagi menggerakkan paparan.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Batal",
"ui.canvases": "Kanvas",
"ui.center": "Tengah",
"ui.center_arc_follows_cursor": "Lengkok titik pusat mengikut arah kursor",
"ui.center_arc_follows_cursor_tip": "Lengkok titik pusat berjalan ikut arah jam atau lawan arah jam mengikut cara kursor mengelilingi pusatnya. Mati: sentiasa lawan arah jam dari permulaan.",
"ui.centerline": "Garis tengah",
"ui.chain": "Rantaian",
"ui.chamfer": "Serong",
@@ -3930,6 +3986,7 @@
"ui.circle": "Bulatan",
"ui.circular_pattern": "Corak Membulat",
"ui.clear": "Kosongkan",
"ui.clear_print_face": "Kosongkan muka cetakan",
"ui.clear_recent": "Kosongkan Terkini",
"ui.clear_the_selection": "Kosongkan pilihan",
"ui.click_selects_whole_bodies": "Klik untuk memilih keseluruhan jasad",
@@ -3964,6 +4021,14 @@
"ui.constant": "Malar",
"ui.constraints_conflict": "Kekangan bercanggah",
"ui.construction": "Binaan",
"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",
"ui.construction.ribbon_label": "Garis bantu",
"ui.context_menu": "Menu konteks",
"ui.continuity_g0": "Lipatan G0",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Poligon",
"ui.preferences": "Tetapan",
"ui.press_a_key": "tekan kekunci…",
"ui.print_face_cleared": "{body}: muka cetakan dikosongkan",
"ui.print_face_input": "Muka cetakan",
"ui.print_face_one_body": "Muka cetakan dimiliki oleh satu jasad: hantar jasad itu sahaja untuk menukarnya",
"ui.print_face_pick": "Pilih satu muka rata pada jasad pepejal untuk menjadikannya muka cetakan",
"ui.print_face_set": "{body}: muka cetakan ditetapkan; Hantar ke penghiris meletakkan jasad di atasnya",
"ui.profile": "Profil",
"ui.profile_plane": "Satah Profil",
"ui.profiles": "Profil",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Putar",
"ui.ribbon_exit": "Keluar",
"ui.ribbon_labels": "Tunjukkan teks di bawah ikon reben",
"ui.ribbon_menus_skip_pinned": "Kecualikan arahan tersemat daripada menu lungsur",
"ui.ribbon_pin_footer": "Arahan tersemat dipaparkan pada reben. Lalukan tetikus pada baris untuk menyemat atau menyahsemat.",
"ui.ribbon_workspace": "Ruang kerja",
"ui.right": "Kanan",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Pusat pandangan",
"ui.viewport_status_clear": "Matikan {name}",
"ui.viewport_status_clear_hint": "Matikan {name} · Esc",
"ui.viewport_status_edges": "{count} tepi",
"ui.viewport_status_from": "dari {direction}",
"ui.viewport_status_key": "Papar petunjuk untuk {name}",
"ui.viewport_status_minimum_radius": "Jejari minimum",
"ui.viewport_status_pairs": "{count} pasangan",
@@ -4634,7 +4707,9 @@
"update.button": "Kemas kini",
"update.button_version": "Kemas kini ke {version}",
"update.check_automatically": "Semak kemas kini secara automatik",
"update.check_for_updates": "Semak kemas kini",
"update.check_now": "Semak sekarang",
"update.check_unavailable": "Semakan kemas kini tidak tersedia untuk pemasangan ini.",
"update.checking": "Menyemak…",
"update.copy": "Salin",
"update.dmg_opened": "Imej cakera telah dibuka dan disahkan: seret SolidCraft ke Applications untuk menggantikan salinan lama, kemudian mulakan semula SolidCraft.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} telah dipasang. Mulakan semula untuk menggunakannya.",
"update.installer_started": "Pemasang bermula apabila SolidCraft ditutup.",
"update.later": "Ingatkan saya kemudian",
"update.managed": "Kemas kini tersedia: kemas kini SolidCraft dalam {manager}",
"update.no_notes": "Tiada nota untuk keluaran ini.",
"update.package_hint": "Salinan ini dipasang melalui {kind}. Muat turun pakej baharu, kemudian pasang dengan:",
"update.prefs_dev": "Ini ialah binaan pembangunan ({version}). Semakan kemas kini tidak dilakukan.",
"update.prefs_flatpak": "Flatpak mengemas kini {version}: jalankan {command} atau guna pusat perisian. SolidCraft tidak menyemak.",
"update.prefs_label": "Kemas kini",
"update.prefs_managed": "Ini ialah SolidCraft {version}, diurus oleh {manager}. Semakan hanya meminta keluaran terkini daripada GitHub dan tidak menghantar apa-apa lagi; kemas kini SolidCraft dalam {manager}.",
"update.prefs_note": "SolidCraft {version} ({kind}). Semakan hanya meminta keluaran terkini dari GitHub, tanpa data lain dihantar.",
"update.prefs_web": "Muat semula halaman untuk mendapatkan aplikasi web terkini. Jika rangkaian tidak tersedia atau kemas kini tidak dapat selesai dimuat turun tepat pada masanya, versi yang disimpan dibuka sebaliknya; muat turun yang sedang berjalan diteruskan untuk muat semula seterusnya.",
"update.refresh_checking": "Menyemak keluaran terbaharu…",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Design du åpner eller lagrer, vises her.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Bestemmes av Til objekt. Velg Vinkel for å endre vinkelutstrekningen.",
"Diameter": "Diameter",
"Did this assembly open the wrong way up?": "Ble denne sammenstillingen åpnet feil vei opp?",
"Did this part open the wrong way up?": "Ble denne delen åpnet feil vei opp?",
"Dimension new shapes": "Sett mål på nye figurer",
"Direction": "Retning",
"Discard": "Forkast",
@@ -601,6 +603,7 @@
"Square": "Kvadrat",
"Stand it up": "Reis den opp",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Reis den opp: en kvart omdreining om X, som én Flytt-funksjon du kan angre.",
"Stand this assembly up?": "Reise denne sammenstillingen opp?",
"Stand this part up?": "Reise denne delen opp?",
"Start angle": "Startvinkel",
"Start bulge": "Utbuling ved starten",
@@ -772,6 +775,8 @@
"at the face": "ved flaten",
"bom.group_children": "Underdeler i raden til overordnet del",
"browser.body_name": "Kropp{number}",
"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.",
"browser.calibrate_title": "Kalibrer: {name}",
"browser.capture_many": "{count} komponenter er flyttet, men de nye posisjonene er ikke registrert.",
"browser.capture_note": "Registrer posisjon beholder den nye posisjonen. Tilbakestill gjenoppretter den forrige.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Plukk opp utseende",
"cmd.appearance_replace": "Erstatt utseende",
"cmd.assembly_bom": "Stykkliste",
"cmd.body.print_face": "Utskriftsflate",
"cmd.body_rename": "Gi nytt navn til kropp",
"cmd.browser_collapse_all": "Skjul alle",
"cmd.browser_expand_all": "Utvid alle",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Klikk på et annet styrende mål for å sette inn navnet",
"dialog.dimension_name": "Mål: {name}",
"dialog.edit_title": "REDIGER {title}",
"dialog.no_shown_body": "Ingen synlig kropp å endre: vis en, eller velg Ny kropp",
"dialog.select": "↖ Velg",
"dialog.selected_count": "↖ {count} valgt",
"dialog.selection_guidance": "Velg et felt for å endre det. Klikk på valgte objekter for å fjerne dem fra utvalget; Ctrl/Cmd+klikk veksler også mellom valgt og ikke valgt. Se over forhåndsvisningen, og trykk deretter OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Flytter en del i en sammenstilling bare langs frihetsgradene som leddene tillater. Velg en flate på delen, og dra deretter en tilgjengelig pil eller bue.",
"help.parameters.change": "Åpner tabellen over navngitte verdier og uttrykk som styrer målene i designet. Bruk den til å endre en størrelse ett sted og la alle funksjoner som bruker den følge med.",
"help.pointcloud.insert": "Sett inn en PLY-, XYZ- eller PTS-skanning som en punktsky ved siden av modellen. Bruk den som referanse når du bygger en modell fra måledata.",
"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.",
"help.sketch.equation_curve": "Tegner en kurve ut fra formler: x(t) og y(t), eller y = f(x), over et intervall. Formlene kan bruke parametere, og kurven følger dem: bruk den til en sinusbølge, en spiral eller evolventen til en tannhjulstann.",
"help.solid.align": "Flytter de valgte kroppene slik at et punkt, en flate eller et plan på dem møter et punkt, en flate eller et plan et annet sted. Bruk det til å plassere en del mot en annen uten å måle forskyvningen.",
"help.solid.chamfer": "Lager en plan fas langs de valgte kantene med avstanden du angir. Bruk verktøyet til å fjerne skarpe kanter eller gjøre det lettere å føre inn en pinne eller skrue.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Bygger alle funksjoner i tidslinjen på nytt fra begynnelsen. Bruk det til å oppdatere modellen og se hvilke funksjoner som feiler.",
"help.timeline.delete": "Sletter de valgte funksjonene fra tidslinjen; sletting av en skisse sletter også funksjonene som er bygget på den. Bruk det til å fjerne et trinn som ikke lenger er ønsket.",
"help.timeline.play": "Spill av de gjenstående funksjonene én om gangen, med en halv sekunds pause etter hver gjenoppbygging. Kjør denne kommandoen igjen eller trykk Escape for å stoppe; Gå til slutten hopper umiddelbart.",
"history.sketch_under_constrained": "Underbestemt ({count} frihetsgrader)",
"history.sketch_under_constrained_one": "Underbestemt ({count} frihetsgrad)",
"hole_presets.confirm_delete": "Bekreft sletting",
"hole_presets.delete": "Slett",
"hole_presets.delete_prompt": "Slette denne personlige forhåndsinnstillingen? Eksisterende hull endres ikke.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Jevner ut taggete kanter.",
"prefs.applies_on_restart": "Tas i bruk ved omstart",
"prefs.assistant": "Assistent",
"prefs.auto_operation": "Velg operasjon automatisk",
"prefs.auto_operation_note": "Funksjonsdialoger velger Slå sammen, Ny kropp eller Klipp ut ut fra hva det nye materialet berører, helt til du velger selv. Av: de beholder operasjonen de åpnes med.",
"prefs.axes": "Akser",
"prefs.axis": "Akse",
"prefs.bodies_folders": "Kroppsmapper",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Nederst i midten",
"prefs.bottom_right": "Nederst til høyre",
"prefs.browser": "Modelltre",
"prefs.browser_eye_left": "Øyet ved siden av foldepilen",
"prefs.browser_eye_left_note": "Radene i modelltreet viser vis/skjul-øyet ved siden av foldepilen, som før, i stedet for i en kolonne ytterst til høyre i radene.",
"prefs.browser_reveal_on_hover": "Vis når pekeren er over",
"prefs.browser_show_note": "Vist holder modelltreet på skjermen. Vis når pekeren er over holder det skjult til pekeren kommer over plassen det har, eller over den tynne fanen ved venstre kant, og det forblir åpent mens pekeren er inne i det eller en meny fra det er åpen.",
"prefs.browser_shown": "Vist",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Trer i kraft etter at SolidCraft er startet på nytt. WGPU_ADAPTER_NAME overstyrer dette valget.",
"prefs.graphics_note": "Velg mellom hastighet og jevnhet. Hvert valg viser effekten på et eksempel.",
"prefs.graphics_presets_note": "Forhåndsinnstillinger angir valgene nedenfor samlet.",
"prefs.hide_constraint_glyphs_zoomed_out": "Skjul bindingssymboler når det er zoomet ut",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindingssymbolene forsvinner fra visningen når skissen er zoomet ut så langt at de ville rotet til den. Av: de beholder en fast størrelse på skjermen ved alle zoomnivåer.",
"prefs.history": "Historikk",
"prefs.history_origin_bodies": "Vis origo og kropper øverst",
"prefs.history_origin_bodies_note": "Tidslinjen til venstre eller under modelltreet starter med Kropper-mappen til den aktive komponenten og Origo, med øyne og menyer som i modelltreet. Tidslinjen nederst er uendret.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "STEP- og importfiler",
"prefs.max_file_size_web": "Nettappen åpner STEP- og importfiler opptil 512 MB og design opptil 256 MB: en nettleserfane kan ikke romme større filer. Grensene kan økes i skrivebordsappen.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Store ikoner",
"prefs.menu_list": "Liste",
"prefs.menus": "Menyer",
"prefs.minutes": "minutter",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Mus og styreflate",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Navn på origoplan",
"prefs.origin_plane_names_note": "Navn som vises i modelltreet, dialoger og lister. {top} er XY, {front} er XZ og {right} er YZ, med Z opp. Kommandoer, skript og lagrede design beholder XY, XZ og YZ.",
"prefs.overall_speed": "Samlet hastighet",
"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.perspective_fov": "Synsfelt",
"prefs.placement": "Plassering",
"prefs.prefer_free_sketch_points": "Foretrekk flyttbare skissepunkter når punkter overlapper",
"prefs.preview_choices": "Forhåndsvisning av valgene dine",
"prefs.print_face_highlight": "Utheving av utskriftsflate",
"prefs.print_face_highlight_tip": "Hvor sterkt et legemes utskriftsflate fargelegges og omrisses i visningen. 0 % viser ingen utheving.",
"prefs.profile_active": "Aktiv profil",
"prefs.profile_applied": "Profilen er brukt.",
"prefs.profile_changed": "Innstillingene er endret siden profilen ble lagret. Lagre beholder dem i profilen.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Ingen samsvarende innstillinger",
"prefs.select": "Velg",
"prefs.select_one_item": "Velg ett element",
"prefs.select_other_hidden": "Velg skjult geometri med Velg annet",
"prefs.select_other_hidden_help": "På: ta med skjulte kropper, skisser og tildekte kanter nær pekeren. Av: bruk den opprinnelige listen over synlig geometri under pekeren.",
"prefs.selecting": "Valg",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift legger bare til; Ctrl / Cmd veksler",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Under tegning justeres et punkt vannrett etter ett skissepunkt og loddrett etter et annet samtidig, med en prikket hjelpelinje, og klikket beholder begge justeringene. Av: bare etter forrige punkt, én akse om gangen.",
"prefs.sketch_conflict_colour": "Motstridende geometri i konfliktfargen",
"prefs.sketch_conflict_colour_note": "Mens bindingene i en skisse er i konflikt, tegnes også kurvene og punktene som en feilende binding holder i konfliktfargen, ikke bare symbolet eller målet. Av: de beholder sine vanlige farger.",
"prefs.sketch_construction_lines": "Utvidede hjelpelinjeverktøy",
"prefs.sketch_exit_view": "Når en skisse avsluttes",
"prefs.sketch_exit_view.keep": "Behold gjeldende visning",
"prefs.sketch_exit_view.previous": "Gå tilbake til forrige visning",
"prefs.sketch_exit_view.unless_rotated": "Gå tilbake med mindre visningen er rotert",
"prefs.sketch_exit_view_note": "Hvor visningen går etter Fullfør skisse, Avbryt eller dobbelt Esc.",
"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.",
"prefs.sketch_recent_snap": "Fang automatisk til nylige sentre og midtpunkter",
"prefs.sketch_recent_snap_note": "Under tegning fanges markøren der hjelpelinjene fra de to siste sirkelsentrene eller midtpunktene på rette kanter krysser hverandre. Av: hjelpelinjene vises bare, og markøren flytter seg ikke.",
"prefs.sketch_selection": "Skisse og valg",
"prefs.sketch_selection_heading": "Skisse og valg",
"prefs.sketch_selection_note": "Hvordan skisser bruker punktfangst, og hvordan klikk velger.",
"prefs.sketch_state_icons": "Skisseikoner i fargen til tilstanden deres",
"prefs.sketch_state_icons_note": "I tidslinjen og modelltreet får skisseikonet fargen til skissens tilstand (underbestemt, fullt bundet eller i konflikt), og verktøytipset oppgir tilstanden. Av: alle skisser har samme ikon.",
"prefs.sketch_tab_always": "Behold Skisse-fanen synlig",
"prefs.sketch_tab_always_note": "Skisse-fanen blir stående på båndet, rett etter Modell, også når ingen skisse er åpen. Tegneverktøyene starter da en skisse på det valgte planet eller den valgte flaten, eller på planet du velger deretter, med verktøyet klart i skissen.",
"prefs.sketch_tracking_guides": "Hjelpelinjer for senter og midtpunkt",
@@ -1995,6 +2034,11 @@
"prefs.source": "Kilde",
"prefs.speed": "Hastighet",
"prefs.stand_up_note": "Når en STEP- eller IGES-fil åpnes med Z opp, tilby en kvart omdreining rundt X.",
"prefs.stand_up_unknown": "For filer fra ukjente programmer",
"prefs.stand_up_unknown.ask": "Spør i en dialog",
"prefs.stand_up_unknown.note": "Vis en melding",
"prefs.stand_up_unknown.off": "Ikke tilby",
"prefs.stand_up_unknown_note": "Hvordan tilbudet gis når filhodet ikke nevner noe program som er kjent for å lagre med Y opp. En melding vises bare når filen ser ut til å ligge på ryggen. Spør i en dialog spør om hver slik fil, som før.",
"prefs.status_bar": "Statuslinje",
"prefs.status_bar_note": "En tynn linje langs vinduets nedre kant viser hva som redigeres, tilstanden til den åpne skissen i dens farge og designets enheter; et klikk på enhetene endrer dem. Skissens tilstand vises da ikke lenger i hjørnet av visningen.",
"prefs.status_done": "Ferdig",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK og Avbryt i hjørnet av visningen",
"prefs.view_confirm_corner_note": "Mens en dialog eller en skisse er åpen, gjør et hakemerke og et kryss øverst til høyre i visningen det samme som OK og Avbryt i dialogen, eller fullfører og avbryter skissen. Visningsnavigatoren flyttes ned under dem.",
"prefs.viewport": "Visningsområde",
"prefs.viewport_analysis_status": "Vis analysestatus i visningsområdet",
"prefs.viewport_analysis_status_help": "Vis aktive analyser over visningsområdet, åpne tegnforklaringene deres fra hvert element, og bruk Esc for å slå av den nyeste analysen. Slå av for å gjenopprette tegnforklaringer i hjørnene og den tidligere Esc-oppførselen.",
"prefs.viewport_background": "Bakgrunn",
"prefs.viewport_background_colour": "Farge",
"prefs.viewport_background_theme": "Temaets gradient",
@@ -2049,6 +2095,10 @@
"properties.title": "Egenskaper: {name}",
"recovery.days_ago": "for {count} dager siden",
"recovery.detail": "{file} · {features} funksjoner · automatisk lagret {when}",
"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?",
"recovery.hours_ago": "for {count} t siden",
"recovery.just_now": "nettopp",
"recovery.min_ago": "for {count} min siden",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Flytt funksjoner til gruppe",
"timeline.rename_group": "Gi nytt navn til funksjonsgruppe",
"timeline.reorder_many": "Omorganiser funksjoner",
"timeline.suppress_features": "Undertrykk funksjoner",
"timeline.suppress_folders_tip": "Undertrykk eller opphev undertrykking av alle funksjoner i mappene og de nestede mappene deres",
"timeline.ungroup": "Opphev funksjonsgruppering",
"timeline.unsuppress_features": "Opphev undertrykking av funksjoner",
"to the face": "til flaten",
"to the next face": "til neste flate",
"toolbar.customize_note": "Fanen {tab}. Kryss av for en kommando for å legge til en knapp på verktøylinjen. Du kan også dra en rad fra panelets nedtrekksmeny til verktøylinjen, dra en knapp ut av verktøylinjen for å fjerne den, eller feste en kommando fra nedtrekksmenyen.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Vri knotten mot klokken, hold den, og slipp.",
"ui.3d_mouse_switch_cap": "Kalibrer som Tilbake til midten",
"ui.3d_mouse_switch_trackball": "Kalibrer som trackball",
"ui.3d_mouse_use_direct": "Bruk direkte driver",
"ui.3d_mouse_use_direct_tip": "Les 3D-musen direkte (hidraw) i stedet for gjennom spacenavd.",
"ui.3d_mouse_view_axis": "Visningsakse",
"ui.3d_mouse_view_off": "Av",
"ui.3d_mouse_view_off_tip": "Profilen til 3D-musdriveren slår av denne aksen, så bevegelsen flytter ikke visningen. Hvis du velger en annen akses bevegelse her, bytter de to plass, og den bevegelsen slutter å flytte visningen.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Avbryt",
"ui.canvases": "Lerreter",
"ui.center": "Senter",
"ui.center_arc_follows_cursor": "Senterpunktbue følger markørens retning",
"ui.center_arc_follows_cursor_tip": "En senterpunktbue går med eller mot klokken, etter hvordan markøren beveger seg rundt senteret. Av: alltid mot klokken fra start.",
"ui.centerline": "Senterlinje",
"ui.chain": "Kjede",
"ui.chamfer": "Fas",
@@ -3930,6 +3986,7 @@
"ui.circle": "Sirkel",
"ui.circular_pattern": "Sirkulært mønster",
"ui.clear": "Tøm",
"ui.clear_print_face": "Fjern utskriftsflate",
"ui.clear_recent": "Tøm listen over nylige design",
"ui.clear_the_selection": "Tøm utvalget",
"ui.click_selects_whole_bodies": "Klikk velger hele kropper",
@@ -3964,6 +4021,14 @@
"ui.constant": "Konstant",
"ui.constraints_conflict": "Bindinger i konflikt",
"ui.construction": "Konstruksjon",
"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",
"ui.construction.ribbon_label": "Hjelpelinjer",
"ui.context_menu": "Kontekstmeny",
"ui.continuity_g0": "G0-knekk",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Mangekant",
"ui.preferences": "Innstillinger",
"ui.press_a_key": "trykk på en tast…",
"ui.print_face_cleared": "{body}: utskriftsflate fjernet",
"ui.print_face_input": "Utskriftsflate",
"ui.print_face_one_body": "En utskriftsflate hører til ett legeme: send det legemet alene for å endre den",
"ui.print_face_pick": "Velg en plan flate på et massivt legeme for å gjøre den til utskriftsflaten",
"ui.print_face_set": "{body}: utskriftsflate satt; Send til slicer legger legemet på den",
"ui.profile": "Profil",
"ui.profile_plane": "Profilplan",
"ui.profiles": "Profiler",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Omdreining",
"ui.ribbon_exit": "Avslutt",
"ui.ribbon_labels": "Vis tekst under båndikonene",
"ui.ribbon_menus_skip_pinned": "Utelat festede kommandoer fra nedtrekksmenyene",
"ui.ribbon_pin_footer": "Festede kommandoer vises på båndet. Hold pekeren over en rad for å feste eller løsne kommandoen.",
"ui.ribbon_workspace": "Arbeidsområde",
"ui.right": "Høyre",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Visningssenter",
"ui.viewport_status_clear": "Slå av {name}",
"ui.viewport_status_clear_hint": "Slå av {name} · Esc",
"ui.viewport_status_edges": "{count} kanter",
"ui.viewport_status_from": "fra {direction}",
"ui.viewport_status_key": "Vis tegnforklaring for {name}",
"ui.viewport_status_minimum_radius": "Minimumsradius",
"ui.viewport_status_pairs": "{count} par",
@@ -4634,7 +4707,9 @@
"update.button": "Oppdater",
"update.button_version": "Oppdater til {version}",
"update.check_automatically": "Se etter oppdateringer automatisk",
"update.check_for_updates": "Se etter oppdateringer",
"update.check_now": "Sjekk nå",
"update.check_unavailable": "Søk etter oppdateringer er ikke tilgjengelig for denne installasjonen.",
"update.checking": "Sjekker…",
"update.copy": "Kopier",
"update.dmg_opened": "Diskbildet er åpent og verifisert: dra SolidCraft til Applications, erstatt den gamle kopien, og start deretter SolidCraft på nytt.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} er installert. Start på nytt for å bruke den.",
"update.installer_started": "Installasjonsprogrammet starter når SolidCraft lukkes.",
"update.later": "Minn meg på senere",
"update.managed": "Oppdatering tilgjengelig: oppdater SolidCraft i {manager}",
"update.no_notes": "Denne utgivelsen har ingen merknader.",
"update.package_hint": "Denne kopien kom fra en {kind}. Last ned den nye pakken, og installer den med:",
"update.prefs_dev": "Dette er et utviklingsbygg ({version}): det ser ikke etter oppdateringer.",
"update.prefs_flatpak": "Flatpak oppdaterer denne kopien ({version}): kjør {command} eller bruk programvaresenteret. SolidCraft sjekker ikke.",
"update.prefs_label": "Oppdateringer",
"update.prefs_managed": "Dette er SolidCraft {version}, administrert av {manager}. Ved en oppdateringssjekk spør programmet GitHub om den nyeste utgivelsen, og ingen andre opplysninger sendes. Oppdater SolidCraft i {manager}.",
"update.prefs_note": "Dette er SolidCraft {version} ({kind}). Ved en oppdateringssjekk spør programmet GitHub om den nyeste utgivelsen. Ingen andre opplysninger sendes.",
"update.prefs_web": "Last inn siden på nytt for å få den nyeste nettappen. Hvis nettverket ikke er tilgjengelig, eller oppdateringen ikke rekker å lastes ned i tide, åpnes den lagrede versjonen i stedet; en nedlasting som pågår, fortsetter til neste gang siden lastes inn på nytt.",
"update.refresh_checking": "Ser etter den nyeste utgivelsen…",
+75
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Ontwerpen die u opent of opslaat, verschijnen hier.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Bepaald door Tot object. Kies Hoek om de hoekomvang te bewerken.",
"Diameter": "Diameter",
"Did this assembly open the wrong way up?": "Is deze samenstelling verkeerd om geopend?",
"Did this part open the wrong way up?": "Is dit onderdeel verkeerd om geopend?",
"Dimension new shapes": "Nieuwe vormen maatvoeren",
"Direction": "Richting",
"Discard": "Verwerpen",
@@ -601,6 +603,7 @@
"Square": "Vierkant",
"Stand it up": "Rechtop zetten",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Rechtop zetten: een kwartslag om X, als één Verplaatsen-kenmerk dat u ongedaan kunt maken.",
"Stand this assembly up?": "Deze samenstelling rechtop zetten?",
"Stand this part up?": "Dit onderdeel rechtop zetten?",
"Start angle": "Beginhoek",
"Start bulge": "Beginbolling",
@@ -772,6 +775,8 @@
"at the face": "op het vlak",
"bom.group_children": "Subonderdelen in de rij van hun onderdeel",
"browser.body_name": "Lichaam{number}",
"browser.calibrate_click_a": "Klik op de afbeelding voor Punt A of typ de coördinaten ervan.",
"browser.calibrate_click_b": "Klik op de afbeelding voor Punt B of typ de coördinaten ervan.",
"browser.calibrate_title": "Kalibreren: {name}",
"browser.capture_many": "{count} componenten zijn verplaatst, maar hun posities zijn niet vastgelegd.",
"browser.capture_note": "Vastleggen behoudt de nieuwe positie; Terugzetten zet deze terug.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Uiterlijk oppakken",
"cmd.appearance_replace": "Uiterlijk vervangen",
"cmd.assembly_bom": "Stuklijst",
"cmd.body.print_face": "Printvlak",
"cmd.body_rename": "Lichaam hernoemen",
"cmd.browser_collapse_all": "Alles samenvouwen",
"cmd.browser_expand_all": "Alles uitvouwen",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Klik op een andere aandrijvende maat om de naam ervan in te voegen",
"dialog.dimension_name": "Maat: {name}",
"dialog.edit_title": "{title} BEWERKEN",
"dialog.no_shown_body": "Geen zichtbaar lichaam om te wijzigen: toon er een of kies Nieuw lichaam",
"dialog.select": "↖ Selecteren",
"dialog.selected_count": "↖ {count} geselecteerd",
"dialog.selection_guidance": "Selecteer een invoer om deze te wijzigen. Klik op geselecteerde objecten om ze te verwijderen; Ctrl/Cmd-klik schakelt ze ook om. Controleer het voorbeeld en druk daarna op OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Verplaatst een onderdeel van een samenstelling alleen langs de vrijheidsgraden die zijn verbindingen toestaan. Selecteer een vlak van het onderdeel en sleep dan een beschikbare pijl of boog.",
"help.parameters.change": "Opent de tabel met benoemde waarden en expressies die de maten van het ontwerp aansturen. Gebruik dit om een maat op één plek te wijzigen, zodat elk kenmerk dat die maat gebruikt vanzelf meegaat.",
"help.pointcloud.insert": "Voeg een PLY-, XYZ- of PTS-scan in als puntenwolk naast het model. Gebruik deze als referentie bij het bouwen van een model op basis van gemeten gegevens.",
"help.sketch.construction_line": "Maak een oneindige constructielijn door twee punten. Gebruik deze om een schets op te zetten zonder profielen te splitsen.",
"help.sketch.construction_line.angle": "Maak een oneindige constructielijn onder een hoek ten opzichte van de X-as van de schets. Kies het punt en typ de hoek, of kies de richting ervan.",
"help.sketch.construction_line.bisector": "Kies twee elkaar snijdende rechte schetslijnen en kies dan een van beide bissectrices. Het resultaat volgt zijn bronlijnen.",
"help.sketch.construction_line.horizontal": "Maak een oneindige horizontale constructielijn door een punt. De horizontale beperking blijft gelden bij latere bewerkingen.",
"help.sketch.construction_line.parallel": "Kies een rechte schetslijn en plaats dan een parallelle, oneindige constructielijn. Klik met rechts om een andere referentie te kiezen.",
"help.sketch.construction_line.perpendicular": "Kies een rechte schetslijn en plaats dan een loodrechte, oneindige constructielijn. Klik met rechts om een andere referentie te kiezen.",
"help.sketch.construction_line.tangent_at": "Maak een raaklijn in een punt van een cirkel of cirkelboog, of in een eindpunt van een open spline of kegelsnede. Kies de kromme en dan het raakpunt.",
"help.sketch.construction_line.tangent_external": "Kies een cirkel of cirkelboog en dan een extern punt en een oplossing voor de raaklijn. De voorbeelden tonen alleen raakpunten op de getekende boog.",
"help.sketch.construction_line.tangent_two": "Kies twee cirkels of cirkelbogen en kies dan een gemeenschappelijke raaklijn in het voorbeeld. De raakpunten moeten op de getekende krommen liggen.",
"help.sketch.construction_line.vertical": "Maak een oneindige verticale constructielijn door een punt. De verticale beperking blijft gelden bij latere bewerkingen.",
"help.sketch.equation_curve": "Tekent een curve uit formules: x(t) en y(t), of y = f(x), over een bereik. De formules kunnen parameters gebruiken en de curve volgt ze: gebruik dit voor een sinusgolf, een spiraal of de evolvente van een tandwieltand.",
"help.solid.align": "Verplaatst de geselecteerde lichamen zodat een punt, vlak of werkvlak erop samenvalt met een punt, vlak of werkvlak elders. Gebruik dit om een onderdeel tegen een ander te plaatsen zonder de afstand te meten.",
"help.solid.chamfer": "Snijdt de geselecteerde randen af met een vlakke afschuining van de ingestelde afstand. Gebruik dit om scherpe randen te breken of het inbrengen van een pen of schroef te vergemakkelijken.",
@@ -1772,6 +1789,8 @@
"parts.family.set_screw": "Stelschroef met inbus, vlakke punt",
"parts.family.socket_head_cap_screw": "Cilinderkopschroef met inbus",
"parts.family.washer": "Vlakke onderlegring",
"parts.holes": "Gaten (optioneel)",
"parts.placement_hint": "kies een of meer gaten om in elk een onderdeel te plaatsen, of OK plaatst het in de oorsprong",
"pattern.alignment_2": "Uitlijning 2",
"pattern.alignment_hint": "Aantallen omvatten het origineel. Gecentreerd verdeelt kopieën gelijkmatig; Omkeren kiest de extra zijde bij even totalen.",
"pattern.direction_1": "Richting 1",
@@ -1812,6 +1831,8 @@
"prefs.anti_aliasing_note": "Maakt kartelige randen glad.",
"prefs.applies_on_restart": "Wordt toegepast na herstart",
"prefs.assistant": "Assistent",
"prefs.auto_operation": "De bewerking automatisch kiezen",
"prefs.auto_operation_note": "De dialoogvensters van kenmerken kiezen Samenvoegen, Nieuw lichaam of Knippen op basis van wat het nieuwe materiaal raakt, totdat je er zelf een kiest. Uit: ze houden de bewerking aan waarmee ze openen.",
"prefs.axes": "Assen",
"prefs.axis": "As",
"prefs.bodies_folders": "Lichamenmappen",
@@ -1822,6 +1843,8 @@
"prefs.bottom_centre": "Midden onder",
"prefs.bottom_right": "Rechtsonder",
"prefs.browser": "Browser",
"prefs.browser_eye_left": "Oog naast de uitklappijl",
"prefs.browser_eye_left_note": "Rijen in de browser tonen hun oog om te tonen of te verbergen naast de uitklappijl, zoals voorheen, in plaats van in een kolom aan het rechteruiteinde van de rijen.",
"prefs.browser_reveal_on_hover": "Tonen bij aanwijzen",
"prefs.browser_show_note": "Getoond houdt de browser op het scherm. Tonen bij aanwijzen houdt hem uit beeld totdat de aanwijzer over zijn plek of over het smalle lipje aan zijn linkerrand komt, en hij blijft open zolang de aanwijzer erin staat of een menu ervan open is.",
"prefs.browser_shown": "Getoond",
@@ -1872,6 +1895,8 @@
"prefs.graphics_card_restart": "Wordt van kracht na het opnieuw starten van SolidCraft. WGPU_ADAPTER_NAME heeft voorrang op deze keuze.",
"prefs.graphics_note": "Wissel snelheid in voor een gladder beeld. Elke keuze toont het effect op een voorbeeld.",
"prefs.graphics_presets_note": "Voorinstellingen stellen de onderstaande keuzes samen in.",
"prefs.hide_constraint_glyphs_zoomed_out": "Beperkingssymbolen verbergen bij uitzoomen",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Beperkingssymbolen verdwijnen uit beeld als er zo ver op de schets is uitgezoomd dat ze het beeld zouden overladen. Uit: ze behouden bij elk zoomniveau een vaste grootte op het scherm.",
"prefs.history": "Geschiedenis",
"prefs.history_origin_bodies": "Oorsprong en lichamen bovenaan tonen",
"prefs.history_origin_bodies_note": "De geschiedenis links of onder de browser begint met de map Lichamen van het actieve component en de Oorsprong, met hun oogpictogrammen en menu's zoals in de browser. De geschiedenis onderaan blijft ongewijzigd.",
@@ -1910,6 +1935,9 @@
"prefs.max_file_size_step": "STEP- en importbestanden",
"prefs.max_file_size_web": "De webapp opent STEP- en importbestanden tot 512 MB en ontwerpen tot 256 MB: een browsertabblad kan geen grotere bestanden bevatten. In de desktopapp kunnen deze limieten worden verhoogd.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Grote pictogrammen",
"prefs.menu_list": "Lijst",
"prefs.menus": "Menu's",
"prefs.minutes": "minuten",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Muis en trackpad",
@@ -1929,10 +1957,14 @@
"prefs.origin_plane_names": "Namen van oorsprongsvlakken",
"prefs.origin_plane_names_note": "Namen die worden getoond in de browser, dialoogvensters en lijsten. {top} is XY, {front} is XZ en {right} is YZ, met Z omhoog. Opdrachten, scripts en opgeslagen ontwerpen blijven XY, XZ en YZ gebruiken.",
"prefs.overall_speed": "Totale snelheid",
"prefs.persistent_ribbon_pressed": "Actieve lintopdrachten markeren",
"prefs.persistent_ribbon_pressed_help": "Lintopdrachten markeren zolang hun effect actief is. Zet dit uit om het vorige neutrale uiterlijk en de vorige opdrachthulp te behouden.",
"prefs.perspective_fov": "Gezichtsveld",
"prefs.placement": "Plaatsing",
"prefs.prefer_free_sketch_points": "Verplaatsbare schetspunten voorrang geven als punten overlappen",
"prefs.preview_choices": "Voorbeeld van je keuzes",
"prefs.print_face_highlight": "Markering van het printvlak",
"prefs.print_face_highlight_tip": "Hoe sterk het printvlak van een lichaam in de weergave wordt gekleurd en omlijnd. 0 % toont geen markering.",
"prefs.profile_active": "Actief profiel",
"prefs.profile_applied": "Profiel toegepast.",
"prefs.profile_changed": "De instellingen zijn gewijzigd sinds het profiel is opgeslagen. Opslaan bewaart ze erin.",
@@ -1967,6 +1999,8 @@
"prefs.search_empty": "Geen overeenkomende instellingen",
"prefs.select": "Selecteren",
"prefs.select_one_item": "Eén item selecteren",
"prefs.select_other_hidden": "Verborgen geometrie selecteren met Overige selecteren",
"prefs.select_other_hidden_help": "Aan: ook verborgen lichamen, schetsen en afgedekte randen bij de aanwijzer meenemen. Uit: de oorspronkelijke lijst met zichtbare geometrie onder de aanwijzer gebruiken.",
"prefs.selecting": "Selecteren",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift voegt alleen toe; Ctrl / Cmd schakelt om",
@@ -1978,11 +2012,14 @@
"prefs.sketch_align_points_note": "Tijdens het tekenen lijnt een punt tegelijk horizontaal uit met het ene schetspunt en verticaal met een ander, met een gestippelde hulplijn, en de klik behoudt beide uitlijningen. Uit: alleen met het vorige punt, één as tegelijk.",
"prefs.sketch_conflict_colour": "Conflicterende geometrie in de conflictkleur",
"prefs.sketch_conflict_colour_note": "Zolang de beperkingen van een schets conflicteren, worden ook de curven en punten die een niet-vervulde beperking vasthoudt in de conflictkleur getekend, niet alleen het symbool of de maat ervan. Uit: ze houden hun gewone kleuren.",
"prefs.sketch_construction_lines": "Uitgebreide tools voor constructielijnen",
"prefs.sketch_exit_view": "Wanneer een schets eindigt",
"prefs.sketch_exit_view.keep": "De huidige weergave behouden",
"prefs.sketch_exit_view.previous": "Terug naar de vorige weergave",
"prefs.sketch_exit_view.unless_rotated": "Terug, tenzij geroteerd",
"prefs.sketch_exit_view_note": "Waar de weergave naartoe gaat na Schets afronden, Annuleren of een dubbele Esc.",
"prefs.sketch_open_look_at": "Naar een schets toe draaien wanneer die wordt geopend",
"prefs.sketch_open_look_at_note": "Uit: een schets maken of bewerken laat de weergave zoals die is. Kijken naar richt de weergave op de schets wanneer je dat wilt.",
"prefs.sketch_recent_snap": "Automatisch snappen op recente cirkelmiddens en middelpunten",
"prefs.sketch_recent_snap_note": "Tijdens het tekenen snapt de cursor op het punt waar de hulplijnen van de twee meest recente cirkelmiddens of middelpunten van rechte randen elkaar kruisen. Uit: die hulplijnen worden alleen getoond en de cursor beweegt niet.",
"prefs.sketch_selection": "Schets en selectie",
@@ -1999,6 +2036,11 @@
"prefs.source": "Bron",
"prefs.speed": "Snelheid",
"prefs.stand_up_note": "Nadat een STEP- of IGES-bestand met Z omhoog is geopend, een kwartslag om X aanbieden.",
"prefs.stand_up_unknown": "Voor bestanden uit onbekende programma's",
"prefs.stand_up_unknown.ask": "Vragen in een dialoogvenster",
"prefs.stand_up_unknown.note": "Een melding tonen",
"prefs.stand_up_unknown.off": "Niet aanbieden",
"prefs.stand_up_unknown_note": "Hoe het aanbod wordt gedaan als de header van het bestand geen programma noemt waarvan bekend is dat het met Y omhoog opslaat. Een melding wordt alleen getoond als het bestand op zijn rug lijkt te liggen; Vragen in een dialoogvenster vraagt dat voor elk bestand van dat soort, zoals voorheen.",
"prefs.status_bar": "Statusbalk",
"prefs.status_bar_note": "Een smalle balk langs de onderrand van het venster toont wat er wordt bewerkt, de staat van de open schets in zijn kleur en de eenheden van het ontwerp; een klik op de eenheden wijzigt ze. De staat van de schets staat dan niet meer in de hoek van de weergave.",
"prefs.status_done": "Gereed",
@@ -2041,6 +2083,8 @@
"prefs.view_confirm_corner": "OK en Annuleren in de hoek van de weergave",
"prefs.view_confirm_corner_note": "Zolang een dialoogvenster of een schets open is, doen een vinkje en een kruisje rechtsboven in de weergave hetzelfde als OK en Annuleren van het dialoogvenster, of ze ronden de schets af of annuleren die. De weergavenavigator schuift daaronder naar beneden.",
"prefs.viewport": "Weergavevenster",
"prefs.viewport_analysis_status": "Analysestatus van de viewport tonen",
"prefs.viewport_analysis_status_help": "Toon actieve analyses boven de viewport, open hun legenda's vanuit elk item en gebruik Esc om de nieuwste analyse uit te schakelen. Schakel uit om de legenda's in de hoeken en het vorige Esc-gedrag te herstellen.",
"prefs.viewport_background": "Achtergrond",
"prefs.viewport_background_colour": "Kleur",
"prefs.viewport_background_theme": "Verloop van het thema",
@@ -2053,6 +2097,10 @@
"properties.title": "Eigenschappen: {name}",
"recovery.days_ago": "{count} dagen geleden",
"recovery.detail": "{file} · {features} kenmerken · automatisch opgeslagen {when}",
"recovery.discard_all": "Alles verwerpen",
"recovery.discard_all_detail": "Dit kan niet ongedaan worden gemaakt.",
"recovery.discard_all_title": "{count} automatische opslagen verwerpen?",
"recovery.discard_all_title_one": "1 automatische opslag verwerpen?",
"recovery.hours_ago": "{count} u geleden",
"recovery.just_now": "zojuist",
"recovery.min_ago": "{count} min geleden",
@@ -3507,8 +3555,10 @@
"timeline.move_to_group": "Kenmerken naar map verplaatsen",
"timeline.rename_group": "Kenmerkmap hernoemen",
"timeline.reorder_many": "Kenmerken herordenen",
"timeline.suppress_features": "Kenmerken onderdrukken",
"timeline.suppress_folders_tip": "Alle kenmerken in de mappen en hun geneste mappen onderdrukken of de onderdrukking ervan opheffen",
"timeline.ungroup": "Kenmerkmappen verwijderen",
"timeline.unsuppress_features": "Onderdrukking van kenmerken opheffen",
"to the face": "tot het vlak",
"to the next face": "tot het volgende vlak",
"toolbar.customize_note": "Tabblad {tab}. Vink een opdracht aan om er een werkbalkknop voor te maken. U kunt ook een rij uit de vervolgkeuzelijst van een paneel naar de werkbalk slepen, een knop van de werkbalk af slepen om hem te verwijderen, of de pin op een rij van de vervolgkeuzelijst gebruiken.",
@@ -3620,6 +3670,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
"ui.3d_mouse_switch_cap": "Kalibreren als Return to Center",
"ui.3d_mouse_switch_trackball": "Kalibreren als trackball",
"ui.3d_mouse_use_direct": "Direct stuurprogramma gebruiken",
"ui.3d_mouse_use_direct_tip": "De 3D-muis direct uitlezen (hidraw) in plaats van via spacenavd.",
"ui.3d_mouse_view_axis": "Weergave-as",
"ui.3d_mouse_view_off": "Uit",
"ui.3d_mouse_view_off_tip": "Het profiel van het 3D-muisstuurprogramma schakelt deze as uit, dus de beweging ervan verplaatst de weergave niet. Kies je hier de beweging van een andere as, dan wisselen de twee van plaats en verplaatst die beweging de weergave niet meer.",
@@ -3926,6 +3978,8 @@
"ui.cancel": "Annuleren",
"ui.canvases": "Canvassen",
"ui.center": "Midden",
"ui.center_arc_follows_cursor": "Boog met middelpunt volgt de richting van de cursor",
"ui.center_arc_follows_cursor_tip": "Een boog met middelpunt loopt met de klok mee of ertegen in, afhankelijk van hoe de cursor om het middelpunt heen beweegt. Uit: altijd tegen de klok in vanaf het begin.",
"ui.centerline": "Hartlijn",
"ui.chain": "Keten",
"ui.chamfer": "Afschuining",
@@ -3934,6 +3988,7 @@
"ui.circle": "Cirkel",
"ui.circular_pattern": "Cirkelvormig patroon",
"ui.clear": "Wissen",
"ui.clear_print_face": "Printvlak wissen",
"ui.clear_recent": "Recent wissen",
"ui.clear_the_selection": "De selectie wissen",
"ui.click_selects_whole_bodies": "Klik selecteert volledige lichamen",
@@ -3968,6 +4023,14 @@
"ui.constant": "Constant",
"ui.constraints_conflict": "Beperkingen conflicteren",
"ui.construction": "Constructie",
"ui.construction.angle_hint": "Constructielijn: klik op een punt en typ dan een hoek of kies een richting",
"ui.construction.bisector_hint": "Bissectrice: kies twee elkaar snijdende rechte lijnen en dan de gewenste oplossing",
"ui.construction.common_hint": "Gemeenschappelijke raaklijn: kies twee cirkels/bogen en dan de gewenste oplossing",
"ui.construction.contact_hint": "Raaklijn: kies een cirkel/boog of open spline/kegelsnede en dan het raakpunt (vrije krommen: eindpunten)",
"ui.construction.external_hint": "Raaklijn: kies een cirkel of boog, een extern punt en dan de gewenste oplossing",
"ui.construction.free_hint": "Constructielijn: klik op twee punten; klik met rechts om opnieuw te beginnen",
"ui.construction.point_hint": "Constructielijn: klik op het punt waar de lijn doorheen moet",
"ui.construction.reference_hint": "Constructielijn: kies een rechte lijn en dan een punt; klik met rechts om de referentie te wijzigen",
"ui.construction.ribbon_label": "Hulplijnen",
"ui.context_menu": "Contextmenu",
"ui.continuity_g0": "G0-knik",
@@ -4372,6 +4435,11 @@
"ui.polygon": "Veelhoek",
"ui.preferences": "Voorkeuren",
"ui.press_a_key": "druk op een toets…",
"ui.print_face_cleared": "{body}: printvlak gewist",
"ui.print_face_input": "Printvlak",
"ui.print_face_one_body": "Een printvlak hoort bij één lichaam: stuur dat lichaam alleen om het te wijzigen",
"ui.print_face_pick": "Selecteer één vlak van een massief lichaam om het het printvlak te maken",
"ui.print_face_set": "{body}: printvlak ingesteld; Naar slicer sturen legt het lichaam erop",
"ui.profile": "Profiel",
"ui.profile_plane": "Profielvlak",
"ui.profiles": "Profielen",
@@ -4435,6 +4503,7 @@
"ui.revolve": "Revolve",
"ui.ribbon_exit": "Afsluiten",
"ui.ribbon_labels": "Tekst onder lintpictogrammen tonen",
"ui.ribbon_menus_skip_pinned": "Vastgepinde opdrachten weglaten uit de vervolgkeuzelijsten",
"ui.ribbon_pin_footer": "Vastgepinde opdrachten verschijnen in het lint. Beweeg over een rij om vast te pinnen of los te maken.",
"ui.ribbon_workspace": "Werkruimte",
"ui.right": "Rechts",
@@ -4598,6 +4667,8 @@
"ui.view_centre": "Weergavemidden",
"ui.viewport_status_clear": "{name} uitschakelen",
"ui.viewport_status_clear_hint": "{name} uitschakelen · Esc",
"ui.viewport_status_edges": "{count} randen",
"ui.viewport_status_from": "vanaf {direction}",
"ui.viewport_status_key": "Legenda tonen voor {name}",
"ui.viewport_status_minimum_radius": "Minimale straal",
"ui.viewport_status_pairs": "{count} paren",
@@ -4638,7 +4709,9 @@
"update.button": "Bijwerken",
"update.button_version": "Bijwerken naar {version}",
"update.check_automatically": "Automatisch controleren op updates",
"update.check_for_updates": "Controleren op updates",
"update.check_now": "Nu controleren",
"update.check_unavailable": "Controleren op updates is niet beschikbaar voor deze installatie.",
"update.checking": "Controleren…",
"update.copy": "Kopiëren",
"update.dmg_opened": "Het schijfkopie is geopend en geverifieerd: sleep SolidCraft naar Programma's, vervang de oude kopie en start SolidCraft daarna opnieuw.",
@@ -4652,11 +4725,13 @@
"update.installed": "SolidCraft {version} is geïnstalleerd. Start opnieuw om het te gebruiken.",
"update.installer_started": "Het installatieprogramma start wanneer SolidCraft wordt gesloten.",
"update.later": "Herinner me later",
"update.managed": "Update beschikbaar: werk SolidCraft bij in {manager}",
"update.no_notes": "Deze release heeft geen notities.",
"update.package_hint": "Deze kopie kwam van een {kind}. Download het nieuwe pakket en installeer het daarna met:",
"update.prefs_dev": "Dit is een ontwikkelbuild ({version}): er wordt niet op updates gecontroleerd.",
"update.prefs_flatpak": "Flatpak werkt deze kopie ({version}) bij: voer {command} uit of gebruik uw softwarecentrum. SolidCraft controleert niet.",
"update.prefs_label": "Updates",
"update.prefs_managed": "Dit is SolidCraft {version}, beheerd door {manager}. Een controle vraagt GitHub om de nieuwste release en verzendt verder niets; werk SolidCraft bij in {manager}.",
"update.prefs_note": "Dit is SolidCraft {version} ({kind}). Een controle vraagt GitHub om de nieuwste release en verzendt verder niets.",
"update.prefs_web": "Laad de pagina opnieuw om de nieuwste web-app te krijgen. Als het netwerk niet beschikbaar is of de update niet op tijd kan worden gedownload, wordt de opgeslagen versie geopend; een download die bezig is, gaat door voor de volgende keer opnieuw laden.",
"update.refresh_checking": "Zoeken naar de nieuwste release…",
@@ -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"
}
}
+1169 -1
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -606,6 +606,7 @@
"cmd.appearance_library": "Biblioteca de aparências",
"cmd.appearance_list": "Listar aparências",
"cmd.assembly_bom": "Lista de materiais",
"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 +1293,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 +1574,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 +1845,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",
@@ -2198,6 +2204,11 @@
"ui.polygon": "Polígono",
"ui.preferences": "Preferências",
"ui.press_a_key": "pressione uma tecla…",
"ui.print_face_cleared": "{body}: face de impressão removida",
"ui.print_face_input": "Face de impressão",
"ui.print_face_one_body": "Uma face de impressão pertence a um único corpo: envie esse corpo sozinho para alterá-la",
"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 +2399,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.",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Proiectele deschise sau salvate apar aici.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determinat de Până la obiect. Alegeți Unghi pentru a edita întinderea unghiulară.",
"Diameter": "Diametru",
"Did this assembly open the wrong way up?": "Acest ansamblu s-a deschis cu orientarea greșită?",
"Did this part open the wrong way up?": "Această piesă s-a deschis cu orientarea greșită?",
"Dimension new shapes": "Cotează formele noi",
"Direction": "Direcție",
"Discard": "Renunță",
@@ -601,6 +603,7 @@
"Square": "Pătrat",
"Stand it up": "Ridică-l în picioare",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Ridică-l în picioare: un sfert de rotație în jurul lui X, ca o singură operație de mutare ce poate fi anulată.",
"Stand this assembly up?": "Ridicați acest ansamblu în picioare?",
"Stand this part up?": "Ridicați această piesă în picioare?",
"Start angle": "Unghi de început",
"Start bulge": "Bombare inițială",
@@ -772,6 +775,8 @@
"at the face": "la față",
"bom.group_children": "Subpiesele în rândul piesei lor",
"browser.body_name": "Corp{number}",
"browser.calibrate_click_a": "Faceți clic pe imagine pentru Punctul A sau tastați coordonatele acestuia.",
"browser.calibrate_click_b": "Faceți clic pe imagine pentru Punctul B sau tastați coordonatele acestuia.",
"browser.calibrate_title": "Calibrare: {name}",
"browser.capture_many": "{count} componente au fost mutate, dar pozițiile lor nu sunt capturate.",
"browser.capture_note": "Captura păstrează noua poziție; Revenire o readuce.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Preia aspectul",
"cmd.appearance_replace": "Înlocuiește aspectul",
"cmd.assembly_bom": "Listă de materiale",
"cmd.body.print_face": "Față de imprimare",
"cmd.body_rename": "Redenumește corpul",
"cmd.browser_collapse_all": "Restrânge tot",
"cmd.browser_expand_all": "Extinde tot",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Faceți clic pe altă cotă de comandă pentru a-i insera numele",
"dialog.dimension_name": "Cotă: {name}",
"dialog.edit_title": "EDITARE {title}",
"dialog.no_shown_body": "Niciun corp afișat de modificat: afișați unul sau alegeți Corp nou",
"dialog.select": "↖ Selectează",
"dialog.selected_count": "↖ {count} selectate",
"dialog.selection_guidance": "Selectați o intrare pentru a o modifica. Faceți clic pe obiectele selectate pentru a le elimina; Ctrl/Cmd-clic le comută de asemenea. Verificați previzualizarea, apoi apăsați OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Mută o piesă din ansamblu doar pe direcțiile de mișcare permise de îmbinările sale. Selectați o față a piesei, apoi trageți o săgeată sau un arc disponibil.",
"help.parameters.change": "Deschide tabelul cu valori denumite și expresii care controlează cotele proiectului. Folosiți-l pentru a schimba o dimensiune într-un singur loc și a lăsa toate operațiile care o folosesc să o urmeze.",
"help.pointcloud.insert": "Inserați o scanare PLY, XYZ sau PTS ca nor de puncte alături de model. Folosiți-o ca referință la construirea unui model pe baza datelor măsurate.",
"help.sketch.construction_line": "Creați o dreaptă de construcție infinită prin două puncte. Folosiți-o pentru a structura o schiță fără a diviza profilurile.",
"help.sketch.construction_line.angle": "Creați o dreaptă de construcție infinită sub un unghi față de axa X a schiței. Alegeți punctul și tastați unghiul sau alegeți direcția acestuia.",
"help.sketch.construction_line.bisector": "Alegeți două linii drepte ale schiței care se intersectează, apoi una dintre cele două bisectoare. Rezultatul urmărește liniile sursă.",
"help.sketch.construction_line.horizontal": "Creați o dreaptă de construcție orizontală infinită printr-un punct. Constrângerea orizontală rămâne valabilă la editările ulterioare.",
"help.sketch.construction_line.parallel": "Alegeți o linie dreaptă a schiței, apoi plasați o dreaptă de construcție paralelă infinită. Clic dreapta pentru a alege o altă referință.",
"help.sketch.construction_line.perpendicular": "Alegeți o linie dreaptă a schiței, apoi plasați o dreaptă de construcție perpendiculară infinită. Clic dreapta pentru a alege o altă referință.",
"help.sketch.construction_line.tangent_at": "Creați o tangentă într-un punct al unui cerc sau arc de cerc ori în capătul unui spline sau al unei curbe conice deschise. Alegeți curba, apoi punctul de tangență.",
"help.sketch.construction_line.tangent_external": "Alegeți un cerc sau un arc de cerc, apoi un punct exterior și o soluție de tangentă. Previzualizările arată doar punctele de tangență de pe arcul desenat.",
"help.sketch.construction_line.tangent_two": "Alegeți două cercuri sau arce de cerc, apoi o tangentă comună în previzualizare. Punctele de tangență trebuie să se afle pe curbele desenate.",
"help.sketch.construction_line.vertical": "Creați o dreaptă de construcție verticală infinită printr-un punct. Constrângerea verticală rămâne valabilă la editările ulterioare.",
"help.sketch.equation_curve": "Desenează o curbă din formule: x(t) și y(t) sau y = f(x), pe un interval. Formulele pot folosi parametri, iar curba îi urmează: folosiți-o pentru o sinusoidă, o spirală sau evolventa unui dinte de roată dințată.",
"help.solid.align": "Mută corpurile selectate astfel încât un punct, o față sau un plan de pe ele să se întâlnească cu un punct, o față sau un plan din altă parte. Folosiți-l pentru a așeza o piesă lângă alta fără a măsura decalajul.",
"help.solid.chamfer": "Teșește muchiile selectate cu o înclinare plană la distanța stabilită. Folosiți-l pentru a îndepărta muchiile ascuțite sau pentru a ușura introducerea unui pin sau a unui șurub.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Reconstruiește toate operațiile din cronologie, de la început. Se folosește pentru a reîmprospăta modelul și a vedea ce operații eșuează.",
"help.timeline.delete": "Șterge operațiile selectate din cronologie; ștergerea unei schițe șterge și operațiile construite din ea. Se folosește pentru a elimina un pas care nu mai este dorit.",
"help.timeline.play": "Redă operațiile rămase pe rând, cu o pauză de o jumătate de secundă după fiecare reconstrucție. Rulați din nou această comandă sau apăsați Escape pentru a opri; Mergi la sfârșit sare imediat.",
"history.sketch_under_constrained": "Constrângeri insuficiente ({count} grade de libertate)",
"history.sketch_under_constrained_one": "Constrângeri insuficiente ({count} grad de libertate)",
"hole_presets.confirm_delete": "Confirmă ștergerea",
"hole_presets.delete": "Șterge",
"hole_presets.delete_prompt": "Ștergeți această presetare personală? Găurile existente rămân neschimbate.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Netezește muchiile zimțate.",
"prefs.applies_on_restart": "Se aplică la repornire",
"prefs.assistant": "Asistent",
"prefs.auto_operation": "Alege automat operația",
"prefs.auto_operation_note": "Ferestrele operațiilor aleg Unire, Corp nou sau Decupează în funcție de ce atinge materialul nou, până când alegeți una. Dezactivat: păstrează operația cu care se deschid.",
"prefs.axes": "Axe",
"prefs.axis": "Axă",
"prefs.bodies_folders": "Foldere pentru corpuri",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Centru jos",
"prefs.bottom_right": "Dreapta jos",
"prefs.browser": "Navigator",
"prefs.browser_eye_left": "Ochiul lângă săgeata de restrângere",
"prefs.browser_eye_left_note": "Rândurile din navigator își afișează ochiul de afișare/ascundere lângă săgeata de restrângere, ca înainte, în loc de o coloană la capătul din dreapta al rândurilor.",
"prefs.browser_reveal_on_hover": "Afișează la survol",
"prefs.browser_show_note": "Afișat ține navigatorul pe ecran. Afișează la survol îl ține ascuns până când indicatorul ajunge pe locul lui sau pe fila subțire de la marginea lui stângă, iar navigatorul rămâne deschis cât timp indicatorul este în interiorul lui sau cât timp un meniu al lui este deschis.",
"prefs.browser_shown": "Afișat",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Intră în vigoare după repornirea SolidCraft. WGPU_ADAPTER_NAME suprascrie această alegere.",
"prefs.graphics_note": "Alegeți între viteză și netezime. Fiecare opțiune își arată efectul pe un eșantion.",
"prefs.graphics_presets_note": "Presetările stabilesc împreună opțiunile de mai jos.",
"prefs.hide_constraint_glyphs_zoomed_out": "Ascunde simbolurile de constrângere la micșorare",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Simbolurile de constrângere dispar din vizualizare când schița este micșorată atât de mult încât ar aglomera-o. Dezactivat: rămân la o dimensiune fixă pe ecran la orice nivel de zoom.",
"prefs.history": "Istoric",
"prefs.history_origin_bodies": "Afișează originea și corpurile în partea de sus",
"prefs.history_origin_bodies_note": "Istoricul din stânga sau de sub navigator începe cu dosarul Corpuri al componentei active și cu Originea, cu ochii și meniurile lor ca în navigator. Istoricul din partea de jos rămâne neschimbat.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "Fișiere STEP și de import",
"prefs.max_file_size_web": "Aplicația web deschide fișiere STEP și de import de până la 512 MB și proiecte de până la 256 MB: o filă de browser nu poate conține fișiere mai mari. Aplicația desktop permite creșterea acestor limite.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Pictograme mari",
"prefs.menu_list": "Listă",
"prefs.menus": "Meniuri",
"prefs.minutes": "minute",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Mouse și trackpad",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Numele planelor de origine",
"prefs.origin_plane_names_note": "Nume afișate în navigator, în dialoguri și în liste. {top} este XY, {front} este XZ și {right} este YZ, cu Z în sus. Comenzile, scripturile și proiectele salvate păstrează XY, XZ și YZ.",
"prefs.overall_speed": "Viteză generală",
"prefs.persistent_ribbon_pressed": "Evidențiază comenzile active din panglică",
"prefs.persistent_ribbon_pressed_help": "Evidențiază comenzile din panglică cât timp efectele lor sunt active. Dezactivați pentru a păstra aspectul neutru anterior și ajutorul pentru comenzi.",
"prefs.perspective_fov": "Câmp vizual",
"prefs.placement": "Plasare",
"prefs.prefer_free_sketch_points": "Preferă punctele de schiță mobile când punctele se suprapun",
"prefs.preview_choices": "Previzualizarea opțiunilor alese",
"prefs.print_face_highlight": "Evidențierea feței de imprimare",
"prefs.print_face_highlight_tip": "Cât de puternic este colorată și conturată fața de imprimare a unui corp în vizualizare. 0 % nu afișează evidențiere.",
"prefs.profile_active": "Profil activ",
"prefs.profile_applied": "Profil aplicat.",
"prefs.profile_changed": "Setările s-au modificat de la salvarea profilului. Salvează le păstrează în el.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Nicio setare corespunzătoare",
"prefs.select": "Selectează",
"prefs.select_one_item": "Selectați un element",
"prefs.select_other_hidden": "Selectează geometria ascunsă cu Selectează altul",
"prefs.select_other_hidden_help": "Activat: include corpurile ascunse, schițele ascunse și muchiile acoperite din apropierea indicatorului. Dezactivat: folosește lista inițială de geometrie vizibilă de sub indicator.",
"prefs.selecting": "Selectare",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift doar adaugă; Ctrl / Cmd comută",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "În timpul desenării, un punct se aliniază simultan pe orizontală cu un punct de schiță și pe verticală cu altul, cu un ghid punctat, iar clicul păstrează ambele alinieri. Dezactivat: doar cu punctul anterior, pe o singură axă odată.",
"prefs.sketch_conflict_colour": "Geometrie în conflict, în culoarea de conflict",
"prefs.sketch_conflict_colour_note": "Cât timp constrângerile unei schițe sunt în conflict, curbele și punctele pe care le leagă o constrângere eșuată sunt desenate și ele în culoarea de conflict, nu doar simbolul sau cota ei. Dezactivat: își păstrează culorile obișnuite.",
"prefs.sketch_construction_lines": "Scule extinse pentru dreptele de construcție",
"prefs.sketch_exit_view": "Când se încheie o schiță",
"prefs.sketch_exit_view.keep": "Păstrează vizualizarea curentă",
"prefs.sketch_exit_view.previous": "Revino la vizualizarea anterioară",
"prefs.sketch_exit_view.unless_rotated": "Revino, dacă nu a fost rotită",
"prefs.sketch_exit_view_note": "Unde ajunge vizualizarea după Finalizează schița, Anulează sau un dublu Esc.",
"prefs.sketch_open_look_at": "Rotește vizualizarea spre schiță la deschiderea ei",
"prefs.sketch_open_look_at_note": "Dezactivat: crearea sau editarea unei schițe lasă vizualizarea neschimbată. Comanda Privește spre orientează vizualizarea spre schiță atunci când doriți.",
"prefs.sketch_recent_snap": "Agățare automată la centrele și punctele de mijloc recente",
"prefs.sketch_recent_snap_note": "În timpul desenării, cursorul se agață acolo unde se intersectează ghidurile ultimelor două centre de cerc sau puncte de mijloc ale liniilor drepte. Dezactivat: aceste ghiduri rămân doar vizuale, iar cursorul nu se mișcă.",
"prefs.sketch_selection": "Schiță și selecție",
"prefs.sketch_selection_heading": "Schiță și selecție",
"prefs.sketch_selection_note": "Cum se agață schițele și cum selectează clicurile.",
"prefs.sketch_state_icons": "Pictogramele schițelor în culoarea stării lor",
"prefs.sketch_state_icons_note": "În Istoric și în navigator, pictograma unei schițe preia culoarea stării ei (constrângeri insuficiente, complet constrânsă sau în conflict), iar sugestia ei indică starea. Dezactivat: toate schițele au aceeași pictogramă.",
"prefs.sketch_tab_always": "Păstrează fila Schiță afișată",
"prefs.sketch_tab_always_note": "Fila Schiță rămâne în panglică, imediat după Model, cât timp nu este deschisă nicio schiță. Sculele ei de desenare pornesc atunci o schiță pe planul sau fața selectată ori pe planul pe care îl alegeți în continuare, cu scula deja activă în ea.",
"prefs.sketch_tracking_guides": "Ghiduri de aliniere pentru centru și punct de mijloc",
@@ -1995,6 +2034,11 @@
"prefs.source": "Sursă",
"prefs.speed": "Viteză",
"prefs.stand_up_note": "După ce un fișier STEP sau IGES se deschide cu Z în sus, oferă o rotire cu un sfert de tură în jurul lui X.",
"prefs.stand_up_unknown": "Pentru fișiere din programe necunoscute",
"prefs.stand_up_unknown.ask": "Întreabă într-un dialog",
"prefs.stand_up_unknown.note": "Afișează o notă",
"prefs.stand_up_unknown.off": "Nu oferi",
"prefs.stand_up_unknown_note": "Cum se face oferta atunci când antetul fișierului nu indică niciun program cunoscut că salvează cu Y în sus. O notă apare doar când fișierul pare să zacă pe spate; Întreabă într-un dialog întreabă pentru fiecare astfel de fișier, ca înainte.",
"prefs.status_bar": "Bară de stare",
"prefs.status_bar_note": "O bară subțire de-a lungul marginii de jos a ferestrei arată ce se editează, starea schiței deschise prin culoarea ei și unitățile proiectului; un clic pe unități le schimbă. Starea schiței nu mai apare atunci în colțul vizualizării.",
"prefs.status_done": "Gata",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK și Anulează în colțul vizualizării",
"prefs.view_confirm_corner_note": "Cât timp este deschis un dialog sau o schiță, o bifă și o cruce în dreapta sus a vizualizării fac ce fac OK și Anulează din dialog, sau finalizează și anulează schița. Controlul de navigare al vizualizării se mută mai jos, sub ele.",
"prefs.viewport": "Fereastră de vizualizare",
"prefs.viewport_analysis_status": "Afișează starea analizelor deasupra ferestrei de vizualizare",
"prefs.viewport_analysis_status_help": "Afișează analizele active deasupra ferestrei de vizualizare, deschide legenda fiecăreia din elementul ei și folosește Esc pentru a dezactiva cea mai recentă analiză. Dezactivați pentru a reveni la legendele din colțuri și la comportamentul anterior al tastei Esc.",
"prefs.viewport_background": "Fundal",
"prefs.viewport_background_colour": "Culoare",
"prefs.viewport_background_theme": "Gradientul temei",
@@ -2049,6 +2095,10 @@
"properties.title": "Proprietăți: {name}",
"recovery.days_ago": "acum {count} zile",
"recovery.detail": "{file} · {features} operații · salvat automat {when}",
"recovery.discard_all": "Renunță la toate",
"recovery.discard_all_detail": "Această acțiune nu poate fi anulată.",
"recovery.discard_all_title": "Renunțați la salvările automate ({count})?",
"recovery.discard_all_title_one": "Renunțați la 1 salvare automată?",
"recovery.hours_ago": "acum {count} h",
"recovery.just_now": "chiar acum",
"recovery.min_ago": "acum {count} min",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Mută operațiile în dosar",
"timeline.rename_group": "Redenumește dosarul de operații",
"timeline.reorder_many": "Reordonează operațiile",
"timeline.suppress_features": "Suprimă operațiile",
"timeline.suppress_folders_tip": "Suprimă sau anulează suprimarea tuturor operațiilor din dosare și din dosarele lor imbricate",
"timeline.ungroup": "Elimină dosarele de operații",
"timeline.unsuppress_features": "Anulează suprimarea operațiilor",
"to the face": "până la față",
"to the next face": "până la fața următoare",
"toolbar.customize_note": "Fila {tab}. Bifați o comandă pentru a-i da un buton în bara de instrumente. Puteți și să trageți un rând din lista derulantă a unui panou pe bara de instrumente, să trageți un buton în afara barei pentru a-l elimina sau să folosiți pionezul de pe un rând din lista derulantă.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Răsuciți capacul în sens invers acelor de ceasornic, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_switch_cap": "Calibrează ca dispozitiv cu revenire la centru",
"ui.3d_mouse_switch_trackball": "Calibrează ca trackball",
"ui.3d_mouse_use_direct": "Folosește driverul direct",
"ui.3d_mouse_use_direct_tip": "Citește mouse-ul 3D direct (hidraw) în loc de prin spacenavd.",
"ui.3d_mouse_view_axis": "Axa vizualizării",
"ui.3d_mouse_view_off": "Dezactivat",
"ui.3d_mouse_view_off_tip": "Profilul driverului mouse-ului 3D dezactivează această axă, deci mișcarea ei nu mișcă vizualizarea. Alegerea aici a mișcării altei axe le interschimbă pe cele două, iar acea mișcare nu mai mișcă vizualizarea.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Anulează",
"ui.canvases": "Pânze",
"ui.center": "Centru",
"ui.center_arc_follows_cursor": "Arcul după centru urmează direcția cursorului",
"ui.center_arc_follows_cursor_tip": "Un arc după centru merge în sens orar sau antiorar, după cum cursorul se rotește în jurul centrului său. Dezactivat: întotdeauna în sens antiorar de la început.",
"ui.centerline": "Linie de axă",
"ui.chain": "Lanț",
"ui.chamfer": "Teșire",
@@ -3930,6 +3986,7 @@
"ui.circle": "Cerc",
"ui.circular_pattern": "Matrice circulară",
"ui.clear": "Șterge",
"ui.clear_print_face": "Șterge fața de imprimare",
"ui.clear_recent": "Golește recentele",
"ui.clear_the_selection": "Șterge selecția",
"ui.click_selects_whole_bodies": "Clicul selectează corpuri întregi",
@@ -3964,6 +4021,14 @@
"ui.constant": "Constant",
"ui.constraints_conflict": "Conflict de constrângeri",
"ui.construction": "Construcție",
"ui.construction.angle_hint": "Dreaptă de construcție: clic pe un punct, apoi tastați un unghi sau alegeți o direcție",
"ui.construction.bisector_hint": "Bisectoare: alegeți două linii drepte care se intersectează, apoi soluția dorită",
"ui.construction.common_hint": "Tangentă comună: alegeți două cercuri/arce, apoi soluția dorită",
"ui.construction.contact_hint": "Tangentă: alegeți un cerc/arc sau un spline/o curbă conică deschisă, apoi punctul de tangență (curbe de formă liberă: capetele)",
"ui.construction.external_hint": "Tangentă: alegeți un cerc sau un arc, un punct exterior, apoi soluția dorită",
"ui.construction.free_hint": "Dreaptă de construcție: clic pe două puncte; clic dreapta pentru a relua",
"ui.construction.point_hint": "Dreaptă de construcție: clic pe punctul prin care trece",
"ui.construction.reference_hint": "Dreaptă de construcție: alegeți o linie dreaptă, apoi un punct; clic dreapta pentru a schimba referința",
"ui.construction.ribbon_label": "Linii aux.",
"ui.context_menu": "Meniu contextual",
"ui.continuity_g0": "Muchie G0",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Poligon",
"ui.preferences": "Preferințe",
"ui.press_a_key": "apăsați o tastă…",
"ui.print_face_cleared": "{body}: fața de imprimare a fost ștearsă",
"ui.print_face_input": "Față de imprimare",
"ui.print_face_one_body": "O față de imprimare aparține unui singur corp: trimiteți acel corp singur pentru a o schimba",
"ui.print_face_pick": "Selectați o față plană a unui corp solid pentru a o face fața de imprimare",
"ui.print_face_set": "{body}: fața de imprimare a fost setată; Trimite la slicer așază corpul pe ea",
"ui.profile": "Profil",
"ui.profile_plane": "Planul profilului",
"ui.profiles": "Profiluri",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Revoluție",
"ui.ribbon_exit": "Ieșire",
"ui.ribbon_labels": "Afișează text sub pictogramele panglicii",
"ui.ribbon_menus_skip_pinned": "Lasă comenzile fixate în afara listelor derulante",
"ui.ribbon_pin_footer": "Comenzile fixate apar pe bandă. Treceți cursorul peste un rând pentru a-l fixa sau defixa.",
"ui.ribbon_workspace": "Spațiu de lucru",
"ui.right": "Dreapta",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Centrul vizualizării",
"ui.viewport_status_clear": "Dezactivează {name}",
"ui.viewport_status_clear_hint": "Dezactivează {name} · Esc",
"ui.viewport_status_edges": "{count} muchii",
"ui.viewport_status_from": "din {direction}",
"ui.viewport_status_key": "Afișează legenda pentru {name}",
"ui.viewport_status_minimum_radius": "Rază minimă",
"ui.viewport_status_pairs": "{count} perechi",
@@ -4634,7 +4707,9 @@
"update.button": "Actualizează",
"update.button_version": "Actualizează la {version}",
"update.check_automatically": "Verifică automat actualizările",
"update.check_for_updates": "Caută actualizări",
"update.check_now": "Verifică acum",
"update.check_unavailable": "Căutarea actualizărilor nu este disponibilă pentru această instalare.",
"update.checking": "Se verifică…",
"update.copy": "Copiază",
"update.dmg_opened": "Imaginea de disc este deschisă și verificată: trageți SolidCraft în Applications, înlocuind copia veche, apoi reporniți SolidCraft.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} este instalat. Reporniți pentru a-l folosi.",
"update.installer_started": "Programul de instalare pornește când SolidCraft se închide.",
"update.later": "Amintește-mi mai târziu",
"update.managed": "Actualizare disponibilă: actualizați SolidCraft în {manager}",
"update.no_notes": "Această versiune nu are note.",
"update.package_hint": "Această copie provine dintr-un {kind}. Descărcați noul pachet, apoi instalați-l cu:",
"update.prefs_dev": "Aceasta este o versiune de dezvoltare ({version}): nu verifică actualizările.",
"update.prefs_flatpak": "Flatpak actualizează această copie ({version}): rulați {command} sau folosiți centrul de software. SolidCraft nu verifică.",
"update.prefs_label": "Actualizări",
"update.prefs_managed": "Aceasta este SolidCraft {version}, gestionat de {manager}. O verificare cere GitHub ultima versiune și nu trimite nimic altceva; actualizați SolidCraft în {manager}.",
"update.prefs_note": "Aceasta este SolidCraft {version} ({kind}). O verificare cere GitHub ultima versiune și nu trimite nimic altceva.",
"update.prefs_web": "Reîncărcați pagina pentru a obține cea mai recentă aplicație web. Dacă rețeaua nu este disponibilă sau actualizarea nu poate termina descărcarea la timp, se deschide versiunea salvată; o descărcare în curs continuă pentru următoarea reîncărcare.",
"update.refresh_checking": "Se caută cea mai nouă versiune…",
+27
View File
@@ -813,6 +813,7 @@
"cmd.appearance_pick_up": "Взять внешний вид",
"cmd.appearance_replace": "Заменить внешний вид",
"cmd.assembly_bom": "Спецификация",
"cmd.body.print_face": "Грань печати",
"cmd.body_rename": "Переименовать тело",
"cmd.browser_collapse_all": "Свернуть всё",
"cmd.browser_expand_all": "Развернуть всё",
@@ -1807,6 +1808,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": "Показан",
@@ -1857,6 +1860,8 @@
"prefs.graphics_card_restart": "Вступит в силу после перезапуска 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": "История слева или под браузером начинается с папки «Тела» активного компонента и начала координат, с их глазками и меню, как в браузере. История внизу не меняется.",
@@ -1894,6 +1899,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": "Мышь и тачпад",
@@ -1917,6 +1925,8 @@
"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": "Настройки изменились после сохранения профиля. «Сохранить» запоминает их в профиле.",
@@ -1951,6 +1961,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 переключает",
@@ -2021,6 +2033,8 @@
"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": "Градиент темы",
@@ -3558,6 +3572,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-мыши отключает эту ось, поэтому её движение не двигает вид. Если выбрать здесь движение другой оси, они поменяются местами, и это движение перестанет двигать вид.",
@@ -3872,6 +3888,7 @@
"ui.circle": "Окружность",
"ui.circular_pattern": "Круговой массив",
"ui.clear": "Очистить",
"ui.clear_print_face": "Сбросить грань печати",
"ui.clear_recent": "Очистить недавние",
"ui.clear_the_selection": "Снять выделение",
"ui.click_selects_whole_bodies": "Щелчок выделяет тела целиком",
@@ -4309,6 +4326,11 @@
"ui.polygon": "Многоугольник",
"ui.preferences": "Настройки",
"ui.press_a_key": "нажмите клавишу…",
"ui.print_face_cleared": "{body}: грань печати сброшена",
"ui.print_face_input": "Грань печати",
"ui.print_face_one_body": "Грань печати относится к одному телу: отправьте это тело отдельно, чтобы изменить её",
"ui.print_face_pick": "Выберите плоскую грань твёрдого тела, чтобы сделать её гранью печати",
"ui.print_face_set": "{body}: грань печати задана; «Отправить в слайсер» кладёт тело на неё",
"ui.profile": "Профиль",
"ui.profile_plane": "Плоскость профиля",
"ui.profiles": "Профили",
@@ -4370,6 +4392,7 @@
"ui.revolve": "Вращение",
"ui.ribbon_exit": "Выход",
"ui.ribbon_labels": "Показывать текст под значками ленты",
"ui.ribbon_menus_skip_pinned": "Не включать закреплённые команды в выпадающие меню",
"ui.ribbon_pin_footer": "Закреплённые команды отображаются на ленте. Наведите курсор на строку, чтобы закрепить или открепить её.",
"ui.ribbon_workspace": "Рабочая область",
"ui.right": "Справа",
@@ -4533,6 +4556,8 @@
"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}",
@@ -4573,7 +4598,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.",
+27
View File
@@ -819,6 +819,7 @@
"cmd.appearance_pick_up": "Преузми изглед",
"cmd.appearance_replace": "Замени изглед",
"cmd.assembly_bom": "Саставница",
"cmd.body.print_face": "Површина за штампу",
"cmd.body_rename": "Преименуј тело",
"cmd.browser_collapse_all": "Скупи све",
"cmd.browser_expand_all": "Прошири све",
@@ -1840,6 +1841,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": "Приказано",
@@ -1890,6 +1893,8 @@
"prefs.graphics_card_restart": "Ступа на снагу након поновног покретања 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": "Временска трака лево или испод стабла модела почиње фасциклом Тела активне компоненте и Исходиштем, са њиховим оком и менијима као у стаблу модела. Временска трака на дну остаје непромењена.",
@@ -1928,6 +1933,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": "Миш и додирна табла",
@@ -1953,6 +1961,8 @@
"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": "Подешавања су промењена откако је профил сачуван. Притисните Сачувај да их задржите у њему.",
@@ -1987,6 +1997,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 укључује и искључује",
@@ -2069,6 +2081,8 @@
"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": "Градијент теме",
@@ -3654,6 +3668,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 миша искључује ову осу, па њено кретање не помера приказ. Избор кретања друге осе овде их замењује, а то кретање престаје да помера приказ.",
@@ -3970,6 +3986,7 @@
"ui.circle": "Круг",
"ui.circular_pattern": "Кружни низ",
"ui.clear": "Очисти",
"ui.clear_print_face": "Уклони површину за штампу",
"ui.clear_recent": "Очисти недавно",
"ui.clear_the_selection": "Поништи избор",
"ui.click_selects_whole_bodies": "Клик бира цела тела",
@@ -4416,6 +4433,11 @@
"ui.polygon": "Полигон",
"ui.preferences": "Подешавања",
"ui.press_a_key": "притисните тастер…",
"ui.print_face_cleared": "{body}: површина за штампу уклоњена",
"ui.print_face_input": "Површина за штампу",
"ui.print_face_one_body": "Површина за штампу припада једном телу: пошаљите то тело само да бисте је променили",
"ui.print_face_pick": "Изаберите равну површину пуног тела да постане површина за штампу",
"ui.print_face_set": "{body}: површина за штампу постављена; Пошаљи у слајсер полаже тело на њу",
"ui.profile": "Профил",
"ui.profile_plane": "Раван профила",
"ui.profiles": "Профили",
@@ -4479,6 +4501,7 @@
"ui.revolve": "Ротирај",
"ui.ribbon_exit": "Изађи",
"ui.ribbon_labels": "Прикажи текст испод икона на траци",
"ui.ribbon_menus_skip_pinned": "Изостави прикачене команде из падајућих менија",
"ui.ribbon_pin_footer": "Прикачене команде приказују се на траци. Задржите показивач изнад реда да га прикачите или откачите.",
"ui.ribbon_workspace": "Радни простор",
"ui.right": "Десно",
@@ -4642,6 +4665,8 @@
"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}",
@@ -4682,7 +4707,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.",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Konstruktioner du öppnar eller sparar visas här.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Bestäms av Till objekt. Välj Vinkel för att redigera vinkelutsträckningen.",
"Diameter": "Diameter",
"Did this assembly open the wrong way up?": "Öppnades den här sammanställningen med fel sida upp?",
"Did this part open the wrong way up?": "Öppnades den här delen med fel sida upp?",
"Dimension new shapes": "Måttsätt nya former",
"Direction": "Riktning",
"Discard": "Förkasta",
@@ -601,6 +603,7 @@
"Square": "Kvadrat",
"Stand it up": "Res upp den",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Res upp den: ett kvartsvarv runt X, som en enda Flytta-funktion som du kan ångra.",
"Stand this assembly up?": "Resa upp denna sammanställning?",
"Stand this part up?": "Resa upp denna del?",
"Start angle": "Startvinkel",
"Start bulge": "Startbukt",
@@ -772,6 +775,8 @@
"at the face": "vid ytan",
"bom.group_children": "Underdelar i sin dels rad",
"browser.body_name": "Kropp{number}",
"browser.calibrate_click_a": "Klicka på bilden för punkt A eller skriv dess koordinater.",
"browser.calibrate_click_b": "Klicka på bilden för punkt B eller skriv dess koordinater.",
"browser.calibrate_title": "Kalibrera: {name}",
"browser.capture_many": "{count} komponenter flyttades men deras lägen har inte fångats.",
"browser.capture_note": "Fånga behåller det nya läget; Återställ sätter tillbaka det.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Plocka upp utseende",
"cmd.appearance_replace": "Ersätt utseende",
"cmd.assembly_bom": "Stycklista",
"cmd.body.print_face": "Utskriftsyta",
"cmd.body_rename": "Byt namn på kropp",
"cmd.browser_collapse_all": "Fäll ihop alla",
"cmd.browser_expand_all": "Expandera alla",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "Klicka på ett annat styrande mått för att infoga dess namn",
"dialog.dimension_name": "Mått: {name}",
"dialog.edit_title": "REDIGERA {title}",
"dialog.no_shown_body": "Ingen synlig kropp att ändra: visa en, eller välj Ny kropp",
"dialog.select": "↖ Markera",
"dialog.selected_count": "↖ {count} markerade",
"dialog.selection_guidance": "Markera en indata för att ändra den. Klicka på markerade objekt för att ta bort dem; Ctrl/Cmd-klick växlar dem också. Granska förhandsvisningen och tryck sedan på OK.",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "Flyttar en del i sammanställningen endast längs de frihetsgrader som dess leder tillåter. Markera en yta på delen och dra sedan en tillgänglig pil eller båge.",
"help.parameters.change": "Öppnar tabellen med namngivna värden och uttryck som styr konstruktionens mått. Använd den för att ändra en storlek på ett ställe och låta varje funktion som använder den följa med.",
"help.pointcloud.insert": "Infoga en skanning i formatet PLY, XYZ eller PTS som ett punktmoln vid sidan av modellen. Använd den som referens när du bygger en modell från uppmätta data.",
"help.sketch.construction_line": "Skapa en oändlig konstruktionslinje genom två punkter. Använd den för att lägga upp en skiss utan att dela profilerna.",
"help.sketch.construction_line.angle": "Skapa en oändlig konstruktionslinje i en vinkel mot skissens X-axel. Välj punkten och skriv vinkeln, eller välj dess riktning.",
"help.sketch.construction_line.bisector": "Välj två skärande raka skisslinjer och välj sedan någon av vinkelbisektriserna. Resultatet följer sina källlinjer.",
"help.sketch.construction_line.horizontal": "Skapa en oändlig horisontell konstruktionslinje genom en punkt. Det horisontella villkoret gäller även efter senare ändringar.",
"help.sketch.construction_line.parallel": "Välj en rak skisslinje och placera sedan en parallell, oändlig konstruktionslinje. Högerklicka för att välja en annan referens.",
"help.sketch.construction_line.perpendicular": "Välj en rak skisslinje och placera sedan en vinkelrät, oändlig konstruktionslinje. Högerklicka för att välja en annan referens.",
"help.sketch.construction_line.tangent_at": "Skapa en tangent i en punkt på en cirkel eller cirkelbåge, eller i en ändpunkt på en öppen spline eller konisk kurva. Välj kurvan och sedan beröringspunkten.",
"help.sketch.construction_line.tangent_external": "Välj en cirkel eller cirkelbåge, sedan en yttre punkt och en tangentlösning. Förhandsvisningarna visar bara beröringspunkter på den ritade bågen.",
"help.sketch.construction_line.tangent_two": "Välj två cirklar eller cirkelbågar och välj sedan en gemensam tangent i förhandsvisningen. Beröringspunkterna måste ligga på de ritade kurvorna.",
"help.sketch.construction_line.vertical": "Skapa en oändlig vertikal konstruktionslinje genom en punkt. Det vertikala villkoret gäller även efter senare ändringar.",
"help.sketch.equation_curve": "Ritar en kurva utifrån formler: x(t) och y(t), eller y = f(x), över ett intervall. Formlerna kan använda parametrar och kurvan följer dem: använd den för en sinusvåg, en spiral eller evolventen på en kugge.",
"help.solid.align": "Flyttar de markerade kropparna så att en punkt, yta eller ett plan på dem möter en punkt, yta eller ett plan någon annanstans. Använd det för att placera en del mot en annan utan att mäta förskjutningen.",
"help.solid.chamfer": "Fasar de markerade kanterna med en plan fas på det avstånd du anger. Använd det för att bryta skarpa kanter eller underlätta införandet av ett stift eller en skruv.",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "Bygger om alla funktioner i tidslinjen från början. Använd den för att uppdatera modellen och se vilka funktioner som misslyckas.",
"help.timeline.delete": "Tar bort de markerade funktionerna från tidslinjen; om du tar bort en skiss tas även funktionerna som bygger på den bort. Använd den för att ta bort ett steg som inte längre behövs.",
"help.timeline.play": "Spela upp de återstående funktionerna en i taget, med en halv sekunds paus efter varje ombyggnad. Kör kommandot igen eller tryck på Escape för att stoppa; Gå till slutet hoppar direkt.",
"history.sketch_under_constrained": "Underbestämd ({count} frihetsgrader)",
"history.sketch_under_constrained_one": "Underbestämd ({count} frihetsgrad)",
"hole_presets.confirm_delete": "Bekräfta borttagning",
"hole_presets.delete": "Ta bort",
"hole_presets.delete_prompt": "Ta bort den här personliga förinställningen? Befintliga hål påverkas inte.",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "Jämnar ut hackiga kanter.",
"prefs.applies_on_restart": "Tillämpas vid omstart",
"prefs.assistant": "Assistent",
"prefs.auto_operation": "Välj operation automatiskt",
"prefs.auto_operation_note": "Funktionsdialoger väljer Sammanfoga, Ny kropp eller Klipp ut utifrån vad det nya materialet rör vid, tills du väljer själv. Av: de behåller den operation de öppnas med.",
"prefs.axes": "Axlar",
"prefs.axis": "Axel",
"prefs.bodies_folders": "Kroppsmappar",
@@ -1820,6 +1841,8 @@
"prefs.bottom_centre": "Nere i mitten",
"prefs.bottom_right": "Nere till höger",
"prefs.browser": "Webbläsare",
"prefs.browser_eye_left": "Ögat bredvid expanderingspilen",
"prefs.browser_eye_left_note": "Raderna i webbläsaren visar sitt visa/dölj-öga bredvid expanderingspilen, som tidigare, i stället för i en kolumn längst till höger i raderna.",
"prefs.browser_reveal_on_hover": "Visa vid hovring",
"prefs.browser_show_note": "Visas håller webbläsaren på skärmen. Visa vid hovring håller den dold tills pekaren kommer över dess plats eller den smala fliken vid dess vänstra kant, och den förblir öppen medan pekaren är inuti den eller en meny från den är öppen.",
"prefs.browser_shown": "Visas",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "Träder i kraft efter att SolidCraft startats om. WGPU_ADAPTER_NAME åsidosätter detta val.",
"prefs.graphics_note": "Avväg hastighet mot jämnhet. Varje val visar sin effekt på ett exempel.",
"prefs.graphics_presets_note": "Förinställningar anger valen nedan tillsammans.",
"prefs.hide_constraint_glyphs_zoomed_out": "Dölj villkorssymboler vid utzoomning",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Villkorssymbolerna försvinner från vyn när skissen zoomas ut så långt att de skulle skräpa ned den. Av: de behåller en fast storlek på skärmen vid varje zoomnivå.",
"prefs.history": "Historik",
"prefs.history_origin_bodies": "Visa origo och kroppar överst",
"prefs.history_origin_bodies_note": "Historiken till vänster eller under webbläsaren börjar med den aktiva komponentens mapp Kroppar och Origo, med sina ögon och menyer som i webbläsaren. Historiken längst ned är oförändrad.",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "STEP- och importfiler",
"prefs.max_file_size_web": "Webbappen öppnar STEP- och importfiler upp till 512 MB och konstruktioner upp till 256 MB: en webbläsarflik kan inte rymma större filer. Gränserna kan höjas i skrivbordsappen.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Stora ikoner",
"prefs.menu_list": "Lista",
"prefs.menus": "Menyer",
"prefs.minutes": "minuter",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Mus och styrplatta",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "Namn på origoplan",
"prefs.origin_plane_names_note": "Namn som visas i webbläsaren, dialoger och listor. {top} är XY, {front} är XZ och {right} är YZ, med Z uppåt. Kommandon, skript och sparade konstruktioner behåller XY, XZ och YZ.",
"prefs.overall_speed": "Total hastighet",
"prefs.persistent_ribbon_pressed": "Framhäv aktiva kommandon i menyfliksområdet",
"prefs.persistent_ribbon_pressed_help": "Framhäv kommandon i menyfliksområdet medan deras effekter är aktiva. Stäng av för att behålla det tidigare neutrala utseendet och kommandohjälpen.",
"prefs.perspective_fov": "Synfält",
"prefs.placement": "Placering",
"prefs.prefer_free_sketch_points": "Föredra flyttbara skisspunkter när punkter överlappar",
"prefs.preview_choices": "Förhandsvisning av dina val",
"prefs.print_face_highlight": "Markering av utskriftsyta",
"prefs.print_face_highlight_tip": "Hur starkt en kropps utskriftsyta färgas och konturmarkeras i vyn. 0 % visar ingen markering.",
"prefs.profile_active": "Aktiv profil",
"prefs.profile_applied": "Profilen tillämpades.",
"prefs.profile_changed": "Inställningarna har ändrats sedan profilen sparades. Spara behåller dem i den.",
@@ -1965,6 +1997,8 @@
"prefs.search_empty": "Inga matchande inställningar",
"prefs.select": "Markera",
"prefs.select_one_item": "Markera ett objekt",
"prefs.select_other_hidden": "Markera dold geometri med Markera annan",
"prefs.select_other_hidden_help": "På: ta med dolda kroppar, skisser och skymda kanter nära pekaren. Av: använd den ursprungliga listan över synlig geometri under pekaren.",
"prefs.selecting": "Markering",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift lägger bara till; Ctrl / Cmd växlar",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "Vid ritning riktas en punkt in vågrätt mot en skisspunkt och lodrätt mot en annan samtidigt, med en prickad hjälplinje, och klicket behåller båda inriktningarna. Av: endast mot föregående punkt, en axel i taget.",
"prefs.sketch_conflict_colour": "Motstridig geometri i konfliktfärgen",
"prefs.sketch_conflict_colour_note": "Medan villkoren i en skiss står i konflikt ritas de kurvor och punkter som ett misslyckat villkor binder i konfliktfärgen också, inte bara dess symbol eller mått. Av: de behåller sina vanliga färger.",
"prefs.sketch_construction_lines": "Utökade verktyg för konstruktionslinjer",
"prefs.sketch_exit_view": "När en skiss avslutas",
"prefs.sketch_exit_view.keep": "Behåll den aktuella vyn",
"prefs.sketch_exit_view.previous": "Återgå till föregående vy",
"prefs.sketch_exit_view.unless_rotated": "Återgå om inte roterad",
"prefs.sketch_exit_view_note": "Dit vyn går efter Slutför skiss, Avbryt eller en dubbel Esc-tryckning.",
"prefs.sketch_open_look_at": "Vrid vyn mot en skiss när den öppnas",
"prefs.sketch_open_look_at_note": "Av: att skapa eller redigera en skiss lämnar vyn som den är. Titta på riktar vyn mot skissen när du vill.",
"prefs.sketch_recent_snap": "Snappa automatiskt till senaste centrum och mittpunkter",
"prefs.sketch_recent_snap_note": "Vid ritning snappar markören dit där hjälplinjerna från de två senaste cirkelcentrumen eller mittpunkterna på raka kanter korsar varandra. Av: hjälplinjerna visas bara och markören flyttas inte.",
"prefs.sketch_selection": "Skiss och markering",
"prefs.sketch_selection_heading": "Skiss och markering",
"prefs.sketch_selection_note": "Hur skisser snappar och hur klick markerar.",
"prefs.sketch_state_icons": "Skissikoner i färgen för sitt tillstånd",
"prefs.sketch_state_icons_note": "I Historiken och webbläsaren tar skissikonen färgen för skissens tillstånd (underbestämd, fullt definierad eller i konflikt), och dess verktygstips anger tillståndet. Av: alla skisser har samma ikon.",
"prefs.sketch_tab_always": "Visa alltid fliken Skiss",
"prefs.sketch_tab_always_note": "Fliken Skiss ligger kvar i menyfliksområdet, direkt efter Modell, medan ingen skiss är öppen. Dess ritverktyg startar då en skiss på det markerade planet eller den markerade ytan, eller på det plan du väljer härnäst, med verktyget redo i den.",
"prefs.sketch_tracking_guides": "Hjälplinjer för justering mot centrum och mittpunkt",
@@ -1995,6 +2034,11 @@
"prefs.source": "Källa",
"prefs.speed": "Hastighet",
"prefs.stand_up_note": "Erbjud en kvartsvridning kring X efter att en STEP- eller IGES-fil öppnats med Z uppåt.",
"prefs.stand_up_unknown": "För filer från okända program",
"prefs.stand_up_unknown.ask": "Fråga i en dialog",
"prefs.stand_up_unknown.note": "Visa ett meddelande",
"prefs.stand_up_unknown.off": "Erbjud inte",
"prefs.stand_up_unknown_note": "Hur erbjudandet görs när filens huvud inte anger något program som är känt för att spara med Y uppåt. Ett meddelande visas bara när filen verkar ligga på rygg; Fråga i en dialog frågar om varje sådan fil, som tidigare.",
"prefs.status_bar": "Statusfält",
"prefs.status_bar_note": "Ett smalt fält längs fönstrets nederkant visar vad som redigeras, den öppna skissens tillstånd i dess färg och konstruktionens enheter; ett klick på enheterna ändrar dem. Skissens tillstånd visas då inte längre i vyns hörn.",
"prefs.status_done": "Klar",
@@ -2037,6 +2081,8 @@
"prefs.view_confirm_corner": "OK och Avbryt i vyns hörn",
"prefs.view_confirm_corner_note": "Medan en dialog eller en skiss är öppen gör en bock och ett kryss uppe till höger i vyn samma sak som dialogens OK och Avbryt, eller slutför och avbryter skissen. Vynavigatorn flyttas ned under dem.",
"prefs.viewport": "Vyfönster",
"prefs.viewport_analysis_status": "Visa analysstatus i vyfönstret",
"prefs.viewport_analysis_status_help": "Visa aktiva analyser ovanför vyfönstret, öppna deras teckenförklaringar från varje objekt och använd Esc för att stänga av den senaste analysen. Stäng av för att återställa teckenförklaringar i hörnen och det tidigare Esc-beteendet.",
"prefs.viewport_background": "Bakgrund",
"prefs.viewport_background_colour": "Färg",
"prefs.viewport_background_theme": "Temats gradient",
@@ -2049,6 +2095,10 @@
"properties.title": "Egenskaper: {name}",
"recovery.days_ago": "för {count} dagar sedan",
"recovery.detail": "{file} · {features} funktioner · autosparad {when}",
"recovery.discard_all": "Förkasta alla",
"recovery.discard_all_detail": "Det går inte att ångra.",
"recovery.discard_all_title": "Förkasta {count} autospar?",
"recovery.discard_all_title_one": "Förkasta 1 autospar?",
"recovery.hours_ago": "för {count} h sedan",
"recovery.just_now": "nyss",
"recovery.min_ago": "för {count} min sedan",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "Flytta funktioner till mapp",
"timeline.rename_group": "Byt namn på funktionsmapp",
"timeline.reorder_many": "Ändra funktionsordning",
"timeline.suppress_features": "Undertryck funktioner",
"timeline.suppress_folders_tip": "Undertryck eller häv undertryckning av alla funktioner i mapparna och deras kapslade mappar",
"timeline.ungroup": "Ta bort funktionsmappar",
"timeline.unsuppress_features": "Häv undertryckning av funktioner",
"to the face": "till ytan",
"to the next face": "till nästa yta",
"toolbar.customize_note": "Fliken {tab}. Bocka för ett kommando för att ge det en verktygsfältsknapp. Du kan också dra en rad i en panels rullgardinsmeny till verktygsfältet, dra en knapp från verktygsfältet för att ta bort den, eller använda nålen på en rad i rullgardinsmenyn.",
@@ -3616,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Vrid knappen moturs, håll den och släpp sedan.",
"ui.3d_mouse_switch_cap": "Kalibrera som återgång till centrum",
"ui.3d_mouse_switch_trackball": "Kalibrera som styrkula",
"ui.3d_mouse_use_direct": "Använd direkt drivrutin",
"ui.3d_mouse_use_direct_tip": "Läs 3D-musen direkt (hidraw) i stället för via spacenavd.",
"ui.3d_mouse_view_axis": "Vyaxel",
"ui.3d_mouse_view_off": "Av",
"ui.3d_mouse_view_off_tip": "3D-musdrivrutinens profil stänger av den här axeln, så dess rörelse flyttar inte vyn. Om du väljer en annan axels rörelse här byter de två plats, och den rörelsen slutar flytta vyn.",
@@ -3922,6 +3976,8 @@
"ui.cancel": "Avbryt",
"ui.canvases": "Dukar",
"ui.center": "Centrum",
"ui.center_arc_follows_cursor": "Båge med centrumpunkt följer markörens riktning",
"ui.center_arc_follows_cursor_tip": "En båge med centrumpunkt går medurs eller moturs beroende på hur markören rör sig runt dess centrum. Av: alltid moturs från start.",
"ui.centerline": "Centrumlinje",
"ui.chain": "Kedja",
"ui.chamfer": "Fasning",
@@ -3930,6 +3986,7 @@
"ui.circle": "Cirkel",
"ui.circular_pattern": "Cirkulärt mönster",
"ui.clear": "Rensa",
"ui.clear_print_face": "Rensa utskriftsyta",
"ui.clear_recent": "Rensa senaste",
"ui.clear_the_selection": "Rensa markeringen",
"ui.click_selects_whole_bodies": "Klick markerar hela kroppar",
@@ -3964,6 +4021,14 @@
"ui.constant": "Konstant",
"ui.constraints_conflict": "Villkor i konflikt",
"ui.construction": "Konstruktion",
"ui.construction.angle_hint": "Konstruktionslinje: klicka på en punkt och skriv sedan en vinkel eller välj en riktning",
"ui.construction.bisector_hint": "Vinkelbisektris: välj två skärande raka linjer och sedan önskad lösning",
"ui.construction.common_hint": "Gemensam tangent: välj två cirklar/bågar och sedan önskad lösning",
"ui.construction.contact_hint": "Tangent: välj en cirkel/båge eller öppen spline/konisk kurva och sedan beröringspunkten (friformskurvor: ändpunkter)",
"ui.construction.external_hint": "Tangent: välj en cirkel eller båge, en yttre punkt och sedan önskad lösning",
"ui.construction.free_hint": "Konstruktionslinje: klicka på två punkter; högerklicka för att börja om",
"ui.construction.point_hint": "Konstruktionslinje: klicka på punkten som linjen ska gå genom",
"ui.construction.reference_hint": "Konstruktionslinje: välj en rak linje och sedan en punkt; högerklicka för att byta referens",
"ui.construction.ribbon_label": "Hjälplinjer",
"ui.context_menu": "Snabbmeny",
"ui.continuity_g0": "G0-veck",
@@ -4368,6 +4433,11 @@
"ui.polygon": "Polygon",
"ui.preferences": "Inställningar",
"ui.press_a_key": "tryck på en tangent…",
"ui.print_face_cleared": "{body}: utskriftsyta rensad",
"ui.print_face_input": "Utskriftsyta",
"ui.print_face_one_body": "En utskriftsyta hör till en kropp: skicka den kroppen ensam för att ändra den",
"ui.print_face_pick": "Välj en plan yta på en solid kropp för att göra den till utskriftsytan",
"ui.print_face_set": "{body}: utskriftsyta angiven; Skicka till slicer lägger kroppen på den",
"ui.profile": "Profil",
"ui.profile_plane": "Profilplan",
"ui.profiles": "Profiler",
@@ -4431,6 +4501,7 @@
"ui.revolve": "Rotation",
"ui.ribbon_exit": "Avsluta",
"ui.ribbon_labels": "Visa text under menyfliksikoner",
"ui.ribbon_menus_skip_pinned": "Utelämna fästa kommandon från rullgardinsmenyerna",
"ui.ribbon_pin_footer": "Fästa kommandon visas i menyfliken. Håll muspekaren över en rad för att fästa eller lossa den.",
"ui.ribbon_workspace": "Arbetsyta",
"ui.right": "Höger",
@@ -4594,6 +4665,8 @@
"ui.view_centre": "Vycentrum",
"ui.viewport_status_clear": "Stäng av {name}",
"ui.viewport_status_clear_hint": "Stäng av {name} · Esc",
"ui.viewport_status_edges": "{count} kanter",
"ui.viewport_status_from": "från {direction}",
"ui.viewport_status_key": "Visa teckenförklaring för {name}",
"ui.viewport_status_minimum_radius": "Minsta radie",
"ui.viewport_status_pairs": "{count} par",
@@ -4634,7 +4707,9 @@
"update.button": "Uppdatera",
"update.button_version": "Uppdatera till {version}",
"update.check_automatically": "Sök efter uppdateringar automatiskt",
"update.check_for_updates": "Sök efter uppdateringar",
"update.check_now": "Sök nu",
"update.check_unavailable": "Sökning efter uppdateringar är inte tillgänglig för den här installationen.",
"update.checking": "Söker…",
"update.copy": "Kopiera",
"update.dmg_opened": "Diskavbilden är öppen och verifierad: dra SolidCraft till Program och ersätt den gamla kopian, starta sedan om SolidCraft.",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} är installerad. Starta om för att använda den.",
"update.installer_started": "Installationsprogrammet startar när SolidCraft stängs.",
"update.later": "Påminn mig senare",
"update.managed": "Uppdatering tillgänglig: uppdatera SolidCraft i {manager}",
"update.no_notes": "Den här utgåvan har inga anteckningar.",
"update.package_hint": "Den här kopian kom från ett {kind}. Ladda ned det nya paketet och installera det sedan med:",
"update.prefs_dev": "Detta är ett utvecklingsbygge ({version}): det söker inte efter uppdateringar.",
"update.prefs_flatpak": "Flatpak uppdaterar den här kopian ({version}): kör {command} eller använd din programhanterare. SolidCraft söker inte själv.",
"update.prefs_label": "Uppdateringar",
"update.prefs_managed": "Detta är SolidCraft {version}, hanterat av {manager}. En sökning frågar GitHub efter den senaste utgåvan och skickar inget annat; uppdatera SolidCraft i {manager}.",
"update.prefs_note": "Detta är SolidCraft {version} ({kind}). En sökning frågar GitHub efter den senaste utgåvan och skickar inget annat.",
"update.prefs_web": "Läs in sidan igen för att få den senaste webbappen. Om nätverket inte är tillgängligt eller om uppdateringen inte hinner laddas ned i tid öppnas den sparade versionen i stället; en nedladdning som pågår fortsätter till nästa omläsning.",
"update.refresh_checking": "Söker efter den senaste utgåvan…",
+74
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "Açtığınız veya kaydettiğiniz tasarımlar burada görünür.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Nesneye kadar seçeneğiyle belirlenir. Açısal kapsamı düzenlemek için Açı seçeneğini seçin.",
"Diameter": "Çap",
"Did this assembly open the wrong way up?": "Bu montaj yanlış yönde mi açıldı?",
"Did this part open the wrong way up?": "Bu parça yanlış yönde mi açıldı?",
"Dimension new shapes": "Yeni şekillere ölçü ver",
"Direction": "Yön",
"Discard": "At",
@@ -601,6 +603,7 @@
"Square": "Kare",
"Stand it up": "Dik çevir",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Dik çevir: X etrafında çeyrek tur; geri alınabilen tek bir Taşı özelliği olarak eklenir.",
"Stand this assembly up?": "Bu montaj dik çevrilsin mi?",
"Stand this part up?": "Bu parça dik çevrilsin mi?",
"Start angle": "Başlangıç açısı",
"Start bulge": "Başlangıç şişkinliği",
@@ -772,6 +775,8 @@
"at the face": "yüzde",
"bom.group_children": "Alt parçalar kendi parçalarının satırında",
"browser.body_name": "Gövde {number}",
"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.",
"browser.calibrate_title": "Kalibre et: {name}",
"browser.capture_many": "{count} bileşen taşındı ancak konumları yakalanmadı.",
"browser.capture_note": "Konumu yakala yeni konumu korur; Konumu geri al önceki konuma döndürür.",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "Görünümü Al",
"cmd.appearance_replace": "Görünümü Değiştir",
"cmd.assembly_bom": "Malzeme listesi",
"cmd.body.print_face": "Baskı yüzeyi",
"cmd.body_rename": "Gövdeyi yeniden adlandır",
"cmd.browser_collapse_all": "Tümünü Daralt",
"cmd.browser_expand_all": "Tümünü Genişlet",
@@ -1396,6 +1402,16 @@
"help.occurrence.move": "Montaj parçasını yalnızca eklemlerinin izin verdiği serbestliklerde taşır. Bir parça yüzü seçtikten sonra kullanılabilir oku veya yayı sürükleyin.",
"help.parameters.change": "Tasarımın ölçülerini yöneten adlandırılmış değerlerin ve ifadelerin tablosunu açar. Bir ölçüyü tek yerden değiştirip onu kullanan her özelliğin buna uymasını sağlamak için kullanın.",
"help.pointcloud.insert": "PLY, XYZ veya PTS taramasını modelin yanına nokta bulutu olarak ekler. Ölçülmüş verilerden model oluştururken referans olarak kullanı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.",
"help.sketch.equation_curve": "Formüllerden bir eğri çizer: bir aralık boyunca x(t) ve y(t) ya da y = f(x). Formüller parametre kullanabilir ve eğri onları izler: sinüs dalgası, spiral veya bir dişli dişinin evolventi için kullanın.",
"help.solid.align": "Seçili gövdeleri, üzerlerindeki bir nokta, yüz veya düzlem başka bir yerdeki nokta, yüz veya düzlemle buluşacak şekilde taşır. Ofseti ölçmeden bir parçayı diğerinin yanına yerleştirmek için kullanın.",
"help.solid.chamfer": "Seçili kenarları, belirlediğiniz mesafede düz bir pahla keser. Keskin kenarları kırmak veya bir pim ya da vidanın girişini kolaylaştırmak için kullanın.",
@@ -1423,6 +1439,8 @@
"help.timeline.compute_all": "Zaman çizelgesindeki her özelliği baştan yeniden oluşturur. Modeli tazelemek ve hangi özelliklerin başarısız olduğunu görmek için kullanın.",
"help.timeline.delete": "Seçili özellikleri zaman çizelgesinden siler; bir eskizi silmek ondan oluşturulan özellikleri de siler. Artık istenmeyen bir adımı kaldırmak için kullanın.",
"help.timeline.play": "Kalan özellikleri, her yeniden hesaplamadan sonra yarım saniye bekleyerek tek tek yeniden oynatır. Durdurmak için bu komutu yeniden çalıştırın veya Esc tuşuna basın; Sona git hemen atlar.",
"history.sketch_under_constrained": "Eksik kısıtlanmış ({count} serbestlik derecesi)",
"history.sketch_under_constrained_one": "Eksik kısıtlanmış ({count} serbestlik derecesi)",
"hole_presets.confirm_delete": "Silmeyi onayla",
"hole_presets.delete": "Sil",
"hole_presets.delete_prompt": "Bu kişisel ön ayar silinsin mi? Mevcut delikler değişmez.",
@@ -1823,6 +1841,8 @@
"prefs.bottom_centre": "Alt orta",
"prefs.bottom_right": "Sağ alt",
"prefs.browser": "Tarayıcı",
"prefs.browser_eye_left": "Göz, daraltma okunun yanında",
"prefs.browser_eye_left_note": "Tarayıcı satırları göster/gizle gözünü, satırların sağ ucundaki bir sütun yerine eskiden olduğu gibi daraltma okunun yanında gösterir.",
"prefs.browser_reveal_on_hover": "Üzerine gelince göster",
"prefs.browser_show_note": "Görünür, tarayıcıyı ekranda tutar. Üzerine gelince göster, imleç onun yerine veya sol kenarındaki ince sekmeye gelene kadar onu gizli tutar; imleç içindeyken veya ondan açılan bir menü açıkken açık kalır.",
"prefs.browser_shown": "Görünür",
@@ -1873,6 +1893,8 @@
"prefs.graphics_card_restart": "SolidCraft'ı yeniden başlattıktan sonra etkili olur. WGPU_ADAPTER_NAME bu seçimi geçersiz kılar.",
"prefs.graphics_note": "Hızdan ödün verip pürüzsüzlük kazanın. Her seçenek, etkisini bir örnek üzerinde gösterir.",
"prefs.graphics_presets_note": "Ön ayarlar aşağıdaki seçimleri birlikte belirler.",
"prefs.hide_constraint_glyphs_zoomed_out": "Uzaklaşıldığında kısıtlama simgelerini gizle",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Eskiz, kalabalık yaratacak kadar uzaklaştırıldığında kısıtlama simgeleri görünümden çıkar. Kapalıyken: her yakınlaştırma düzeyinde sabit ekran boyutunda kalırlar.",
"prefs.history": "Geçmiş",
"prefs.history_origin_bodies": "Başlangıç ve gövdeleri üstte göster",
"prefs.history_origin_bodies_note": "Soldaki veya tarayıcının altındaki Geçmiş, etkin bileşenin Gövdeler klasörü ve Başlangıç ile başlar; gözleri ve menüleri tarayıcıdaki gibidir. Alttaki Geçmiş değişmez.",
@@ -1911,6 +1933,9 @@
"prefs.max_file_size_step": "STEP ve içe aktarma dosyaları",
"prefs.max_file_size_web": "Web uygulaması 512 MB’a kadar STEP ve içe aktarma dosyalarını, 256 MB’a kadar tasarımları açar: tarayıcı sekmesi daha büyük dosyaları barındıramaz. Masaüstü uygulamasında bu sınırlar artırılabilir.",
"prefs.megabytes": "MB",
"prefs.menu_grid": "Büyük simgeler",
"prefs.menu_list": "Liste",
"prefs.menus": "Menüler",
"prefs.minutes": "dakika",
"prefs.minutes_suffix": " min",
"prefs.mouse": "Fare ve trackpad",
@@ -1930,10 +1955,14 @@
"prefs.origin_plane_names": "Orijin düzlemi adları",
"prefs.origin_plane_names_note": "Tarayıcıda, diyaloglarda ve listelerde gösterilen adlar. Z yukarı olmak üzere {top} XY, {front} XZ ve {right} YZ'dir. Komutlar, betikler ve kayıtlı tasarımlar XY, XZ ve YZ adlarını korur.",
"prefs.overall_speed": "Genel hız",
"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.perspective_fov": "Görüş alanı",
"prefs.placement": "Yerleşim",
"prefs.prefer_free_sketch_points": "Noktalar örtüştüğünde hareketli eskiz noktalarını tercih et",
"prefs.preview_choices": "Seçimlerinizin önizlemesi",
"prefs.print_face_highlight": "Baskı yüzeyi vurgusu",
"prefs.print_face_highlight_tip": "Bir gövdenin baskı yüzeyinin görünümde ne kadar güçlü renklendirilip çerçeveleneceği. %0 vurgu göstermez.",
"prefs.profile_active": "Etkin profil",
"prefs.profile_applied": "Profil uygulandı.",
"prefs.profile_changed": "Profil kaydedildikten sonra ayarlar değişti. Kaydet, onları profilde saklar.",
@@ -1968,6 +1997,8 @@
"prefs.search_empty": "Eşleşen ayar yok",
"prefs.select": "Seçim",
"prefs.select_one_item": "Bir öğe seçin",
"prefs.select_other_hidden": "Gizli geometriyi Diğerini seç ile seç",
"prefs.select_other_hidden_help": "Açıkken: imlecin yakınındaki gizli gövdeleri, eskizleri ve örtülü kenarları da dahil eder. Kapalıyken: imlecin altındaki görünür geometrinin özgün listesini kullanır.",
"prefs.selecting": "Seçme",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift yalnızca ekler; Ctrl / Cmd değiştirir",
@@ -1979,16 +2010,21 @@
"prefs.sketch_align_points_note": "Çizim sırasında bir nokta, noktalı bir kılavuzla aynı anda bir eskiz noktasıyla yatay, bir diğeriyle dikey olarak hizalanır ve tıklama her iki hizalamayı da korur. Kapalı: yalnızca önceki noktayla, her seferinde tek eksende.",
"prefs.sketch_conflict_colour": "Çakışan geometri çakışma renginde",
"prefs.sketch_conflict_colour_note": "Bir eskizin kısıtlamaları çakışırken, başarısız olan kısıtlamanın tuttuğu eğriler ve noktalar yalnızca simgesi veya ölçüsü değil, çakışma renginde de çizilir. Kapalıyken olağan renklerini korurlar.",
"prefs.sketch_construction_lines": "Genişletilmiş yardımcı doğru araçları",
"prefs.sketch_exit_view": "Bir eskiz bittiğinde",
"prefs.sketch_exit_view.keep": "Geçerli görünümü koru",
"prefs.sketch_exit_view.previous": "Önceki görünüme dön",
"prefs.sketch_exit_view.unless_rotated": "Döndürülmediyse dön",
"prefs.sketch_exit_view_note": "Eskizi Bitir, İptal veya çift Esc sonrası görünümün nereye gittiği.",
"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.",
"prefs.sketch_recent_snap": "Son merkezlere ve orta noktalara otomatik yapış",
"prefs.sketch_recent_snap_note": "Çizim sırasında imleç, son iki daire merkezi veya düz kenar orta noktasından gelen kılavuzların kesiştiği yere yapışır. Kapalıyken bu kılavuzlar yalnızca görsel kalır ve imleç hareket etmez.",
"prefs.sketch_selection": "Eskiz ve seçim",
"prefs.sketch_selection_heading": "Eskiz ve seçim",
"prefs.sketch_selection_note": "Eskizlerin nasıl yapıştığı ve tıklamaların nasıl seçtiği.",
"prefs.sketch_state_icons": "Eskiz simgeleri durumlarının renginde",
"prefs.sketch_state_icons_note": "Geçmiş'te ve tarayıcıda bir eskizin simgesi durumunun rengini alır (eksik kısıtlanmış, tam kısıtlı veya çakışan) ve araç ipucu durumu belirtir. Kapalıyken: her eskizin simgesi aynıdır.",
"prefs.sketch_tab_always": "Eskiz sekmesini hep göster",
"prefs.sketch_tab_always_note": "Hiçbir eskiz açık değilken Eskiz sekmesi şeritte, Model’den hemen sonra kalır. Çizim araçları bu durumda seçili düzlemde veya yüzde ya da bir sonra seçtiğiniz düzlemde, araç hazır olarak bir eskiz başlatır.",
"prefs.sketch_tracking_guides": "Merkez ve orta nokta hizalama kılavuzları",
@@ -1998,6 +2034,11 @@
"prefs.source": "Kaynak",
"prefs.speed": "Hız",
"prefs.stand_up_note": "Bir STEP veya IGES dosyası Z yukarı açıldığında, X etrafında çeyrek tur öner.",
"prefs.stand_up_unknown": "Bilinmeyen programlardan gelen dosyalar için",
"prefs.stand_up_unknown.ask": "Diyalogda sor",
"prefs.stand_up_unknown.note": "Not göster",
"prefs.stand_up_unknown.off": "Önerme",
"prefs.stand_up_unknown_note": "Dosyanın başlığı, Y yukarı olarak kaydettiği bilinen bir program belirtmediğinde önerinin nasıl sunulacağı. Not yalnızca dosya sırtüstü yatıyor gibi göründüğünde gösterilir; Diyalogda sor, daha önce olduğu gibi böyle her dosya için sorar.",
"prefs.status_bar": "Durum çubuğu",
"prefs.status_bar_note": "Pencerenin alt kenarındaki ince bir çubuk, nelerin düzenlendiğini, açık eskizin durumunu rengiyle ve tasarımın birimlerini gösterir; birimlere tıklamak onları değiştirir. Bu durumda eskizin durumu artık görünümün köşesinde gösterilmez.",
"prefs.status_done": "Tamam",
@@ -2040,6 +2081,8 @@
"prefs.view_confirm_corner": "Görünümün köşesinde Tamam ve İptal",
"prefs.view_confirm_corner_note": "Bir diyalog veya eskiz açıkken, görünümün sağ üstündeki bir onay işareti ve çarpı işareti, diyalogun Tamam ve İptal düğmelerinin ya da eskizi bitirip iptal etmenin işini yapar. Görünüm yönlendiricileri bunların altına iner.",
"prefs.viewport": "Görünüm alanı",
"prefs.viewport_analysis_status": "Görünüm alanı analiz durumunu göster",
"prefs.viewport_analysis_status_help": "Etkin analizleri görünüm alanının üstünde gösterir, her öğeden göstergesini açar ve en yeni analizi kapatmak için Esc tuşunu kullanır. Köşe göstergelerini ve Esc'in önceki davranışını geri yüklemek için kapatın.",
"prefs.viewport_background": "Arka plan",
"prefs.viewport_background_colour": "Renk",
"prefs.viewport_background_theme": "Temanın degradesi",
@@ -2052,6 +2095,10 @@
"properties.title": "Özellikler: {name}",
"recovery.days_ago": "{count} gün önce",
"recovery.detail": "{file} · {features} özellik · otomatik kaydedilen: {when}",
"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ı?",
"recovery.hours_ago": "{count} sa. önce",
"recovery.just_now": "az önce",
"recovery.min_ago": "{count} dk. önce",
@@ -3506,8 +3553,10 @@
"timeline.move_to_group": "Özellikleri klasöre taşı",
"timeline.rename_group": "Özellik klasörünü yeniden adlandır",
"timeline.reorder_many": "Özellikleri yeniden sırala",
"timeline.suppress_features": "Özellikleri bastır",
"timeline.suppress_folders_tip": "Klasörlerdeki ve iç içe klasörlerindeki her özelliği bastır veya bastırmayı kaldır",
"timeline.ungroup": "Özellik klasörünü kaldır",
"timeline.unsuppress_features": "Özelliklerin bastırmasını kaldır",
"to the face": "yüze kadar",
"to the next face": "sonraki yüze kadar",
"toolbar.customize_note": "{tab} sekmesi. Bir komuta araç çubuğu düğmesi eklemek için komutu işaretleyin. Ayrıca panelin açılır menüsündeki bir satırı araç çubuğuna sürükleyebilir, bir düğmeyi kaldırmak için araç çubuğunun dışına sürükleyebilir veya açılır menü satırındaki raptiyeyi kullanabilirsiniz.",
@@ -3619,6 +3668,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Başlığı saat yönünün tersine çevirin, tutun, sonra bırakın.",
"ui.3d_mouse_switch_cap": "Merkeze dönüş olarak kalibre et",
"ui.3d_mouse_switch_trackball": "İztopu olarak kalibre et",
"ui.3d_mouse_use_direct": "Doğrudan sürücüyü kullan",
"ui.3d_mouse_use_direct_tip": "3D fareyi spacenavd yerine doğrudan (hidraw) oku.",
"ui.3d_mouse_view_axis": "Görünüm ekseni",
"ui.3d_mouse_view_off": "Kapalı",
"ui.3d_mouse_view_off_tip": "3D fare sürücüsünün profili bu ekseni kapatıyor, bu yüzden hareketi görünümü oynatmaz. Burada başka bir eksenin hareketini seçerseniz ikisi yer değiştirir ve o hareket görünümü oynatmayı bırakır.",
@@ -3925,6 +3976,8 @@
"ui.cancel": "İptal",
"ui.canvases": "Tuvaller",
"ui.center": "Merkez",
"ui.center_arc_follows_cursor": "Merkez noktalı yay imlecin yönünü izler",
"ui.center_arc_follows_cursor_tip": "Merkez noktalı yay, imlecin merkezinin etrafında dönüş yönüne göre saat yönünde veya saat yönünün tersine ilerler. Kapalıyken: başlangıçtan itibaren her zaman saat yönünün tersine.",
"ui.centerline": "Merkez çizgisi",
"ui.chain": "Zincir",
"ui.chamfer": "Pah",
@@ -3933,6 +3986,7 @@
"ui.circle": "Daire",
"ui.circular_pattern": "Dairesel desen",
"ui.clear": "Temizle",
"ui.clear_print_face": "Baskı yüzeyini temizle",
"ui.clear_recent": "Son kullanılanları temizle",
"ui.clear_the_selection": "Seçimi temizle",
"ui.click_selects_whole_bodies": "Tıklama tüm gövdeyi seçer",
@@ -3967,6 +4021,14 @@
"ui.constant": "Sabit",
"ui.constraints_conflict": "Kısıtlamalar çakışıyor",
"ui.construction": "Yapı çizgisi",
"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",
"ui.construction.ribbon_label": "Yardımcılar",
"ui.context_menu": "Bağlam menüsü",
"ui.continuity_g0": "G0 kırık",
@@ -4371,6 +4433,11 @@
"ui.polygon": "Çokgen",
"ui.preferences": "Ayarlar",
"ui.press_a_key": "bir tuşa basın…",
"ui.print_face_cleared": "{body}: baskı yüzeyi temizlendi",
"ui.print_face_input": "Baskı yüzeyi",
"ui.print_face_one_body": "Baskı yüzeyi tek bir gövdeye aittir: değiştirmek için o gövdeyi tek başına gönderin",
"ui.print_face_pick": "Baskı yüzeyi yapmak için katı bir gövdenin düz bir yüzeyini seçin",
"ui.print_face_set": "{body}: baskı yüzeyi ayarlandı; Dilimleyiciye gönder gövdeyi bu yüzeyin üzerine koyar",
"ui.profile": "Profil",
"ui.profile_plane": "Profil düzlemi",
"ui.profiles": "Profiller",
@@ -4434,6 +4501,7 @@
"ui.revolve": "Döndür",
"ui.ribbon_exit": "Çıkış",
"ui.ribbon_labels": "Şerit simgelerinin altında metin göster",
"ui.ribbon_menus_skip_pinned": "Sabitlenen komutları açılır menülerin dışında bırak",
"ui.ribbon_pin_footer": "Sabitlenen komutlar şeritte görünür. Sabitlemek veya kaldırmak için bir satırın üzerine gelin.",
"ui.ribbon_workspace": "Çalışma alanı",
"ui.right": "Sağ",
@@ -4597,6 +4665,8 @@
"ui.view_centre": "Görünüm merkezi",
"ui.viewport_status_clear": "Kapat: {name}",
"ui.viewport_status_clear_hint": "Kapat: {name} · Esc",
"ui.viewport_status_edges": "{count} kenar",
"ui.viewport_status_from": "{direction} yönünden",
"ui.viewport_status_key": "Göstergeyi göster: {name}",
"ui.viewport_status_minimum_radius": "Minimum yarıçap",
"ui.viewport_status_pairs": "{count} çift",
@@ -4637,7 +4707,9 @@
"update.button": "Güncelle",
"update.button_version": "{version} sürümüne güncelle",
"update.check_automatically": "Güncellemeleri otomatik denetle",
"update.check_for_updates": "Güncellemeleri denetle",
"update.check_now": "Şimdi denetle",
"update.check_unavailable": "Bu kurulum için güncelleme denetimi kullanılamıyor.",
"update.checking": "Denetleniyor…",
"update.copy": "Kopyala",
"update.dmg_opened": "Disk görüntüsü açıldı ve doğrulandı: SolidCraft'ı “Uygulamalar” klasörüne sürükleyin, eski sürümü değiştirin ve SolidCraft'ı yeniden başlatın.",
@@ -4651,11 +4723,13 @@
"update.installed": "SolidCraft {version} yüklendi. Kullanmak için yeniden başlatın.",
"update.installer_started": "Yükleme programı, SolidCraft kapatıldığında başlar.",
"update.later": "Daha sonra hatırlat",
"update.managed": "Güncelleme mevcut: SolidCraft'ı {manager} üzerinden güncelleyin",
"update.no_notes": "Bu sürüm için not bulunmuyor.",
"update.package_hint": "Bu kurulum bir {kind} kaynağından geliyor. Yeni paketi indirip şu komutla yükleyin:",
"update.prefs_dev": "Bu bir geliştirme derlemesi ({version}): güncelleme aramaz.",
"update.prefs_flatpak": "Bu kurulum ({version}) Flatpak tarafından güncellenir: {command} komutunu çalıştırın veya yazılım yöneticisini kullanın. SolidCraft kendisi denetlemez.",
"update.prefs_label": "Güncellemeler",
"update.prefs_managed": "Bu, {manager} tarafından yönetilen SolidCraft {version}. Denetim, GitHub'a en yeni sürümü sorar ve başka hiçbir şey göndermez; SolidCraft'ı {manager} üzerinden güncelleyin.",
"update.prefs_note": "Bu, SolidCraft {version} ({kind}). Denetim, GitHub'a en yeni sürümü sorar ve başka hiçbir şey göndermez.",
"update.prefs_web": "En yeni web uygulamasını almak için sayfayı yeniden yükleyin. Ağ kullanılamazsa veya güncelleme indirmesi zamanında bitiremezse bunun yerine kaydedilmiş sürüm açılır; süren bir indirme bir sonraki yeniden yükleme için devam eder.",
"update.refresh_checking": "En yeni sürüm denetleniyor…",
+108
View File
@@ -775,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": "«Зафіксувати положення» зберігає нове положення; «Відновити положення» повертає попереднє.",
@@ -817,6 +819,7 @@
"cmd.appearance_pick_up": "Взяти вигляд",
"cmd.appearance_replace": "Замінити вигляд",
"cmd.assembly_bom": "Специфікація матеріалів",
"cmd.body.print_face": "Грань друку",
"cmd.body_rename": "Перейменувати тіло",
"cmd.browser_collapse_all": "Згорнути все",
"cmd.browser_expand_all": "Розгорнути все",
@@ -824,6 +827,7 @@
"cmd.browser_move": "Перемістити елементи браузера",
"cmd.browser_order": "Упорядкувати елементи браузера",
"cmd.browser_rename_group": "Перейменувати групу",
"cmd.browser_reparent": "Перемістити до компонента",
"cmd.browser_ungroup": "Розгрупувати",
"cmd.browser_visibility": "Показати/приховати",
"cmd.canvas_calibrate": "Калібрувати полотно",
@@ -850,6 +854,7 @@
"cmd.component_metadata": "Властивості деталі",
"cmd.component_metadata_set": "Змінити властивості деталі",
"cmd.component_move_bodies": "Перемістити тіла до компонента",
"cmd.component_move_features": "Перемістити операції до компонента",
"cmd.component_move_sketches": "Перемістити ескізи до компонента",
"cmd.component_paste_from_design": "Вставити з іншого проєкту як компонент",
"cmd.component_paste_new": "Вставити як новий",
@@ -1079,6 +1084,13 @@
"cmd.render_start": "Рендеринг зображення",
"cmd.render_status": "Перебіг рендерингу",
"cmd.select_clear": "Зняти виділення",
"cmd.settings_profile_apply": "Застосувати профіль налаштувань",
"cmd.settings_profile_delete": "Видалити профіль налаштувань",
"cmd.settings_profile_export": "Експортувати профіль налаштувань",
"cmd.settings_profile_import": "Імпортувати профіль налаштувань",
"cmd.settings_profile_list": "Профілі налаштувань",
"cmd.settings_profile_rename": "Перейменувати профіль налаштувань",
"cmd.settings_profile_save": "Зберегти профіль налаштувань",
"cmd.sheet_convert": "Перетворити на листовий метал",
"cmd.sheet_export_bend_table": "Експортувати таблицю згинів",
"cmd.sheet_export_dxf": "Експортувати розгортку як DXF",
@@ -1390,7 +1402,18 @@
"help.occurrence.move": "Переміщує екземпляр компонента в збірці",
"help.parameters.change": "Відкриває таблицю іменованих значень і виразів, що керують розмірами проєкту. Використовуйте, щоб змінити розмір в одному місці, а всі операції, які його використовують, підлаштувалися.",
"help.pointcloud.insert": "Вставте скан у форматі PLY, XYZ або PTS як хмару точок поряд із моделлю. Використовуйте її як довідку під час побудови моделі за виміряними даними.",
"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.equation_curve": "Створює криву за параметричними рівняннями X(t), Y(t) у площині ескізу.",
"help.sketch.inspect": "Відкриває звіт про відкритий ескіз: скільки ступенів вільності залишилося і чи він повністю обмежений. Використовуйте його, щоб дізнатися, що ще рухається, перш ніж завершити ескіз.",
"help.solid.align": "Переміщує вибрані тіла так, щоб точка, грань чи площина на них зійшлася з точкою, гранню чи площиною деінде. Використовуйте, щоб притиснути деталь до іншої, не вимірюючи зсуву.",
"help.solid.chamfer": "Зрізає вибрані ребра плоским скосом на заданій відстані. Використовуйте, щоб притупити гострі ребра або полегшити вставляння штифта чи гвинта.",
"help.solid.combine": "Об'єднує тіла, вирізає їх із цільового тіла або залишає лише спільний для них об'єм, і може зберегти тіла-інструменти. Використовуйте, щоб з'єднати деталі, вирізати одну форму іншою або знайти, де два тіла перекриваються.",
@@ -1593,6 +1616,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} с тому",
@@ -1767,6 +1794,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",
@@ -1819,6 +1848,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": "Показувати браузер",
@@ -1869,6 +1900,8 @@
"prefs.graphics_card_restart": "Набуває чинності після перезапуску 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": "Історія ліворуч або під браузером починається з теки «Тіла» активного компонента та початку координат, з їхніми значками ока й меню, як у браузері. Історія внизу не змінюється.",
@@ -1907,6 +1940,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": " хв",
"prefs.mouse": "Миша й трекпад",
@@ -1926,19 +1962,42 @@
"prefs.origin_plane_names": "Назви площин початку координат",
"prefs.origin_plane_names_note": "Назви, що показуються в браузері, діалогах і списках. {top} — це XY, {front} — XZ, {right} — YZ, а Z спрямована вгору. Команди, скрипти й збережені проєкти зберігають XY, XZ і YZ.",
"prefs.overall_speed": "Загальна швидкість",
"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": "Налаштування змінено після збереження профілю. «Зберегти» додасть їх до профілю.",
"prefs.profile_deleted": "Профіль видалено.",
"prefs.profile_export": "Експортувати…",
"prefs.profile_exported": "Профіль експортовано.",
"prefs.profile_imported": "Профіль імпортовано. Виберіть його в полі «Активний профіль», щоб перейти на нього.",
"prefs.profile_name_hint": "Назва профілю, напр. Ноутбук",
"prefs.profile_rename": "Перейменувати",
"prefs.profile_renamed": "Профіль перейменовано.",
"prefs.profile_save": "Зберегти",
"prefs.profile_saved": "Поточні налаштування збережено в профілі.",
"prefs.profile_skipped": "Профіль застосовано; налаштувань, що зберегли поточне значення: {n} (див. журнал).",
"prefs.profiles": "Профілі налаштувань",
"prefs.profiles_note": "Комбінації клавіш, панель інструментів, навігація, кругове меню та налаштування інтерфейсу, що зберігаються разом під однією назвою й перемикаються одночасно.",
"prefs.project_options_dialog": "Діалог параметрів проєкту",
"prefs.project_options_dialog_note": "Відкривати діалог параметрів проєкту замість бічної панелі",
"prefs.quality_default": "типово",
"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": "Максимальний обсяг пам'яті для кешу геометрії попереднього стану",
"prefs.reports_saved": "Збережено: {n}",
"prefs.ribbon_reveal": "Показувати результати пошуку на стрічці",
"prefs.ribbon_reveal_note": "Наведення курсора на результат у «Пошуку команд» показує, де команда розташована на стрічці: її вкладку, кнопку або рядок у випадному меню панелі. Команда, запущена з пошуку, блимає там, а запущена команда з випадного меню позначає свою панель.",
"prefs.right_click": "Клацання правою",
"prefs.saving": "Автозбереження й відновлення",
"prefs.scale_note": "Також Ctrl + / Ctrl − у вікні. Кнопки мають 28 px за 100%.",
@@ -1947,6 +2006,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 перемикає",
@@ -1958,11 +2019,16 @@
"prefs.sketch_align_points_note": "Автоматично вирівнювати точки при наближенні",
"prefs.sketch_conflict_colour": "Конфліктна геометрія кольором конфлікту",
"prefs.sketch_conflict_colour_note": "Поки обмеження ескізу суперечать одне одному, криві та точки, які утримує проблемне обмеження, також малюються кольором конфлікту, а не лише його піктограма чи розмір. Вимкнено: вони зберігають свої звичайні кольори.",
"prefs.sketch_construction_lines": "Розширені інструменти допоміжних прямих",
"prefs.sketch_exit_view": "Коли ескіз завершується",
"prefs.sketch_exit_view.keep": "Зберегти поточний вид",
"prefs.sketch_exit_view.previous": "Повернутися до попереднього виду",
"prefs.sketch_exit_view.unless_rotated": "Повернутися, якщо вид не оберталося",
"prefs.sketch_exit_view_note": "Куди переходить вид після «Завершити ескіз», «Скасувати» або подвійного Esc.",
"prefs.sketch_open_look_at": "Повертати вид до ескізу під час його відкриття",
"prefs.sketch_open_look_at_note": "Вимкнено: створення чи редагування ескізу не змінює вид. «Наведення» повертає вид до ескізу, коли ви цього захочете.",
"prefs.sketch_recent_snap": "Автоприв'язка до нещодавніх центрів і середніх точок",
"prefs.sketch_recent_snap_note": "Під час креслення курсор прив'язується до місця, де перетинаються допоміжні лінії двох останніх центрів кіл або середніх точок прямих відрізків. Вимкнено: ці лінії лише показуються, а курсор не зсувається.",
"prefs.sketch_selection": "Ескіз і вибір",
"prefs.sketch_selection_heading": "Ескіз і вибір",
"prefs.sketch_selection_note": "Як ескізи прив'язуються й як клацання вибирають.",
@@ -2024,9 +2090,13 @@
"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": "Показувати налаштування перерізу у вікні перегляду. Escape і активна кнопка стрічки вимикають його. Вимкніть, щоб зберегти попередні елементи керування перерізом.",
"prefs.wheel": "Колесо",
"prefs.wheel_trackpad": "Колесо й трекпад",
"prefs.workspace": "Робоча область",
@@ -2036,6 +2106,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} хв тому",
@@ -3605,6 +3679,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-миші вимикає цю вісь, тож її рух не рухає вигляд. Якщо вибрати тут рух іншої осі, вони поміняються місцями, і цей рух перестане рухати вигляд.",
@@ -3921,6 +3997,7 @@
"ui.circle": "Коло",
"ui.circular_pattern": "Круговий масив",
"ui.clear": "Очистити",
"ui.clear_print_face": "Скинути грань друку",
"ui.clear_recent": "Очистити недавні",
"ui.clear_the_selection": "Зняти виділення",
"ui.click_selects_whole_bodies": "Клацання виділяє тіла цілком",
@@ -3955,6 +4032,14 @@
"ui.constant": "Стала",
"ui.constraints_conflict": "Конфлікт обмежень",
"ui.construction": "Конструкційна",
"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": "Допоміжна пряма: виберіть пряму, потім точку; клацніть правою кнопкою, щоб змінити опорний об'єкт",
"ui.construction.ribbon_label": "Доп. лінії",
"ui.context_menu": "Контекстне меню",
"ui.continuity_g0": "G0 злам",
@@ -4345,12 +4430,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": "Одиниці файлу",
@@ -4359,6 +4449,11 @@
"ui.polygon": "Багатокутник",
"ui.preferences": "Налаштування",
"ui.press_a_key": "натисніть клавішу…",
"ui.print_face_cleared": "{body}: грань друку скинуто",
"ui.print_face_input": "Грань друку",
"ui.print_face_one_body": "Грань друку належить одному тілу: надішліть це тіло окремо, щоб змінити її",
"ui.print_face_pick": "Виберіть плоску грань твердого тіла, щоб зробити її гранню друку",
"ui.print_face_set": "{body}: грань друку задано; «Надіслати в слайсер» кладе тіло на неї",
"ui.profile": "Профіль",
"ui.profile_plane": "Площина профілю",
"ui.profiles": "Профілі",
@@ -4422,6 +4517,7 @@
"ui.revolve": "Обертання",
"ui.ribbon_exit": "Вихід",
"ui.ribbon_labels": "Показувати текст під піктограмами стрічки",
"ui.ribbon_menus_skip_pinned": "Не показувати закріплені команди у випадних меню",
"ui.ribbon_pin_footer": "Закріплені команди відображаються на стрічці. Наведіть курсор на рядок, щоб закріпити або відкріпити його.",
"ui.ribbon_workspace": "Робоча область",
"ui.right": "Справа",
@@ -4481,6 +4577,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": "Розріз",
@@ -4585,9 +4686,14 @@
"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_theme_follow": "За темою",
"ui.visual_shaded_hidden_edges": "Затінений із прихованими ребрами",
@@ -4625,7 +4731,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.",
+88
View File
@@ -60,6 +60,7 @@
"BLEND SURFACE": "MẶT NỐI MƯỢT",
"BOUNDARY SURFACE": "BỀ MẶT BIÊN DẠNG",
"BROWSER": "CÂY ĐỐI TƯỢNG",
"Back to the original order": "Trở về thứ tự ban đầu",
"Ball": "Cầu",
"Bar": "Thép thanh",
"Base Center": "Tâm đáy",
@@ -595,6 +596,8 @@
"Snap to face and edge centres": "Bắt vào tâm mặt và tâm cạnh",
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft không thể gửi báo cáo trực tiếp. Hãy gửi trên GitHub; ứng dụng không cần mã truy cập.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft đã đóng trước khi các thiết kế này được lưu. Bạn có thể mở bản tự động lưu gần nhất của chúng.",
"Sort A to Z": "Sắp xếp từ A đến Z",
"Sort Z to A": "Sắp xếp từ Z đến A",
"Spacing 2 must be a non-zero number": "Khoảng cách 2 phải là một số khác 0",
"Spacing must be a non-zero number": "Khoảng cách phải là một số khác 0",
"Specialise": "Chuyên biệt hóa",
@@ -819,6 +822,7 @@
"cmd.appearance_pick_up": "Lấy diện mạo",
"cmd.appearance_replace": "Thay diện mạo",
"cmd.assembly_bom": "Bảng kê vật tư (BOM)",
"cmd.body.print_face": "Mặt in",
"cmd.body_rename": "Đổi tên thân khối",
"cmd.browser_collapse_all": "Thu gọn tất cả",
"cmd.browser_expand_all": "Mở rộng tất cả",
@@ -1412,6 +1416,7 @@
"help.sketch.construction_line.tangent_two": "Chọn hai đường tròn hoặc cung tròn rồi chọn tiếp tuyến chung trong bản xem trước. Tiếp điểm phải nằm trên các đường cong đã vẽ.",
"help.sketch.construction_line.vertical": "Tạo đường dựng hình thẳng đứng vô hạn qua một điểm. Ràng buộc đứng được giữ khi chỉnh sửa sau này.",
"help.sketch.equation_curve": "Vẽ một đường cong từ công thức trên một khoảng: x(t) và y(t), hoặc y = f(x). Công thức có thể dùng tham số và đường cong thay đổi theo: dùng cho sóng sin, đường xoắn ốc hay đường thân khai của răng bánh răng.",
"help.sketch.inspect": "Mở báo cáo về bản phác thảo đang mở: còn bao nhiêu bậc tự do và đã ràng buộc hoàn toàn chưa. Dùng nó để biết phần nào vẫn còn di chuyển được trước khi hoàn tất bản phác thảo.",
"help.solid.align": "Di chuyển các khối đã chọn để một điểm, mặt hoặc mặt phẳng trên chúng gặp một điểm, mặt hoặc mặt phẳng ở nơi khác. Dùng để đặt chi tiết áp vào chi tiết khác mà không cần đo khoảng cách.",
"help.solid.chamfer": "Vát các cạnh đã chọn theo khoảng cách bạn thiết lập. Dùng để triệt tiêu cạnh sắc hoặc giúp lắp chốt, bu lông dễ dàng hơn.",
"help.solid.combine": "Hợp nhất các khối, cắt trừ khỏi khối đích, hoặc chỉ giữ lại phần thể tích giao nhau, đồng thời có thể giữ lại các khối công cụ. Dùng để gộp chi tiết, dùng hình này cắt hình kia hoặc tìm vị trí hai khối giao nhau.",
@@ -1614,6 +1619,10 @@
"material.selection.mixed": "Các vật liệu khác nhau",
"measure.at": "Tại",
"measure.item": "Mục {number}",
"measure.quick_copied": "Đã sao chép số đo",
"measure.quick_copy": "Nhấp để sao chép số đo",
"measure.quick_edges": "{count} cạnh",
"measure.quick_faces": "{count} mặt",
"messages.ago_h": "{n} giờ trước",
"messages.ago_min": "{n} phút trước",
"messages.ago_s": "{n} giây trước",
@@ -1766,6 +1775,11 @@
"not a move": "không phải một phép di chuyển",
"not a number": "không phải là số",
"open a sketch first": "hãy mở một phác thảo trước",
"parameters.apply_live": "Áp dụng mỗi thay đổi khi rời khỏi ô của nó",
"parameters.apply_live_tip": "Tắt: các thay đổi chờ đến khi nhấn Áp dụng hoặc OK, rồi thiết kế được dựng lại một lần cho tất cả. Hủy hoặc Escape sẽ bỏ chúng.",
"parameters.held": "{count} thay đổi đang chờ Áp dụng",
"parameters.held_hint": "Sửa biểu thức và đơn vị. Thay đổi chờ đến khi nhấn Áp dụng hoặc OK.",
"parameters.held_one": "1 thay đổi đang chờ Áp dụng",
"parts.automatic_name": "tự động",
"parts.dimension.across_flats": "Cỡ cờ lê",
"parts.dimension.bore": "Đường kính lỗ",
@@ -1788,6 +1802,8 @@
"parts.family.set_screw": "Vít trí lục giác chìm, đầu bằng",
"parts.family.socket_head_cap_screw": "Bu lông lục giác chìm đầu trụ",
"parts.family.washer": "Vòng đệm phẳng",
"parts.holes": "Lỗ (tùy chọn)",
"parts.placement_hint": "chọn một hoặc nhiều lỗ để lắp một chi tiết vào mỗi lỗ, hoặc nhấn OK để đặt tại gốc tọa độ",
"pattern.alignment_2": "Căn chỉnh 2",
"pattern.alignment_hint": "Số lượng bao gồm cả bản gốc. Căn giữa sẽ cân bằng các bản sao; Đổi hướng sẽ chọn phía bổ sung cho tổng số chẵn.",
"pattern.direction_1": "Hướng 1",
@@ -1840,6 +1856,8 @@
"prefs.bottom_centre": "Giữa phía dưới",
"prefs.bottom_right": "Góc dưới bên phải",
"prefs.browser": "Cây thư mục",
"prefs.browser_eye_left": "Biểu tượng mắt cạnh mũi tên thu gọn",
"prefs.browser_eye_left_note": "Các hàng trong cây thư mục hiển thị biểu tượng mắt bật/tắt cạnh mũi tên thu gọn như trước đây, thay vì trong một cột ở đầu bên phải của các hàng.",
"prefs.browser_reveal_on_hover": "Hiện khi di chuột",
"prefs.browser_show_note": "Hiển thị giữ trình duyệt trên màn hình. Hiện khi di chuột ẩn nó cho đến khi con trỏ đến vị trí của nó hoặc thẻ mảnh ở cạnh trái, và nó vẫn mở khi con trỏ ở bên trong hoặc một menu của nó đang mở.",
"prefs.browser_shown": "Hiển thị",
@@ -1890,6 +1908,8 @@
"prefs.graphics_card_restart": "Có hiệu lực sau khi khởi động lại SolidCraft. Biến môi trường WGPU_ADAPTER_NAME sẽ ghi đè lựa chọn này.",
"prefs.graphics_note": "Cân đối giữa tốc độ và độ mượt mà. Mỗi tùy chọn đều hiển thị hiệu ứng trực quan trên mẫu thử.",
"prefs.graphics_presets_note": "Các cấu hình định sẵn sẽ thiết lập đồng thời các tùy chọn bên dưới.",
"prefs.hide_constraint_glyphs_zoomed_out": "Ẩn biểu tượng ràng buộc khi thu nhỏ",
"prefs.hide_constraint_glyphs_zoomed_out_note": "Biểu tượng ràng buộc biến mất khi bản phác được thu nhỏ đủ xa để không làm rối khung nhìn. Tắt: chúng giữ kích thước màn hình cố định ở mọi mức thu phóng.",
"prefs.history": "Lịch sử",
"prefs.history_origin_bodies": "Hiển thị gốc tọa độ và thân khối ở trên cùng",
"prefs.history_origin_bodies_note": "Cây Lịch sử ở bên trái hoặc dưới cây thư mục sẽ bắt đầu với thư mục Thân khối và Gốc tọa độ của cụm chi tiết đang kích hoạt, cùng biểu tượng mắt bật/tắt và trình đơn tương tự như trong cây thư mục. Dòng Lịch sử ở cạnh dưới không thay đổi.",
@@ -1950,12 +1970,16 @@
"prefs.origin_plane_names": "Tên các mặt phẳng gốc",
"prefs.origin_plane_names_note": "Tên hiển thị trong trình duyệt, hộp thoại và danh sách. {top} là XY, {front} là XZ và {right} là YZ, với Z hướng lên. Lệnh, script và thiết kế đã lưu vẫn dùng XY, XZ và YZ.",
"prefs.overall_speed": "Tốc độ tổng thể",
"prefs.parameter_list_rows": "Số dòng danh sách tham số",
"prefs.parameter_list_rows_tip": "Phím mũi tên xuống trong ô giá trị trống liệt kê các tham số, mục yêu thích trước, tối đa số này. Ít hơn giúp danh sách nhẹ trong thiết kế lớn.",
"prefs.persistent_ribbon_pressed": "Tô sáng lệnh đang bật trên dải băng",
"prefs.persistent_ribbon_pressed_help": "Tô sáng các lệnh trên dải băng khi hiệu ứng của chúng đang bật. Tắt tùy chọn này để giữ giao diện trung tính và trợ giúp lệnh như trước.",
"prefs.perspective_fov": "Trường nhìn",
"prefs.placement": "Vị trí đặt",
"prefs.prefer_free_sketch_points": "Ưu tiên điểm phác thảo có thể di chuyển khi các điểm chồng lên nhau",
"prefs.preview_choices": "Xem trước các lựa chọn của bạn",
"prefs.print_face_highlight": "Tô sáng mặt in",
"prefs.print_face_highlight_tip": "Mức tô màu và viền mặt in của thân trong khung nhìn. 0 % không tô sáng.",
"prefs.profile_active": "Hồ sơ đang dùng",
"prefs.profile_applied": "Đã áp dụng hồ sơ.",
"prefs.profile_changed": "Cài đặt đã thay đổi kể từ khi lưu hồ sơ. “Lưu” sẽ ghi chúng vào hồ sơ.",
@@ -1977,6 +2001,8 @@
"prefs.quality_fastest": "nhanh nhất",
"prefs.quality_slowest": "chậm nhất (chất lượng cao nhất)",
"prefs.quality_smoother": "mượt mà hơn",
"prefs.quick_measure": "Đo nhanh ở góc khung nhìn",
"prefs.quick_measure_note": "Hiển thị kích thước của đối tượng được chọn ở góc dưới bên phải khung nhìn, phía trên Lịch sử: chiều dài của cạnh (hoặc Ø và R với cạnh tròn), diện tích của mặt, vị trí của điểm, khoảng cách và góc giữa hai đối tượng, tổng của nhiều cạnh hoặc mặt. Nhấp để sao chép.",
"prefs.render_previous_geometry_budget": "Dùng ngân sách hình học trước đây cho bản xem trước kết xuất",
"prefs.render_previous_geometry_budget_help": "Khôi phục giới hạn hình học cố định trước đây (512 MiB trên máy tính 64 bit, 128 MiB trên các nền tảng khác) thay cho ngân sách dựa trên thiết bị.",
"prefs.reports_saved": "Đã lưu {n}",
@@ -1990,6 +2016,8 @@
"prefs.search_empty": "Không có cài đặt nào khớp",
"prefs.select": "Chọn",
"prefs.select_one_item": "Chọn một mục",
"prefs.select_other_hidden": "Chọn hình học ẩn bằng Chọn đối tượng khác",
"prefs.select_other_hidden_help": "Bật: bao gồm các khối, bản phác thảo ẩn và cạnh bị che gần con trỏ. Tắt: dùng danh sách ban đầu chỉ gồm hình học đang hiển thị ngay dưới con trỏ.",
"prefs.selecting": "Đang chọn",
"prefs.shift": "Shift",
"prefs.shift_add_ctrl_toggle": "Shift chỉ thêm; Ctrl / Cmd đảo trạng thái chọn",
@@ -2068,6 +2096,8 @@
"prefs.under_cursor": "Dưới con trỏ chuột",
"prefs.units_precision": "Đơn vị và độ chính xác",
"prefs.updates": "Cập nhật ứng dụng",
"prefs.value_boxes_in_view": "Hiện giá trị cạnh tay nắm trong khung nhìn",
"prefs.value_boxes_in_view_tip": "Giá trị của lệnh (Khoảng cách, Bán kính…) trong một ô cạnh tay nắm, phía trên mô hình. Tắt: bạn nhập giá trị trong cửa sổ lệnh, cửa sổ này nhận bàn phím sau khi chọn, và không có gì che mô hình.",
"prefs.view": "Hành vi góc nhìn",
"prefs.view_confirm_corner": "Nút Đồng ý và Hủy bỏ ở góc khung nhìn",
"prefs.view_confirm_corner_note": "Khi hộp thoại hoặc môi trường phác thảo đang mở, dấu tích và dấu chéo ở góc trên bên phải khung nhìn có chức năng tương tự nút Đồng ý và Hủy bỏ của hộp thoại, hoặc để hoàn tất và hủy phác thảo. Khối điều hướng góc nhìn sẽ dời xuống bên dưới chúng.",
@@ -2077,6 +2107,10 @@
"prefs.viewport_background": "Nền",
"prefs.viewport_background_colour": "Màu",
"prefs.viewport_background_theme": "Dải màu của chủ đề",
"prefs.viewport_section_status": "Hiện trạng thái mặt cắt trong khung nhìn",
"prefs.viewport_section_status_help": "Hiện thiết lập mặt cắt trong khung nhìn. Escape và nút dải công cụ đang bật sẽ tắt mặt cắt. Tắt tùy chọn để giữ cách điều khiển mặt cắt trước đây.",
"prefs.viewport_status_summary": "Tóm tắt các mục trạng thái khung nhìn",
"prefs.viewport_status_summary_help": "Gộp từ ba mục trạng thái khung nhìn trở lên thành một huy hiệu tóm tắt. Tắt để giữ mọi mục trong hàng đầy đủ.",
"prefs.wheel": "Con lăn chuột",
"prefs.wheel_trackpad": "Con lăn chuột và bàn di cảm ứng",
"prefs.workspace": "Không gian làm việc",
@@ -2955,7 +2989,9 @@
"render.output_ready": "Sẵn sàng kết xuất",
"render.output_run_check": "Chạy kiểm tra",
"render.output_size_limit": "{method}: tối đa {dimension} px mỗi cạnh và {pixels} điểm ảnh. Kích thước lớn nhất vừa khít: {width} × {height}.",
"render.output_size_method_profile": "Để kết xuất {width} × {height}, hãy chọn {method} với cấu hình tài nguyên CPU {profile} hoặc một kích thước nhỏ hơn.",
"render.output_size_ok": "Kích thước ổn · {width} × {height} px",
"render.output_size_profile": "Để kết xuất {width} × {height}, hãy chọn cấu hình tài nguyên CPU {profile} hoặc một kích thước nhỏ hơn.",
"render.output_swap_orientation": "Hoán đổi chiều rộng và chiều cao",
"render.output_tools_inspector": "Chỉnh kích thước và định dạng trong bảng thuộc tính Đầu ra.",
"render.output_view_image": "Xem ảnh",
@@ -3168,6 +3204,7 @@
"render.size_custom": "Tùy chỉnh…",
"render.size_full_hd": "Full HD",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · cần {profile}",
"render.size_presets": "Cỡ đặt sẵn",
"render.size_presets_pending": "Sẽ có cùng trình chỉnh Đầu ra",
"render.size_preview": "Xem trước",
@@ -3455,6 +3492,24 @@
"sheet.flange_type_contour": "Đường viền",
"sheet.flange_type_edge": "Cạnh",
"sheet.no_body": "chưa có khối tấm kim loại: hãy bắt đầu bằng Cánh gập > Cơ sở (Base), hoặc chuyển một tấm phẳng thành tấm kim loại bằng lệnh 'Chuyển thành tấm kim loại' (chọn mặt phẳng lớn của nó)",
"sheet.personal_rules.confirm_delete": "Xác nhận xóa",
"sheet.personal_rules.delete": "Xóa",
"sheet.personal_rules.delete_prompt": "Xóa quy tắc cá nhân này? Các quy tắc đã có trong thiết kế sẽ không thay đổi.",
"sheet.personal_rules.full": "Thư viện cá nhân đã chứa tối đa 64 quy tắc.",
"sheet.personal_rules.invalid_name": "Nhập một tên ngắn gọn, không chứa ký tự xuống dòng hoặc ký tự điều khiển.",
"sheet.personal_rules.invalid_values": "Hãy điền đủ các giá trị của quy tắc: mỗi giá trị phải tính được trong thiết kế này, với độ dày dương và hệ số K từ 0 đến 1.",
"sheet.personal_rules.missing": "Quy tắc cá nhân đã chọn không còn khả dụng.",
"sheet.personal_rules.name": "Tên quy tắc",
"sheet.personal_rules.name_exists": "Quy tắc cá nhân với tên này đã tồn tại. Vui lòng sử dụng tên khác.",
"sheet.personal_rules.name_hint": "Tên duy nhất",
"sheet.personal_rules.overwrite": "Lưu các thay đổi vào quy tắc cá nhân đã chọn",
"sheet.personal_rules.rename": "Đổi tên",
"sheet.personal_rules.rule": "Quy tắc cá nhân",
"sheet.personal_rules.save": "Lưu mới",
"sheet.personal_rules.scope": "Được lưu trong hồ sơ cá nhân của bạn cho tất cả thiết kế.",
"sheet.personal_rules.select": "Chọn một quy tắc",
"sheet.personal_rules.title": "Quy tắc cá nhân",
"sheet.personal_rules.unsupported": "Quy tắc này có các giá trị mà phiên bản hiện tại không thể sử dụng.",
"shortcut.json_error": "tham số phải là JSON: {error}",
"shortcut.no_command": "không có lệnh `{id}`; văn bản trước JSON phải là mã lệnh",
"shortcut.pin": "Ghim lên đầu hộp S",
@@ -3491,6 +3546,7 @@
"sketch_text.align_right": "Phải",
"sketch_text.bold": "In đậm",
"sketch_text.builtin_font": "{family} (tích hợp sẵn)",
"sketch_text.formula": "Bắt đầu bằng = để tạo chữ từ các tham số: ='W' + number(Width)",
"sketch_text.italic": "In nghiêng",
"sketch_text.loading_fonts": "Đang tìm kiếm các phông chữ đã cài đặt…",
"sketch_text.on_path": "Văn bản đi theo đường dẫn: đường cong định vị văn bản",
@@ -3631,6 +3687,8 @@
"ui.3d_mouse_section": "Chuột 3D",
"ui.3d_mouse_show_in_view": "Hiện chuột 3D trong khung nhìn 3D",
"ui.3d_mouse_show_in_view_tip": "Hình chuột 3D của bạn ở góc dưới bên trái, chuyển động theo bạn.",
"ui.3d_mouse_sketch_orbit": "Xoay bản phác thảo bằng chuột 3D",
"ui.3d_mouse_sketch_orbit_tip": "Chuột 3D cũng xoay khung nhìn khi bản phác thảo đang mở và được nhìn thẳng, như chuột thường. Tắt: chỉ di chuyển và thu phóng bản phác thảo cho đến khi bạn nghiêng khung nhìn.",
"ui.3d_mouse_skip": "Thiết bị của tôi không làm được điều này",
"ui.3d_mouse_skip_tip": "Bỏ qua chuyển động này theo cả hai chiều; việc hiệu chuẩn sẽ không dùng nó.",
"ui.3d_mouse_speed": "Tốc độ chuột 3D",
@@ -3659,6 +3717,8 @@
"ui.3d_mouse_step_twist_counterclockwise": "Xoay núm điều khiển ngược chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_switch_cap": "Hiệu chuẩn theo kiểu trở về giữa",
"ui.3d_mouse_switch_trackball": "Hiệu chuẩn như bi xoay",
"ui.3d_mouse_use_direct": "Dùng trình điều khiển trực tiếp",
"ui.3d_mouse_use_direct_tip": "Đọc chuột 3D trực tiếp (hidraw) thay vì qua spacenavd.",
"ui.3d_mouse_view_axis": "Trục khung nhìn",
"ui.3d_mouse_view_off": "Tắt",
"ui.3d_mouse_view_off_tip": "Hồ sơ của trình điều khiển chuột 3D tắt trục này, nên chuyển động của nó không làm khung nhìn di chuyển. Chọn chuyển động của trục khác ở đây sẽ hoán đổi hai trục, và chuyển động đó không còn di chuyển khung nhìn.",
@@ -3792,6 +3852,8 @@
"ui.agent_chat_remove": "Xóa khỏi hàng đợi",
"ui.agent_chat_remove_file": "Bỏ tệp này khỏi tin nhắn",
"ui.agent_chat_remove_imported": "Gỡ khỏi tin nhắn này; chi tiết vẫn nằm trong thiết kế (sử dụng Hoàn tác để xóa)",
"ui.agent_chat_reply_cut_off": "Câu trả lời đã chạm giới hạn độ dài trước khi xong nên có thể chưa đầy đủ. Hãy yêu cầu AI Agent tiếp tục, hoặc trả lời ngắn hơn.",
"ui.agent_chat_reply_declined": "Mô hình đã từ chối trả lời tin nhắn này.",
"ui.agent_chat_retry": "Thử lại",
"ui.agent_chat_right": "Phải (Hình chiếu cạnh)",
"ui.agent_chat_save": "Lưu",
@@ -3975,6 +4037,7 @@
"ui.circle": "Hình tròn",
"ui.circular_pattern": "Mảng tròn quanh trục",
"ui.clear": "Xóa",
"ui.clear_print_face": "Xóa mặt in",
"ui.clear_recent": "Xóa danh sách gần đây",
"ui.clear_the_selection": "Hủy chọn",
"ui.click_selects_whole_bodies": "Nhấp chuột chọn toàn bộ thân khối",
@@ -4407,12 +4470,17 @@
"ui.plugins_skipped": "Không thể đọc {count} thư mục.",
"ui.plugins_version": "{version}",
"ui.point": "Điểm",
"ui.point_cloud_add_filter": "Thêm bộ lọc…",
"ui.point_cloud_auto_range": "Phạm vi tự động",
"ui.point_cloud_color_by": "Tô màu theo",
"ui.point_cloud_file_colors": "Màu trong tệp",
"ui.point_cloud_filter_by": "Lọc theo",
"ui.point_cloud_height_high": "Cao nhất",
"ui.point_cloud_height_low": "Thấp nhất",
"ui.point_cloud_legend_filter": "Hiện và ẩn trong chú giải ở trên",
"ui.point_cloud_no_file_colors": "Tệp không có màu",
"ui.point_cloud_pause": "Tạm dừng",
"ui.point_cloud_play": "Phát",
"ui.point_cloud_single_color": "Một màu",
"ui.point_cloud_size": "Kích thước điểm",
"ui.point_cloud_units": "Đơn vị của tệp",
@@ -4421,6 +4489,11 @@
"ui.polygon": "Đa giác",
"ui.preferences": "Tùy chọn cài đặt",
"ui.press_a_key": "nhấn một phím…",
"ui.print_face_cleared": "{body}: đã xóa mặt in",
"ui.print_face_input": "Mặt in",
"ui.print_face_one_body": "Mặt in được chọn cho một thân: hãy gửi riêng thân đó để thay đổi",
"ui.print_face_pick": "Chọn một mặt phẳng của thân đặc để đặt làm mặt in",
"ui.print_face_set": "{body}: đã đặt mặt in; Gửi đến Slicer sẽ đặt thân lên mặt này",
"ui.profile": "Biên dạng",
"ui.profile_plane": "Mặt phẳng biên dạng",
"ui.profiles": "Các biên dạng",
@@ -4503,6 +4576,7 @@
"ui.selectOther": "Chọn đối tượng khác…",
"ui.selectTangentChain": "Chọn chuỗi tiếp tuyến",
"ui.select_all": "Chọn tất cả",
"ui.select_components_by_face": "Chọn thành phần (chọn một mặt sẽ chọn thành phần chứa mặt đó)",
"ui.selection": "Vùng chọn",
"ui.selection_modifiers": "Phím bổ trợ chọn",
"ui.selection_shift_add_only": "Shift+nhấp chỉ thêm, không bỏ chọn",
@@ -4544,6 +4618,11 @@
"ui.sketch_intersect_selection_filter": "Bộ lọc lựa chọn",
"ui.sketch_modifiers": "Phím bổ trợ phác thảo",
"ui.sketch_project_geometry": "Hình học",
"ui.sketch_report.all_constrained": "Mọi thứ đã được ràng buộc hoàn toàn.",
"ui.sketch_report.dof": "Bậc tự do",
"ui.sketch_report.loose": "Chưa ràng buộc hoàn toàn",
"ui.sketch_report.status": "Trạng thái",
"ui.sketch_report.title": "Báo cáo phác thảo",
"ui.sketch_scale": "Tỷ lệ phác thảo",
"ui.sketches": "Các phác thảo",
"ui.slice": "Cắt lát",
@@ -4653,7 +4732,14 @@
"ui.viewport_status_key": "Hiển thị chú giải cho {name}",
"ui.viewport_status_minimum_radius": "Bán kính nhỏ nhất",
"ui.viewport_status_pairs": "{count} cặp",
"ui.viewport_status_section_hatch": "{setting} · gạch mặt cắt",
"ui.viewport_status_section_plane": "Mặt phẳng hiện tại",
"ui.viewport_status_section_tilted": "nghiêng · {offset}",
"ui.viewport_status_stripes": "{count} sọc",
"ui.viewport_status_summary_clear": "Tắt tất cả",
"ui.viewport_status_summary_heading": "Trong khung nhìn này",
"ui.viewport_status_summary_label": "{name} và {count} mục khác",
"ui.viewport_status_summary_more": "+{count}",
"ui.viewport_theme_follow": "Theo chủ đề giao diện",
"ui.visual_shaded_hidden_edges": "Tô bóng kèm nét khuất",
"ui.visual_shaded_with_edges": "Tô bóng kèm hiển thị cạnh",
@@ -4690,7 +4776,9 @@
"update.button": "Cập nhật",
"update.button_version": "Cập nhật lên {version}",
"update.check_automatically": "Tự động kiểm tra bản cập nhật",
"update.check_for_updates": "Kiểm tra bản cập nhật",
"update.check_now": "Kiểm tra ngay",
"update.check_unavailable": "Không thể kiểm tra bản cập nhật cho bản cài đặt này.",
"update.checking": "Đang kiểm tra…",
"update.copy": "Sao chép",
"update.dmg_opened": "Ảnh đĩa đã mở và được xác thực: kéo SolidCraft vào thư mục Applications để thay thế bản cũ, sau đó khởi động lại SolidCraft.",
+77
View File
@@ -181,6 +181,8 @@
"Designs you open or save appear here.": "您開啟或儲存的設計會顯示在此處。",
"Determined by To Object. Choose Angle to edit the angular extent.": "由「到物件」確定。選擇「角度」可編輯角度範圍。",
"Diameter": "直徑",
"Did this assembly open the wrong way up?": "此組合件開啟時的朝向是否不對?",
"Did this part open the wrong way up?": "此零件開啟時的朝向是否不對?",
"Dimension new shapes": "為新圖形標註尺寸",
"Direction": "方向",
"Discard": "捨棄",
@@ -601,6 +603,7 @@
"Square": "正方形",
"Stand it up": "將其立起",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "將其立起:繞 X 軸旋轉四分之一週,作為一個可復原的移動特徵。",
"Stand this assembly up?": "立起此組合件?",
"Stand this part up?": "立起此零件?",
"Start angle": "起始角度",
"Start bulge": "起始鼓度",
@@ -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": "「擷取位置」保留新位置;「還原位置」還原原先的位置。",
@@ -814,6 +819,7 @@
"cmd.appearance_pick_up": "吸取外觀",
"cmd.appearance_replace": "取代外觀",
"cmd.assembly_bom": "物料清單",
"cmd.body.print_face": "列印底面",
"cmd.body_rename": "重新命名實體",
"cmd.browser_collapse_all": "全部摺疊",
"cmd.browser_expand_all": "全部展開",
@@ -1323,6 +1329,7 @@
"dialog.dimension_insert_hint": "點擊其他驅動尺寸以插入其名稱",
"dialog.dimension_name": "尺寸:{name}",
"dialog.edit_title": "編輯 {title}",
"dialog.no_shown_body": "沒有可變更的已顯示實體:請顯示一個,或選擇「新建實體」",
"dialog.select": "↖ 選擇",
"dialog.selected_count": "↖ 已選擇 {count} 個",
"dialog.selection_guidance": "選擇輸入項進行修改。點擊已選物件可取消選擇;按住 Ctrl/Cmd 點擊也可切換選擇。檢查預覽後,再按「確定」。",
@@ -1395,6 +1402,16 @@
"help.occurrence.move": "僅沿接頭允許的自由度移動組合件零件。請選取零件的面,然後拖曳可用的箭頭或圓弧。",
"help.parameters.change": "開啟用於驅動設計尺寸的具名數值與運算式表。用於在同一處修改尺寸,讓所有使用該尺寸的特徵隨之更新。",
"help.pointcloud.insert": "將 PLY、XYZ 或 PTS 掃描資料作為點雲插入模型旁。以實測資料建立模型時,可作為參考。",
"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.equation_curve": "依公式繪製曲線:在指定範圍內使用 x(t) 和 y(t),或 y = f(x)。公式可以使用參數,曲線會隨之更新:可用於正弦波、螺旋線或齒輪齒形的漸開線。",
"help.solid.align": "移動所選實體,使其上的點、面或平面與別處的點、面或平面對齊。用於將一個零件貼靠到另一個零件上,而無需測量偏移量。",
"help.solid.chamfer": "以指定的距離在所選邊上切出平直的斜面。用於去除銳邊,或使銷釘、螺絲更容易插入。",
@@ -1422,6 +1439,8 @@
"help.timeline.compute_all": "從頭重新計算時間軸中的所有特徵。用於重新整理模型,並查看哪些特徵失敗。",
"help.timeline.delete": "從時間軸中刪除所選特徵;刪除草圖時,由它建立的特徵也會一併刪除。用於移除不再需要的步驟。",
"help.timeline.play": "逐一重播其餘特徵,每次重建後暫停半秒。再次執行此命令或按 Escape 可停止;「移至結尾」會立即跳到結尾。",
"history.sketch_under_constrained": "拘束不足({count} 個自由度)",
"history.sketch_under_constrained_one": "拘束不足({count} 個自由度)",
"hole_presets.confirm_delete": "確認刪除",
"hole_presets.delete": "刪除",
"hole_presets.delete_prompt": "刪除此個人預設?現有的孔不會改變。",
@@ -1810,6 +1829,8 @@
"prefs.anti_aliasing_note": "平滑鋸齒狀邊緣。",
"prefs.applies_on_restart": "重新啟動後生效",
"prefs.assistant": "助理",
"prefs.auto_operation": "自動選擇操作",
"prefs.auto_operation_note": "特徵視窗會依新材料接觸到的內容,自動選擇「接合」、「新建實體」或「剪下」,直到您自行選擇為止。關閉時:維持開啟時的操作。",
"prefs.axes": "軸",
"prefs.axis": "軸",
"prefs.bodies_folders": "實體資料夾",
@@ -1820,6 +1841,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": "顯示",
@@ -1870,6 +1893,8 @@
"prefs.graphics_card_restart": "重新啟動 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": "左側或瀏覽器下方的歷程記錄,會以作用中元件的「實體」資料夾和「原點」開頭,其顯示切換(眼睛圖示)和選單與瀏覽器中相同。底部的歷程記錄維持不變。",
@@ -1908,6 +1933,9 @@
"prefs.max_file_size_step": "STEP 和匯入檔案",
"prefs.max_file_size_web": "網頁版可開啟最大 512 MB 的 STEP 和匯入檔案,以及最大 256 MB 的設計:瀏覽器分頁無法容納更大的檔案。桌面版可提高這些上限。",
"prefs.megabytes": "MB",
"prefs.menu_grid": "大圖示",
"prefs.menu_list": "清單",
"prefs.menus": "選單",
"prefs.minutes": "分鐘",
"prefs.minutes_suffix": " min",
"prefs.mouse": "滑鼠與觸控板",
@@ -1927,10 +1955,14 @@
"prefs.origin_plane_names": "原點平面名稱",
"prefs.origin_plane_names_note": "顯示於瀏覽器、對話方塊和清單中的名稱。{top} 為 XY,{front} 為 XZ,{right} 為 YZ,Z 軸朝上。命令、指令碼和已儲存的設計仍使用 XY、XZ 和 YZ。",
"prefs.overall_speed": "整體速度",
"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": "儲存設定檔後設定已有變更。按「儲存」可將變更保存到設定檔中。",
@@ -1965,6 +1997,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 切換選擇",
@@ -1976,16 +2010,21 @@
"prefs.sketch_align_points_note": "繪製時,點可同時與一個草圖點水平對齊、並與另一個草圖點垂直對齊,並顯示點線輔助線,按一下會保留兩個對齊。關閉:只與前一個點對齊,一次一個軸向。",
"prefs.sketch_conflict_colour": "衝突幾何以衝突顏色顯示",
"prefs.sketch_conflict_colour_note": "草圖的拘束發生衝突時,失敗拘束所涉及的曲線與點也會以衝突顏色繪製,而不只是其符號或尺寸。關閉時:維持原本的顏色。",
"prefs.sketch_construction_lines": "延伸建構直線工具",
"prefs.sketch_exit_view": "草圖結束時",
"prefs.sketch_exit_view.keep": "保持目前檢視",
"prefs.sketch_exit_view.previous": "返回先前的檢視",
"prefs.sketch_exit_view.unless_rotated": "未旋轉時返回",
"prefs.sketch_exit_view_note": "「完成草圖」、取消或連按兩次 Esc 後檢視的去向。",
"prefs.sketch_open_look_at": "開啟草圖時轉向正對草圖",
"prefs.sketch_open_look_at_note": "關閉:建立或編輯草圖時維持目前的檢視。需要時可用「注視」正對草圖。",
"prefs.sketch_recent_snap": "自動鎖點至最近使用的圓心與中點",
"prefs.sketch_recent_snap_note": "繪製時,游標會鎖點至最近使用的兩個圓心或直線邊中點的輔助線交會處。關閉時:這些輔助線僅供顯示,游標不會移動。",
"prefs.sketch_selection": "草圖與選取",
"prefs.sketch_selection_heading": "草圖與選取",
"prefs.sketch_selection_note": "草圖如何貼齊,以及點擊如何選取。",
"prefs.sketch_state_icons": "草圖圖示以其狀態的顏色顯示",
"prefs.sketch_state_icons_note": "在歷程記錄與瀏覽器中,草圖圖示會採用其狀態的顏色(拘束不足、完全拘束或衝突),其工具提示也會註明該狀態。關閉時:所有草圖使用相同圖示。",
"prefs.sketch_tab_always": "保持顯示「草圖」索引標籤",
"prefs.sketch_tab_always_note": "沒有開啟草圖時,「草圖」索引標籤仍保留在功能區中,位於「模型」之後。使用其繪圖工具時,會在所選的平面或面上、或在你接著選取的平面上開始草圖,並直接啟用該工具。",
"prefs.sketch_tracking_guides": "圓心與中點對齊輔助線",
@@ -1995,6 +2034,11 @@
"prefs.source": "來源",
"prefs.speed": "速度",
"prefs.stand_up_note": "以 Z 軸向上開啟 STEP 或 IGES 檔案後,詢問是否繞 X 軸旋轉 90°。",
"prefs.stand_up_unknown": "來自未知程式的檔案",
"prefs.stand_up_unknown.ask": "在對話方塊中詢問",
"prefs.stand_up_unknown.note": "顯示提示",
"prefs.stand_up_unknown.off": "不提供",
"prefs.stand_up_unknown_note": "當檔案標頭沒有指出任何已知以 Y 軸向上儲存的程式時,要如何提出詢問。「顯示提示」只會在檔案看起來平躺時顯示;「在對話方塊中詢問」則和以往一樣,對每一個這類檔案都會詢問。",
"prefs.status_bar": "狀態列",
"prefs.status_bar_note": "視窗底部的細長列會顯示正在編輯的內容、以顏色表示的開啟中草圖狀態,以及設計的單位;按一下單位即可變更。草圖狀態就不會再顯示在檢視的角落。",
"prefs.status_done": "完成",
@@ -2037,6 +2081,8 @@
"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": "主題漸層",
@@ -2049,6 +2095,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} 分鐘前",
@@ -3503,8 +3553,10 @@
"timeline.move_to_group": "將特徵移至資料夾",
"timeline.rename_group": "重新命名特徵資料夾",
"timeline.reorder_many": "特徵重新排序",
"timeline.suppress_features": "抑制特徵",
"timeline.suppress_folders_tip": "抑制或取消抑制資料夾及其巢狀資料夾中的所有特徵",
"timeline.ungroup": "移除特徵資料夾",
"timeline.unsuppress_features": "取消抑制特徵",
"to the face": "到面",
"to the next face": "到下一個面",
"toolbar.customize_note": "{tab} 索引標籤。勾選命令可將其按鈕新增到工具列。您也可以將面板下拉選單中的某一行拖到工具列,將按鈕拖出工具列以移除,或使用下拉選單行中的固定圖示。",
@@ -3616,6 +3668,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 滑鼠驅動程式的設定檔關閉了此軸,因此它的動作不會移動檢視。在此選擇另一個軸的動作會將兩者互換,該動作將不再移動檢視。",
@@ -3922,6 +3976,8 @@
"ui.cancel": "取消",
"ui.canvases": "畫布",
"ui.center": "中心",
"ui.center_arc_follows_cursor": "中心點圓弧跟隨游標的方向",
"ui.center_arc_follows_cursor_tip": "中心點圓弧會依游標繞其圓心的方向,順時針或逆時針繪製。關閉時:一律從起點起逆時針繪製。",
"ui.centerline": "中心線",
"ui.chain": "鏈",
"ui.chamfer": "倒角",
@@ -3930,6 +3986,7 @@
"ui.circle": "圓",
"ui.circular_pattern": "環形陣列",
"ui.clear": "清除",
"ui.clear_print_face": "清除列印底面",
"ui.clear_recent": "清除最近",
"ui.clear_the_selection": "清除選擇",
"ui.click_selects_whole_bodies": "點擊選擇整個實體",
@@ -3964,6 +4021,14 @@
"ui.constant": "恆定",
"ui.constraints_conflict": "拘束條件衝突",
"ui.construction": "建構",
"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": "建構直線:選取一條直線,再選取一點;按右鍵可變更參考",
"ui.construction.ribbon_label": "輔助線",
"ui.context_menu": "右鍵選單",
"ui.continuity_g0": "G0 摺痕",
@@ -4368,6 +4433,11 @@
"ui.polygon": "多邊形",
"ui.preferences": "偏好設定",
"ui.press_a_key": "按一個鍵…",
"ui.print_face_cleared": "{body}:已清除列印底面",
"ui.print_face_input": "列印底面",
"ui.print_face_one_body": "列印底面屬於單一實體:請單獨傳送該實體以變更它",
"ui.print_face_pick": "選取實體的一個平面,將其設為列印底面",
"ui.print_face_set": "{body}:已設定列印底面;傳送到切片軟體時實體會放在此面上",
"ui.profile": "輪廓",
"ui.profile_plane": "輪廓平面",
"ui.profiles": "輪廓",
@@ -4431,6 +4501,7 @@
"ui.revolve": "旋轉",
"ui.ribbon_exit": "結束",
"ui.ribbon_labels": "顯示功能區圖示下的文字",
"ui.ribbon_menus_skip_pinned": "下拉選單中不列出已固定的命令",
"ui.ribbon_pin_footer": "固定的命令會顯示在功能區中。將指標停留在某列上即可固定或取消固定。",
"ui.ribbon_workspace": "工作區",
"ui.right": "右視",
@@ -4594,6 +4665,8 @@
"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} 對",
@@ -4634,7 +4707,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。",
@@ -4648,11 +4723,13 @@
"update.installed": "SolidCraft {version} 已安裝。重新啟動後生效。",
"update.installer_started": "SolidCraft 關閉後將啟動安裝程式。",
"update.later": "稍後提醒",
"update.managed": "有可用更新:請在 {manager} 中更新 SolidCraft",
"update.no_notes": "此版本沒有發佈說明。",
"update.package_hint": "此副本來自 {kind}。下載新套件後用以下命令安裝:",
"update.prefs_dev": "這是開發版本({version}),不檢查更新。",
"update.prefs_flatpak": "此副本({version})由 Flatpak 更新:執行 {command} 或使用軟體中心。SolidCraft 不會自行檢查。",
"update.prefs_label": "更新",
"update.prefs_managed": "目前為 SolidCraft {version},由 {manager} 管理。檢查時只向 GitHub 查詢最新版本,不傳送其他任何資料;請在 {manager} 中更新 SolidCraft。",
"update.prefs_note": "目前為 SolidCraft {version}({kind})。檢查時只向 GitHub 查詢最新版本,不傳送其他任何資料。",
"update.prefs_web": "重新載入頁面以取得最新的網頁應用程式。如果網路無法使用,或更新無法及時下載完成,則會改為開啟已儲存的版本;進行中的下載會繼續,供下次重新載入時使用。",
"update.refresh_checking": "正在檢查最新版本…",
+88
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": "专用操作",
@@ -819,6 +822,7 @@
"cmd.appearance_pick_up": "拾取外观",
"cmd.appearance_replace": "替换外观",
"cmd.assembly_bom": "物料清单",
"cmd.body.print_face": "打印底面",
"cmd.body_rename": "重命名实体",
"cmd.browser_collapse_all": "全部折叠",
"cmd.browser_expand_all": "全部展开",
@@ -1412,6 +1416,7 @@
"help.sketch.construction_line.tangent_two": "选择两个圆或圆弧,再从预览中选择公切线。接点必须位于已绘曲线上。",
"help.sketch.construction_line.vertical": "创建经过指定点的无限竖直构造直线。后续编辑时保持竖直约束。",
"help.sketch.equation_curve": "在一个范围内按公式绘制曲线:x(t) 和 y(t),或 y = f(x)。公式可以使用参数,曲线随参数变化。适用于正弦波、螺旋线或齿轮的渐开线。",
"help.sketch.inspect": "打开当前草图的报告:显示剩余的自由度以及是否已完全约束。在完成草图之前,用它找出仍然可以移动的部分。",
"help.solid.align": "移动所选实体,使其上的点、面或平面与别处的点、面或平面对齐。用于把一个零件贴靠到另一个零件上,而无需测量距离。",
"help.solid.chamfer": "用指定的距离在所选边上切出一个平直的斜面。用于去除锐边,或使销钉、螺钉更容易插入。",
"help.solid.combine": "将多个实体合并、从目标实体中切除,或只保留它们共有的体积,还可以保留工具实体。用于合并零件、用一个形体切割另一个形体,或找出两个实体重叠的位置。",
@@ -1614,6 +1619,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} 秒前",
@@ -1766,6 +1775,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 +1802,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 +1856,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": "显示",
@@ -1890,6 +1908,8 @@
"prefs.graphics_card_restart": "重启 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": "左侧或浏览器下方的历史以活动组件的实体文件夹和原点开头,并带有与浏览器相同的显示切换和菜单。底部的历史保持不变。",
@@ -1950,12 +1970,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": "保存配置文件后设置已更改。“保存”会将其写入配置文件。",
@@ -1977,6 +2001,8 @@
"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": "恢复先前的固定几何内存上限(64位桌面为512 MiB,其他平台为128 MiB),替代基于设备的预算。",
"prefs.reports_saved": "已保存 {n} 份",
@@ -1990,6 +2016,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 切换选择",
@@ -2068,6 +2096,8 @@
"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": "打开对话框或草图时,视图右上角的勾号和叉号与对话框的确定和取消作用相同,或完成和取消草图。视图导航器会下移到它们下方。",
@@ -2077,6 +2107,10 @@
"prefs.viewport_background": "背景",
"prefs.viewport_background_colour": "颜色",
"prefs.viewport_background_theme": "主题渐变",
"prefs.viewport_section_status": "显示视口剖面状态",
"prefs.viewport_section_status_help": "在视口中显示剖面设置。按 Escape 或单击活动的功能区按钮可关闭剖面。禁用以保留原有剖面控件。",
"prefs.viewport_status_summary": "汇总视口状态项",
"prefs.viewport_status_summary_help": "将三个或更多视口状态项折叠为一个汇总徽章。禁用后,所有项目仍显示在完整的一行中。",
"prefs.wheel": "滚轮",
"prefs.wheel_trackpad": "滚轮与触控板",
"prefs.workspace": "工作区",
@@ -2955,7 +2989,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_swap_orientation": "交换宽度和高度",
"render.output_tools_inspector": "请在输出检查器中编辑尺寸和格式。",
"render.output_view_image": "查看图像",
@@ -3168,6 +3204,7 @@
"render.size_custom": "自定义…",
"render.size_full_hd": "全高清",
"render.size_hd": "HD",
"render.size_needs_profile": "{size} · 需要“{profile}”",
"render.size_presets": "尺寸预设",
"render.size_presets_pending": "随输出编辑器推出",
"render.size_preview": "预览",
@@ -3455,6 +3492,24 @@
"sheet.flange_type_contour": "轮廓",
"sheet.flange_type_edge": "边线",
"sheet.no_body": "没有钣金实体:请使用法兰 > Base 创建,或使用转换为钣金将板材转换为钣金(选择其较大的平面)。",
"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 框顶部",
@@ -3491,6 +3546,7 @@
"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.on_path": "文字沿路径排列:由曲线放置",
@@ -3631,6 +3687,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 鼠标速度",
@@ -3659,6 +3717,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 鼠标驱动程序的配置文件关闭了此轴,因此它的动作不会移动视图。在此选择另一个轴的动作会将两者互换,该动作将不再移动视图。",
@@ -3792,6 +3852,8 @@
"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": "保存",
@@ -3975,6 +4037,7 @@
"ui.circle": "圆",
"ui.circular_pattern": "圆形阵列",
"ui.clear": "清除",
"ui.clear_print_face": "清除打印底面",
"ui.clear_recent": "清除最近",
"ui.clear_the_selection": "清除选择",
"ui.click_selects_whole_bodies": "单击选择整个实体",
@@ -4407,12 +4470,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": "文件单位",
@@ -4421,6 +4489,11 @@
"ui.polygon": "多边形",
"ui.preferences": "首选项",
"ui.press_a_key": "按一个键…",
"ui.print_face_cleared": "{body}:已清除打印底面",
"ui.print_face_input": "打印底面",
"ui.print_face_one_body": "打印底面属于单个实体:请单独发送该实体以更改它",
"ui.print_face_pick": "选择实体的一个平面,将其设为打印底面",
"ui.print_face_set": "{body}:已设置打印底面;发送到切片软件时实体将放在此面上",
"ui.profile": "轮廓",
"ui.profile_plane": "轮廓平面",
"ui.profiles": "轮廓",
@@ -4503,6 +4576,7 @@
"ui.selectOther": "选择其他…",
"ui.selectTangentChain": "选择相切链",
"ui.select_all": "全选",
"ui.select_components_by_face": "选择组件(单击面即选择其所属组件)",
"ui.selection": "选择",
"ui.selection_modifiers": "选择修饰键",
"ui.selection_shift_add_only": "Shift+点击仅添加、不取消选择",
@@ -4544,6 +4618,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": "切片",
@@ -4653,7 +4732,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": "带边着色",
@@ -4690,7 +4776,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。",
+115 -19
View File
@@ -243,6 +243,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 {
@@ -697,38 +735,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));
}
+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));
}
}
+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),
_ => {}
}
}
+88 -12
View File
@@ -2736,6 +2736,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 +2759,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 +2852,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 +3048,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 +3064,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 +3080,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,15 +3110,16 @@ 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);
}
}
@@ -3366,10 +3438,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 {
@@ -4014,7 +4090,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")));
}
+62
View File
@@ -647,3 +647,65 @@ 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}");
}
}
+41 -21
View File
@@ -350,8 +350,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 +370,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 +381,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 +413,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 +451,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 +466,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 +505,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;
@@ -1240,7 +1254,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}");
+29 -26
View File
@@ -172,32 +172,7 @@ impl Document {
author: u64,
author_path: &[u64],
) -> Result<FaceAnchor> {
// A construction certificate that names this face alone, else its exact history name.
let certified = body
.body
.face_plane(index)
.ok_or_else(|| DocError::Invalid("this face has no exact planar source; choose a fixed plane instead".into()))
.and_then(|source| {
let target = self.strong_face_target(body, index, source.origin)?;
if target.certificate.is_none() {
return Err(DocError::Invalid("this face has no supported construction certificate; choose a fixed plane instead".into()));
}
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.
if !indices.contains(&index) {
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 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()))?;
@@ -220,6 +195,34 @@ impl Document {
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)
.ok_or_else(|| DocError::Invalid("this face has no exact planar source; choose a fixed plane instead".into()))
.and_then(|source| {
let target = self.strong_face_target(body, index, source.origin)?;
if target.certificate.is_none() {
return Err(DocError::Invalid("this face has no supported construction certificate; choose a fixed plane instead".into()));
}
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.
if !indices.contains(&index) {
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)
});
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),
}
}
/// The faces an attachment stands for, and a note when they were found by fallback.
///
/// An extrude's cap is certified by the loop of sketch curves around it, so editing that
@@ -0,0 +1,6 @@
//! Browser groups can nest and be ordered. A group saves its `parent` group (omitted at the top
//! level); a group's items and a folder's `browser_order` list may name groups as numbers beside
//! item keys as strings; and `browser_order` has a `"<component>/components"` list ordering a
//! component's sub-components by placement id. Older designs read unchanged: they have no
//! parents and no group entries. The last release refuses the newer format and asks for an
//! update instead of dropping the nesting and the order.
@@ -0,0 +1,7 @@
//! A point cloud keeps the other properties its file gives its points (#8816): named numbers per
//! point in `complex` (`columns`, each whole-numbered or not, and the `names` a whole-numbered one's
//! values carry), and it can be coloured by one of them (`color_by: "property"`, with
//! `color_property`, the ramp over `height_range`) and shown only where `filters` keep it (ranges,
//! or sets of named values). Older designs need no transformation: their clouds have no other
//! properties and no filters. The last release refuses a design saved with this (the newer format
//! number) instead of opening its clouds without their properties, colouring and filters.
+5
View File
@@ -0,0 +1,5 @@
//! A design saves the print face of its bodies (`print_faces`, by body name): the flat face a
//! slicer export lays on the print bed, held by the same face target a sketch on a face holds.
//! An older design has none, and every body is exported as it lies, as before. The last
//! release refuses a design that has print faces and asks for an update, instead of opening it
//! and losing them on the next save.
+5
View File
@@ -10,6 +10,7 @@ pub mod appearance_pack;
mod assembly;
mod body_transfer;
mod body_transfer_sheet;
mod browser_tree;
pub mod pbr_appearance;
pub use body_transfer::{valid_transfer_matrix, validate_body_transfer};
pub mod canvas;
@@ -53,6 +54,8 @@ pub mod physical_material;
mod piece_names;
pub mod pipe_thread_catalog;
pub mod point_cloud;
mod point_cloud_complex;
pub mod point_cloud_distribution;
mod points;
mod profile_refs;
pub mod progress;
@@ -61,6 +64,8 @@ pub use thread_catalog::{TappedThread, TappedThreadFamily, parse_tapped_thread,
mod walk;
pub use profile_refs::{capture_profile_selection, profile_fallback_notes, profile_selection_indices, profile_selection_indices_lenient};
pub mod plastic;
mod print_face;
pub use print_face::PrintFace;
pub mod project;
mod project3d;
pub mod render_aging;
+87 -24
View File
@@ -22,7 +22,8 @@ pub struct SavedModel {
/// Records refer to these at most.
const MAX_ITEMS: usize = 1_000_000;
/// Bodies, sketches and sheet bodies all the states hold together, at most (a state is a list of clones).
/// Bodies, sketches, sheet bodies and sketch keys all the states hold together, at most (a state is
/// a list of clones).
const MAX_HELD: usize = 50_000_000;
/// Longest text a record holds (bytes). A feature's warning can quote every generated name of
/// the edges it picked: 122,701 characters in one design, whose saved model was refused, so it
@@ -48,6 +49,11 @@ fn unhex(v: &Json) -> Result<u64> {
v.as_str().and_then(|s| u64::from_str_radix(s, 16).ok()).ok_or_else(|| bad("saved model: key"))
}
/// A sketch-key set's list `key` (`set` or `drop`).
fn keys_list<'a>(set: &'a Json, key: &str) -> Result<&'a Vec<Json>> {
set.get(key).and_then(Json::as_array).filter(|l| l.len() <= MAX_ITEMS).ok_or_else(|| bad("saved model: sketch keys"))
}
fn bad(what: &str) -> DocError {
DocError::Invalid(what.into())
}
@@ -128,9 +134,36 @@ struct Writer {
sheet_index: HashMap<String, usize>,
states: Vec<Json>,
state_index: HashMap<usize, usize>,
/// The states' sketch keys, each distinct map once, written as the changes from the set
/// written before it: every state used to repeat the whole map (one entry per solved sketch),
/// 85 % of a 5,484-feature design's records, which then did not fit the cache's manifest.
sketch_key_sets: Vec<Json>,
sketch_key_index: HashMap<BTreeMap<u64, u64>, usize>,
last_sketch_keys: Option<(BTreeMap<u64, u64>, usize)>,
}
impl Writer {
/// The place of `keys` among the sketch-key sets, written the first time a state holds them
/// (as the changes from the set written last: a later state mostly adds a sketch or two).
fn sketch_key_set(&mut self, keys: &BTreeMap<u64, u64>) -> usize {
if let Some(i) = self.sketch_key_index.get(keys) {
return *i;
}
let record = match &self.last_sketch_keys {
Some((base, at)) => {
let set: Vec<Json> = keys.iter().filter(|(f, k)| base.get(f) != Some(k)).map(|(f, k)| json!([f, hex(*k)])).collect();
let drop: Vec<u64> = base.keys().filter(|f| !keys.contains_key(f)).copied().collect();
json!({"base": at, "set": set, "drop": drop})
}
None => json!({"set": keys.iter().map(|(f, k)| json!([f, hex(*k)])).collect::<Vec<_>>(), "drop": []}),
};
self.sketch_key_sets.push(record);
let i = self.sketch_key_sets.len() - 1;
self.sketch_key_index.insert(keys.clone(), i);
self.last_sketch_keys = Some((keys.clone(), i));
i
}
fn names(&mut self, b: &ModelBody) -> usize {
let id = b.names.id();
if let Some(i) = self.names_index.get(&id) {
@@ -144,9 +177,6 @@ impl Writer {
}
fn model_body(&mut self, b: &ModelBody) -> Option<usize> {
if b.body.is_mesh() {
return None;
}
let key = BodyKey::of(b);
if let Some(i) = self.model_body_index.get(&key) {
return Some(*i);
@@ -216,7 +246,7 @@ impl Writer {
"reserved_body_names": st.reserved_body_names.iter().collect::<Vec<_>>(),
"sheets": sheets, "construct": serde_json::to_value(&st.construct).ok()?,
"threads": serde_json::to_value(&st.threads).ok()?,
"sketch_keys": st.sketch_keys.iter().map(|(f, k)| json!([f, hex(*k)])).collect::<Vec<_>>(),
"sketch_key_set": self.sketch_key_set(&st.sketch_keys),
"blend_edges": serde_json::to_value(&st.blend_edges).ok()?, "fitted": serde_json::to_value(&st.fitted).ok()?,
"tapered": serde_json::to_value(&st.tapered).ok()?,
});
@@ -228,7 +258,7 @@ impl Writer {
impl Model {
/// The model as a design file keeps it: `None` unless it is `doc`'s evaluation without an
/// error, holds no mesh bodies, and `doc` keeps no names saved before format 14. Works out
/// error and `doc` keeps no names saved before format 14. Works out
/// the face names not asked for yet (a saved model keeps them as worked out).
pub fn saved(&self, doc: &Document) -> Option<SavedModel> {
if self.results.is_empty() || self.results.iter().any(|r| r.error.is_some()) || !self.is_current(doc) || has_legacy_names(doc) {
@@ -250,7 +280,7 @@ impl Model {
}
let records = json!({
"features": features, "states": w.states, "model_bodies": w.model_bodies, "names": w.names, "sketches": w.sketches,
"sheets": w.sheets,
"sheets": w.sheets, "sketch_key_sets": w.sketch_key_sets,
});
Ok(SavedModel { records, bodies: w.bodies })
})
@@ -331,9 +361,49 @@ impl SavedModel {
for s in list("sheets")? {
sheets.push(serde_json::from_value::<crate::sheet::SheetBody>(s.clone()).map_err(|_| bad("saved model: sheet"))?);
}
let mut states = Vec::new();
// The sketch-key sets, each the changes from an earlier one (`base`): a short record can
// stand for a large set, so what they hold counts towards MAX_HELD as they are built.
let mut key_sets: Vec<BTreeMap<u64, u64>> = Vec::new();
let mut held = 0usize;
for s in list("states")? {
for k in list("sketch_key_sets")? {
// A set is the changes from one before it.
let base = match k.get("base") {
Some(b) => Some(key_sets.get(index(Some(b), key_sets.len())?).ok_or_else(|| bad("saved model: sketch keys"))?),
None => None,
};
let (drop, set) = (keys_list(k, "drop")?, keys_list(k, "set")?);
// Counted before the base is copied: at most what the set can hold.
held = held.saturating_add(base.map_or(0, BTreeMap::len)).saturating_add(set.len());
if held > MAX_HELD {
return Err(bad("saved model: sketch keys"));
}
let mut keys = base.cloned().unwrap_or_default();
for f in drop {
keys.remove(&f.as_u64().ok_or_else(|| bad("saved model: sketch keys"))?);
}
for e in set {
keys.insert(e.get(0).and_then(Json::as_u64).ok_or_else(|| bad("saved model: sketch keys"))?, unhex(e.get(1).unwrap_or(&Json::Null))?);
}
key_sets.push(keys);
}
let records = list("states")?;
// What every state holds, counted from its runs and its sketch-key set before any is
// built (a hostile file cannot make the states clone more than MAX_HELD items).
for s in records {
for key in ["bodies", "sketches"] {
for run in s.get(key).and_then(Json::as_array).ok_or_else(|| bad("saved model: state"))? {
held = held.saturating_add(run.get(1).and_then(Json::as_u64).and_then(|n| usize::try_from(n).ok()).unwrap_or(usize::MAX));
}
}
held = held.saturating_add(s.get("sheets").and_then(Json::as_array).map_or(0, Vec::len));
held = held.saturating_add(key_sets.get(index(s.get("sketch_key_set"), key_sets.len())?).map_or(0, BTreeMap::len));
if held > MAX_HELD {
return Err(bad("saved model: states"));
}
}
// Each state is independent of the others once the records they share are read: built
// on threads (a 5,484-feature design spent 3.5 s building them one after another).
let built = kernel::map_on_threads(records, 16, |s: &Json| -> Result<ModelState> {
// Runs of consecutive indices, each within the list it indexes.
let ids = |key: &str, len: usize| -> Result<Vec<usize>> {
let mut out = Vec::new();
@@ -349,12 +419,8 @@ impl SavedModel {
};
let field = |key: &str| parse(s.get(key));
let invalid = |_| bad("saved model: state");
let mut sketch_keys = BTreeMap::new();
for k in s.get("sketch_keys").and_then(Json::as_array).ok_or_else(|| bad("saved model: state"))? {
sketch_keys
.insert(k.get(0).and_then(Json::as_u64).ok_or_else(|| bad("saved model: state"))?, unhex(k.get(1).unwrap_or(&Json::Null))?);
}
let st = ModelState {
let sketch_keys = key_sets.get(index(s.get("sketch_key_set"), key_sets.len())?).cloned().ok_or_else(|| bad("saved model: state"))?;
Ok(ModelState {
frame_bodies: Default::default(),
bodies: ids("bodies", model_bodies.len())?.into_iter().filter_map(|i| model_bodies.get(i).cloned()).collect(),
sketches: ids("sketches", sketches.len())?.into_iter().filter_map(|i| sketches.get(i).cloned()).collect(),
@@ -377,13 +443,9 @@ impl SavedModel {
blend_edges: serde_json::from_value(field("blend_edges")?).map_err(invalid)?,
fitted: serde_json::from_value(field("fitted")?).map_err(invalid)?,
tapered: serde_json::from_value(field("tapered")?).map_err(invalid)?,
};
held = held.saturating_add(st.bodies.len()).saturating_add(st.sketches.len()).saturating_add(st.sheets.len());
if held > MAX_HELD {
return Err(bad("saved model: states"));
}
states.push(Arc::new(st));
}
})
});
let states = built.into_iter().map(|r| r.unwrap_or_else(|| Err(bad("saved model: state"))).map(Arc::new)).collect::<Result<Vec<_>>>()?;
Ok(states)
}
@@ -457,7 +519,8 @@ impl SavedModel {
let mut stored = std::collections::HashSet::new();
// The sheet bodies, each written once and named by place in the states: all kept.
let sheets = list("sheets").cloned().unwrap_or_default();
let mut used = bytes(sheets.as_slice()).iter().fold(0usize, |t, b| t.saturating_add(*b));
let key_sets = list("sketch_key_sets").cloned().unwrap_or_default();
let mut used = bytes(sheets.as_slice()).iter().chain(bytes(key_sets.as_slice()).iter()).fold(0usize, |t, b| t.saturating_add(*b));
for s in std::iter::once(last).chain(order) {
let (mut adds, mut new_names, mut new_sketches) = (HashMap::new(), std::collections::HashSet::new(), std::collections::HashSet::new());
let mut records = *state_bytes.get(s)?;
@@ -549,7 +612,7 @@ impl SavedModel {
}
let records = json!({
"features": out_features, "states": out_states, "model_bodies": out_bodies, "names": kept_of(names.as_slice(), &names_used),
"sketches": kept_of(sketches.as_slice(), &sketches_used), "sheets": sheets,
"sketches": kept_of(sketches.as_slice(), &sketches_used), "sheets": sheets, "sketch_key_sets": key_sets,
});
let kept: Vec<usize> = (0..self.bodies.len()).filter(|i| kernel_used.get(*i) == Some(&true)).collect();
let bodies = kept.iter().filter_map(|i| self.bodies.get(*i).cloned()).collect();
+21
View File
@@ -152,6 +152,27 @@ pub enum NameError {
},
}
/// Whether `text` can be written into a name whole.
///
/// A curve's id goes into the name of the faces the curve makes (`F2:side:<id>`), and a
/// name reads some characters as its own: `|` separates the faces of an edge, `,` the faces
/// around a piece, `~` numbers the edges between two faces, `#` numbers a piece and `]`
/// closes one. An id holding one of them splits every name it lands in: the id `c|1` gave
/// the edges of an extrusion the names `F2:side:c|1#1|F2:side:c|1#2~1`, three separators
/// where an edge names two faces and separates them once (#5858).
///
/// The test is the name's own round trip rather than a list of characters: the text is
/// written into a name and read back, and it must read back as itself. A list would have
/// to follow every change of the grammar (#5861), and would miss an id that reads back as
/// something else (`s1.l4` reads as profile 1 of curve `l4`).
pub fn text_fits_name(text: &str) -> bool {
let name = format!("F0:side:{text}");
match FaceName::parse(&name) {
Ok(f) => f.to_string() == name && matches!(&f.base, Base::Made { role: Role::Side { profile: None, curve }, .. } if curve == text),
Err(_) => false,
}
}
impl FaceName {
/// The feature that made the face: its base's, a copy's when it is one.
pub fn feature(&self) -> Option<FeatureId> {
+12
View File
@@ -236,3 +236,15 @@ fn hostile_names_never_panic() {
// A fillet on an open boundary's edge.
assert!(matches!(face("F1:blend:F10:start").base, Base::Blend { rounds: Rounds::Edge(ref e), .. } if e.faces.len() == 1));
}
#[test]
fn a_text_that_cannot_be_written_into_a_name_whole_is_told() {
// A curve's id goes into the name of the faces the curve makes, so it has to come
// back out of the name as itself (#5858).
for good in ["c1", "l4", "s01.l4", "c-1", "c_1", "a.b", "x"] {
assert!(text_fits_name(good), "`{good}` fits a name");
}
for bad in ["c|1", "c,1", "c~1", "c]1", "c#1", "", "s1.l4"] {
assert!(!text_fits_name(bad), "`{bad}` does not fit a name");
}
}
+2
View File
@@ -127,6 +127,7 @@ fn same_geometry_inputs(a: &Document, b: &Document) -> bool {
text_params,
next_component_color_index,
materials,
print_faces,
user_materials,
appearances,
body_components,
@@ -162,6 +163,7 @@ fn same_geometry_inputs(a: &Document, b: &Document) -> bool {
&& *text_params == b.text_params
&& *next_component_color_index == b.next_component_color_index
&& *materials == b.materials
&& *print_faces == b.print_faces
&& *user_materials == b.user_materials
&& *appearances == b.appearances
&& *body_components == b.body_components
+136 -9
View File
@@ -11,6 +11,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use solidcraft_geom::Vec3;
use crate::canvas::{base64_decode, base64_encode};
pub use crate::point_cloud_complex::{Column, Complex, Filter, Floats, Keep, MAX_COLUMNS, MAX_NAMES, Names};
use crate::{DocError, IDENTITY, Mat, Result};
/// Most points one cloud may hold.
@@ -19,6 +20,10 @@ pub const MAX_POINTS: usize = 20_000_000;
pub const MAX_COORD: f64 = 1e9;
/// Point sizes on screen (pixels).
pub const POINT_SIZE: std::ops::RangeInclusive<f64> = 0.5..=16.0;
/// Most filters on one cloud.
pub const MAX_FILTERS: usize = 8;
/// Properties every cloud has besides its file's: the coordinates in the design.
pub const COORDINATES: [&str; 3] = ["x", "y", "z"];
/// Length units a point cloud file may be in, and millimetres per unit.
pub const UNITS: [(&str, f64); 5] = [("mm", 1.0), ("cm", 10.0), ("m", 1000.0), ("in", 25.4), ("ft", 304.8)];
@@ -65,6 +70,24 @@ pub struct PointCloud {
pub height_range: Option<[f64; 2]>,
#[serde(default = "yes")]
pub visible: bool,
/// The file's other properties of the points.
#[serde(default, skip_serializing_if = "Complex::is_empty")]
pub complex: Complex,
/// The property `color_by: property` colours by.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color_property: Option<String>,
/// Only the points every filter keeps are shown.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub filters: Vec<Filter>,
}
/// Where a property's values come from.
#[derive(Clone, Copy, Debug)]
pub enum Source<'a> {
/// A coordinate of each point in the design.
Coordinate(usize),
/// A column of the file: each point's.
Column(&'a [f32]),
}
/// How a cloud's points are coloured.
@@ -78,6 +101,8 @@ pub enum ColorBy {
Single,
/// By height in the design: blue low, through cyan, green and yellow, to red high.
Height,
/// By `color_property`: the height ramp over its range or, for whole numbers, a palette.
Property,
}
impl ColorBy {
@@ -90,6 +115,7 @@ impl ColorBy {
"file" => Some(ColorBy::File),
"single" => Some(ColorBy::Single),
"height" => Some(ColorBy::Height),
"property" => Some(ColorBy::Property),
_ => None,
}
}
@@ -98,6 +124,7 @@ impl ColorBy {
ColorBy::File => "file",
ColorBy::Single => "single",
ColorBy::Height => "height",
ColorBy::Property => "property",
}
}
}
@@ -120,6 +147,27 @@ pub fn height_ramp(t: f64) -> [u8; 3] {
std::array::from_fn(|c| (a[c] * (1.0 - f) + b[c] * f).round().clamp(0.0, 255.0) as u8)
}
/// Colours of whole-number labels, the label modulo their count; negative labels are grey.
pub const PALETTE: [[u8; 3]; 10] = [
[240, 140, 40],
[245, 205, 65],
[230, 65, 65],
[165, 75, 190],
[65, 190, 115],
[75, 150, 220],
[240, 120, 180],
[140, 105, 75],
[120, 215, 215],
[190, 190, 80],
];
pub fn label_color(v: f64) -> [u8; 3] {
if !(v.is_finite() && v >= 0.0) {
return PLAIN.map(byte);
}
PALETTE[(v.round() as usize) % PALETTE.len()]
}
/// A colour channel (0..1) as a byte.
pub fn byte(v: f32) -> u8 {
(v.clamp(0.0, 1.0) * 255.0).round() as u8
@@ -182,15 +230,57 @@ impl PointCloud {
let (lo, hi) = (at(HEIGHT_TAIL)?, at(1.0 - HEIGHT_TAIL)?);
Some(if hi - lo > 1e-9 { [lo, hi] } else { [lo - 0.5, lo + 0.5] })
}
/// The property colours come from: a column, or a coordinate (`z` for the height ramp).
pub fn colored_property(&self) -> Option<&str> {
match self.color_by {
ColorBy::Height => Some("z"),
ColorBy::Property => self.color_property.as_deref(),
ColorBy::File | ColorBy::Single => None,
}
}
/// Where the values of `name` are: a coordinate of each point or a column of the file.
pub fn source(&self, name: &str) -> Option<Source<'_>> {
if let Some(k) = COORDINATES.iter().position(|n| *n == name) {
return Some(Source::Coordinate(k));
}
self.complex.column(name).map(|col| Source::Column(&col.values.0))
}
/// Every property the cloud can be coloured and filtered by: its coordinates, then its columns.
pub fn property_names(&self) -> impl Iterator<Item = &str> {
COORDINATES.into_iter().chain(self.complex.columns.iter().map(|c| c.name.as_str()))
}
/// The smallest and largest value of a property, the coordinates in the design.
pub fn span(&self, property: &str) -> Option<[f64; 2]> {
if let Some(span) = self.complex.span(property) {
return Some(span);
}
let k = COORDINATES.iter().position(|n| *n == property)?;
let v = self.world_corners()?.map(|p| [p.x, p.y, p.z][k]);
Some([v.iter().copied().fold(f64::INFINITY, f64::min), v.iter().copied().fold(f64::NEG_INFINITY, f64::max)])
}
/// The ramp's range as far as it can be told without the elements: the one set, or else the
/// coloured property's span within every range filtering it. The automatic range colouring
/// by a property is exactly what the filters keep
/// ([`crate::point_cloud_distribution::color_range`]), which this bounds.
pub fn color_range(&self) -> Option<[f64; 2]> {
if self.height_range.is_some() {
return self.height_range;
}
let property = self.colored_property()?;
let span = if self.color_by == ColorBy::Height { self.auto_height_range() } else { self.span(property) }?;
let shown =
self.filters.iter().filter(|f| f.property == property).filter_map(Filter::range).fold(span, |[lo, hi], [a, b]| [lo.max(a), hi.min(b)]);
Some(if shown[0] <= shown[1] { crate::point_cloud_distribution::ramp_range(shown) } else { span })
}
/// The heights the height ramp runs between now: the cloud's own, else the automatic ones.
pub fn height_range_now(&self) -> Option<[f64; 2]> {
self.height_range.or_else(|| self.auto_height_range())
}
/// The colour of each point (by index) as the cloud is shown.
pub fn shade(&self) -> impl Fn(usize) -> [u8; 3] + '_ {
/// The colour of each point (by index) as the cloud is shown, the ramp over `range`.
pub fn shade(&self, range: Option<[f64; 2]>) -> impl Fn(usize) -> [u8; 3] + '_ {
let single = self.color.unwrap_or(PLAIN).map(byte);
let plain = PLAIN.map(byte);
let range = if self.color_by == ColorBy::Height { self.height_range_now() } else { None };
let column = self.color_property.as_deref().and_then(|n| self.complex.column(n));
move |i| match self.color_by {
ColorBy::File => self.colors.get(i).unwrap_or(plain),
ColorBy::Single => single,
@@ -198,6 +288,14 @@ impl PointCloud {
(Some([lo, hi]), Some(p)) => height_ramp((self.world_z(p) - lo) / (hi - lo)),
_ => plain,
},
ColorBy::Property => match (column, column.and_then(|c| c.values.0.get(i))) {
(Some(c), Some(v)) if c.integer => label_color(f64::from(*v)),
(Some(_), Some(v)) => match range {
Some([lo, hi]) if hi > lo => height_ramp((f64::from(*v) - lo) / (hi - lo)),
_ => plain,
},
_ => plain,
},
}
}
/// The corners of the box around the points, placed in the design (mm).
@@ -240,6 +338,18 @@ impl PointCloud {
if !rigid(&self.transform) {
return bad("its placement is not a rigid move");
}
self.complex.check(self.points.len())?;
if self.filters.len() > MAX_FILTERS
|| self.filters.iter().any(|f| match &f.keep {
Keep::Range { range: [lo, hi] } => !(lo.is_finite() && hi.is_finite() && lo <= hi),
Keep::Values { values } => values.len() > MAX_NAMES,
})
{
return bad("its filters are not ranges, lowest first");
}
if self.color_by == ColorBy::Property && self.color_property.is_none() {
return bad("it is coloured by a property without naming it");
}
Ok(())
}
}
@@ -422,6 +532,9 @@ mod tests {
color: None,
height_range: None,
visible: true,
complex: Complex::default(),
color_property: None,
filters: Vec::new(),
}
}
@@ -443,6 +556,20 @@ mod tests {
assert_eq!((unit_scale("M"), unit_scale("yd")), (Some(1000.0), None));
}
#[test]
fn the_ramp_spans_what_is_shown_unless_set() {
let mut c = cloud(vec![0.0; 9], Vec::new());
c.complex.columns.push(Column { name: "t".into(), integer: false, values: Floats(vec![0.0, 10.0, 20.0].into()) });
(c.color_by, c.color_property) = (ColorBy::Property, Some("t".into()));
assert_eq!(c.color_range(), Some([0.0, 20.0]), "the whole span");
c.filters = vec![Filter { property: "t".into(), keep: Keep::Range { range: [5.0, 50.0] } }];
assert_eq!(c.color_range(), Some([5.0, 20.0]), "the span within the filter");
c.filters.push(Filter { property: "layer".into(), keep: Keep::Range { range: [0.0, 1.0] } });
assert_eq!(c.color_range(), Some([5.0, 20.0]), "another property's filter changes nothing");
c.height_range = Some([1.0, 2.0]);
assert_eq!(c.color_range(), Some([1.0, 2.0]), "a range set is kept");
}
#[test]
fn damaged_clouds_are_refused() {
// Not whole points, not finite, not base64.
@@ -474,26 +601,26 @@ mod tests {
let colors: Vec<u8> = (0..102).flat_map(|i| [i as u8, 7, 9]).collect();
let mut c = cloud(values, colors);
c.origin = Vec3::ZERO;
assert_eq!(c.shade()(3), [3, 7, 9], "the file's colours");
assert_eq!(c.shade(c.color_range())(3), [3, 7, 9], "the file's colours");
c.colors = Colors::default();
assert_eq!(c.shade()(3), PLAIN.map(byte), "none: plain");
assert_eq!(c.shade(c.color_range())(3), PLAIN.map(byte), "none: plain");
c.color_by = ColorBy::Single;
c.color = Some([1.0, 0.5, 0.0]);
assert_eq!(c.shade()(50), [255, 128, 0]);
assert_eq!(c.shade(c.color_range())(50), [255, 128, 0]);
// The stray point doesn't stretch the automatic range.
c.color_by = ColorBy::Height;
// 102 points: the 2nd and the 99th (of 0…101) once sorted.
assert_eq!(c.auto_height_range(), Some([2.0, 99.0]));
{
let shade = c.shade();
let shade = c.shade(c.color_range());
assert_eq!((shade(0), shade(100), shade(101)), (height_ramp(0.0), height_ramp(1.0), height_ramp(1.0)));
assert_eq!(shade(50), height_ramp(48.0 / 97.0));
}
// A range of its own; moved up 100 mm, everything is above it.
c.height_range = Some([0.0, 50.0]);
assert_eq!(c.shade()(25), height_ramp(0.5));
assert_eq!(c.shade(c.color_range())(25), height_ramp(0.5));
c.transform[3][2] = 100.0;
assert_eq!(c.shade()(0), height_ramp(1.0));
assert_eq!(c.shade(c.color_range())(0), height_ramp(1.0));
assert_eq!((height_ramp(0.0), height_ramp(1.0), height_ramp(f64::NAN)), ([40, 70, 200], [230, 70, 50], [40, 70, 200]));
let back: PointCloud = serde_json::from_str(&serde_json::to_string(&c).unwrap()).unwrap();
assert_eq!(back, c);
+215
View File
@@ -0,0 +1,215 @@
//! What a point cloud holds besides its points' places and colours: named numbers on its points
//! (a PLY file's other vertex properties), to colour the cloud by and to filter it with. Whole
//! numbers may have names (`role: 0 outer wall, 1 inner wall` in a PLY file's comments): they
//! are labels, filtered by a set of values rather than a range.
use std::sync::Arc;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::canvas::{base64_decode, base64_encode};
use crate::point_cloud::MAX_POINTS;
use crate::{DocError, Result};
/// Most named numbers per point.
pub const MAX_COLUMNS: usize = 64;
/// One named number on every point.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Column {
pub name: String,
/// Whole numbers (labels, counts): coloured by a palette rather than a ramp.
#[serde(default)]
pub integer: bool,
pub values: Floats,
}
impl Column {
/// The smallest and largest finite value.
pub fn span(&self) -> Option<[f64; 2]> {
let mut it = self.values.0.iter().filter(|v| v.is_finite()).map(|v| f64::from(*v));
let first = it.next()?;
Some(it.fold([first, first], |[lo, hi], v| [lo.min(v), hi.max(v)]))
}
}
/// The complex a cloud's points form: named numbers on the points, and the names of
/// whole-numbered ones. Points alone are a complex of dimension 0.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Complex {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub columns: Vec<Column>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub names: Vec<Names>,
}
/// Most named values of one label.
pub const MAX_NAMES: usize = 256;
/// The names of a label's values.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Names {
pub property: String,
pub values: Vec<(i32, String)>,
}
impl Names {
/// `property: 0 name, 1 another name, -1 none`, or `None` for any other text.
pub fn parse(text: &str) -> Option<Names> {
let (property, rest) = text.split_once(':')?;
let property = property.trim();
if property.is_empty() || property.contains(char::is_whitespace) {
return None;
}
let values = rest
.split(',')
.map(|entry| {
let (value, name) = entry.trim().split_once(' ')?;
let name = name.trim();
(!name.is_empty()).then(|| Some((value.parse::<i32>().ok()?, name.to_string()))).flatten()
})
.collect::<Option<Vec<_>>>()?;
(!values.is_empty() && values.len() <= MAX_NAMES).then(|| Names { property: property.to_string(), values })
}
pub fn name(&self, value: i32) -> Option<&str> {
self.values.iter().find(|(v, _)| *v == value).map(|(_, n)| n.as_str())
}
}
impl Complex {
pub fn is_empty(&self) -> bool {
self.columns.is_empty() && self.names.is_empty()
}
/// The column named `name`.
pub fn column(&self, name: &str) -> Option<&Column> {
self.columns.iter().find(|c| c.name == name)
}
/// The smallest and largest value named `name`.
pub fn span(&self, name: &str) -> Option<[f64; 2]> {
self.column(name).and_then(Column::span)
}
/// The names of `property`'s values, when it is a label.
pub fn names(&self, property: &str) -> Option<&Names> {
self.names.iter().find(|n| n.property == property).filter(|_| self.integer(property))
}
/// Whether the values named `name` are whole numbers.
pub fn integer(&self, name: &str) -> bool {
self.column(name).is_some_and(|c| c.integer)
}
/// Every column has one number per point, the cloud having `points` points.
pub fn check(&self, points: usize) -> Result<()> {
let bad = |what: String| Err(DocError::Invalid(format!("point cloud properties: {what}")));
if self.columns.len() > MAX_COLUMNS {
return bad("too many properties".into());
}
for c in &self.columns {
if c.values.0.len() != points {
return bad(format!("property `{}` does not have one number per point", c.name));
}
}
if self.names.len() > MAX_COLUMNS || self.names.iter().any(|n| n.values.is_empty() || n.values.len() > MAX_NAMES) {
return bad("too many named values".into());
}
Ok(())
}
}
/// Which points are shown: those whose property lies in a range (both ends included), or, for a
/// label, is one of a set of values.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Filter {
pub property: String,
#[serde(flatten)]
pub keep: Keep,
}
/// The values a filter keeps.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Keep {
Range { range: [f64; 2] },
Values { values: Vec<i32> },
}
impl Filter {
pub fn range(&self) -> Option<[f64; 2]> {
match &self.keep {
Keep::Range { range } => Some(*range),
Keep::Values { .. } => None,
}
}
}
/// Numbers, shared between copies of the design.
#[derive(Clone, Debug, Default)]
pub struct Floats(pub Arc<[f32]>);
impl PartialEq for Floats {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0) || self.0.iter().map(|v| v.to_bits()).eq(other.0.iter().map(|v| v.to_bits()))
}
}
/// The largest base64 text of `n` four-byte words.
fn base64_len(n: usize) -> usize {
(n * 4).div_ceil(3) * 4 + 16
}
fn words<'de, D: Deserializer<'de>>(d: D, max: usize) -> std::result::Result<Vec<[u8; 4]>, D::Error> {
let s = String::deserialize(d)?;
if s.len() > base64_len(max) {
return Err(serde::de::Error::custom("too many numbers"));
}
let b = base64_decode(&s).ok_or_else(|| serde::de::Error::custom("numbers are not base64"))?;
if !b.len().is_multiple_of(4) {
return Err(serde::de::Error::custom("numbers are not whole four-byte words"));
}
Ok(b.as_chunks::<4>().0.to_vec())
}
impl Serialize for Floats {
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
s.serialize_str(&base64_encode(&self.0.iter().flat_map(|v| v.to_le_bytes()).collect::<Vec<u8>>()))
}
}
impl<'de> Deserialize<'de> for Floats {
fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
Ok(Floats(words(d, MAX_POINTS)?.into_iter().map(f32::from_le_bytes).collect()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn properties_round_trip_and_are_checked_against_the_points() {
let complex = Complex {
columns: vec![
Column { name: "t".into(), integer: false, values: Floats(vec![0.0, 0.5, 1.0].into()) },
Column { name: "role".into(), integer: true, values: Floats(vec![0.0, 4.0, 4.0].into()) },
],
names: Vec::new(),
};
let back: Complex = serde_json::from_str(&serde_json::to_string(&complex).unwrap()).unwrap();
assert_eq!(back, complex);
complex.check(3).unwrap();
assert!(complex.check(2).is_err(), "three numbers for two points");
assert_eq!(complex.span("role"), Some([0.0, 4.0]));
assert!(complex.integer("role") && !complex.integer("t") && !complex.integer("nope"));
// Named values make a label, and a filter keeps a range or a set of them.
let names = Names::parse("role: 0 outer wall, 4 bridge, -1 none").unwrap();
assert_eq!((names.name(4), names.name(-1), names.name(2)), (Some("bridge"), Some("none"), None));
for text in ["t: seconds since the start", "role 0 wall", ": 0 a", "role: x wall", "role: 0 "] {
assert!(Names::parse(text).is_none(), "{text}");
}
let mut named = complex.clone();
named.names = vec![names, Names::parse("t: 0 zero").unwrap()];
assert!(named.names("role").is_some() && named.names("t").is_none(), "only whole numbers are labels");
let set: Filter = serde_json::from_str(r#"{"property": "role", "values": [0, 4]}"#).unwrap();
let range: Filter = serde_json::from_str(r#"{"property": "t", "range": [1, 3]}"#).unwrap();
assert_eq!((set.range(), range.range()), (None, Some([1.0, 3.0])));
assert_eq!(serde_json::to_value(&set).unwrap(), serde_json::json!({"property": "role", "values": [0, 4]}));
}
}
+246
View File
@@ -0,0 +1,246 @@
//! How much of a point cloud lies at each value of one of its properties, over what its filters
//! keep: the histograms the Edit Point Cloud card draws in its range tracks, split into the
//! colours the view draws, and the values where a label changes along a property, which the
//! tracks snap to.
//!
//! Each point weighs the same, the cloud weighing 1. A range filter keeps the points between its
//! ends, as the view does; a filter by values keeps those whose rounded value it holds; the
//! colour sorts the points kept into the colours they are drawn in. The values the points kept
//! take, from the least to the greatest, are the ramp's automatic range: the colours span exactly
//! what is shown.
use crate::point_cloud::{ColorBy, Filter, Keep, PALETTE, PointCloud, Source};
/// Most bins of one histogram.
pub const MAX_BINS: usize = 8192;
/// What the histograms need of a cloud's geometry, worked out once for its points.
#[derive(Clone, Debug, Default)]
pub struct Geometry {
/// Each point in the design.
world: Vec<[f64; 3]>,
}
impl Geometry {
pub fn of(c: &PointCloud) -> Geometry {
Geometry { world: (0..c.len()).map(|i| c.world_point(i).map_or([0.0; 3], |p| [p.x, p.y, p.z])).collect() }
}
}
/// How a histogram is split by colour, into slots; the last slot holds what has no value to be
/// coloured by.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Colour<'a> {
/// One colour.
One,
/// The ramp over `range` of `property`, in `bands` equal bands; below and above the range
/// count with the first and the last.
Ramp { property: &'a str, range: [f64; 2], bands: usize },
/// The palette of a whole-numbered `property`: a slot per colour of [`PALETTE`], then one
/// for negative values.
Palette { property: &'a str },
}
impl Colour<'_> {
/// How many slots the histogram has.
pub fn slots(&self) -> usize {
match self {
Colour::One => 1,
Colour::Ramp { bands, .. } => bands + 1,
Colour::Palette { .. } => PALETTE.len() + 2,
}
}
fn property(&self) -> Option<&str> {
match self {
Colour::One => None,
Colour::Ramp { property, .. } | Colour::Palette { property } => Some(property),
}
}
/// How a property's values are cut into bands, and the slot of each band.
fn cut(&self) -> Option<Cut> {
match *self {
Colour::One => None,
Colour::Ramp { range: [lo, hi], bands, .. } => Some(Cut::Grid { start: lo, step: (hi - lo) / bands as f64, count: bands + 1 }),
Colour::Palette { .. } => Some(INTEGERS),
}
}
fn slot(&self, band: usize) -> usize {
match *self {
Colour::One => 0,
Colour::Ramp { bands, .. } => band.saturating_sub(1).min(bands.saturating_sub(1)),
Colour::Palette { .. } => match integer(band) {
Some(v) if v >= 0 => v as usize % PALETTE.len(),
_ => PALETTE.len(),
},
}
}
}
/// How much lies in each bin, by colour slot.
#[derive(Clone, Debug, PartialEq)]
pub struct Histogram {
pub bins: usize,
/// `slots[s][b]`: the measure of slot `s` in bin `b`.
pub slots: Vec<Vec<f64>>,
/// The least and the greatest value the points kept take, within the axis or not.
pub extent: Option<[f64; 2]>,
}
/// Where values are cut into bands, by increasing values.
#[derive(Clone, Copy, Debug)]
enum Cut {
/// Below (band 0), within (1, both ends included) and above (2) a range.
Range([f64; 2]),
/// `count` values `start + k·step`: band 0 lies below the first, band `k` from the `k`th on.
Grid { start: f64, step: f64, count: usize },
}
/// The bands of whole numbers: band `k` holds the values rounding to `k - 2³¹`.
const INTEGERS: Cut = Cut::Grid { start: 0.5 - 2_147_483_648.0, step: 1.0, count: u32::MAX as usize };
/// The whole number a band of [`INTEGERS`] holds.
fn integer(band: usize) -> Option<i64> {
i64::try_from(band).ok().map(|k| k - 2_147_483_648)
}
impl Cut {
fn band(&self, v: f64) -> usize {
match *self {
Cut::Range([a, b]) => usize::from(v >= a) + usize::from(v > b),
Cut::Grid { start, step, count } => {
if !(v >= start) {
return 0;
}
(((v - start) / step).floor() as usize).saturating_add(1).min(count)
}
}
}
}
/// A filter as a cut and the bands it keeps.
struct Kept<'a> {
cut: Cut,
values: Option<&'a [i32]>,
}
impl Kept<'_> {
fn of(f: &Filter) -> Kept<'_> {
match &f.keep {
Keep::Range { range } => Kept { cut: Cut::Range(*range), values: None },
Keep::Values { values } => Kept { cut: INTEGERS, values: Some(values) },
}
}
fn keeps(&self, v: f64) -> bool {
let band = self.cut.band(v);
match self.values {
None => band == 1,
Some(values) => integer(band).is_some_and(|v| values.iter().any(|k| i64::from(*k) == v)),
}
}
}
/// The value of `s` at point `i`.
fn value(s: Source, i: usize, world: &[[f64; 3]]) -> f64 {
match s {
Source::Coordinate(k) => world.get(i).and_then(|p| p.get(k)).copied().unwrap_or(f64::NAN),
Source::Column(v) => v.get(i).map_or(f64::NAN, |x| f64::from(*x)),
}
}
/// How much of the cloud lies in each of `bins` bins over `axis` of `property`, by `colour`,
/// within every one of `filters`; `None` for an axis or a number of bins it can't have.
pub fn histogram(
c: &PointCloud,
g: &Geometry,
property: &str,
axis: [f64; 2],
bins: usize,
colour: &Colour,
filters: &[Filter],
) -> Option<Histogram> {
if !(axis[0].is_finite() && axis[1].is_finite() && axis[1] > axis[0]) || bins == 0 || bins > MAX_BINS {
return None;
}
if let Colour::Ramp { range: [lo, hi], bands, .. } = *colour
&& !(hi > lo && bands > 0 && bands <= MAX_BINS)
{
return None;
}
let slots = colour.slots();
let mut out = vec![vec![0.0; bins]; slots];
let mut extent = None;
let Some(p) = c.source(property) else { return Some(Histogram { bins, slots: out, extent }) };
// A point is not filtered by a property the cloud doesn't have, as in the view.
let kept: Vec<(Kept, Source)> = filters.iter().filter_map(|f| Some((Kept::of(f), c.source(&f.property)?))).collect();
let coloured = colour.property().and_then(|n| c.source(n));
let (colour_cut, plain) = (colour.cut(), slots - 1);
let m = 1.0 / g.world.len().max(1) as f64;
let width = (axis[1] - axis[0]) / bins as f64;
for i in 0..g.world.len() {
if !kept.iter().all(|(k, s)| k.keeps(value(*s, i, &g.world))) {
continue;
}
let v = value(p, i, &g.world);
if !v.is_finite() {
continue;
}
extent = Some(extent.map_or([v, v], |[lo, hi]: [f64; 2]| [lo.min(v), hi.max(v)]));
if !(v >= axis[0] && v <= axis[1]) {
continue;
}
let slot = match (colour_cut, coloured) {
(Some(cut), Some(s)) => colour.slot(cut.band(value(s, i, &g.world))),
(Some(_), None) => plain,
(None, _) => 0,
};
// The last bin holds the axis' upper end too.
let bin = (((v - axis[0]) / width).floor() as usize).min(bins - 1);
if let Some(b) = out.get_mut(slot).and_then(|s| s.get_mut(bin)) {
*b += m;
}
}
Some(Histogram { bins, slots: out, extent })
}
/// A range for a ramp from the values it spans: a single value gets one unit around it.
pub fn ramp_range([lo, hi]: [f64; 2]) -> [f64; 2] {
if hi > lo { [lo, hi] } else { [lo - 0.5, lo + 0.5] }
}
/// The ramp's range: the one set; else, coloured by a property, exactly the values it takes over
/// what `filters` keep (the cloud's own bound, [`PointCloud::color_range`], when nothing is
/// kept); coloured by height, the cloud's own (its points but the highest and lowest few).
pub fn color_range(c: &PointCloud, g: &Geometry, filters: &[Filter]) -> Option<[f64; 2]> {
color_range_by(c, |property, axis| histogram(c, g, property, axis, 1, &Colour::One, filters).and_then(|h| h.extent))
}
/// [`color_range`], with the values the coloured property takes over what is kept given by
/// `extent` (of the property and an axis over its span): worked out elsewhere, or not yet.
pub fn color_range_by(c: &PointCloud, extent: impl FnOnce(&str, [f64; 2]) -> Option<[f64; 2]>) -> Option<[f64; 2]> {
let property = c.colored_property()?;
if c.height_range.is_some() || c.color_by != ColorBy::Property {
return c.color_range();
}
let axis = ramp_range(c.span(property)?);
extent(property, axis).map(ramp_range).or_else(|| c.color_range())
}
/// The values of `property` where the whole-numbered `label` changes, point after point in order
/// of `property`, lowest first.
pub fn label_changes(c: &PointCloud, g: &Geometry, label: &str, property: &str) -> Vec<f64> {
let (Some(p), Some(l)) = (c.source(property), c.source(label)) else { return Vec::new() };
let mut at: Vec<(f64, i64)> = (0..g.world.len())
.filter_map(|i| {
let (v, l) = (value(p, i, &g.world), value(l, i, &g.world));
v.is_finite().then(|| (v, if l.is_finite() { l.round() as i64 } else { i64::MIN }))
})
.collect();
at.sort_by(|a, b| a.0.total_cmp(&b.0));
let mut out: Vec<f64> = at.windows(2).filter(|w| w[0].1 != w[1].1).map(|w| w[1].0).collect();
out.dedup();
out
}
#[cfg(test)]
#[path = "point_cloud_distribution_tests.rs"]
mod tests;
@@ -0,0 +1,125 @@
use solidcraft_geom::Vec3;
use super::*;
use crate::IDENTITY;
use crate::point_cloud::{ColorBy, Colors, Column, Complex, Floats, Points};
/// A cloud of `points` with `columns` on them.
fn cloud(points: &[[f32; 3]], columns: Vec<Column>) -> PointCloud {
PointCloud {
id: 1,
name: "c".into(),
file: String::new(),
origin: Vec3::new(0.0, 0.0, 0.0),
points: Points::new(points.iter().flatten().copied().collect::<Vec<f32>>().into()),
colors: Colors::default(),
source_points: points.len() as u64,
units: "mm".into(),
transform: IDENTITY,
point_size: 2.0,
color_by: ColorBy::File,
color: None,
height_range: None,
visible: true,
complex: Complex { columns, names: Vec::new() },
color_property: None,
filters: Vec::new(),
}
}
fn column(name: &str, integer: bool, values: &[f32]) -> Column {
Column { name: name.into(), integer, values: Floats(values.to_vec().into()) }
}
fn range(property: &str, range: [f64; 2]) -> Filter {
Filter { property: property.into(), keep: Keep::Range { range } }
}
fn close(a: &[f64], b: &[f64]) -> bool {
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-9)
}
/// Ten points along x, `t` the same as x.
fn row() -> PointCloud {
let points: Vec<[f32; 3]> = (0..10).map(|i| [i as f32 + 0.5, 0.0, 0.0]).collect();
let t: Vec<f32> = points.iter().map(|p| p[0]).collect();
cloud(&points, vec![column("t", false, &t)])
}
#[test]
fn each_point_weighs_alike_and_a_range_keeps_those_within_it() {
let c = row();
let g = Geometry::of(&c);
let all = histogram(&c, &g, "t", [0.0, 10.0], 5, &Colour::One, &[]).unwrap();
assert!(close(&all.slots[0], &[0.2; 5]));
// Kept from t = 2 to 5: three points, ends included.
let kept = histogram(&c, &g, "t", [0.0, 10.0], 5, &Colour::One, &[range("t", [2.0, 5.0])]).unwrap();
assert!(close(&kept.slots[0], &[0.0, 0.2, 0.1, 0.0, 0.0]));
// Filtered by another property: x from 0 to 4 keeps t from 0 to 4.
let by_x = histogram(&c, &g, "t", [0.0, 10.0], 5, &Colour::One, &[range("x", [0.0, 4.0])]).unwrap();
assert!(close(&by_x.slots[0], &[0.2, 0.2, 0.0, 0.0, 0.0]));
// A filter on a property the cloud doesn't have keeps everything, as in the view.
assert_eq!(histogram(&c, &g, "t", [0.0, 10.0], 5, &Colour::One, &[range("nope", [0.0, 1.0])]), Some(all));
// An axis or a number of bins it can't have.
for (axis, bins) in [([1.0, 1.0], 4), ([0.0, f64::NAN], 4), ([0.0, 1.0], 0), ([0.0, 1.0], MAX_BINS + 1)] {
assert!(histogram(&c, &g, "t", axis, bins, &Colour::One, &[]).is_none());
}
}
#[test]
fn colours_split_what_is_kept() {
// Four points, roles 0, 3, 3 and -1, t from 0 to 3.
let c = cloud(
&[[0.0; 3], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [3.0, 0.0, 0.0]],
vec![column("t", false, &[0.0, 1.0, 2.0, 3.0]), column("role", true, &[0.0, 3.0, 3.0, -1.0])],
);
let g = Geometry::of(&c);
let colour = Colour::Palette { property: "role" };
let h = histogram(&c, &g, "t", [0.0, 4.0], 4, &colour, &[]).unwrap();
assert_eq!(h.slots.len(), colour.slots());
assert!(close(&h.slots[0], &[0.25, 0.0, 0.0, 0.0]));
assert!(close(&h.slots[3], &[0.0, 0.25, 0.25, 0.0]));
assert!(close(&h.slots[PALETTE.len()], &[0.0, 0.0, 0.0, 0.25]), "a negative label");
// A filter by values keeps role 3 only.
let three = Filter { property: "role".into(), keep: Keep::Values { values: vec![3] } };
let kept = histogram(&c, &g, "t", [0.0, 4.0], 4, &colour, &[three]).unwrap();
let total: f64 = kept.slots.iter().flatten().sum();
assert!((total - 0.5).abs() < 1e-9 && close(&kept.slots[3], &h.slots[3]));
// A ramp over t in two bands, and coloured by a property the cloud doesn't have: plain.
let ramp = Colour::Ramp { property: "t", range: [0.0, 4.0], bands: 2 };
let r = histogram(&c, &g, "t", [0.0, 4.0], 4, &ramp, &[]).unwrap();
assert!(close(&r.slots[0], &[0.25, 0.25, 0.0, 0.0]) && close(&r.slots[1], &[0.0, 0.0, 0.25, 0.25]));
let none = histogram(&c, &g, "t", [0.0, 4.0], 4, &Colour::Palette { property: "nope" }, &[]).unwrap();
assert!(close(&none.slots[PALETTE.len() + 1], &[0.25; 4]));
}
#[test]
fn labels_change_where_the_next_point_has_another() {
// Six points in order of t, layers 0 0 1 1 2 2.
let points: Vec<[f32; 3]> = (0..6).map(|i| [i as f32, 0.0, 0.0]).collect();
let c = cloud(&points, vec![column("t", false, &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]), column("layer", true, &[0.0, 0.0, 1.0, 1.0, 2.0, 2.0])]);
let g = Geometry::of(&c);
assert_eq!(label_changes(&c, &g, "layer", "t"), vec![2.0, 4.0]);
assert!(label_changes(&c, &g, "nope", "t").is_empty());
}
#[test]
fn the_automatic_range_is_what_every_filter_keeps() {
// Filtered by x from 2 to 4, coloured by t: the range is what is kept of t, though no filter
// is on t (its span within filters on t would be all of it).
let mut c = cloud(&[[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [4.0, 0.0, 0.0], [20.0, 0.0, 0.0]], vec![column("t", false, &[0.0, 3.0, 5.0, 7.0])]);
c.color_by = ColorBy::Property;
c.color_property = Some("t".into());
let g = Geometry::of(&c);
let x = [range("x", [2.0, 4.0])];
assert_eq!(c.color_range(), Some([0.0, 7.0]));
assert_eq!(color_range(&c, &g, &x), Some([3.0, 5.0]));
// The point at x = 20 alone: one value, one unit wide.
assert_eq!(color_range(&c, &g, &[range("x", [19.0, 21.0])]), Some([6.5, 7.5]));
// Nothing kept: the cloud's own bound; a range set is kept as it is.
assert_eq!(color_range(&c, &g, &[range("x", [50.0, 60.0])]), Some([0.0, 7.0]));
c.height_range = Some([1.0, 2.0]);
assert_eq!(color_range(&c, &g, &x), Some([1.0, 2.0]));
let h = histogram(&c, &g, "t", [0.0, 1.0], 4, &Colour::One, &x).unwrap();
assert_eq!(h.extent, Some([3.0, 5.0]), "the extent reaches past the axis");
}
+105
View File
@@ -0,0 +1,105 @@
//! The print face: the flat face of a body that a slicer export lays on the print bed (#8429).
//! A body has at most one (`Document::print_faces`, by body name). It is held by the target a
//! sketch on a face holds ([`Document::flat_face_target`]), so a rebuild that adds, removes or
//! reorders faces finds the same face again, or says it is gone.
use solidcraft_geom::Vec3;
use crate::config::ConfigFaceTarget;
use crate::{DocError, Document, ModelBody, ModelState, Result};
/// Where a print face is: a point on it and its outward unit normal, in the coordinates of the
/// body it was found on.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PrintFace {
/// The face's index on the body (its first piece, when a change split it).
pub index: usize,
pub point: Vec3,
pub normal: Vec3,
}
impl Document {
/// The target that marks flat face `index` of `body` as its print face.
pub fn print_face_target(&self, body: &ModelBody, index: usize) -> Result<ConfigFaceTarget> {
if body.body.is_mesh() {
return Err(DocError::Invalid("not supported yet: a print face on a mesh body".into()));
}
if body.body.planar_face(index).is_none() {
return Err(DocError::Invalid("only a flat face can be the print face".into()));
}
// The sketch-on-a-face wording ("choose a fixed plane") offers what a print face has not.
self.flat_face_target(body, index).map_err(|_| {
DocError::Invalid(
"this face can't be the print face: the body's history no longer tells it apart from its other faces (as after a shell of a shell body); pick a face of another body, or edit the feature that reshaped it".into(),
)
})
}
/// Where `body`'s print face is now. `None`: it has none. An error: the face can't be found
/// any more, or is no longer flat.
pub fn print_face(&self, state: &ModelState, body: &ModelBody) -> Option<Result<PrintFace>> {
let target = self.print_faces.get(body.definition_name())?;
Some(self.locate_print_face(state, body, target))
}
/// The print face a copy of `source` gets (Move/Copy, Mirror): the face of `copy` named after
/// `source`'s print face (a copy's faces carry their source's names, followed by the copy's
/// instance, `F1:+x@F2.1`). `None`:
/// `source` has no print face.
pub fn copied_print_face(&self, state: &ModelState, source: &ModelBody, copy: &ModelBody) -> Option<Result<ConfigFaceTarget>> {
let target = self.print_faces.get(source.definition_name())?;
let found = || {
let (indices, _) = self.anchored_face_indices(state, source, target)?;
let first = indices.first().copied().ok_or_else(|| DocError::Unknown("the print face no longer exists".into()))?;
let names = crate::naming::face_names(source);
let name =
names.get(first).map(|n| crate::naming::strip_piece(n)).ok_or_else(|| DocError::Unknown("the print face has no name".into()))?;
let index = crate::naming::face_names(copy)
.iter()
.position(|n| crate::naming::strip_piece(without_last_instance(n)) == name)
.ok_or_else(|| DocError::Unknown(format!("the copy has no face `{name}`")))?;
self.print_face_target(copy, index)
};
Some(found())
}
fn locate_print_face(&self, state: &ModelState, body: &ModelBody, target: &ConfigFaceTarget) -> Result<PrintFace> {
if body.body.is_mesh() {
return Err(DocError::Invalid("not supported yet: a print face on a mesh body".into()));
}
let (indices, _) = self.anchored_face_indices(state, body, target)?;
let first = indices.first().copied().ok_or_else(|| DocError::Unknown("the print face no longer exists".into()))?;
let plane = body.body.planar_face(first).ok_or_else(|| DocError::Invalid("the print face is no longer flat".into()))?;
let scale = 1. + body.body.size();
for index in indices.iter().skip(1) {
let other = body.body.planar_face(*index).ok_or_else(|| DocError::Invalid("the print face is no longer flat".into()))?;
if plane.normal().dot(other.normal()).abs() < 1. - 1e-8 || (other.origin - plane.origin).dot(plane.normal()).abs() > 1e-7 * scale {
return Err(DocError::Invalid("the print face's pieces no longer lie on one plane".into()));
}
}
// The surface's own orientation is how the kernel built it, so the side the material is
// on is taken from the face's triangles, which point out of the body.
let mesh = body.mesh();
let mut area = Vec3::ZERO;
for (t, f) in mesh.triangles.iter().zip(&mesh.tri_face) {
if let (true, Some([a, b, c])) = (indices.contains(&(*f as usize)), mesh.tri(t)) {
area += (b - a).cross(c - a);
}
}
let normal = plane.normal().normalized().ok_or_else(|| DocError::Invalid("the print face's plane is degenerate".into()))?;
let outward = area.dot(normal);
if !(outward.abs() > 0.) {
return Err(DocError::Invalid("the print face has no surface to lie on".into()));
}
Ok(PrintFace { index: first, point: plane.origin, normal: if outward < 0. { -normal } else { normal } })
}
}
/// A face name without the copy instance at its end (`F1:+x@F2.1` is `F1:+x`; a copy of a copy,
/// `F1:+x@F2.1@F5.1`, is `F1:+x@F2.1`).
fn without_last_instance(n: &str) -> &str {
let Some(at) = n.rfind('@') else { return n };
let instance = n.get(at + 1..).and_then(|t| t.strip_prefix('F')).and_then(|t| t.split_once('.'));
let digits = |t: &str| !t.is_empty() && t.bytes().all(|b| b.is_ascii_digit());
if instance.is_some_and(|(feature, k)| digits(feature) && digits(k)) { n.get(..at).unwrap_or(n) } else { n }
}
+7 -6
View File
@@ -238,13 +238,14 @@ fn resolve_uncached(
}
LinkSource::Origin => vec![LinkGeom::Point(plane.to_local(Vec3::ZERO))],
LinkSource::Axis { name } => {
let d = match name.to_ascii_uppercase().as_str() {
"X" => Vec3::X,
"Y" => Vec3::Y,
"Z" => Vec3::Z,
_ => return Err(DocError::Unknown(format!("axis `{name}`"))),
let (o, d) = match name.to_ascii_uppercase().as_str() {
"X" => (Vec3::ZERO, Vec3::X),
"Y" => (Vec3::ZERO, Vec3::Y),
"Z" => (Vec3::ZERO, Vec3::Z),
// A construction axis, by name (#9324), where the timeline so far puts it.
_ => crate::eval::construction_axis_in(Some(doc), st, name, 0)?,
};
let o = plane.to_local(Vec3::ZERO);
let o = plane.to_local(o);
let d2 = Vec2::new(d.dot(plane.x), d.dot(plane.y));
match d2.normalized() {
Some(u) if d2.len() > 1e-9 => vec![LinkGeom::Line(o - u * AXIS_HALF, o + u * AXIS_HALF)],
+71 -9
View File
@@ -39,6 +39,12 @@ pub struct RenderSetup {
pub stage: solidcraft_render::stage::Stage,
}
/// The CPU request a size check belongs to, so a refusal can name the profile that admits it.
#[derive(Clone, Copy)]
struct CpuSize {
progressive: Option<solidcraft_render::job::CpuProgressiveOptions>,
}
// Reuse Camera's representation and defaults, but reject misspelled saved fields. A remote
// derive constructs Camera itself, so adding a Camera field requires updating this adapter.
// Navigation/named-view deserialization keeps its existing compatibility policy.
@@ -80,27 +86,29 @@ impl Default for RenderSetup {
impl RenderSetup {
pub fn validate(&self) -> Result<()> {
let limits = match &self.job {
Some(RenderJob::CpuRaytrace { options }) => options.budget.image_limits(options.progressive.is_some()),
Some(RenderJob::GpuPath { .. }) => solidcraft_render::job::GPU_PATH_LIMITS,
_ => RenderLimits::default(),
let (limits, cpu) = match &self.job {
Some(RenderJob::CpuRaytrace { options }) => {
(options.budget.image_limits(options.progressive.is_some()), Some(CpuSize { progressive: options.progressive }))
}
Some(RenderJob::GpuPath { .. }) => (solidcraft_render::job::GPU_PATH_LIMITS, None),
_ => (RenderLimits::default(), None),
};
self.validate_with_limits(limits)
self.validate_with_limits(limits, cpu)
}
/// Runtime CPU lowering has already moved typed options out of this setup.
/// Saving still uses validate(), so a legacy setup cannot smuggle a large profile.
pub fn validate_for_cpu(&self, budget: solidcraft_render::job::CpuBudget) -> Result<()> {
budget.validate_image(self.width, self.height, None).map_err(|e| DocError::Invalid(e.to_string()))?;
self.validate_with_limits(budget.image_limits(false))
self.validate_with_limits(budget.image_limits(false), Some(CpuSize { progressive: None }))
}
/// Typed GPU options have already been lowered; retain their dimension envelope.
pub fn validate_for_gpu(&self) -> Result<()> {
self.validate_with_limits(solidcraft_render::job::GPU_PATH_LIMITS)
self.validate_with_limits(solidcraft_render::job::GPU_PATH_LIMITS, None)
}
fn validate_with_limits(&self, limits: RenderLimits) -> Result<()> {
fn validate_with_limits(&self, limits: RenderLimits, cpu: Option<CpuSize>) -> Result<()> {
self.surfaces.validate()?;
self.optics.validate()?;
if !self.optics.is_empty() && !matches!(self.job, Some(RenderJob::GpuPath { .. })) {
@@ -123,9 +131,14 @@ impl RenderSetup {
if let Some((w, h)) = limits.fit(width, height, None)
&& (w, h) != (width, height)
{
// A CPU refusal names the profile that can render the size (#8941). It is never
// selected for the person: resolution and profile change only when they choose.
let advice = cpu.map_or_else(String::new, |cpu| {
format!("; {}", solidcraft_render::job::CpuBudget::size_advice(self.width, self.height, cpu.progressive))
});
return Err(DocError::Invalid(format!(
"render output {width} × {height} exceeds the image limits ({} pixels per side, {} pixels in all); \
the largest image at this aspect is {w} × {h}",
the largest image at this aspect is {w} × {h}{advice}",
limits.max_dimension, limits.max_pixels
)));
}
@@ -538,9 +551,58 @@ mod tests {
let setup = RenderSetup { width: 7680, height: 4320, ..Default::default() };
let message = setup.validate().unwrap_err().to_string();
assert!(message.contains("the largest image at this aspect is 27"), "{message}");
assert!(!message.contains("CPU resource profile"), "a raster refusal names no CPU profile: {message}");
assert!(RenderSetup { width: 2048, height: 2048, ..Default::default() }.validate().is_ok());
}
/// #8941: 4K UHD under the default Compact profile names Studio, the profile that renders it;
/// a 16K-wide image under Studio names Workstation; past Workstation no profile is named.
/// The saved profile is never changed by the check, and GPU path tracing keeps its own limits.
#[test]
fn a_cpu_size_refusal_names_the_profile_that_can_render_it() {
use solidcraft_render::job::CpuBudget;
let cpu = |budget, width, height| RenderSetup {
width,
height,
job: Some(RenderJob::CpuRaytrace { options: CpuJob { budget, ..Default::default() } }),
..Default::default()
};
let compact = cpu(CpuBudget::Compact, 3840, 2160);
let message = compact.validate().unwrap_err().to_string();
assert!(message.contains("3840 × 2160 exceeds the image limits (4096 pixels per side, 4194304 pixels in all)"), "{message}");
assert!(message.contains("the largest image at this aspect is 2730 × 1536"), "{message}");
assert!(message.contains("choose the Studio CPU resource profile, or a smaller size"), "{message}");
assert!(matches!(&compact.job, Some(RenderJob::CpuRaytrace { options }) if options.budget == CpuBudget::Compact));
let message = compact.validate_for_cpu(CpuBudget::Compact).unwrap_err().to_string();
assert!(message.contains("choose the Studio CPU resource profile"), "{message}");
cpu(CpuBudget::Studio, 3840, 2160).validate().unwrap();
RenderSetup { width: 3840, height: 2160, ..Default::default() }.validate_for_cpu(CpuBudget::Studio).unwrap();
let message = cpu(CpuBudget::Studio, 15360, 4320).validate().unwrap_err().to_string();
assert!(message.contains("choose the Workstation CPU resource profile"), "{message}");
cpu(CpuBudget::Workstation, 15360, 4320).validate().unwrap();
let message = cpu(CpuBudget::Workstation, 16384, 16384).validate().unwrap_err().to_string();
assert!(message.contains("no CPU resource profile accepts this size; choose a smaller size"), "{message}");
for profile in ["Compact", "Studio", "Workstation"] {
assert!(!message.contains(&format!("the {profile} CPU")), "{message}");
}
// GPU path tracing: 4K renders, an over-limit size names no CPU profile.
let gpu = |width, height| RenderSetup {
width,
height,
job: Some(RenderJob::GpuPath { transparent_background: false, options: Default::default() }),
..Default::default()
};
gpu(3840, 2160).validate().unwrap();
gpu(3840, 2160).validate_for_gpu().unwrap();
let message = gpu(8193, 8193).validate().unwrap_err().to_string();
assert!(message.contains("(8192 pixels per side, 16777216 pixels in all)"), "{message}");
assert!(!message.contains("CPU resource profile"), "{message}");
}
#[test]
fn portable_jobs_preserve_complete_numerical_intent_and_legacy_omission() {
use serde_json::json;
+129
View File
@@ -741,3 +741,132 @@ fn a_sketch_too_large_to_search_whole_says_profiles_may_be_missing() {
m.evaluate(&plate);
assert!(m.result(plate_sketch).and_then(|r| r.warning.as_deref()).is_none());
}
fn browser_group(id: u64, component: u64, folder: &str, parent: Option<u64>, items: &[BrowserEntry]) -> BrowserGroup {
BrowserGroup { id, name: format!("G{id}"), component, folder: folder.into(), parent, items: items.to_vec() }
}
fn item(key: &str) -> BrowserEntry {
BrowserEntry::item(key)
}
fn group(id: u64) -> BrowserEntry {
BrowserEntry::Group(id)
}
/// The design with what `browser_pruned` drops gone (the design itself when nothing is).
fn pruned(doc: &Document) -> Document {
let mut out = doc.clone();
if let Some((groups, order)) = doc.browser_pruned() {
(out.browser_groups, out.browser_order) = (groups, order);
}
out
}
/// Pruning keeps every group but cuts a parent that loops, is the group itself, is past the
/// nesting limit, or is in another component or folder, to the top level; and it drops entries
/// for deleted sketches and placements, repeats, and the lists of a missing component. Body names
/// are never judged.
#[test]
fn browser_pruning_cuts_bad_parents_and_drops_stale_and_repeated_entries() {
let mut doc = Document::new("prune");
let sketch = doc.add_feature(FeatureKind::Sketch { plane: PlaneRef::Origin { name: "XY".into() }, sketch: Sketch::new() }, None).unwrap();
let key = sketch.to_string();
doc.components.push(Component { id: 7, name: "Part".into(), ..Default::default() });
doc.occurrences.push(Occurrence { id: 20, component: 7, parent: 0, name: "Part".into(), transform: IDENTITY, grounded: false });
let deep = MAX_GROUP_NESTING as u64 + 2;
doc.browser_groups = vec![
browser_group(1, 0, "bodies", Some(2), &[]),
browser_group(2, 0, "bodies", Some(1), &[]),
browser_group(3, 0, "bodies", Some(3), &[]),
browser_group(4, 7, "bodies", Some(5), &[]),
browser_group(5, 0, "bodies", None, &[item("a"), group(7), group(7), item("a")]),
browser_group(6, 0, "sketches", Some(5), &[item(&key), item("999")]),
browser_group(7, 0, "bodies", Some(5), &[]),
browser_group(8, 0, "bodies", None, &[item("a"), item("b")]),
];
doc.browser_groups.extend((0..deep).map(|i| browser_group(100 + i, 0, "construction", i.checked_sub(1).map(|p| 100 + p), &[])));
doc.browser_order.insert("0/bodies".into(), vec![item("Lid"), item("Ghost"), item("Lid"), group(5), group(5)]);
doc.browser_order.insert("0/components".into(), vec![item("21"), item("20")]);
doc.browser_order.insert("99/bodies".into(), vec![item("x")]);
let after = pruned(&doc);
let parent = |id| after.browser_group(id).unwrap().parent;
assert_eq!(after.browser_groups.len(), doc.browser_groups.len(), "no group is dropped");
assert_eq!([1, 2, 3, 4, 6].map(parent), [None; 5], "a loop, itself, another component, another folder");
assert_eq!(parent(7), Some(5));
for i in 0..deep {
assert_eq!(parent(100 + i), (1..MAX_GROUP_NESTING as u64).contains(&i).then(|| 99 + i), "level {} of a chain {deep} deep", i + 1);
}
assert_eq!(after.browser_group(5).unwrap().items, [item("a"), group(7)]);
assert_eq!(after.browser_group(8).unwrap().items, [item("b")], "an item stays in the first group that has it");
assert_eq!(after.browser_group(6).unwrap().items, [item(&key)], "the deleted sketch goes");
assert_eq!(after.browser_order["0/bodies"], [item("Lid"), item("Ghost"), group(5)], "body names stay; repeats go");
assert_eq!(after.browser_order["0/components"], [item("20")], "the deleted placement goes");
assert!(!after.browser_order.contains_key("99/bodies"));
assert!(Document::new("clean").browser_pruned().is_none(), "nothing to prune is not a change");
}
/// Levels and ancestry follow the parents. Groups a list does not name go after the last one it
/// names, in creation order, or first when it names none: above the items, as a design saved
/// before groups could be ordered has always shown them.
#[test]
fn group_levels_and_the_order_of_groups_a_list_does_not_name() {
let mut doc = Document::new("order");
doc.browser_groups = vec![
browser_group(1, 0, "bodies", None, &[item("x")]),
browser_group(2, 0, "bodies", None, &[]),
browser_group(3, 0, "bodies", None, &[]),
browser_group(4, 0, "bodies", Some(1), &[]),
browser_group(5, 0, "bodies", Some(4), &[]),
];
assert_eq!([1, 2, 4, 5].map(|id| doc.group_level(id)), [1, 1, 2, 3]);
assert!(doc.group_within(5, 1) && doc.group_within(5, 5) && !doc.group_within(1, 5) && !doc.group_within(2, 5));
assert_eq!(doc.group_height(1), 3);
let top = |doc: &Document| doc.ordered_entries(0, "bodies", None);
assert_eq!(top(&doc), [group(1), group(2), group(3)], "no list: creation order");
doc.browser_order.insert("0/bodies".into(), vec![item("a")]);
assert_eq!(top(&doc), [group(1), group(2), group(3), item("a")], "a list of items only: groups above them");
doc.browser_order.insert("0/bodies".into(), vec![item("a"), group(2), item("b"), group(2), group(9)]);
assert_eq!(top(&doc), [item("a"), group(2), group(1), group(3), item("b")], "after the last listed; repeats and unknown ignored");
assert_eq!(doc.ordered_entries(0, "bodies", Some(1)), [group(4), item("x")], "inside a group too");
}
/// Nested groups and a `components` order survive a save and a load, saved as numbers (groups) and
/// strings (items). A design from before groups could nest reads with the browser it always had:
/// no parents, groups in creation order above the items in their saved order, saved back as read.
#[test]
fn browser_data_survives_a_save_and_old_designs_read_unchanged() {
let mut doc = Document::new("nested");
doc.components.push(Component { id: 7, name: "Part".into(), ..Default::default() });
for id in [20, 21] {
doc.occurrences.push(Occurrence { id, component: 7, parent: 0, name: format!("P{id}"), transform: IDENTITY, grounded: false });
}
doc.browser_groups = vec![browser_group(2, 0, "bodies", None, &[item("Lid"), group(3)]), browser_group(3, 0, "bodies", Some(2), &[item("Base")])];
doc.browser_order.insert("0/bodies".into(), vec![item("Cap"), group(2)]);
doc.browser_order.insert("0/components".into(), vec![item("21"), item("20")]);
let saved = doc.to_json();
let value: serde_json::Value = serde_json::from_str(&saved).unwrap();
assert_eq!(value["browser_order"]["0/bodies"], serde_json::json!(["Cap", 2]));
assert_eq!((&value["browser_groups"][1]["parent"], value["browser_groups"][0].get("parent")), (&serde_json::json!(2), None));
let loaded = Document::from_json(&saved).unwrap();
assert_eq!((&loaded.browser_groups, &loaded.browser_order), (&doc.browser_groups, &doc.browser_order));
let resaved: serde_json::Value = serde_json::from_str(&loaded.to_json()).unwrap();
assert_eq!((&resaved["browser_groups"], &resaved["browser_order"]), (&value["browser_groups"], &value["browser_order"]));
let groups = serde_json::json!([
{"id": 3, "name": "Fasteners", "component": 0, "folder": "bodies", "items": ["Bolt", "Nut"]},
{"id": 4, "name": "Plates", "component": 0, "folder": "bodies", "items": ["Plate"]},
]);
for (order, top) in [
(serde_json::json!({}), vec![group(3), group(4)]),
(serde_json::json!({"0/bodies": ["Washer", "Bolt", "Cap"]}), vec![group(3), group(4), item("Washer"), item("Bolt"), item("Cap")]),
] {
let mut old: serde_json::Value = serde_json::from_str(&Document::new("old").to_json()).unwrap();
(old["browser_groups"], old["browser_order"]) = (groups.clone(), order.clone());
let loaded = Document::from_json(&old.to_string()).unwrap();
assert!(loaded.browser_groups.iter().all(|g| g.parent.is_none()), "{order}");
assert_eq!(loaded.ordered_entries(0, "bodies", None), top, "{order}");
let resaved: serde_json::Value = serde_json::from_str(&loaded.to_json()).unwrap();
assert_eq!(resaved["browser_groups"], groups, "{order}");
}
}
+162 -18
View File
@@ -3,9 +3,17 @@
//! kept apart from the number parameters: a text has no unit and no number expression reads it.
//! A parameter edit or a configuration row that sets one relabels every text built from it, and
//! the features that use those texts follow in the same rebuild.
//!
//! The words can show a number parameter's value too (#9281): `'Width: ' + Width` reads
//! `Width: 85 mm`, a length in the design's units, an angle in degrees (`30°`), up to three
//! decimals without trailing zeros. `number(Width)` is the number alone (`85`) and
//! `number(Width, 2)` gives it two decimals (`85.00`).
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::expr::Value;
use crate::{DocError, Document, FeatureKind, MAX_PARAMS, Result};
/// The unit that marks a text parameter where parameters are listed, added or imported.
@@ -49,8 +57,29 @@ pub fn unquote(s: &str) -> String {
enum Term {
Text(String),
Name(String),
/// `number(name)` or `number(name, decimals)`, with the name's byte range.
Number {
name: String,
at: (usize, usize),
decimals: Option<u8>,
},
}
/// How the words show a number parameter.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Show {
/// As named: its value and unit, up to three decimals.
Named,
/// In `number(...)`: the number alone, with these decimals or up to three.
Number(Option<u8>),
}
/// The function that shows a number parameter's value without its unit.
const NUMBER: &str = "number";
/// Most decimals `number(name, decimals)` takes.
const MAX_DECIMALS: u8 = 8;
/// The terms of a words expression with their byte ranges: quoted texts (single or double
/// quotes, a quote doubled inside stands for one) and parameter names, joined by `+`.
fn terms(s: &str) -> Result<Vec<(Term, usize, usize)>> {
@@ -96,10 +125,16 @@ fn terms(s: &str) -> Result<Vec<(Term, usize, usize)>> {
want_term = false;
} else if c.is_alphabetic() || c == '_' {
let start = i;
while cs.get(i).is_some_and(|x| x.1.is_alphanumeric() || x.1 == '_') {
i += 1;
i = word_end(&cs, i);
let word = s.get(at(start)..at(i)).unwrap_or_default().to_string();
let open = skip_blanks(&cs, i);
if word == NUMBER && cs.get(open).is_some_and(|x| x.1 == '(') {
let (term, end) = number_call(s, &cs, open + 1)?;
i = end;
out.push((term, at(start), at(i)));
} else {
out.push((Term::Name(word), at(start), at(i)));
}
out.push((Term::Name(s.get(at(start)..at(i)).unwrap_or_default().to_string()), at(start), at(i)));
want_term = false;
} else {
return Err(DocError::Expr(format!("unexpected `{c}`: a text expression joins quoted texts and text parameters with +")));
@@ -112,11 +147,88 @@ fn terms(s: &str) -> Result<Vec<(Term, usize, usize)>> {
Ok(out)
}
fn word_end(cs: &[(usize, char)], mut i: usize) -> usize {
while cs.get(i).is_some_and(|x| x.1.is_alphanumeric() || x.1 == '_') {
i += 1;
}
i
}
fn skip_blanks(cs: &[(usize, char)], mut i: usize) -> usize {
while cs.get(i).is_some_and(|x| x.1.is_whitespace()) {
i += 1;
}
i
}
/// `number(` has been read up to `i`: the name, optional decimals and `)`; the term and where
/// it ends.
fn number_call(s: &str, cs: &[(usize, char)], i: usize) -> Result<(Term, usize)> {
let at = |i: usize| cs.get(i).map_or(s.len(), |x| x.0);
let usage = || DocError::Expr("number() takes a number parameter and, if wanted, its decimals: number(Width, 2)".into());
let start = skip_blanks(cs, i);
if !cs.get(start).is_some_and(|x| x.1.is_alphabetic() || x.1 == '_') {
return Err(usage());
}
let end = word_end(cs, start);
let name = s.get(at(start)..at(end)).unwrap_or_default().to_string();
let mut i = skip_blanks(cs, end);
let mut decimals = None;
if cs.get(i).is_some_and(|x| x.1 == ',') {
let from = skip_blanks(cs, i + 1);
let mut to = from;
while cs.get(to).is_some_and(|x| x.1.is_ascii_digit()) {
to += 1;
}
let digits = s.get(at(from)..at(to)).unwrap_or_default();
let n: u8 = digits.parse().map_err(|_| usage())?;
if n > MAX_DECIMALS {
return Err(DocError::Expr(format!("number() shows at most {MAX_DECIMALS} decimals")));
}
decimals = Some(n);
i = skip_blanks(cs, to);
}
if !cs.get(i).is_some_and(|x| x.1 == ')') {
return Err(usage());
}
Ok((Term::Number { name, at: (at(start), at(end)), decimals }, i + 1))
}
/// A number parameter's value as the words show it: `v` in millimetres, radians or no unit
/// (`len`, `ang`: its dimension), lengths in `units`.
pub fn show_number(v: f64, len: i8, ang: i8, units: &str, show: Show) -> Result<String> {
let (value, unit) = match (len, ang) {
(0, 0) => (v, ""),
(1, 0) => {
let (scale, name) = crate::display_units::unit_scale(units);
(v * scale, name)
}
(0, 1) => (v.to_degrees(), "°"),
_ => return Err(DocError::Expr("a text shows lengths, angles and plain numbers only".into())),
};
if !value.is_finite() || value.abs() > 1e12 {
return Err(DocError::Expr("the number is out of range for a text".into()));
}
let digits = match show {
Show::Number(Some(d)) => format!("{value:.*}", usize::from(d)),
Show::Named | Show::Number(None) => {
let t = format!("{value:.3}");
let t = t.trim_end_matches('0').trim_end_matches('.');
if t == "-0" { "0".to_string() } else { t.to_string() }
}
};
Ok(match (show, unit) {
(Show::Number(_), _) | (_, "") => digits,
(Show::Named, "°") => format!("{digits}°"),
(Show::Named, unit) => format!("{digits} {unit}"),
})
}
/// The parameters a words expression names (none when it does not parse).
pub fn references(s: &str) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for (t, _, _) in terms(s).unwrap_or_default() {
if let Term::Name(n) = t
if let Term::Name(n) | Term::Number { name: n, .. } = t
&& !out.contains(&n)
{
out.push(n);
@@ -131,23 +243,27 @@ pub fn rename_reference(s: &str, old: &str, new: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut last = 0;
for (t, a, b) in terms {
if matches!(&t, Term::Name(n) if n == old) {
out.push_str(s.get(last..a).unwrap_or_default());
out.push_str(new);
last = b;
}
let at = match &t {
Term::Name(n) if n == old => (a, b),
Term::Number { name, at, .. } if name == old => *at,
_ => continue,
};
out.push_str(s.get(last..at.0).unwrap_or_default());
out.push_str(new);
last = at.1;
}
out.push_str(s.get(last..).unwrap_or_default());
out
}
/// The words an expression comes to, each parameter's text from `lookup`.
pub fn eval(s: &str, lookup: &dyn Fn(&str) -> Result<String>) -> Result<String> {
pub fn eval(s: &str, lookup: &dyn Fn(&str, Show) -> Result<String>) -> Result<String> {
let mut out = String::new();
for (t, _, _) in terms(s)? {
match t {
Term::Text(text) => out.push_str(&text),
Term::Name(n) => out.push_str(&lookup(&n)?),
Term::Name(n) => out.push_str(&lookup(&n, Show::Named)?),
Term::Number { name, decimals, .. } => out.push_str(&lookup(&name, Show::Number(decimals))?),
}
if out.len() > MAX_LEN {
return Err(DocError::Expr("the text is too long".into()));
@@ -161,12 +277,14 @@ impl Document {
self.text_params.iter().find(|p| p.name == name)
}
/// The words a words expression comes to in this design.
pub fn eval_words(&self, e: &str) -> Result<String> {
eval(e, &|n| match self.text_param(n) {
Some(p) => Ok(p.text.clone()),
None if self.name_taken(n) => Err(DocError::Expr(format!("`{n}` is a number parameter: a text's words take text parameters"))),
None => Err(DocError::Expr(format!("unknown text parameter `{n}`"))),
/// The words a words expression comes to in this design, number parameters from `vals`.
pub fn eval_words(&self, e: &str, vals: &BTreeMap<String, Value>) -> Result<String> {
eval(e, &|n, show| match (self.text_param(n), vals.get(n)) {
(Some(_), _) if show != Show::Named => Err(DocError::Expr(format!("`{n}` is a text parameter: number() takes a number parameter"))),
(Some(p), _) => Ok(p.text.clone()),
(None, Some(v)) => show_number(v.v, v.len, v.ang, &self.units, show).map_err(|error| DocError::Expr(format!("`{n}`: {error}"))),
(None, None) if self.name_taken(n) => Err(DocError::Expr(format!("`{n}` has no value"))),
(None, None) => Err(DocError::Expr(format!("unknown parameter `{n}`"))),
})
}
@@ -230,7 +348,7 @@ mod tests {
#[test]
fn words_expressions_join_quoted_texts_and_parameters() {
let look = |n: &str| match n {
let look = |n: &str, _: Show| match n {
"Label" => Ok("TAG-1".to_string()),
_ => Err(DocError::Expr(format!("unknown text parameter `{n}`"))),
};
@@ -249,4 +367,30 @@ mod tests {
assert_eq!(unquote("'a' + 'b'"), "'a' + 'b'");
assert_eq!(unquote("TAG-1"), "TAG-1");
}
/// #9281: a number parameter in the words shows its value; `number()` drops the unit and
/// can fix the decimals; a rename reaches inside `number()`.
#[test]
fn number_parameters_show_their_value_in_the_words() {
let look = |n: &str, show: Show| match n {
"Width" => show_number(85.0, 1, 0, "mm", show),
"Gap" => show_number(12.7, 1, 0, "in", show),
"Tilt" => show_number(30f64.to_radians(), 0, 1, "mm", show),
"Count" => show_number(4.0, 0, 0, "mm", show),
"Area" => show_number(1.0, 2, 0, "mm", show),
_ => Err(DocError::Expr(format!("unknown parameter `{n}`"))),
};
assert_eq!(eval("'Width: ' + Width", &look).unwrap(), "Width: 85 mm");
assert_eq!(eval("Gap", &look).unwrap(), "0.5 in");
assert_eq!(eval("Tilt + ' / ' + Count", &look).unwrap(), "30° / 4");
assert_eq!(eval("number(Width) + 'x' + number( Width , 2 )", &look).unwrap(), "85x85.00");
assert_eq!(eval("number(Tilt, 1)", &look).unwrap(), "30.0");
for bad in ["number(Width", "number()", "number(Width, x)", "number(Width, 9)", "number(Width 2)", "Area", "number('a')"] {
assert!(eval(bad, &look).is_err(), "{bad}");
}
assert_eq!(references("number(Width, 1) + Count"), ["Width", "Count"]);
assert_eq!(rename_reference("number(Width, 1) + Width + 'Width'", "Width", "W"), "number(W, 1) + W + 'Width'");
assert_eq!(show_number(-0.0001, 1, 0, "mm", Show::Named).unwrap(), "0 mm");
assert!(show_number(f64::NAN, 0, 0, "mm", Show::Named).is_err());
}
}
+293 -35
View File
@@ -11,7 +11,7 @@ use std::collections::BTreeMap;
use serde_json::{Value, json};
use solidcraft_doc::FeatureKind;
use solidcraft_doc::{BrowserEntry, FeatureKind, MAX_GROUP_NESTING};
use super::CommandSpec;
use crate::params::{bad, bool_, str_};
@@ -24,11 +24,11 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("sketch: id or name; plane: as sketch.create; frame?: as sketch.create; author_path?: exact owner occurrence ids; explicitly rebinds or freezes the plane while preserving geometry and dimension names"),
CommandSpec::new("browser.group", "New Group", group)
.icon("folder")
.params("folder: bodies|sketches|construction; component?: id (default 0); name?; items?: [keys] (they leave other groups)"),
CommandSpec::new("browser.ungroup", "Ungroup", ungroup).icon("folder").params("group: id (its items go back to the folder)"),
.params("folder?: bodies|sketches|construction; component?: id (default 0); parent?: group id (the new group nests in it, taking its component and folder; at most 8 levels); name?; items?: [keys] (they leave other groups)"),
CommandSpec::new("browser.ungroup", "Ungroup", ungroup).icon("folder").params("group: id (its items and child groups go up to the group that holds it, or the folder)"),
CommandSpec::new("browser.rename_group", "Rename Group", rename_group).icon("rename").params("group: id, name"),
CommandSpec::new("browser.move", "Move Browser Items", move_items).icon("reorder")
.params("folder, component?, items: [keys]; group?: id (into it; omit to take them out of groups); before?: key (position in the group)"),
.params("folder, component?, items: [keys (strings) and group ids (numbers)]; group?: id (into it; omit for the folder's top level); before?: key or group id (position in it; default the end). A group cannot go into itself or past 8 levels"),
CommandSpec::new("document.units", "Change Design Units", document_units)
.icon("settings")
.params("units: mm|cm|m|in|ft (display and unit-less parameters; values keep their meaning)"),
@@ -41,7 +41,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("view.apply", "Restore Named View", view_apply).icon("camera").noundo().params(
"view?: front|back|top|bottom|left|right|iso|home (frames the model) | a named view's name; or camera?: {target: [x,y,z], yaw, pitch, distance, fov?, roll?} (radians, mm, about the up axis; yaw: heading of the line of sight, 0 along +Y (Front), π/2 along +X (Left), π (Back), −π/2 (Right); pitch: the eye's elevation, within ±π/2, π/2 = Top; roll defaults to 0, turns the picture clockwise, must be finite and is clamped to ±π; the View angles readout shows these in degrees; up is refused: the up axis comes from the design settings) → camera. The viewport (and the MCP screenshot) moves there. View state: not saved, not an undo step",
),
CommandSpec::new("browser.order", "Order Browser Items", order).icon("reorder").params("folder, component?, order: [keys] (the folder's items in display order)"),
CommandSpec::new("browser.order", "Order Browser Items", order).icon("reorder").params("folder: bodies|sketches|construction|components, component?, order: [keys (strings) and top-level group ids (numbers)] (display order; for `components`, the ids of the components placed in it)"),
CommandSpec::new("browser.visibility", "Show/Hide", visibility).icon("eye").params(
"visible: bool; items?: [sketch id or name | body name | construction name | joint name | O|X|Y|Z|XY|XZ|YZ]; \
or joints?: [joint id or name] (their glyphs; they still constrain and move parts); \
@@ -98,11 +98,102 @@ fn group_id(s: &Session, p: &Value, cmd: &str) -> Result<u64> {
if s.doc.browser_groups.iter().any(|g| g.id == id) { Ok(id) } else { Err(bad(cmd, format!("no group {id}"))) }
}
/// A list of groups (numbers) and items (strings): a number is always a group.
fn entries(p: &Value, k: &str, cmd: &str) -> Result<Vec<BrowserEntry>> {
let list = match p.get(k) {
Some(Value::Array(a)) => a
.iter()
.map(|x| match x {
Value::String(s) => Some(BrowserEntry::Item(s.clone())),
_ => x.as_u64().map(BrowserEntry::Group),
})
.collect::<Option<Vec<_>>>(),
None => Some(Vec::new()),
_ => None,
};
let list = list.ok_or_else(|| bad(cmd, format!("`{k}` must be a list of item keys (strings) and group ids (numbers)")))?;
if list.len() > 10_000 || list.iter().any(|e| e.as_item().is_some_and(|k| k.is_empty() || k.len() > 256)) {
return Err(bad(cmd, format!("`{k}` has bad keys")));
}
Ok(list)
}
/// A number in a list of items or an order names a group; say so when there is none, as a sketch
/// or plane id passed as a number is the likely mistake (item keys are strings).
fn no_group(cmd: &str, id: u64) -> crate::EngineError {
bad(cmd, format!("no group {id} (a number is a group id; pass item keys as strings)"))
}
/// The group a command names under `key`, if it names one (`null` or absent: none).
fn optional_group(s: &Session, p: &Value, key: &str, cmd: &str) -> Result<Option<u64>> {
match p.get(key) {
None | Some(Value::Null) => Ok(None),
Some(v) => {
let id = v.as_u64().ok_or_else(|| bad(cmd, format!("`{key}` must be a group id")))?;
if s.doc.browser_group(id).is_some() { Ok(Some(id)) } else { Err(no_group(cmd, id)) }
}
}
}
/// The list a group's contents (or the folder's top level, `None`) are ordered by.
fn holder_list<'a>(d: &'a mut solidcraft_doc::Document, component: u64, folder: &str, holder: Option<u64>) -> Option<&'a mut Vec<BrowserEntry>> {
match holder {
Some(g) => d.browser_groups.iter_mut().find(|x| x.id == g).map(|g| &mut g.items),
None => Some(d.browser_order.entry(format!("{component}/{folder}")).or_default()),
}
}
/// Name every group under `holder` in its list, where they are drawn, so a position among them
/// can be found.
fn settle(d: &mut solidcraft_doc::Document, component: u64, folder: &str, holder: Option<u64>) {
let all = d.ordered_entries(component, folder, holder);
if let Some(list) = holder_list(d, component, folder, holder) {
*list = all;
}
}
/// Take entries out of every list of a component's folder: its groups' and the top level's.
fn take_out(d: &mut solidcraft_doc::Document, component: u64, folder: &str, gone: &[BrowserEntry]) {
for g in d.browser_groups.iter_mut().filter(|g| g.component == component && g.folder == folder) {
g.items.retain(|e| !gone.contains(e));
}
if let Some(list) = d.browser_order.get_mut(&format!("{component}/{folder}")) {
list.retain(|e| !gone.contains(e));
if list.is_empty() {
d.browser_order.remove(&format!("{component}/{folder}"));
}
}
}
fn group(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "browser.group";
let folder = folder_of(p, cmd)?;
let component = component_of(s, p, cmd)?;
let items = keys(p, "items", cmd)?;
let parent = optional_group(s, p, "parent", cmd)?;
let (mut component, mut folder) = (None, None);
if let Some(g) = parent.and_then(|g| s.doc.browser_group(g)) {
// Omitted, they come from the parent; given, they must agree with it.
if p.get("component").is_some() && component_of(s, p, cmd)? != g.component {
return Err(bad(cmd, "`parent` belongs to another component"));
}
if p.get("folder").is_some() && folder_of(p, cmd)? != g.folder {
return Err(bad(cmd, "`parent` belongs to another folder"));
}
if s.doc.group_level(g.id) >= MAX_GROUP_NESTING {
return Err(bad(cmd, format!("groups nest at most {MAX_GROUP_NESTING} levels")));
}
(component, folder) = (Some(g.component), Some(g.folder.clone()));
}
let folder = match folder {
Some(f) => f,
None => folder_of(p, cmd)?,
};
let component = match component {
Some(c) => c,
None => component_of(s, p, cmd)?,
};
let items = match entries(p, "items", cmd)?.into_iter().map(|e| e.as_item().map(str::to_string)).collect::<Option<Vec<_>>>() {
Some(items) => items,
None => return Err(bad(cmd, "`items` are item keys: move groups with browser.move")),
};
let d = s.doc_mut();
let id = d.allocate_id()?;
let name = match str_(p, "name").map(str::trim).filter(|n| !n.is_empty()) {
@@ -110,16 +201,32 @@ fn group(s: &mut Session, p: &Value) -> Result<Value> {
Some(_) => return Err(bad(cmd, "`name` is too long")),
None => format!("Group{}", d.browser_groups.iter().filter(|g| g.folder == folder).count() + 1),
};
for g in d.browser_groups.iter_mut().filter(|g| g.component == component && g.folder == folder) {
g.items.retain(|k| !items.contains(k));
}
d.browser_groups.push(solidcraft_doc::BrowserGroup { id, name: name.clone(), component, folder, items });
Ok(json!({ "group": id, "name": name }))
let gone: Vec<BrowserEntry> = items.iter().map(|k| BrowserEntry::item(k)).collect();
take_out(d, component, &folder, &gone);
let items = gone;
d.browser_groups.push(solidcraft_doc::BrowserGroup { id, name: name.clone(), component, folder, parent, items });
Ok(json!({ "group": id, "name": name, "parent": parent }))
}
/// A group's contents go up to the group that holds it (the folder's top level for a top-level
/// group), in the place the group had; so do its child groups. Its own entry goes.
fn ungroup(s: &mut Session, p: &Value) -> Result<Value> {
let id = group_id(s, p, "browser.ungroup")?;
s.doc_mut().browser_groups.retain(|g| g.id != id);
let d = s.doc_mut();
let Some(g) = d.browser_group(id).cloned() else { return Ok(json!({ "group": id })) };
settle(d, g.component, &g.folder, Some(id));
settle(d, g.component, &g.folder, g.parent);
let inside = d.browser_group(id).map(|g| g.items.clone()).unwrap_or_default();
for child in d.browser_groups.iter_mut().filter(|c| c.parent == Some(id)) {
child.parent = g.parent;
}
if let Some(list) = holder_list(d, g.component, &g.folder, g.parent)
&& let Some(at) = list.iter().position(|e| *e == BrowserEntry::Group(id))
{
list.splice(at..=at, inside);
}
take_out(d, g.component, &g.folder, &[BrowserEntry::Group(id)]);
d.browser_groups.retain(|x| x.id != id);
Ok(json!({ "group": id }))
}
@@ -138,43 +245,91 @@ fn move_items(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "browser.move";
let folder = folder_of(p, cmd)?;
let component = component_of(s, p, cmd)?;
let items = keys(p, "items", cmd)?;
let items = entries(p, "items", cmd)?;
if items.is_empty() {
return Err(bad(cmd, "`items` must list item keys"));
return Err(bad(cmd, "`items` must list item keys or group ids"));
}
let to = match p.get("group") {
Some(Value::Null) | None => None,
Some(_) => Some(group_id(s, p, cmd)?),
};
let to = optional_group(s, p, "group", cmd)?;
if let Some(g) = to
&& !s.doc.browser_groups.iter().any(|x| x.id == g && x.component == component && x.folder == folder)
{
return Err(bad(cmd, "that group is in another folder"));
}
let before = str_(p, "before").map(str::to_string);
let d = s.doc_mut();
for g in d.browser_groups.iter_mut().filter(|g| g.component == component && g.folder == folder) {
g.items.retain(|k| !items.contains(k));
if Some(g.id) == to {
let at = before.as_ref().and_then(|b| g.items.iter().position(|k| k == b)).unwrap_or(g.items.len());
for (i, k) in items.iter().enumerate() {
g.items.insert(at + i, k.clone());
}
let to_level = to.map_or(0, |g| s.doc.group_level(g));
for e in &items {
let BrowserEntry::Group(id) = e else { continue };
let Some(g) = s.doc.browser_group(*id) else { return Err(no_group(cmd, *id)) };
if g.component != component || g.folder != folder {
return Err(bad(cmd, format!("group {id} is in another folder")));
}
if to.is_some_and(|t| s.doc.group_within(t, *id)) {
return Err(bad(cmd, format!("group {id} cannot go inside itself")));
}
if to_level + s.doc.group_height(*id) > MAX_GROUP_NESTING {
return Err(bad(cmd, format!("groups nest at most {MAX_GROUP_NESTING} levels")));
}
}
let before = match p.get("before") {
None | Some(Value::Null) => None,
Some(Value::String(k)) => Some(BrowserEntry::Item(k.clone())),
Some(v) => {
let id = v.as_u64().ok_or_else(|| bad(cmd, "`before` must be an item key or a group id"))?;
if s.doc.browser_group(id).is_none() {
return Err(no_group(cmd, id));
}
Some(BrowserEntry::Group(id))
}
};
let d = s.doc_mut();
// Where a group list is silent about its child groups, the ones it names come first: name
// them all, so `before` can be one of them.
settle(d, component, &folder, to);
take_out(d, component, &folder, &items);
for e in &items {
if let BrowserEntry::Group(id) = e
&& let Some(g) = d.browser_groups.iter_mut().find(|g| g.id == *id)
{
g.parent = to;
}
}
if let Some(list) = holder_list(d, component, &folder, to) {
let at = before.as_ref().and_then(|b| list.iter().position(|e| e == b)).unwrap_or(list.len());
list.splice(at..at, items.iter().cloned());
}
Ok(json!({ "items": items, "group": to }))
}
fn order(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "browser.order";
let folder = folder_of(p, cmd)?;
let component = component_of(s, p, cmd)?;
let order = keys(p, "order", cmd)?;
let components = str_(p, "folder") == Some("components");
let folder = if components { "components".to_string() } else { folder_of(p, cmd)? };
let order = entries(p, "order", cmd)?;
for e in &order {
match e {
// Sub-components, by placement id, all of them placed in this component.
BrowserEntry::Item(k) if components => {
let placed = k.parse::<u64>().ok().is_some_and(|o| s.doc.occurrences.iter().any(|x| x.id == o && x.parent == component));
if !placed {
return Err(bad(cmd, "the `components` folder is ordered by the ids of the components placed in it"));
}
}
BrowserEntry::Item(_) => {}
BrowserEntry::Group(id) => match s.doc.browser_group(*id) {
None => return Err(no_group(cmd, *id)),
Some(g) if components || g.component != component || g.folder != folder || g.parent.is_some() => {
return Err(bad(cmd, format!("group {id} is not a top-level group of this folder")));
}
Some(_) => {}
},
}
}
let key = format!("{component}/{folder}");
let d = s.doc_mut();
if order.is_empty() {
d.browser_order.remove(&key);
} else {
// An item that sits in a group belongs to the group's list only.
d.browser_order.insert(key, order);
}
Ok(json!({}))
@@ -2022,17 +2177,17 @@ mod tests {
let g = run(&mut s, "browser.group", json!({"folder": "bodies", "items": [a.clone()]}))["group"].as_u64().unwrap();
assert_eq!(s.doc.browser_groups[0].name, "Group1");
run(&mut s, "browser.move", json!({"folder": "bodies", "items": [b.clone()], "group": g, "before": a.clone()}));
assert_eq!(s.doc.browser_groups[0].items, vec![b.clone(), a.clone()]);
assert_eq!(s.doc.browser_groups[0].items, vec![b.clone().into(), a.clone().into()]);
run(&mut s, "browser.rename_group", json!({"group": g, "name": "Fasteners"}));
run(&mut s, "body.rename", json!({"body": a, "name": "Bolt"}));
assert_eq!(s.doc.browser_groups[0].items, vec![b.clone(), "Bolt".to_string()]);
assert_eq!(s.doc.browser_groups[0].items, vec![b.clone().into(), "Bolt".into()]);
// Out of the group, then a folder order; saved with the design.
run(&mut s, "browser.move", json!({"folder": "bodies", "items": [b.clone()]}));
assert_eq!(s.doc.browser_groups[0].items, vec!["Bolt".to_string()]);
assert_eq!(s.doc.browser_groups[0].items, vec!["Bolt".into()]);
run(&mut s, "browser.order", json!({"folder": "bodies", "order": [b.clone(), "Bolt"]}));
let saved: solidcraft_doc::Document = serde_json::from_str(&serde_json::to_string(&*s.doc).unwrap()).unwrap();
assert_eq!(saved.browser_groups, s.doc.browser_groups);
assert_eq!(saved.browser_order["0/bodies"], vec![b, "Bolt".to_string()]);
assert_eq!(saved.browser_order["0/bodies"], vec![b.into(), "Bolt".into()]);
run(&mut s, "browser.ungroup", json!({"group": g}));
assert!(s.doc.browser_groups.is_empty());
// Hostile input is refused.
@@ -2215,4 +2370,107 @@ mod tests {
let e = s.execute("browser.visibility", &json!({"visible": false, "features": ["Sphere1"], "path": [first]})).expect_err("other component");
assert!(e.to_string().contains("component"), "{e}");
}
use solidcraft_doc::{BrowserEntry, MAX_GROUP_NESTING};
fn three_bodies(s: &mut Session) {
for (i, name) in ["A", "B", "C"].into_iter().enumerate() {
run(s, "solid.box", json!({"length": 10, "width": 10, "height": 10, "corner": [i as f64 * 20.0, 0, 0], "body_name": name}));
}
}
fn new_group(s: &mut Session, mut p: Value) -> u64 {
p["folder"] = json!("bodies");
run(s, "browser.group", p)["group"].as_u64().unwrap()
}
fn state(s: &Session) -> (Vec<solidcraft_doc::BrowserGroup>, std::collections::BTreeMap<String, Vec<BrowserEntry>>) {
(s.doc.browser_groups.clone(), s.doc.browser_order.clone())
}
fn refused(s: &mut Session, id: &str, p: Value) {
let before = s.doc.to_json();
let r = s.execute(id, &p);
assert!(matches!(r, Err(crate::EngineError::BadParams { .. })), "{id} {p}: {r:?}");
assert_eq!(s.doc.to_json(), before, "{id} {p}: a refused command changes nothing");
}
/// Nest a group, order groups and components, ungroup; each step undoes and redoes, and none of
/// it evaluates anything.
#[test]
fn groups_nest_reorder_and_ungroup_with_undo_redo_and_no_evaluation() {
let mut s = Session::default();
three_bodies(&mut s);
let mut placed = Vec::new();
for name in ["P", "Q"] {
placed.push(run(&mut s, "component.create", json!({"name": name}))["occurrence"].as_u64().unwrap());
run(&mut s, "component.activate", json!({"component": 0}));
}
let mut states = vec![state(&s)];
s.evaluations = 0;
let outer = new_group(&mut s, json!({"items": ["A"]}));
states.push(state(&s));
// The inner group takes its component and folder from the parent.
let inner = run(&mut s, "browser.group", json!({"parent": outer, "items": ["B"]}))["group"].as_u64().unwrap();
states.push(state(&s));
let g = s.doc.browser_group(inner).unwrap();
assert_eq!((g.parent, g.component, g.folder.as_str()), (Some(outer), 0, "bodies"));
run(&mut s, "browser.order", json!({"folder": "components", "order": [placed[1].to_string(), placed[0].to_string()]}));
states.push(state(&s));
let mut shown = placed.clone();
s.doc.sort_placements(0, &mut shown);
assert_eq!(shown, [placed[1], placed[0]]);
run(&mut s, "browser.order", json!({"folder": "bodies", "order": ["C", outer]}));
states.push(state(&s));
run(&mut s, "browser.move", json!({"folder": "bodies", "items": [inner], "before": "C"}));
states.push(state(&s));
assert_eq!(s.doc.ordered_entries(0, "bodies", None), [BrowserEntry::Group(inner), "C".into(), BrowserEntry::Group(outer)]);
run(&mut s, "browser.ungroup", json!({"group": outer}));
states.push(state(&s));
// The group's items go where it was.
assert_eq!(s.doc.browser_order["0/bodies"], [BrowserEntry::Group(inner), "C".into(), "A".into()]);
for i in (0..states.len() - 1).rev() {
run(&mut s, "edit.undo", json!({}));
assert_eq!(state(&s), states[i], "undo to step {i}");
}
for (i, expected) in states.iter().enumerate().skip(1) {
run(&mut s, "edit.redo", json!({}));
assert_eq!(state(&s), *expected, "redo to step {i}");
}
assert_eq!(s.evaluations, 0, "changing only the browser evaluates nothing");
}
#[test]
fn nesting_and_ordering_that_cannot_stand_are_refused() {
let mut s = Session::default();
three_bodies(&mut s);
let part = run(&mut s, "component.create", json!({"name": "Part"}));
run(&mut s, "component.activate", json!({"component": 0}));
let outer = new_group(&mut s, json!({"items": ["A"]}));
let inner = new_group(&mut s, json!({"parent": outer}));
// A group inside itself or its own child.
refused(&mut s, "browser.move", json!({"folder": "bodies", "items": [outer], "group": outer}));
refused(&mut s, "browser.move", json!({"folder": "bodies", "items": [outer], "group": inner}));
// Past eight levels, by a new group or by moving one with a child of its own.
let mut chain = vec![new_group(&mut s, json!({}))];
while chain.len() < MAX_GROUP_NESTING {
chain.push(new_group(&mut s, json!({"parent": chain[chain.len() - 1]})));
}
refused(&mut s, "browser.group", json!({"folder": "bodies", "parent": chain[MAX_GROUP_NESTING - 1]}));
let heavy = new_group(&mut s, json!({}));
new_group(&mut s, json!({"parent": heavy}));
refused(&mut s, "browser.move", json!({"folder": "bodies", "items": [heavy], "group": chain[MAX_GROUP_NESTING - 2]}));
// A parent that is missing, or in another folder or component.
refused(&mut s, "browser.group", json!({"folder": "bodies", "parent": 424242}));
refused(&mut s, "browser.group", json!({"folder": "sketches", "parent": outer}));
refused(&mut s, "browser.group", json!({"parent": outer, "component": part["component"]}));
// Orders and moves naming a group that cannot be there; a number is a group, not an item key.
refused(&mut s, "browser.order", json!({"folder": "bodies", "order": [inner]}));
refused(&mut s, "browser.order", json!({"folder": "bodies", "order": [424242]}));
refused(&mut s, "browser.move", json!({"folder": "bodies", "items": ["B"], "before": 424242}));
refused(&mut s, "browser.group", json!({"folder": "bodies", "items": [12]}));
// The components folder takes placements of that component, and nothing else.
refused(&mut s, "browser.order", json!({"folder": "components", "order": ["424242"]}));
refused(&mut s, "browser.order", json!({"folder": "components", "order": [outer]}));
}
}
+214 -1
View File
@@ -16,7 +16,8 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("timeline.paste", "Paste Features", paste).icon("paste").params(
"translate?: [x,y,z] (moves the copies' geometry); component?: where the copies go (default: the active one), \
component_path?: its placement (default: the active one, else its only one); the copies go at the marker, each where \
its original is in the world (copies into their own component keep its coordinates); body transfers are left out",
its original is in the world (copies into their own component keep its coordinates); body transfers are left out; \
from_design?: true to paste sketch features from the host's cross-design clipboard snapshot",
),
];
@@ -35,6 +36,7 @@ fn copy(s: &mut Session, p: &Value) -> Result<Value> {
let copied: Vec<solidcraft_doc::Feature> = s.doc.features.iter().filter(|f| ids.contains(&f.id)).cloned().collect();
let names: Vec<String> = copied.iter().map(|f| f.name.clone()).collect();
s.clipboard = copied;
s.foreign_copy = Some(std::sync::Arc::new(crate::ForeignCopy::of_features(s)));
Ok(json!({"copied": names}))
}
@@ -55,6 +57,11 @@ pub(in crate::cmd) fn explicit_planes(p: &mut PlaneRef) {
fn paste(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "timeline.paste";
match p.get("from_design") {
Some(Value::Bool(true)) => return super::clipboard_foreign::paste(s, p),
None | Some(Value::Bool(false)) => {}
_ => return Err(bad(cmd, "`from_design` must be true or false")),
}
if s.clipboard.is_empty() {
return Err(bad(cmd, "nothing copied (timeline.copy first)"));
}
@@ -249,6 +256,212 @@ mod tests {
run(s, "inspect.measure", json!({}))["total"]["volume_mm3"].as_f64().unwrap_or(f64::NAN)
}
fn dimensioned_sketch() -> Session {
let mut s = Session::default();
run(&mut s, "parameters.change", json!({"name": "base", "expression": "5 mm"}));
run(&mut s, "parameters.change", json!({"name": "width", "expression": "base * 4"}));
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "S"}));
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [20, 10]}));
run(&mut s, "sketch.constraint.coincident", json!({"a": "p1", "b": "origin"}));
let width = run(&mut s, "sketch.dimension", json!({"entities": ["l1"], "value": "width"}))["param"].as_str().unwrap().to_string();
run(&mut s, "sketch.dimension", json!({"entities": ["l2"], "value": format!("{width} / 2")}));
run(&mut s, "sketch.finish", json!({}));
s
}
fn sketch_area(s: &Session, id: u64) -> f64 {
s.model.state().sketch(id).unwrap().profiles.iter().map(|p| p.area).sum()
}
#[test]
fn foreign_sketch_paste_keeps_parameter_graph_snapshot_and_undo() {
let mut source = dimensioned_sketch();
run(&mut source, "timeline.copy", json!({"features": ["S"]}));
let mut target = dimensioned_sketch();
run(&mut target, "document.units", json!({"units": "in"}));
run(&mut target, "parameters.change", json!({"name": "base", "expression": "7 mm"}));
target.foreign_copy = source.foreign_copy.clone();
run(&mut source, "parameters.change", json!({"name": "base", "expression": "30 mm"}));
drop(source);
let before = target.doc.to_json();
let undo = target.undo.len();
let result = run(&mut target, "timeline.paste", json!({"from_design": true}));
let id = result["pasted"][0]["id"].as_u64().unwrap();
assert_eq!(result["pasted"][0]["name"], "S (2)");
assert!((sketch_area(&target, id) - 200.).abs() < 1e-6);
assert_eq!(target.doc.param("base").unwrap().expr, "7 mm");
assert_eq!(target.doc.param("base_2").unwrap().expr, "5 mm");
assert_eq!(target.doc.param("width_2").unwrap().expr, "base_2 * 4");
assert_eq!(target.undo.len(), undo + 1);
let after = target.doc.to_json();
run(&mut target, "edit.undo", json!({}));
assert_eq!(target.doc.to_json(), before);
run(&mut target, "edit.redo", json!({}));
assert_eq!(target.doc.to_json(), after);
let second = run(&mut target, "timeline.paste", json!({"from_design": true}))["pasted"][0]["id"].as_u64().unwrap();
assert!((sketch_area(&target, second) - 200.).abs() < 1e-6);
run(&mut target, "parameters.change", json!({"name": "base_2", "expression": "10 mm"}));
assert!((sketch_area(&target, id) - 800.).abs() < 1e-6);
assert!((sketch_area(&target, second) - 200.).abs() < 1e-6, "each paste owns its parameters");
assert!(solidcraft_doc::Document::from_json(&target.doc.to_json()).is_ok());
}
#[test]
fn foreign_sketch_paste_preserves_world_position_in_a_placed_component() {
use solidcraft_geom::Vec3;
let mut source = dimensioned_sketch();
let arc = [Vec3::new(1., 0., 0.), Vec3::new(0., 0., 1.), Vec3::new(-1., 0., 0.)];
for space in [solidcraft_sketch::WireSpace::World, solidcraft_sketch::WireSpace::Component] {
source.doc_mut().sketch_mut(1).unwrap().add_drawn_arc_in(&arc, space).unwrap();
}
source.refresh();
run(&mut source, "timeline.copy", json!({"features": ["S"]}));
let mut target = Session::default();
let made = run(&mut target, "component.create", json!({"name": "Target", "activate": false}));
let component = made["component"].as_u64().unwrap();
let occurrence = made["occurrence"].as_u64().unwrap();
run(&mut target, "occurrence.ground", json!({"occurrence": occurrence, "grounded": false}));
run(&mut target, "occurrence.move", json!({"occurrence": occurrence, "translate": [100, 20, 30], "axis": [0, 0, 1], "angle": "90 deg"}));
target.foreign_copy = source.foreign_copy.clone();
let id = run(&mut target, "timeline.paste", json!({"from_design": true, "component": component, "component_path": [occurrence]}))["pasted"]
[0]["id"]
.as_u64()
.unwrap();
let original = source.world_state().sketch(1).unwrap().plane;
let pasted = target.world_state().sketch(id).unwrap().plane;
assert!(pasted.origin.dist(original.origin) < 1e-6, "{original:?} vs {pasted:?}");
assert!(pasted.x.dist(original.x) < 1e-6);
assert!(pasted.y.dist(original.y) < 1e-6);
assert!((sketch_area(&target, id) - 200.).abs() < 1e-6);
assert_eq!(target.doc.feature(id).unwrap().component, component);
for (original, pasted) in
source.world_state().sketch(1).unwrap().sketch.wires.iter().zip(&target.world_state().sketch(id).unwrap().sketch.wires)
{
for (a, b) in original.pts.iter().chain(&original.fit).zip(pasted.pts.iter().chain(&pasted.fit)) {
assert!(a.dist(*b) < 1e-6, "wire {:?}: {a:?} vs {b:?}", original.space);
}
let (a, b) = (original.arc.unwrap(), pasted.arc.unwrap());
assert!(a.center.dist(b.center) < 1e-6);
assert!(a.normal.dist(b.normal) < 1e-6);
assert!((a.radius - b.radius).abs() < 1e-6);
}
let moved = run(
&mut target,
"timeline.paste",
json!({"from_design": true, "component": component, "component_path": [occurrence], "translate": [2, 3, 4]}),
)["pasted"][0]["id"]
.as_u64()
.unwrap();
let frame = target.placed_document().path_transform(component, &[occurrence]).unwrap();
let delta = solidcraft_doc::apply_vector(&frame, Vec3::new(2., 3., 4.));
for (original, pasted) in
source.world_state().sketch(1).unwrap().sketch.wires.iter().zip(&target.world_state().sketch(moved).unwrap().sketch.wires)
{
for (a, b) in original.pts.iter().chain(&original.fit).zip(pasted.pts.iter().chain(&pasted.fit)) {
assert!((*a + delta).dist(*b) < 1e-6, "translated wire {:?}: {a:?} vs {b:?}", original.space);
}
}
}
#[test]
fn foreign_sketch_paste_refuses_external_references_and_bad_parameters_atomically() {
let mut source = dimensioned_sketch();
let mut target = dimensioned_sketch();
// The same numeric ID in the target must never become the sketch's missing plane.
if let solidcraft_doc::FeatureKind::Sketch { plane, .. } = &mut source.doc_mut().feature_mut(1).unwrap().kind {
*plane = solidcraft_doc::PlaneRef::Construction { feature: 1.into() };
}
run(&mut source, "timeline.copy", json!({"features": ["S"]}));
target.foreign_copy = source.foreign_copy.clone();
let before = target.doc.to_json();
let undo = target.undo.len();
let error = target.execute("timeline.paste", &json!({"from_design": true})).unwrap_err().to_string();
assert!(error.contains("fixed plane"), "{error}");
assert_eq!(target.doc.to_json(), before);
assert_eq!(target.undo.len(), undo);
for params in
[json!({"from_design":"yes"}), json!({"from_design":true,"translate":"bad"}), json!({"from_design":true,"component_path":["bad"]})]
{
assert!(target.execute("timeline.paste", &params).is_err());
assert_eq!(target.doc.to_json(), before);
}
}
#[test]
fn foreign_sketch_paste_remaps_links_and_rejects_missing_source_sketches() {
let mut source = dimensioned_sketch();
run(&mut source, "sketch.create", json!({"plane":"XY","offset":5,"name":"Projected"}));
run(&mut source, "sketch.project", json!({"refs":[{"sketch":1,"curve":"l1"}]}));
run(&mut source, "sketch.finish", json!({}));
let mut target = dimensioned_sketch();
run(&mut source, "timeline.copy", json!({"features":["Projected"]}));
target.foreign_copy = source.foreign_copy.clone();
let before = target.doc.to_json();
let err = target.execute("timeline.paste", &json!({"from_design":true})).unwrap_err().to_string();
assert!(err.contains("external projected geometry"), "{err}");
assert_eq!(before, target.doc.to_json());
run(&mut source, "timeline.copy", json!({"features":["S","Projected"]}));
target.foreign_copy = source.foreign_copy.clone();
let result = run(&mut target, "timeline.paste", json!({"from_design":true}));
let first = result["pasted"][0]["id"].as_u64().unwrap();
let second = result["pasted"][1]["id"].as_u64().unwrap();
let copied = target.doc.sketch(second).unwrap();
assert!(matches!(copied.links[0].source, solidcraft_sketch::LinkSource::SketchCurve { sketch, .. } if sketch == first));
assert!(target.model.result(second).unwrap().error.is_none());
}
#[test]
fn foreign_sketch_paste_keeps_plane_input_units_and_text_parameters() {
let mut source = Session::default();
run(&mut source, "document.units", json!({"units":"in"}));
run(&mut source, "parameters.add", json!({"name":"height","expression":"2","unit":""}));
run(&mut source, "parameters.add", json!({"name":"Label","expression":"ABC","unit":"text"}));
run(&mut source, "sketch.create", json!({"plane":"XY","offset":"height","name":"Text"}));
run(&mut source, "sketch.text", json!({"text_expression":"Label + '-X'","at":[0,0],"height":"5 mm"}));
run(&mut source, "sketch.finish", json!({}));
run(&mut source, "timeline.copy", json!({"features":["Text"]}));
let mut target = Session::default();
run(&mut target, "parameters.add", json!({"name":"Label","expression":"other","unit":"text"}));
run(&mut target, "parameters.add", json!({"name":"d1","expression":"reserved","unit":"text"}));
target.foreign_copy = source.foreign_copy.clone();
let id = run(&mut target, "timeline.paste", json!({"from_design":true}))["pasted"][0]["id"].as_u64().unwrap();
assert!((target.model.state().sketch(id).unwrap().plane.origin.z - 50.8).abs() < 1e-9);
assert_eq!(target.doc.text_param("Label").unwrap().text, "other");
assert_eq!(target.doc.text_param("Label_2").unwrap().text, "ABC");
assert_eq!(target.doc.text_param("d1").unwrap().text, "reserved");
assert!(target.doc.param("d1").is_none());
let state = target.model.state();
let text = &state.sketch(id).unwrap().sketch.links[0].source;
assert!(matches!(text, solidcraft_sketch::LinkSource::Text { text, .. } if text == "ABC-X"));
assert!(solidcraft_doc::Document::from_json(&target.doc.to_json()).is_ok());
}
#[test]
fn foreign_sketch_paste_copies_equation_parameters_but_not_the_local_variable() {
let mut source = Session::default();
run(&mut source, "parameters.add", json!({"name":"height","expression":"3 mm"}));
run(&mut source, "sketch.create", json!({"plane":"XY","name":"Curve"}));
run(&mut source, "sketch.equation_curve", json!({"x":"t","y":"height + t * t","start":-2,"end":2}));
run(&mut source, "sketch.finish", json!({}));
run(&mut source, "timeline.copy", json!({"features":["Curve"]}));
let mut target = Session::default();
run(&mut target, "parameters.add", json!({"name":"height","expression":"99 mm"}));
target.foreign_copy = source.foreign_copy.clone();
let id = run(&mut target, "timeline.paste", json!({"from_design":true}))["pasted"][0]["id"].as_u64().unwrap();
let state = target.model.state();
let sketch = &state.sketch(id).unwrap().sketch;
let equation = &sketch.equations[0];
assert_eq!(equation.x.as_deref(), Some("t"));
assert_eq!(equation.y, "height_2 + t * t");
let curve = sketch.curves.iter().find(|c| c.id == equation.curve).unwrap();
for point in curve.kind.point_ids() {
let p = sketch.points[point].pos;
assert!((p.y - 3. - p.x * p.x).abs() < 1e-8, "{p:?}");
}
assert!(target.doc.param("t").is_none());
}
/// Features pasted into a component placed elsewhere (offset and turned) are carried into its
/// frame: the pasted body sits where the original is in the world, in that component, and the
/// pasted sketch is named after the original.
+310
View File
@@ -0,0 +1,310 @@
//! Independent sketch features pasted from the immutable clipboard of another design.
use std::collections::{BTreeMap, BTreeSet};
use serde_json::{Value, json};
use solidcraft_doc::{Document, Feature, FeatureKind, Mat, PlaneRef};
use crate::params::{bad, vec3};
use crate::{Result, Sel, Session};
const CMD: &str = "timeline.paste";
/// References to geometry outside the copied sketches must never resolve against coincident
/// IDs or names in the destination. Fixed and expression-placed planes are self-contained.
fn independent_plane(plane: &PlaneRef, depth: usize) -> Result<()> {
if depth > 64 {
return Err(bad(CMD, "copied plane nesting exceeds 64"));
}
match plane {
PlaneRef::Origin { .. } | PlaneRef::Custom { .. } | PlaneRef::Placed { .. } => Ok(()),
PlaneRef::Offset { base, .. } | PlaneRef::Flipped { base, .. } | PlaneRef::AtAngle { base, line: None, .. } => {
independent_plane(base, depth + 1)
}
_ => Err(bad(CMD, "the copied sketch's plane depends on model geometry; convert it to a fixed plane before pasting into another design")),
}
}
fn independent_links(sketch: &solidcraft_sketch::Sketch, features: &BTreeSet<u64>, moved: bool) -> Result<()> {
use solidcraft_sketch::LinkSource as L;
for link in &sketch.links {
let supported = match &link.source {
L::Text { .. } | L::SketchAxis { .. } => true,
L::SketchCurve { sketch, .. } | L::SketchPoint { sketch, .. } => features.contains(sketch),
L::Origin | L::Axis { .. } => !moved,
L::Plane { name } => !moved && solidcraft_geom::Plane::named(name).is_some(),
_ => false,
};
if !supported {
return Err(bad(
CMD,
"the copied sketch has external projected geometry; unlink it or copy its source sketches before pasting into another design",
));
}
}
Ok(())
}
fn free_parameter_name(base: &str, taken: &mut BTreeSet<String>) -> Result<String> {
for index in 1..=solidcraft_doc::MAX_PARAMS + 1 {
let name = if index == 1 { base.to_string() } else { format!("{base}_{index}") };
if Document::param_name_problem(&name).is_none() && taken.insert(name.clone()) {
return Ok(name);
}
}
Err(bad(CMD, format!("no free name for copied parameter {base}")))
}
fn model_parameter_name(taken: &mut BTreeSet<String>) -> Result<String> {
for index in 1..=taken.len() + 1 {
let name = format!("d{index}");
if taken.insert(name.clone()) {
return Ok(name);
}
}
Err(bad(CMD, "no free model parameter name"))
}
/// Bring the complete expression dependency graph, including dimensions driven by another
/// dimension. Use the source definitions' units, never a destination parameter with the same name.
fn parameters(target: &mut Document, source: &Document, features: &[Feature], mapped: &mut BTreeMap<String, String>) -> Result<()> {
let definitions: BTreeMap<_, _> = source.param_defs().into_iter().map(|p| (p.name.clone(), p)).collect();
let mut pending: Vec<String> = features.iter().flat_map(|f| f.kind.expressions()).flat_map(solidcraft_doc::expr::references).collect();
let mut texts = BTreeSet::new();
for feature in features {
if let FeatureKind::Sketch { sketch, .. } = &feature.kind {
pending.extend(sketch.constraints.iter().filter_map(|c| c.param.clone()));
let text_and_points = sketch
.links
.iter()
.filter_map(|l| match &l.source {
solidcraft_sketch::LinkSource::Text { exprs, .. } => Some(exprs.list()),
_ => None,
})
.flatten()
.chain(sketch.points.iter().filter_map(|p| p.exprs.as_deref()).flat_map(solidcraft_sketch::PointExprs::list));
pending.extend(text_and_points.flat_map(|(_, e)| solidcraft_doc::expr::references(e)));
for equation in &sketch.equations {
pending
.extend(equation.list().into_iter().flat_map(|(_, e)| solidcraft_doc::expr::references(e)).filter(|n| n != equation.variable()));
}
texts.extend(sketch.text_word_exprs().into_iter().flat_map(|e| solidcraft_doc::text_params::references(e)));
}
}
let mut used = BTreeSet::new();
let mut work = 0;
while let Some(name) = pending.pop() {
work += 1;
if work > 100_000 {
return Err(bad(CMD, "copied parameter references exceed work limit"));
}
if used.insert(name.clone()) {
let p = definitions.get(&name).ok_or_else(|| bad(CMD, format!("copied sketch uses missing parameter {name}")))?;
pending.extend(solidcraft_doc::expr::references(&p.expr));
}
}
if target.params.len() + target.text_params.len() + used.len() + texts.len() > solidcraft_doc::MAX_PARAMS {
return Err(bad(CMD, "too many copied parameters"));
}
let mut taken: BTreeSet<_> = target.param_defs().into_iter().map(|p| p.name).chain(target.text_params.iter().map(|p| p.name.clone())).collect();
for name in &used {
if mapped.contains_key(name) {
continue;
}
let definition = definitions.get(name).ok_or_else(|| bad(CMD, "missing copied parameter definition"))?;
let model = source.param(name).is_none_or(|p| p.model);
let new = if model {
let new = model_parameter_name(&mut taken)?;
target.params.push(solidcraft_doc::Param {
name: new.clone(),
expr: definition.expr.clone(),
unit: definition.unit.clone(),
comment: String::new(),
model: true,
});
new
} else {
let new = free_parameter_name(name, &mut taken)?;
let mut p = source.param(name).cloned().ok_or_else(|| bad(CMD, "missing copied user parameter"))?;
p.name = new.clone();
target.params.push(p);
new
};
mapped.insert(name.clone(), new);
}
for name in texts {
let mut p = source.text_param(&name).cloned().ok_or_else(|| bad(CMD, format!("copied sketch uses missing text parameter {name}")))?;
p.name = free_parameter_name(&name, &mut taken)?;
mapped.insert(name, p.name.clone());
target.text_params.push(p);
}
for (old, new) in mapped.iter() {
if let Some(definition) = definitions.get(old)
&& let Some(p) = target.params.iter_mut().find(|p| p.name == *new)
{
p.expr = solidcraft_doc::clone_expression(&definition.expr, mapped);
p.comment = source.param(old).map(|p| p.comment.clone()).or_else(|| source.param_comments.get(old).cloned()).unwrap_or_default();
}
if let Some(comment) = source.param_comments.get(old) {
target.param_comments.insert(new.clone(), comment.clone());
}
}
Ok(())
}
fn text_expression(source: &str, mapped: &BTreeMap<String, String>) -> String {
let mut result = source.to_string();
let mut temporary = Vec::new();
for (i, (old, new)) in mapped.iter().enumerate() {
let mut token = format!("__paste_text_{i}");
while source.contains(&token) || mapped.keys().chain(mapped.values()).any(|name| name == &token) {
token.push('_');
}
result = solidcraft_doc::text_params::rename_reference(&result, old, &token);
temporary.push((token, new));
}
for (token, new) in temporary {
result = solidcraft_doc::text_params::rename_reference(&result, &token, new);
}
result
}
pub(super) fn paste(s: &mut Session, p: &Value) -> Result<Value> {
let copy = s.foreign_copy.clone().ok_or_else(|| bad(CMD, "nothing was copied in another design"))?;
if copy.features.is_empty() || copy.features.len() > 1000 {
return Err(bad(CMD, "copy 1…1000 sketches before pasting into another design"));
}
let component = match p.get("component") {
None => s.active_component,
Some(v) => {
let key = match v {
Value::String(name) => name.clone(),
Value::Number(n) => n.to_string(),
_ => return Err(bad(CMD, "`component` must be a component id or name")),
};
s.doc.find_component(&key).ok_or_else(|| bad(CMD, format!("no component `{key}`")))?
}
};
let path = match p.get("component_path") {
Some(v) => v
.as_array()
.filter(|a| a.len() <= 64)
.and_then(|a| a.iter().map(Value::as_u64).collect::<Option<Vec<_>>>())
.ok_or_else(|| bad(CMD, "`component_path` must list 0..64 occurrence ids"))?,
None if component == s.active_component => s.active_occurrence_path.clone().unwrap_or_else(|| s.doc.first_path(component)),
None => super::component::placing::placement_of(s, &s.placed_document(), component, None, "component_path", CMD)?.0,
};
let placed = s.placed_document();
let frame = placed.path_transform(component, &path).ok_or_else(|| bad(CMD, "that path is not a placement of the target"))?;
let into_target = solidcraft_doc::mat_inverse(&frame).ok_or_else(|| bad(CMD, "the target's frame is not invertible"))?;
drop(placed);
let mut shift = solidcraft_doc::IDENTITY;
if let Some(t) = p.get("translate") {
let t = vec3(t).ok_or_else(|| bad(CMD, "`translate` must be [x, y, z]"))?;
shift[3] = [t.x, t.y, t.z, 1.];
}
let world_shift = solidcraft_doc::mat_mul(&frame, &solidcraft_doc::mat_mul(&shift, &into_target));
let mut placed_source = (*copy.doc).clone();
for occurrence in &mut placed_source.occurrences {
if let Some(m) = copy.pending_moves.get(&occurrence.id) {
occurrence.transform = *m;
}
}
let selected: BTreeSet<_> = copy.features.iter().map(|f| f.id).collect();
let mut transforms: BTreeMap<u64, Mat> = BTreeMap::new();
for feature in &copy.features {
let FeatureKind::Sketch { plane, sketch } = &feature.kind else {
return Err(bad(CMD, "only sketch features can be pasted between designs; copy a component to include other feature history"));
};
independent_plane(plane, 0)?;
let source_frame = placed_source
.path_transform(feature.component, &placed_source.first_path(feature.component))
.ok_or_else(|| bad(CMD, "the copied sketch's component placement is missing"))?;
let transform = solidcraft_doc::mat_mul(&shift, &solidcraft_doc::mat_mul(&into_target, &source_frame));
independent_links(sketch, &selected, !solidcraft_doc::is_identity(&transform))?;
transforms.insert(feature.id, transform);
}
let mut scratch = s.scratch();
let mut mapped = BTreeMap::new();
let mut ids = BTreeMap::new();
let mut names = BTreeMap::new();
let mut made = Vec::new();
for feature in &copy.features {
let taken = |name: &str| scratch.doc.features.iter().any(|f| f.name == name);
let name = if taken(&feature.name) { solidcraft_doc::copy_name(&feature.name, taken) } else { feature.name.clone() };
let id = scratch.doc_mut().add_feature(feature.kind.clone(), Some(&name))?;
let mut taken: BTreeSet<_> =
scratch.doc.param_defs().into_iter().map(|p| p.name).chain(scratch.doc.text_params.iter().map(|p| p.name.clone())).collect();
let text_names: BTreeSet<_> = scratch.doc.text_params.iter().map(|p| p.name.clone()).collect();
if let Some(added) = scratch.doc_mut().feature_mut(id) {
for name in &mut added.param_names {
if text_names.contains(name) {
*name = model_parameter_name(&mut taken)?;
}
}
}
if let Some(added) = scratch.doc.feature(id) {
mapped.extend(feature.param_names.iter().cloned().zip(added.param_names.iter().cloned()));
}
ids.insert(feature.id, id);
names.insert(feature.name.clone(), name.clone());
made.push(json!({"id": id, "name": name, "from": feature.name}));
}
parameters(scratch.doc_mut(), &copy.doc, &copy.features, &mut mapped)?;
let source_values = copy.doc.param_values().0;
for original in &copy.features {
let mut feature = original.clone();
let transform = transforms.get(&original.id).ok_or_else(|| bad(CMD, "missing copied sketch transform"))?;
// Nonliteral unitless length inputs are interpreted in their design's display units.
// Make that unit explicit before moving the expressions to a design with other units.
if copy.doc.units != s.doc.units {
for (_, e, kind) in feature.kind.inputs_mut() {
if kind == solidcraft_doc::expr::Kind::Length
&& !solidcraft_doc::expr::is_literal(e)
&& let Some((solidcraft_doc::expr::Kind::Length, scale)) = solidcraft_doc::expr::unit_info(copy.doc.units.trim())
&& solidcraft_doc::expr::eval_with(e, &|name| {
source_values.get(name).copied().ok_or_else(|| solidcraft_doc::DocError::Expr(format!("unknown parameter {name}")))
})?
.unitless()
{
*e = format!("({e}) * {scale} mm").into();
}
}
}
feature.remap_clone(&ids, &names, &BTreeMap::new(), &mapped)?;
if let FeatureKind::Sketch { plane, sketch } = &mut feature.kind {
// 3D wires carry their own coordinate space, independently of the sketch plane.
for wire in &mut sketch.wires {
let matrix = if wire.space == solidcraft_sketch::WireSpace::Component { transform } else { &world_shift };
solidcraft_doc::map_wire(wire, matrix);
}
// Patterns and text words have expressions outside FeatureKind's numeric inputs.
for pattern in &mut sketch.patterns {
for e in pattern.count_expression.iter_mut().chain(pattern.expressions.values_mut()) {
*e = solidcraft_doc::clone_expression(e, &mapped);
}
}
for e in sketch.text_word_exprs_mut() {
*e = text_expression(e, &mapped);
}
if !solidcraft_doc::is_identity(transform) {
super::clipboard::explicit_planes(plane);
}
}
feature.kind.transform_geometry(transform);
let id = *ids.get(&original.id).ok_or_else(|| bad(CMD, "missing copied sketch id"))?;
let added = scratch.doc_mut().feature_mut(id).ok_or_else(|| bad(CMD, "missing pasted sketch"))?;
added.kind = feature.kind;
added.component = component;
added.suppressed = original.suppressed;
}
scratch.refresh();
for id in ids.values() {
if let Some(error) = scratch.model.result(*id).and_then(|r| r.error.as_ref()) {
return Err(bad(CMD, format!("the pasted sketch did not rebuild: {error}")));
}
}
scratch.selection = ids.values().map(|id| Sel::Feature { id: *id }).collect();
s.accept_computed(scratch);
Ok(json!({"pasted": made, "errors": [], "notices": []}))
}
+63 -3
View File
@@ -294,6 +294,22 @@ fn row(s: &mut Session, p: &Value) -> Result<Value> {
if rename.as_ref().is_some_and(|n| n != &name && s.doc.configs.rows.iter().any(|r| &r.name == n)) {
return Err(bad(cmd, "configuration name already exists"));
}
let changes: Vec<_> = s
.doc
.configs
.columns
.iter()
.zip(&cells)
.filter_map(|(column, value)| match column {
Column::Param(name) => Some((name.clone(), value.clone())),
_ => None,
})
.collect();
if !changes.is_empty() {
// Check the final parameter graph without activating the row or rebuilding geometry.
let mut trial = s.doc.as_ref().clone();
trial.change_params_atomic(&changes).map_err(|e| bad(cmd, e.to_string()))?;
}
let t = &mut s.doc_mut().configs;
let validated_name = rename.clone().unwrap_or_else(|| name.clone());
match idx {
@@ -664,6 +680,52 @@ mod tests {
assert!((volume(&mut s) - 2000.0).abs() < 1e-6);
}
#[test]
fn configuration_rows_reject_invalid_parameter_expressions_without_changes() {
let mut s = Session::default();
run(&mut s, "parameters.add", json!({"name":"L", "expression":"10 mm"}));
run(&mut s, "config.configure", json!({"params":["L"]}));
let before = s.doc.clone();
let undo_len = s.undo.len();
for expression in ["abc", "1/", "1/0"] {
for params in [
json!({"name":"Bad", "values":{"param:L":expression}}),
json!({"name":"Copy", "from":"Default", "values":{"L":expression}}),
json!({"name":"Default", "rename":"Renamed", "values":{"L":expression}}),
] {
let error = s.execute("config.row", &params).expect_err("invalid expressions must be rejected when saving a row");
assert!(error.to_string().contains("parameter `L`"), "{params}: {error}");
assert_eq!(s.doc, before, "{params}");
assert_eq!(s.undo.len(), undo_len, "{params}");
}
}
}
#[test]
fn configuration_rows_validate_the_final_parameter_graph_without_activating_it() {
let mut s = Session::default();
run(&mut s, "parameters.add", json!({"name":"L", "expression":"10 mm"}));
run(&mut s, "parameters.add", json!({"name":"W", "expression":"L + 2 mm"}));
run(&mut s, "config.configure", json!({"params":["L", "W"]}));
let before = s.doc.clone();
// Changing L first would make a cycle with W's current expression. The final row is valid.
run(&mut s, "config.row", json!({"name":"Reordered", "values":{"L":"W + 2 mm", "W":"20 mm"}}));
assert_eq!(s.doc.params, before.params);
assert_eq!(s.doc.configs.active, before.configs.active);
let saved = s.doc.clone();
let undo_len = s.undo.len();
assert!(s.execute("config.row", &json!({"name":"Cycle", "from":"Reordered", "values":{"W":"L"}})).is_err());
assert_eq!(s.doc, saved);
assert_eq!(s.undo.len(), undo_len);
run(&mut s, "config.activate", json!({"row":"Reordered"}));
let (values, errors) = s.doc.param_values();
assert!(errors.is_empty(), "{errors:?}");
assert_eq!(values["L"].v, 22.0);
assert_eq!(values["W"].v, 20.0);
run(&mut s, "edit.undo", json!({}));
assert_eq!(s.doc, saved);
}
#[test]
fn configured_feature_inputs_and_suppression_match_typed_cells() {
let mut s = Session::default();
@@ -746,9 +808,7 @@ mod tests {
// Bad input.
assert!(s.execute("config.row", &json!({"name": "X", "values": {"nope": 1}})).is_err());
assert!(s.execute("config.activate", &json!({"row": "Missing"})).is_err());
run(&mut s, "config.row", json!({"name": "Broken", "values": {"length": "1/"}}));
assert!(s.execute("config.activate", &json!({"row": "Broken"})).is_err());
run(&mut s, "config.row", json!({"name": "Broken", "delete": true}));
assert!(s.execute("config.row", &json!({"name": "Broken", "values": {"length": "1/"}})).is_err());
run(&mut s, "config.column", json!({"feature": fillet, "delete": true}));
assert_eq!(run(&mut s, "config.table", json!({}))["columns"].as_array().map(Vec::len), Some(1));
}
+7 -1
View File
@@ -1892,6 +1892,7 @@ fn move_body_feature(s: &mut Session, p: &Value) -> Result<Value> {
)?;
if copy {
copy_keeps_appearance(s, &reply, &bodies, cmd)?;
super::print_face::copies_keep_print_faces(s, &reply, &bodies);
}
Ok(reply)
}
@@ -2098,7 +2099,12 @@ fn mirror(s: &mut Session, p: &Value) -> Result<Value> {
let plane = plane_param(s, p.get("plane"), cmd)?;
let combine = bool_(p, "combine").unwrap_or(false);
let (bodies, placements) = placed_bodies(s, bodies, cmd)?;
add_feature(s, p, FeatureKind::Mirror { features, plane, bodies: bodies.into_iter().map(BodyName).collect(), combine, placements })
let reply =
add_feature(s, p, FeatureKind::Mirror { features, plane, bodies: bodies.iter().cloned().map(BodyName).collect(), combine, placements })?;
if !combine {
super::print_face::copies_keep_print_faces(s, &reply, &bodies);
}
Ok(reply)
}
/// Bodies picked where the viewport shows them: each world name ("Body1", or "Body1 (Occ:2)"
+114
View File
@@ -18,6 +18,7 @@ use solidcraft_doc::Document;
use solidcraft_io::Format;
use super::CommandSpec;
use super::print_face;
use crate::params::{bad, bool_, num, str_, string_list};
use crate::{EngineError, Result, Session};
@@ -694,6 +695,26 @@ fn export_to(
Ok(json!({"path": path, "bytes": bytes.len(), "format": format!("{format:?}")}))
}
/// [`export_to`] for a mesh file a slicer is sent (STL or 3MF): each body laid on its print face.
#[cfg(not(target_arch = "wasm32"))]
fn export_laid_to(
s: &Session,
path: &str,
format: Format,
bodies: &[String],
parts: &[(String, Vec<String>)],
quality: solidcraft_io::MeshQuality,
) -> Result<Value> {
let name = std::path::Path::new(path).file_stem().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| s.doc.name.clone());
let mut state = s.doc.painted(&s.world_state());
let laid = print_face::lay_on_print_faces(s, &mut state, bodies, parts);
let bytes = solidcraft_io::export_with_parts(&state, bodies, parts, format, &name, &s.doc.metadata, quality)?;
solidcraft_io::vfs::write(path, &bytes).map_err(|e| EngineError::Other(format!("{path}: {e}")))?;
let mut out = json!({"path": path, "bytes": bytes.len(), "format": format!("{format:?}")});
laid.report(&mut out);
Ok(out)
}
fn export(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "file.export";
let path = path_arg(p, cmd)?;
@@ -1456,6 +1477,51 @@ mod tests {
std::fs::remove_file(path).unwrap();
}
/// #9170: a design holding a mesh body (an inserted STL) keeps its saved model and opens from
/// it. One mesh body left the cache out for the whole model, so every feature, the boxes
/// too, was evaluated again on opening.
#[test]
fn a_design_with_a_mesh_body_keeps_its_saved_model() {
let dir = std::env::temp_dir().join(format!("solidcraft-mesh-cache-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let stl = dir.join("tetra.stl");
let facets = [
[[0, 0, 0], [0, 10, 0], [10, 0, 0]],
[[0, 0, 0], [10, 0, 0], [0, 0, 10]],
[[0, 0, 0], [0, 0, 10], [0, 10, 0]],
[[10, 0, 0], [0, 10, 0], [0, 0, 10]],
];
let mut text = String::from("solid tetra\n");
for f in facets {
text += "facet normal 0 0 0\nouter loop\n";
for [x, y, z] in f {
text += &format!("vertex {x} {y} {z}\n");
}
text += "endloop\nendfacet\n";
}
std::fs::write(&stl, text + "endsolid tetra\n").unwrap();
let mut session = Session::new(Document::new("Boxes and a mesh"));
for x in [0, 40, 80] {
session.execute("solid.box", &json!({"length": 20, "width": 20, "height": 20, "corner": [x, 0, 0], "operation": "new_body"})).unwrap();
}
session.execute("file.insert_mesh", &json!({"path": stl})).unwrap();
let triangles = |s: &Session| s.model.state().bodies.iter().filter_map(|b| b.body.triangle_mesh().map(|(_, t)| t.len())).collect::<Vec<_>>();
assert_eq!(triangles(&session), [4], "one mesh body");
let bytes = session.design_container(solidcraft_io::DesignCompression::Fast, false, true).unwrap();
assert!(solidcraft_io::read_design_geometry(&bytes).is_some(), "the model is saved with the mesh body");
let path = dir.join("boxes-mesh.solidcraft");
std::fs::write(&path, &bytes).unwrap();
let mut reopened = Session::default();
reopened.execute("doc.open", &json!({"path": path})).unwrap();
std::fs::remove_dir_all(&dir).unwrap();
assert_eq!(reopened.model.results.len(), session.model.results.len());
assert!(reopened.model.results.iter().zip(&session.model.results).all(|(a, b)| a.ms == b.ms), "taken from the file, not evaluated");
assert_eq!(triangles(&reopened), [4]);
let volume = |s: &mut Session| s.execute("inspect.measure", &json!({})).unwrap()["total"]["volume_mm3"].as_f64().unwrap();
let (saved, opened) = (volume(&mut session), volume(&mut reopened));
assert!((opened - saved).abs() < 1e-9 && (saved - (3. * 8000. + 1000. / 6.)).abs() < 1e-6, "{opened} vs {saved}");
}
/// A sheet-metal design keeps each sheet body once in its saved model, not again with every
/// later feature's state: a design of many features after its sheet metal wrote the sheets
/// over and over, and past the cache's size limit kept only some of its states.
@@ -1499,6 +1565,54 @@ mod tests {
std::fs::remove_file(path).unwrap();
}
/// A design's saved model keeps each set of sketch keys once, not again with every state:
/// every state wrote the key of each sketch before it, so a design of many sketches and many
/// features after them (5,484 features) passed the cache's size limit and kept only some of
/// its states. A set is written as the changes from the one before it; the model opens from
/// the file with every state (the keys read back: `eval_cost_tests`).
#[test]
fn a_saved_model_keeps_each_set_of_sketch_keys_once() {
let mut s = Session::new(Document::new("Sketches"));
let run = |s: &mut Session, id: &str, p: Value| s.execute(id, &p).unwrap();
for i in 0..6 {
run(&mut s, "sketch.create", json!({"plane":"XY","name":format!("Pad{i}")}));
run(&mut s, "sketch.rectangle.two_point", json!({"p0":[20 * i,0],"p1":[20 * i + 10,10]}));
run(&mut s, "solid.extrude", json!({"distance":5,"operation":"new"}));
}
for i in 0..10 {
run(&mut s, "solid.box", json!({"length":5,"width":5,"height":5,"corner":[20 * i,40,0],"operation":"new"}));
}
let saved = saved_geometry(&s, &s.doc).unwrap();
let features = saved.records["features"].as_array().unwrap();
assert_eq!(features.len(), s.doc.features.len());
assert!(features.iter().all(|f| f.get("state").is_some()), "every state kept");
let states = saved.records["states"].as_array().unwrap();
let sets = saved.records["sketch_key_sets"].as_array().unwrap();
// One set per sketch: each later one adds that sketch's key to the one before it.
assert_eq!(sets.len(), 6, "{sets:?}");
assert_eq!(sets[0]["set"].as_array().unwrap().len(), 1);
for (i, set) in sets.iter().enumerate().skip(1) {
assert_eq!(
(set["base"].as_u64(), set["set"].as_array().unwrap().len(), set["drop"].as_array().unwrap().len()),
(Some(i as u64 - 1), 1, 0)
);
}
let last = sets.len() - 1;
assert_eq!(
states.iter().filter(|st| st["sketch_key_set"] == json!(last)).count(),
2 + 10,
"the last sketch, its extrude and every box share one set"
);
let path = std::env::temp_dir().join(format!("solidcraft-sketch-key-cache-{}.solidcraft", std::process::id()));
let bytes = solidcraft_io::write_design_container_with_cache(&s.doc, solidcraft_io::DesignCompression::Fast, None, Some(&saved)).unwrap();
std::fs::write(&path, bytes).unwrap();
let mut reopened = Session::default();
reopened.execute("doc.open", &json!({"path": path})).unwrap();
assert_eq!(reopened.model.last_recomputed, 0, "taken from the file, not evaluated");
assert!(reopened.model.results.iter().zip(&s.model.results).all(|(a, b)| a.ms == b.ms));
std::fs::remove_file(path).unwrap();
}
#[test]
#[ignore = "manual native cache save benchmark; generate issue303-step.json first"]
fn benchmark_native_cache_save() {
+11 -6
View File
@@ -19,10 +19,12 @@ pub(super) struct Exported {
pub fell_back: bool,
/// The bodies that went in: the ones named, else the selected ones.
pub bodies: Vec<String>,
/// The bodies laid on their print face, and the ones that couldn't be.
pub laid: super::print_face::Laid,
}
/// Export the bodies (the selected ones when none are named). A 3MF that can't be written is
/// written as an STL, and `fell_back` says so.
/// Export the bodies (the selected ones when none are named), each laid on its print face. A
/// 3MF that can't be written is written as an STL, and `fell_back` says so.
pub(super) fn export_bodies(s: &Session, cmd: &str, named: &[String], format: Format) -> Result<Exported> {
let mut bodies = named.to_vec();
if bodies.is_empty() {
@@ -31,7 +33,8 @@ pub(super) fn export_bodies(s: &Session, cmd: &str, named: &[String], format: Fo
if bodies.is_empty() {
return Err(bad(cmd, "select the bodies to send, or give `bodies`"));
}
let doc = s.doc.painted(&s.world_state());
let mut doc = s.doc.painted(&s.world_state());
let laid = super::print_face::lay_on_print_faces(s, &mut doc, &bodies, &[]);
let write = |format| solidcraft_io::export_with_metadata(&doc, &bodies, format, &s.doc.name, &s.doc.metadata);
let mut fell_back = false;
let (bytes, ext) = match (format, write(format)) {
@@ -42,7 +45,7 @@ pub(super) fn export_bodies(s: &Session, cmd: &str, named: &[String], format: Fo
}
(_, Err(e)) => return Err(e.into()),
};
Ok(Exported { bytes, ext, fell_back, bodies })
Ok(Exported { bytes, ext, fell_back, bodies, laid })
}
pub(super) fn open_in_easyprint(s: &mut Session, p: &Value) -> Result<Value> {
@@ -56,7 +59,7 @@ pub(super) fn open_in_easyprint(s: &mut Session, p: &Value) -> Result<Value> {
if !bool_(p, "consent").unwrap_or(false) {
return Err(bad(cmd, "this uploads the model to Printables' servers: ask the user, then pass `consent: true`"));
}
let Exported { bytes, ext, fell_back, .. } = export_bodies(s, cmd, &string_list(cmd, p, "bodies")?, format)?;
let Exported { bytes, ext, fell_back, laid, .. } = export_bodies(s, cmd, &string_list(cmd, p, "bodies")?, format)?;
if bytes.len() > MAX_BYTES {
let mb = bytes.len().div_ceil(1_000_000);
return Err(bad(cmd, format!("the model is {mb} MB and EasyPrint takes {} MB at most: send fewer bodies", MAX_BYTES / 1_000_000)));
@@ -64,5 +67,7 @@ pub(super) fn open_in_easyprint(s: &mut Session, p: &Value) -> Result<Value> {
let name = easyprint::file_name(&s.doc.name, ext);
let size = bytes.len();
upload(&name, bytes).map_err(|e| bad(cmd, e))?;
Ok(json!({"file_name": name, "bytes": size, "format": ext, "stl_fallback": fell_back, "uploading": true}))
let mut out = json!({"file_name": name, "bytes": size, "format": ext, "stl_fallback": fell_back, "uploading": true});
laid.report(&mut out);
Ok(out)
}
+6 -4
View File
@@ -11,7 +11,7 @@ use serde_json::{Value, json};
use solidcraft_io::Format;
use super::easyprint::{Exported, export_bodies};
use super::export_to;
use super::export_laid_to;
use crate::params::bad;
use crate::slicers::{self, Slicer, TargetKind};
use crate::{EngineError, Result, Session};
@@ -81,7 +81,7 @@ pub(super) fn send(s: &mut Session, r: Send<'_>) -> Result<Value> {
return Err(bad(r.cmd, format!("{} does not take {}", target.name, format_name(r.format))));
}
let mut out = match r.dest {
Dest::Path(path) => export_to(s, path, r.format, &r.bodies, &r.parts, r.quality)?,
Dest::Path(path) => export_laid_to(s, path, r.format, &r.bodies, &r.parts, r.quality)?,
Dest::Folder => write_in_folder(s, &r)?,
};
let path = out.get("path").and_then(Value::as_str).unwrap_or_default().to_string();
@@ -92,11 +92,13 @@ pub(super) fn send(s: &mut Session, r: Send<'_>) -> Result<Value> {
/// Write the bodies into the slicer folder.
fn write_in_folder(s: &mut Session, r: &Send<'_>) -> Result<Value> {
use crate::slicer_files;
let Exported { bytes, ext, fell_back, bodies } = export_bodies(s, r.cmd, &r.bodies, r.format)?;
let Exported { bytes, ext, fell_back, bodies, laid } = export_bodies(s, r.cmd, &r.bodies, r.format)?;
let dir = slicer_files::dir().ok_or_else(|| bad(r.cmd, "no per-user folder on this system"))?;
let stem = slicer_files::stem(&s.doc.name, s.path.as_deref(), s.uid, &bodies);
let (file, numbered) = slicer_files::write(&dir, &stem, ext, &bytes).map_err(|e| EngineError::Other(format!("{}: {e}", dir.display())))?;
Ok(json!({"path": file.to_string_lossy(), "bytes": bytes.len(), "format": ext, "numbered": numbered, "stl_fallback": fell_back}))
let mut out = json!({"path": file.to_string_lossy(), "bytes": bytes.len(), "format": ext, "numbered": numbered, "stl_fallback": fell_back});
laid.report(&mut out);
Ok(out)
}
/// Hand the file at `path` to `target`, starting it first if it has to be, and say what happened in
+1 -1
View File
@@ -34,7 +34,7 @@ pub static COMMANDS: &[CommandSpec] = &[
revision (moves whenever the design, what is hidden or a component's uncaptured position changes, \
never on a selection or a view; from one counter for the whole run, so it never repeats). Read-only",
),
CommandSpec::new("sketch.inspect", "Inspect Sketch", sketch_inspect).icon("inspect").noundo().params("sketch?: id|name (default active)"),
CommandSpec::new("sketch.inspect", "Inspect Sketch", sketch_inspect).at("SKETCH", "INSPECT").icon("inspect").noundo().params("sketch?: id|name (default active)"),
CommandSpec::new("model.edges", "List Edges", model_edges).icon("inspect").noundo().params("body: name"),
CommandSpec::new("model.faces", "List Faces", model_faces).icon("inspect").noundo().params(
"body: name → per face: index, name (what face.delete, sheet.convert `face`, solid.shell … take in place of a point), exact, area, centroid (not always on the face), \
+3
View File
@@ -24,6 +24,7 @@ mod bom;
mod browser;
pub(crate) mod clearance;
mod clipboard;
mod clipboard_foreign;
mod compare;
mod component;
mod component_clone;
@@ -78,6 +79,7 @@ mod kernel_heal_tests;
mod parameter_sweep;
mod parameters;
mod plastic;
mod print_face;
mod render;
mod render_ui;
#[cfg(test)]
@@ -332,6 +334,7 @@ pub fn command_specs() -> Vec<&'static CommandSpec> {
v.extend(parameters::COMMANDS.iter());
v.extend(parameter_sweep::COMMANDS.iter());
v.extend(file::COMMANDS.iter());
v.extend(print_face::COMMANDS.iter());
v.extend(metadata::COMMANDS.iter());
v.extend(inspect::COMMANDS.iter());
v.extend(compare::COMMANDS.iter());
+24 -1
View File
@@ -979,7 +979,30 @@ fn keep_sampled_trace(
}
fn list_studies(s: &mut Session, _p: &Value) -> Result<Value> {
Ok(json!({"studies": s.doc.assembly.studies, "traces": associative::list(s)}))
let studies: Vec<Value> = s.doc.assembly.studies.iter().map(|m| study_json(s, m)).collect();
Ok(json!({"studies": studies, "traces": associative::list(s)}))
}
/// A study as `motion.list` gives it: key values in the units `motion.study` takes — degrees
/// for angles, mm for distances, like `joint.list`'s values — each key with its joint's name,
/// so a listed study can be fed back to `motion.study` unchanged.
fn study_json(s: &Session, m: &MotionStudy) -> Value {
let keys: Vec<Value> = m
.keys
.iter()
.map(|k| {
let joint = s.doc.assembly.joints.iter().find(|j| j.id == k.joint);
let angle = joint.is_some_and(|j| j.kind.is_angle(k.index));
json!({
"joint": k.joint,
"joint_name": joint.map(|j| j.name.clone()).unwrap_or_else(|| k.joint.to_string()),
"index": k.index,
"unit": if angle { "deg" } else { "mm" },
"points": k.points.iter().map(|(step, v)| json!([step, if angle { v.to_degrees() } else { *v }])).collect::<Vec<_>>(),
})
})
.collect();
json!({"id": m.id, "name": m.name, "steps": m.steps, "keys": keys})
}
fn delete_study(s: &mut Session, p: &Value) -> Result<Value> {
+29
View File
@@ -639,6 +639,35 @@ fn motion_study_plays_and_exports() {
assert_eq!(run(&mut s, "motion.list", json!({}))["studies"].as_array().map(Vec::len), Some(0));
}
/// Regression (#9741): `motion.study` takes degrees for angle keys, so `motion.list` must give
/// the keys back in those same units — feeding a listed study straight back to `motion.study`
/// must not turn a 90° key into 1.57°.
#[test]
fn motion_list_round_trips_key_units_through_motion_study() {
let hinge_deg = |s: &mut Session| {
run(s, "joint.list", json!({}))["joints"]
.as_array()
.and_then(|a| a.iter().find(|j| j["name"] == "Hinge").cloned())
.and_then(|j| j["values"][0]["value"].as_f64())
.unwrap_or(f64::NAN)
};
let mut s = hinged_plates(0.0);
run(&mut s, "motion.study", json!({"name": "Turn", "steps": 10, "keys": [{"joint": "Hinge", "points": [[0, 0], [10, 90]]}]}));
run(&mut s, "motion.play", json!({"study": "Turn", "step": 10}));
assert!((hinge_deg(&mut s) - 90.0).abs() < 1e-6, "playing step 10 gave {}°", hinge_deg(&mut s));
let listed = run(&mut s, "motion.list", json!({}));
let key = &listed["studies"][0]["keys"][0];
assert_eq!(key["joint_name"], "Hinge");
assert_eq!(key["unit"], "deg");
assert!((key["points"][1][1].as_f64().unwrap_or(f64::NAN) - 90.0).abs() < 1e-6, "{listed}");
// The listed keys go back to `motion.study` unchanged and the study still plays 90°.
let keys = listed["studies"][0]["keys"].clone();
run(&mut s, "motion.study", json!({"study": "Turn", "keys": keys}));
run(&mut s, "motion.play", json!({"study": "Turn", "step": 0}));
run(&mut s, "motion.play", json!({"study": "Turn", "step": 10}));
assert!((hinge_deg(&mut s) - 90.0).abs() < 1e-6, "the round-tripped study played {}°", hinge_deg(&mut s));
}
#[test]
fn exploded_views_show_without_changing_the_design() {
let mut s = rail();
+129 -22
View File
@@ -2,7 +2,8 @@
//! the dimensions in the public standards tables (no vendor geometry). Each part is inserted as
//! its own component, built by ordinary features (so it stays parametric and editable), with a
//! cosmetic thread where it has one; at a hole it gets a rigid joint that puts its axis on the
//! hole's and seats it on the face.
//! hole's and seats it on the face. Inserted at several holes it is still one component, placed
//! once per hole, each placement with its own joint.
//!
//! Part frame: the seating plane is z = 0 (the underside of a head, the bottom of a nut or
//! washer, the top of a set screw or pin); the shank runs down −z.
@@ -18,13 +19,15 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("parts.library", "Standard Parts", library).icon("library")
.noundo()
.params("family? → families (name, standard, sizes, lengths) and, with family, each size's dimensions (mm)"),
CommandSpec::new("parts.fastener", "Insert Fastener", insert).at("SOLID", "INSERT").icon("fastener").params(
CommandSpec::new("parts.fastener", "Insert Fastener", insert_atomic).at("SOLID", "INSERT").icon("fastener").params(
"the Insert Part dialog's command (same as parts.insert): family, size, length?, at?|point?+direction?, name?",
),
CommandSpec::new("parts.insert", "Insert Part", insert).icon("fastener").params(
CommandSpec::new("parts.insert", "Insert Part", insert_atomic).icon("fastener").params(
"family (socket_head_cap_screw | button_head_cap_screw | hex_bolt | hex_nut | washer | set_screw | dowel_pin | bearing); size (\"M6\", or \"608\" for bearings); \
length? (mm; screws, bolts, set screws, pins; default a standard length near 2.5 d); \
at?: [x,y,z] on a hole's rim or wall (the part seats on the hole's face with a rigid joint) | point?: [x,y,z] with direction?: [x,y,z] (no joint); name?",
at?: [x,y,z] on a hole's rim or wall (the part seats on the hole's face with a rigid joint), or a list of them (one component, \
placed and seated once per hole) | point?: [x,y,z] with direction?: [x,y,z] (no joint); name? → component, occurrence, joint?, \
and with at: occurrences, joints (one per hole, in pick order)",
),
CommandSpec::new("parts.resize", "Change Part Size", resize_atomic).icon("scale")
.params("component: id|name of an inserted part; size?; length? — rebuilds it (joints keep it seated)"),
@@ -514,18 +517,40 @@ fn length_param(p: &Value, cmd: &str) -> Result<Option<f64>> {
}
}
/// Building the part and seating it runs many feature and joint commands: as in
/// [`resize_atomic`], they run on a scratch session and the insert is one undo step.
fn insert_atomic(s: &mut Session, p: &Value) -> Result<Value> {
let mut computed = s.scratch();
let result = insert(&mut computed, p)?;
computed.undo.clear();
computed.active_component = s.active_component;
computed.active_occurrence_path = s.active_occurrence_path.clone();
computed.active_sketch = s.active_sketch.filter(|id| computed.doc.feature(*id).is_some());
s.accept_computed(computed);
Ok(result)
}
fn insert(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "parts.insert (Insert Fastener)";
let family = str_(p, "family").ok_or_else(|| bad(cmd, "`family` is required (parts.library)"))?.to_string();
let size = str_(p, "size").ok_or_else(|| bad(cmd, "`size` is required"))?.to_string();
let sh = shape(&family, &size, length_param(p, cmd)?).map_err(|e| bad(cmd, e))?;
// A pick on a hole's rim also touches its end face: step onto the wall just inside the rim.
let at = p.get("at").and_then(vec3).map(|a| hole_wall(s, a).unwrap_or(a));
// The occurrence the hole belongs to, found before the part exists.
let host = match at {
Some(a) => Some(super::joints::occurrence_at(s, a).ok_or_else(|| bad(cmd, "no body at `at` (pick a hole's rim or wall)"))?),
None => None,
};
// Each hole's occurrence is found before the part exists, so a pick off every body fails the
// insert before anything is made.
let mut holes = Vec::new();
let mut seats: Vec<(u64, Vec3)> = Vec::new();
for a in hole_picks(p, cmd)? {
let (a, seat) = hole_wall(s, a).unwrap_or((a, a));
let host = super::joints::occurrence_at(s, a).ok_or_else(|| bad(cmd, "no body at `at` (pick a hole's rim or wall)"))?;
// A rim is drawn as more than one edge, and one click on it picks them all: the picks
// of one rim seat one part.
if seats.iter().any(|(h, q)| *h == host && (*q - seat).len() < 1e-3) {
continue;
}
seats.push((host, seat));
holes.push((a, host));
}
let name = str_(p, "name").map(str::to_string).unwrap_or_else(|| part_name(&family, &size, &sh));
let active = s.active_component;
// Hardware is fitted to other parts: it starts free even as the design's first part (#2804).
@@ -536,15 +561,29 @@ fn insert(s: &mut Session, p: &Value) -> Result<Value> {
set_identity(s, comp, identity(&family, &size, &sh), None);
run(s, "component.activate", json!({"component": active}))?;
let mut out = json!({"component": comp, "occurrence": occ, "name": name});
if let (Some(a), Some(h)) = (at, host) {
// Seat it: its axis on the hole's, its seating plane on the hole's face, the shank going
// into the hole (the joint mates the part's −z with the face's outward z).
let j = run(
s,
"joint.create",
json!({"type": "rigid", "a": {"occurrence": h, "circle": a}, "b": {"occurrence": occ, "point": [0, 0, 0], "z": [0, 0, -1]}, "name": format!("{name} seat")}),
)?;
out["joint"] = j["joint"].clone();
if !holes.is_empty() {
// The first hole has the placement the component came with; each further hole places the
// same part again. All are made before any is seated, while each is still at the part's
// own origin, where the joint below takes the part's seating point to be.
let mut occurrences = vec![json!(occ)];
for _ in 1..holes.len() {
occurrences.push(run(s, "occurrence.copy", json!({"component": comp}))?["occurrence"].clone());
}
let mut joints = Vec::new();
for (i, ((a, h), o)) in holes.into_iter().zip(&occurrences).enumerate() {
// Seat it: its axis on the hole's, its seating plane on the hole's face, the shank
// going into the hole (the joint mates the part's −z with the face's outward z).
let seat = if i == 0 { format!("{name} seat") } else { format!("{name} seat {}", i + 1) };
let j = run(
s,
"joint.create",
json!({"type": "rigid", "a": {"occurrence": h, "circle": a}, "b": {"occurrence": o, "point": [0, 0, 0], "z": [0, 0, -1]}, "name": seat}),
)?;
joints.push(j["joint"].clone());
}
out["joint"] = joints.first().cloned().unwrap_or(Value::Null);
out["occurrences"] = Value::Array(occurrences);
out["joints"] = Value::Array(joints);
} else if let Some(pt) = p.get("point").and_then(vec3) {
// Placed at a point, its +z along `direction`.
let dir = p.get("direction").and_then(vec3).and_then(|d| d.normalized()).unwrap_or(Vec3::Z);
@@ -563,8 +602,28 @@ fn insert(s: &mut Session, p: &Value) -> Result<Value> {
Ok(out)
}
/// A point on the cylindrical wall of the hole at `a`, a little in from the picked rim.
fn hole_wall(s: &Session, a: Vec3) -> Option<Vec3> {
/// The most holes one insert seats a part in.
const MAX_HOLES: usize = 1000;
/// The hole picks in `at`: one point, or a list of them.
fn hole_picks(p: &Value, cmd: &str) -> Result<Vec<Vec3>> {
let finite = |a: Vec3| a.x.is_finite() && a.y.is_finite() && a.z.is_finite();
let one = |v: &Value| vec3(v).filter(|a| finite(*a)).ok_or_else(|| bad(cmd, "`at` must be [x,y,z] or a list of them"));
match p.get("at") {
None | Some(Value::Null) => Ok(Vec::new()),
Some(Value::Array(list)) if list.first().is_some_and(Value::is_array) || list.is_empty() => {
if list.len() > MAX_HOLES {
return Err(bad(cmd, format!("`at` takes at most {MAX_HOLES} holes")));
}
list.iter().map(one).collect()
}
Some(v) => Ok(vec![one(v)?]),
}
}
/// A point on the cylindrical wall of the hole at `a`, a little in from the picked rim, and the
/// point on the hole's axis level with it (the same for every pick on one rim).
fn hole_wall(s: &Session, a: Vec3) -> Option<(Vec3, Vec3)> {
let st = s.model.state();
let dirs = [Vec3::Z * -1.0, Vec3::Z, Vec3::X, Vec3::X * -1.0, Vec3::Y, Vec3::Y * -1.0];
for step in [0.2, 0.5, 1.0, 2.0] {
@@ -576,7 +635,7 @@ fn hole_wall(s: &Session, a: Vec3) -> Option<Vec3> {
let along = v.dot(c.axis);
let on_wall = ((v - c.axis * along).len() - c.radius).abs() < 1e-3 && along > c.start && along < c.end;
if on_wall {
return Some(q);
return Some((q, c.axis_point + c.axis * along));
}
}
}
@@ -800,6 +859,54 @@ mod tests {
assert!(s.model.state().threads.iter().any(|t| t.designation == "M8" && (t.pitch - 1.25).abs() < 1e-9));
}
/// Several holes in one insert: one component placed and seated once per hole, in one undo
/// step (#9341); two picks of one rim are one hole; a pick off every body fails the whole
/// insert.
#[test]
fn one_insert_seats_a_screw_in_each_hole() {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 10, "body_name": "Plate"}));
let centres = [(8.0, 8.0), (32.0, 8.0), (32.0, 22.0), (8.0, 22.0)];
for (x, y) in centres {
run(&mut s, "solid.hole", json!({"position": [x, y, 10], "diameter": 3.4}));
}
let (comps, occs, undo) = (s.doc.components.len(), s.doc.occurrences.len(), s.undo.len());
// A hole off the plate fails the insert, and nothing is made.
let off = json!({"family": "socket_head_cap_screw", "size": "M3", "length": 8, "at": [[9.7, 8, 10], [100, 100, 100]]});
assert!(s.execute("parts.insert", &off).is_err());
assert_eq!((s.doc.components.len(), s.doc.occurrences.len()), (comps, occs));
// Each rim picked at the top face.
// The first hole is picked twice, on both halves of its rim, as one click on a rim picks
// every edge of it: it still gets one screw.
let mut at: Vec<_> = centres.iter().map(|(x, y)| json!([x + 1.7, y, 10])).collect();
at.insert(1, json!([centres[0].0 - 1.7, centres[0].1, 10]));
let r = run(&mut s, "parts.fastener", json!({"family": "socket_head_cap_screw", "size": "M3", "length": 8, "at": at}));
assert_eq!(s.doc.components.len(), comps + 1, "one part: {r}");
let comp = r["component"].as_u64().unwrap_or(0);
let placed: Vec<u64> = r["occurrences"].as_array().into_iter().flatten().filter_map(Value::as_u64).collect();
assert_eq!(placed.len(), 4, "{r}");
assert!(placed.iter().all(|o| s.doc.occurrences.iter().any(|x| x.id == *o && x.component == comp)), "{r}");
assert_eq!(r["joints"].as_array().map(|j| j.iter().filter(|j| j.is_number()).count()), Some(4), "{r}");
assert_eq!(r["joint"], r["joints"][0]);
// Each placement sits in its own hole: the head flush on the top face, the shank down
// the hole's axis.
let st = s.world_state();
let mut seated: Vec<(f64, f64)> = Vec::new();
for b in st.bodies.iter().filter(|b| s.doc.model_body_component(b) == comp) {
let (lo, hi) = (b.mesh().bounds().min, b.mesh().bounds().max);
assert!((hi.z - 13.0).abs() < 1e-6 && (lo.z - 2.0).abs() < 1e-6, "{lo:?} {hi:?}");
seated.push(((lo.x + hi.x) / 2.0, (lo.y + hi.y) / 2.0));
}
assert_eq!(seated.len(), 4, "{seated:?}");
for (x, y) in centres {
assert!(seated.iter().any(|c| (c.0 - x).abs() < 1e-6 && (c.1 - y).abs() < 1e-6), "no screw at ({x}, {y}): {seated:?}");
}
// One undo takes them all away.
assert_eq!(s.undo.len(), undo + 1);
run(&mut s, "edit.undo", json!({}));
assert_eq!((s.doc.components.len(), s.doc.occurrences.len()), (comps, occs));
}
#[test]
fn every_sketch_of_a_standard_part_is_fully_defined() {
for (family, size, resized) in [
+90 -12
View File
@@ -3,7 +3,9 @@
use serde_json::{Value, json};
use solidcraft_doc::Mat;
use solidcraft_doc::point_cloud::{ColorBy, Colors, MAX_COORD, MAX_POINTS, POINT_SIZE, PointCloud, Points, UNITS, unit_scale};
use solidcraft_doc::point_cloud::{
ColorBy, Colors, Filter, Keep, MAX_COORD, MAX_FILTERS, MAX_NAMES, MAX_POINTS, POINT_SIZE, PointCloud, Points, UNITS, unit_scale,
};
use super::CommandSpec;
use crate::params::{bad, bool_, str_};
@@ -13,16 +15,16 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("pointcloud.insert", "Insert Point Cloud", insert)
.at("SOLID", "INSERT")
.icon("import")
.params("path: .ply, .xyz or .pts file (its points join the design as a point cloud: drawn as points, never a body; a PLY's red, green and blue are its colours) → cloud, name, points, source_points, bounds, warnings; name?; units?: mm|cm|m|in|ft (the file's length unit, default mm); max_points?: most points kept (default 5000000, at most 20000000; a larger file is thinned evenly); center?: bool (move the cloud so the middle of its base sits at the origin); point_size?: pixels (0.5…16, default 2); color_by?: file|single|height (the file's colours, the default; one colour; or a ramp by height, blue low to red high); color?: [r, g, b] 0…1 (the single colour; given alone it sets color_by single, null sets color_by file); height_range?: [low, high] design Z in mm for the height ramp, or null to work it out from the points (all but the lowest and highest 2 %)"),
.params("path: .ply, .xyz or .pts file (its points join the design as a point cloud: drawn as points, never a body; a PLY's red, green and blue are its colours, its other vertex properties can colour and filter it) → cloud, name, points, source_points, bounds, warnings; name?; units?: mm|cm|m|in|ft (the file's length unit, default mm); max_points?: most points kept (default 5000000, at most 20000000; a larger file is thinned evenly); center?: bool (move the cloud so the middle of its base sits at the origin); point_size?: pixels (0.5…16, default 2); color_by?: file|single|height or a property's name (the file's colours, the default; one colour; a ramp by height, blue low to red high; or by any property of the file, a ramp over its values or a palette for whole numbers); color?: [r, g, b] 0…1 (the single colour; given alone it sets color_by single, null sets color_by file); color_range?: [low, high] the ramp runs between (design Z in mm by height), or null for the automatic range (by height all but the lowest and highest 2 % of the points; by a property the values it takes over what the filters keep); height_range?: the same as color_range; filters?: [{property, range: [low, high]} or {property, values: [whole numbers]} for a property with named values], or null (only what every filter keeps is shown)"),
CommandSpec::new("pointcloud.edit", "Edit Point Cloud", edit)
.icon("edit")
.params("cloud: id or name; name?; visible?; point_size?: pixels (0.5…16); color_by?: file|single|height (the file's colours, the default; one colour; or a ramp by height, blue low to red high); color?: [r, g, b] 0…1 (the single colour; given alone it sets color_by single, null sets color_by file); height_range?: [low, high] design Z in mm for the height ramp, or null to work it out from the points (all but the lowest and highest 2 %); units?: mm|cm|m|in|ft (the file's length unit: the points are scaled from the unit they came in as); matrix?: [12 or 16 numbers] (where the cloud sits: a rigid row-major 3x4 or 4x4 matrix, as occurrence.move takes it); tolerance? → cloud"),
.params("cloud: id or name; name?; visible?; point_size?: pixels (0.5…16); color_by?: file|single|height or a property's name (the file's colours, the default; one colour; a ramp by height, blue low to red high; or by any property of the file, a ramp over its values or a palette for whole numbers); color?: [r, g, b] 0…1 (the single colour; given alone it sets color_by single, null sets color_by file); color_range?: [low, high] the ramp runs between (design Z in mm by height), or null for the automatic range (by height all but the lowest and highest 2 % of the points; by a property the values it takes over what the filters keep); height_range?: the same as color_range; units?: mm|cm|m|in|ft (the file's length unit: the points are scaled from the unit they came in as); matrix?: [12 or 16 numbers] (where the cloud sits: a rigid row-major 3x4 or 4x4 matrix, as occurrence.move takes it); tolerance?; filters?: [{property, range: [low, high]} or {property, values: [whole numbers]} for a property with named values], or null (only what every filter keeps is shown) → cloud"),
CommandSpec::new("pointcloud.delete", "Delete Point Cloud", delete).icon("delete").params("cloud: id or name → deleted"),
CommandSpec::new("pointcloud.list", "Point Clouds", list)
.icon("list")
.noundo()
.doc_only()
.params("→ clouds: [{cloud, name, file, points, source_points, units, visible, point_size, color_by, color, colored, height_range (the ramp's now, its own or worked out), height_auto, bounds: [[x,y,z], [x,y,z]] in the design, matrix}]"),
.params("→ clouds: [{cloud, name, file, points, source_points, units, visible, point_size, color_by (file, single, height or a property's name), color, colored, properties: [{name, integer, span, names}], filters, color_range (the ramp's now, exact over what the filters keep), height_range (the ramp's own range or the automatic one by height), height_auto, bounds: [[x,y,z], [x,y,z]] in the design, matrix}]"),
];
fn units_of(p: &Value, cmd: &str) -> Result<Option<(&'static str, f64)>> {
@@ -64,18 +66,74 @@ fn color(p: &Value, cmd: &str) -> Result<Option<Option<[f32; 3]>>> {
}
}
/// `color_by`: absent (`None`), or file, single or height.
fn color_by(p: &Value, cmd: &str) -> Result<Option<ColorBy>> {
/// `color_by`: absent (`None`), file, single or height, or the name of a property.
fn color_by(p: &Value, cmd: &str) -> Result<Option<(ColorBy, Option<String>)>> {
match p.get("color_by") {
None | Some(Value::Null) => Ok(None),
Some(v) => v.as_str().and_then(ColorBy::parse).map(Some).ok_or_else(|| bad(cmd, "`color_by` must be file, single or height")),
Some(Value::String(v)) => Ok(Some(match ColorBy::parse(v) {
Some(b) if b != ColorBy::Property => (b, None),
_ => (ColorBy::Property, Some(v.clone())),
})),
Some(_) => Err(bad(cmd, "`color_by` must be file, single, height or a property's name")),
}
}
/// `filters`: absent (`None`), null for none, or [{property, range: [low, high]} or
/// {property, values: [whole numbers]}].
fn filters(p: &Value, cmd: &str) -> Result<Option<Vec<Filter>>> {
let refuse = || {
bad(
cmd,
format!(
"`filters` must be at most {MAX_FILTERS} {{property, range: [low, high]}} (lowest first) or {{property, values: [whole numbers]}}, or null"
),
)
};
match p.get("filters") {
None => Ok(None),
Some(Value::Null) => Ok(Some(Vec::new())),
Some(Value::Array(a)) if a.len() <= MAX_FILTERS => a
.iter()
.map(|f| {
let property = f.get("property").and_then(Value::as_str).ok_or_else(refuse)?.to_string();
if let Some(values) = f.get("values") {
let values = values.as_array().ok_or_else(refuse)?;
let whole = |v: &Value| v.as_i64().and_then(|n| i32::try_from(n).ok());
let values = values.iter().map(whole).collect::<Option<Vec<i32>>>().filter(|v| v.len() <= MAX_NAMES).ok_or_else(refuse)?;
return Ok(Filter { property, keep: Keep::Values { values } });
}
match f.get("range").and_then(Value::as_array).map(|r| r.iter().map(Value::as_f64).collect::<Option<Vec<f64>>>()) {
Some(Some(r)) if matches!(r.as_slice(), &[lo, hi] if lo.is_finite() && hi.is_finite() && lo <= hi) => {
Ok(Filter { property, keep: Keep::Range { range: [r[0], r[1]] } })
}
_ => Err(refuse()),
}
})
.collect::<Result<Vec<Filter>>>()
.map(Some),
Some(_) => Err(refuse()),
}
}
/// A cloud colours and filters only by properties it has.
fn check_properties(c: &PointCloud, cmd: &str) -> Result<()> {
let names: Vec<&str> = c.property_names().collect();
for n in c.color_property.iter().filter(|_| c.color_by == ColorBy::Property).chain(c.filters.iter().map(|f| &f.property)) {
if !names.contains(&n.as_str()) {
return Err(bad(cmd, format!("the point cloud has no property `{n}` (it has {})", names.join(", "))));
}
}
// A set of values filters a label: a property with named whole numbers.
if let Some(f) = c.filters.iter().find(|f| matches!(f.keep, Keep::Values { .. }) && c.complex.names(&f.property).is_none()) {
return Err(bad(cmd, format!("`{}` has no named values: filter it with a range", f.property)));
}
Ok(())
}
/// `height_range`: absent (`None`), null for the automatic one (`Some(None)`), or [low, high].
fn height_range(p: &Value, cmd: &str) -> Result<Option<Option<[f64; 2]>>> {
let refuse = || bad(cmd, "`height_range` must be [low, high] heights in mm, lowest first, or null");
match p.get("height_range") {
let refuse = || bad(cmd, "`height_range` (or `color_range`) must be [low, high], lowest first, or null");
match p.get("color_range").or_else(|| p.get("height_range")) {
None => Ok(None),
Some(Value::Null) => Ok(Some(None)),
Some(Value::Array(a)) => match a.iter().map(Value::as_f64).collect::<Option<Vec<f64>>>().as_deref() {
@@ -90,13 +148,18 @@ fn height_range(p: &Value, cmd: &str) -> Result<Option<Option<[f64; 2]>>> {
/// Apply `color_by`, `color` and `height_range`: a colour given alone means one colour, and
/// `color: null` the file's.
fn set_colors(c: &mut PointCloud, by: Option<ColorBy>, color: Option<Option<[f32; 3]>>, range: Option<Option<[f64; 2]>>) {
fn set_colors(c: &mut PointCloud, by: Option<(ColorBy, Option<String>)>, color: Option<Option<[f32; 3]>>, range: Option<Option<[f64; 2]>>) {
if let Some(k) = color {
c.color = k.or(c.color);
c.color_by = if k.is_some() { ColorBy::Single } else { ColorBy::File };
}
if let Some(b) = by {
if let Some((b, property)) = by {
if property != c.color_property && range.is_none() {
// Another property's values have another range.
c.height_range = None;
}
c.color_by = b;
c.color_property = property.or(c.color_property.take().filter(|_| b == ColorBy::Property));
}
if let Some(r) = range {
c.height_range = r;
@@ -172,8 +235,12 @@ fn insert(s: &mut Session, p: &Value) -> Result<Value> {
color: None,
height_range: None,
visible: true,
complex: read.complex,
color_property: None,
filters: filters(p, cmd)?.unwrap_or_default(),
};
set_colors(&mut cloud, colors.0, colors.1, colors.2);
check_properties(&cloud, cmd)?;
if center && let Some((lo, hi)) = cloud.local_bounds() {
cloud.transform[3] = [-(lo.x + hi.x) / 2.0, -(lo.y + hi.y) / 2.0, -lo.z, 1.0];
}
@@ -207,6 +274,7 @@ fn edit(s: &mut Session, p: &Value) -> Result<Value> {
let i = find(s, p, cmd)?;
let size = point_size(p, cmd)?;
let colors = (color_by(p, cmd)?, color(p, cmd)?, height_range(p, cmd)?);
let filters = filters(p, cmd)?;
let units = units_of(p, cmd)?;
let placement = match p.get("matrix") {
None | Some(Value::Null) => None,
@@ -236,6 +304,10 @@ fn edit(s: &mut Session, p: &Value) -> Result<Value> {
c.point_size = v;
}
set_colors(&mut c, colors.0, colors.1, colors.2);
if let Some(f) = filters {
c.filters = f;
}
check_properties(&c, cmd)?;
if let Some(v) = bool_(p, "visible") {
c.visible = v;
}
@@ -271,7 +343,13 @@ fn list(s: &mut Session, _: &Value) -> Result<Value> {
"units": c.units,
"visible": c.visible,
"point_size": c.point_size,
"color_by": c.color_by.name(),
"color_by": c.colored_property().filter(|_| c.color_by == ColorBy::Property).unwrap_or(c.color_by.name()),
"properties": c
.property_names()
.map(|n| json!({"name": n, "integer": c.complex.integer(n), "span": c.span(n), "names": c.complex.names(n).map(|m| &m.values)}))
.collect::<Vec<_>>(),
"filters": c.filters,
"color_range": solidcraft_doc::point_cloud_distribution::color_range(c, &solidcraft_doc::point_cloud_distribution::Geometry::of(c), &c.filters),
"color": c.color,
"colored": !c.colors.is_empty(),
"height_range": c.height_range_now(),
+417
View File
@@ -0,0 +1,417 @@
//! Laying bodies on their print face for a slicer (#8429). The bodies being sent are copied (the
//! state the export writes is already a copy), and each copy whose body has a print face is
//! turned so the face points down, its centre of mass moved onto the world origin in X and Y,
//! and the face moved onto Z = 0: one rigid transform, applied once. The design and its timeline
//! are not touched. Each body is placed on its own, so several land on the origin together and
//! the slicer arranges them.
use serde_json::Value;
use solidcraft_doc::{ModelState, PrintFace};
use solidcraft_geom::Vec3;
use super::CommandSpec;
use crate::params::{bad, str_};
use crate::schema::print_face::PrintFaceResult;
use crate::{EngineError, Result, Session};
pub static COMMANDS: &[CommandSpec] = &[CommandSpec::new("body.print_face", "Print Face", crate::checked!("body.print_face", print_face))
.icon("print_face")
.key("Ctrl+B")
.contract(&PRINT_FACE_CONTRACT)
.output(crate::schema::output_schema_for::<PrintFaceResult>)
.params("body: its name; point?: [x, y, z] on a flat face of it, in mm (omit to clear) → {body, face?}: marks the face a slicer export (file.send_to_slicer, file.save_mesh with slicer, file.open_in_easyprint) lays the body on: turned so the face points down at Z = 0, the centre of mass over the origin. One per body, replacing the last; flat faces of solid bodies only. A print face that is gone or no longer flat is reported in the export's warnings, and the body exported as it lies")];
const PRINT_FACE_CONTRACT: crate::schema::TypedContract<crate::schema::print_face::PrintFaceParams> = crate::schema::TypedContract::new();
/// Mark or clear a body's print face.
fn print_face(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "body.print_face";
let name = str_(p, "body").ok_or_else(|| bad(cmd, "give `body`"))?;
let point = match p.get("point") {
None | Some(Value::Null) => None,
Some(v) => Some(crate::params::vec3(v).ok_or_else(|| bad(cmd, "`point` must be [x, y, z] of numbers"))?),
};
let world = s.world_state();
let body = world.body(name).ok_or_else(|| bad(cmd, format!("no body `{name}`")))?;
let key = body.definition_name().to_string();
let face = match point {
None => {
s.doc_mut().print_faces.remove(&key);
None
}
Some(point) => {
let index = solidcraft_doc::appearance::face_index_at(body, point).ok_or_else(|| bad(cmd, "no face of the body at `point`"))?;
let target = s.doc.print_face_target(body, index).map_err(|e| bad(cmd, e.to_string()))?;
let face = target.name.clone();
s.doc_mut().print_faces.insert(key, target);
Some(face)
}
};
let reply = PrintFaceResult { body: name.to_string(), face, param_warnings: Vec::new() };
serde_json::to_value(reply).map_err(|e| EngineError::Other(e.to_string()))
}
/// Which bodies were laid on their print face, and a warning for each that has one but was
/// exported as it lies.
#[derive(Debug, Default)]
pub(super) struct Laid {
pub bodies: Vec<String>,
pub warnings: Vec<String>,
}
impl Laid {
/// Add what happened to an export's reply.
pub(super) fn report(self, out: &mut serde_json::Value) {
let Some(obj) = out.as_object_mut() else { return };
if !self.bodies.is_empty() {
obj.insert("print_faces".into(), serde_json::json!(self.bodies));
}
if !self.warnings.is_empty() {
obj.insert("warnings".into(), serde_json::json!(self.warnings));
}
}
}
/// Copies keep the print faces of the bodies they were made from (Move/Copy, Mirror), as they
/// keep their look: the copies are the bodies feature `reply` made, in the order of `sources`.
/// A copy whose face can't be matched is left without one.
pub(super) fn copies_keep_print_faces(s: &mut Session, reply: &Value, sources: &[String]) {
let Some(id) = reply.get("feature").and_then(Value::as_u64) else { return };
let state = s.model.state();
let copies: Vec<_> = state.bodies.iter().filter(|b| b.feature == id).collect();
if copies.len() != sources.len() {
return;
}
let mut inherited = Vec::new();
for (source, copy) in sources.iter().zip(copies) {
let Some(source) = state.body(source) else { continue };
if let Some(Ok(target)) = s.doc.copied_print_face(&state, source, copy) {
inherited.push((copy.definition_name().to_string(), target));
}
}
for (body, target) in inherited {
s.doc_mut().print_faces.insert(body, target);
}
}
/// Lay the bodies of `state` named in `bodies` (all of them when it is empty) on their print faces.
/// A body that is one of several parts of a 3MF object (`parts`, `objects: component`) stays as it
/// lies, so the object keeps its shape: turning one part on its own would pull it out of the rest.
pub(super) fn lay_on_print_faces(s: &Session, state: &mut ModelState, bodies: &[String], parts: &[(String, Vec<String>)]) -> Laid {
let mut laid = Laid::default();
let mut moves = Vec::new();
for (i, b) in state.bodies.iter().enumerate() {
if !bodies.is_empty() && !bodies.contains(&b.name) {
continue;
}
if let Some((object, _)) = parts.iter().find(|(_, members)| members.contains(&b.name)) {
if s.doc.print_faces.contains_key(b.definition_name()) {
laid.warnings.push(format!("`{}` was exported as it lies: it is a part of the object `{object}`, which keeps its shape", b.name));
}
continue;
}
let face = match s.doc.print_face(state, b) {
None => continue,
Some(Ok(face)) => face,
Some(Err(e)) => {
laid.warnings.push(format!("`{}` was exported as it lies: {e}", b.name));
continue;
}
};
let centre = match solidcraft_kernel::measure_mass(&b.body) {
Ok(m) => m.centroid,
Err(e) => {
laid.warnings.push(format!("`{}` was exported as it lies: no centre of mass ({e})", b.name));
continue;
}
};
moves.push((i, bed_matrix(face, centre)));
}
for (i, m) in moves {
let Some(b) = state.bodies.get_mut(i) else { continue };
match solidcraft_kernel::transform_matrix(&b.body, m) {
Ok(body) => {
b.body = body;
laid.bodies.push(b.name.clone());
}
Err(e) => laid.warnings.push(format!("`{}` was exported as it lies: {e}", b.name)),
}
}
laid
}
/// The rotation (rows) that turns unit vector `n` to −Z. A face that already points down is
/// left as it is; one that points straight up is turned half a turn about X.
fn turn_down(n: Vec3) -> [Vec3; 3] {
let down = Vec3::new(0., 0., -1.);
let c = n.dot(down);
let v = n.cross(down);
let s2 = v.len2();
if s2 <= 1e-24 {
return if c > 0. { [Vec3::X, Vec3::Y, Vec3::Z] } else { [Vec3::X, -Vec3::Y, -Vec3::Z] };
}
// Rodrigues: R e = e + v × e + v × (v × e) (1 − c) / |v|²; these are R's columns.
let k = (1. - c) / s2;
let [x, y, z] = [Vec3::X, Vec3::Y, Vec3::Z].map(|e| e + v.cross(e) + v.cross(v.cross(e)) * k);
[Vec3::new(x.x, y.x, z.x), Vec3::new(x.y, y.y, z.y), Vec3::new(x.z, y.z, z.z)]
}
/// The column-major transform that lays `face` on the bed with `centre` above the origin.
fn bed_matrix(face: PrintFace, centre: Vec3) -> [[f64; 4]; 4] {
let r = turn_down(face.normal);
let apply = |p: Vec3| Vec3::new(r[0].dot(p), r[1].dot(p), r[2].dot(p));
let c = apply(centre);
let z = apply(face.point).z;
let t = Vec3::new(-c.x, -c.y, -z);
[[r[0].x, r[1].x, r[2].x, 0.], [r[0].y, r[1].y, r[2].y, 0.], [r[0].z, r[1].z, r[2].z, 0.], [t.x, t.y, t.z, 1.]]
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn run(s: &mut Session, id: &str, p: Value) -> Value {
match s.execute(id, &p) {
Ok(v) => v,
Err(e) => panic!("{id} {p}: {e}"),
}
}
/// A box `L` x 10 x 5 (L = 20 mm) from the origin, called Block.
fn block() -> Session {
let mut s = Session::default();
run(&mut s, "parameters.add", json!({"name": "L", "expression": "20 mm"}));
run(&mut s, "solid.box", json!({"length": "L", "width": 10, "height": 5, "corner": [0, 0, 0], "body_name": "Block"}));
s
}
/// Lay every body of `s` on its print face, as a slicer export does: Block's box and centre
/// of mass afterwards, and what was reported.
fn laid(s: &Session) -> (solidcraft_geom::Aabb3, Vec3, Laid) {
let mut state = s.doc.painted(&s.world_state());
let laid = lay_on_print_faces(s, &mut state, &[], &[]);
let block = state.bodies.iter().find(|b| b.name == "Block").expect("Block");
let m = solidcraft_kernel::measure_mass(&block.body).expect("measure");
(m.bbox, m.centroid, laid)
}
fn near(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6
}
#[test]
fn a_part_of_a_3mf_object_keeps_its_place_in_the_object() {
// With `objects: component` the bodies of a component are the parts of one object:
// turning one of them on its own would pull it out of the others, so it stays as it lies.
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [20, 5, 2.5]}));
let mut state = s.doc.painted(&s.world_state());
let parts = [("Bracket".to_string(), vec!["Block".to_string(), "Other".to_string()])];
let laid = lay_on_print_faces(&s, &mut state, &[], &parts);
assert!(laid.bodies.is_empty(), "{:?}", laid.bodies);
assert_eq!(laid.warnings.len(), 1, "{:?}", laid.warnings);
assert!(laid.warnings.first().is_some_and(|w| w.contains("Block") && w.contains("Bracket")), "{:?}", laid.warnings);
let block = state.bodies.iter().find(|b| b.name == "Block").expect("Block");
let bbox = solidcraft_kernel::measure_mass(&block.body).expect("measure").bbox;
assert!(near(bbox.min.x, 0.) && near(bbox.max.x, 20.) && near(bbox.max.z, 5.), "{bbox:?}");
}
#[test]
fn a_side_face_is_laid_down_at_z_0_with_the_centre_of_mass_over_the_origin() {
let mut s = block();
let reply = run(&mut s, "body.print_face", json!({"body": "Block", "point": [20, 5, 2.5]}));
assert!(reply["face"].is_string(), "{reply}");
let (bbox, centre, laid) = laid(&s);
assert_eq!(laid.bodies, ["Block"]);
assert!(laid.warnings.is_empty(), "{:?}", laid.warnings);
// The 10 x 5 end face is on the bed, so the 20 mm length now stands up.
assert!(near(bbox.min.z, 0.) && near(bbox.max.z, 20.), "{bbox:?}");
assert!(near(centre.x, 0.) && near(centre.y, 0.) && near(centre.z, 10.), "{centre:?}");
assert!(near((bbox.max.x - bbox.min.x) * (bbox.max.y - bbox.min.y), 50.), "{bbox:?}");
// The design itself has not moved.
let m = solidcraft_kernel::measure_mass(&s.model.state().bodies[0].body).expect("measure");
assert!(near(m.bbox.min.x, 0.) && near(m.bbox.max.x, 20.) && near(m.bbox.max.z, 5.), "{:?}", m.bbox);
}
#[test]
fn a_face_that_points_up_is_turned_over() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [10, 5, 5]}));
let (bbox, centre, laid) = laid(&s);
assert_eq!(laid.bodies, ["Block"]);
assert!(near(bbox.min.z, 0.) && near(bbox.max.z, 5.), "{bbox:?}");
assert!(near(bbox.max.x - bbox.min.x, 20.) && near(bbox.max.y - bbox.min.y, 10.), "{bbox:?}");
assert!(near(centre.x, 0.) && near(centre.y, 0.) && near(centre.z, 2.5), "{centre:?}");
}
#[test]
fn the_print_face_follows_the_body_through_a_rebuild() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [20, 5, 2.5]}));
run(&mut s, "parameters.change", json!({"name": "L", "expression": "30 mm"}));
let (bbox, centre, laid) = laid(&s);
assert!(laid.warnings.is_empty(), "{:?}", laid.warnings);
assert!(near(bbox.min.z, 0.) && near(bbox.max.z, 30.), "{bbox:?}");
assert!(near(centre.x, 0.) && near(centre.y, 0.) && near(centre.z, 15.), "{centre:?}");
}
#[test]
fn without_a_print_face_the_body_is_exported_as_it_lies() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [10, 5, 5]}));
let reply = run(&mut s, "body.print_face", json!({"body": "Block"}));
assert!(reply.get("face").is_none(), "{reply}");
assert!(s.doc.print_faces.is_empty());
let (bbox, _, laid) = laid(&s);
assert!(laid.bodies.is_empty() && laid.warnings.is_empty());
assert!(near(bbox.min.x, 0.) && near(bbox.max.x, 20.) && near(bbox.min.z, 0.) && near(bbox.max.z, 5.), "{bbox:?}");
}
#[test]
fn a_print_face_that_was_cut_away_is_reported_and_the_body_exported_as_it_lies() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [10, 5, 5]}));
// A cut takes the top 2 mm off: the marked face is gone; the new top is the cut's.
run(&mut s, "solid.box", json!({"length": 30, "width": 20, "height": 5, "corner": [-5, -5, 3], "operation": "cut"}));
let (bbox, _, laid) = laid(&s);
assert!(laid.bodies.is_empty());
assert_eq!(laid.warnings.len(), 1, "{:?}", laid.warnings);
assert!(laid.warnings[0].contains("Block"), "{:?}", laid.warnings);
assert!(near(bbox.min.x, 0.) && near(bbox.max.z, 3.), "{bbox:?}");
}
#[test]
fn a_print_face_is_saved_with_the_design_and_an_older_design_has_none() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [10, 5, 5]}));
let saved = serde_json::to_value(&*s.doc).expect("save");
assert!(saved["print_faces"]["Block"].is_object(), "{}", saved["print_faces"]);
let opened: solidcraft_doc::Document = serde_json::from_value(saved.clone()).expect("open");
assert_eq!(opened.print_faces, s.doc.print_faces);
let mut older = saved;
older.as_object_mut().expect("object").remove("print_faces");
let opened: solidcraft_doc::Document = serde_json::from_value(older).expect("open");
assert!(opened.print_faces.is_empty());
}
#[test]
fn renaming_the_body_keeps_its_print_face() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [10, 5, 5]}));
run(&mut s, "body.rename", json!({"body": "Block", "name": "Lid"}));
assert!(s.doc.print_faces.contains_key("Lid") && !s.doc.print_faces.contains_key("Block"));
assert_eq!(s.doc.print_faces["Lid"].body, "Lid");
}
/// Every body laid on its print face: (name, box, centre of mass) of those laid.
fn all_laid(s: &Session) -> Vec<(String, solidcraft_geom::Aabb3, Vec3)> {
let mut state = s.doc.painted(&s.world_state());
let laid = lay_on_print_faces(s, &mut state, &[], &[]);
assert!(laid.warnings.is_empty(), "{:?}", laid.warnings);
laid.bodies
.iter()
.map(|name| {
let b = state.bodies.iter().find(|b| &b.name == name).expect("laid body");
let m = solidcraft_kernel::measure_mass(&b.body).expect("measure");
(name.clone(), m.bbox, m.centroid)
})
.collect()
}
#[test]
fn a_copy_keeps_the_print_face_of_the_body_it_was_copied_from() {
let mut s = block();
run(&mut s, "body.print_face", json!({"body": "Block", "point": [20, 5, 2.5]}));
run(&mut s, "solid.move", json!({"bodies": ["Block"], "translate": [0, 50, 0], "copy": true}));
assert_eq!(s.doc.print_faces.len(), 2, "{:?}", s.doc.print_faces.keys().collect::<Vec<_>>());
let laid = all_laid(&s);
assert_eq!(laid.len(), 2, "{laid:?}");
for (name, bbox, centre) in &laid {
assert!(near(bbox.min.z, 0.) && near(bbox.max.z, 20.), "{name}: {bbox:?}");
assert!(near(centre.x, 0.) && near(centre.y, 0.), "{name}: {centre:?}");
}
}
#[test]
fn a_mirrored_copy_keeps_the_mirrored_print_face() {
let mut s = block();
// The end at X = 20; its mirror image in YZ is the copy's end at X = -20.
run(&mut s, "body.print_face", json!({"body": "Block", "point": [20, 5, 2.5]}));
run(&mut s, "solid.mirror", json!({"bodies": ["Block"], "plane": "YZ"}));
assert_eq!(s.doc.print_faces.len(), 2, "{:?}", s.doc.print_faces.keys().collect::<Vec<_>>());
let copy = s.doc.print_faces.keys().find(|k| *k != "Block").expect("the copy's print face").clone();
let world = s.world_state();
let body = world.body(&copy).expect("copy");
let face = s.doc.print_face(&world, body).expect("marked").expect("found");
assert!(near(face.point.x, -20.) && near(face.normal.x, -1.), "{face:?}");
let laid = all_laid(&s);
assert_eq!(laid.len(), 2, "{laid:?}");
for (name, bbox, _) in &laid {
assert!(near(bbox.min.z, 0.) && near(bbox.max.z, 20.), "{name}: {bbox:?}");
}
}
#[test]
fn a_copy_of_a_body_without_a_print_face_gets_none() {
let mut s = block();
run(&mut s, "solid.move", json!({"bodies": ["Block"], "translate": [0, 50, 0], "copy": true}));
run(&mut s, "solid.mirror", json!({"bodies": ["Block"], "plane": "XZ"}));
assert!(s.doc.print_faces.is_empty());
}
#[test]
fn a_point_off_the_body_or_a_body_that_is_not_there_is_refused() {
let mut s = block();
assert!(s.execute("body.print_face", &json!({"body": "Block", "point": [100, 100, 100]})).is_err());
assert!(s.execute("body.print_face", &json!({"body": "Nope", "point": [10, 5, 5]})).is_err());
assert!(s.execute("body.print_face", &json!({"body": "Block", "point": "top"})).is_err());
assert!(s.doc.print_faces.is_empty());
}
fn turned(n: Vec3) -> Vec3 {
let r = turn_down(n);
Vec3::new(r[0].dot(n), r[1].dot(n), r[2].dot(n))
}
#[test]
fn every_normal_is_turned_to_point_down_by_a_rotation() {
let down = Vec3::new(0., 0., -1.);
for n in [Vec3::X, -Vec3::X, Vec3::Y, -Vec3::Y, Vec3::Z, -Vec3::Z, Vec3::new(1., 2., 3.), Vec3::new(-0.3, 0.1, 0.9), Vec3::new(1e-9, 0., 1.)]
{
let n = n.normalized().unwrap();
assert!(turned(n).dist(down) < 1e-12, "{n:?} → {:?}", turned(n));
let r = turn_down(n);
// Orthonormal rows with a positive determinant: a rotation, never a mirror.
for i in 0..3 {
assert!((r[i].len() - 1.).abs() < 1e-12);
for j in i + 1..3 {
assert!(r[i].dot(r[j]).abs() < 1e-12);
}
}
assert!((r[0].cross(r[1]).dot(r[2]) - 1.).abs() < 1e-12);
}
}
#[test]
fn a_face_that_points_straight_up_is_turned_half_a_turn_about_x() {
let r = turn_down(Vec3::Z);
assert_eq!(r, [Vec3::X, -Vec3::Y, -Vec3::Z]);
}
#[test]
fn the_bed_matrix_puts_the_face_at_z_0_and_the_centre_over_the_origin() {
let face = PrintFace { index: 0, point: Vec3::new(3., 4., 10.), normal: Vec3::Z };
let m = bed_matrix(face, Vec3::new(3., 4., 5.));
let apply = |p: Vec3| {
Vec3::new(
m[0][0] * p.x + m[1][0] * p.y + m[2][0] * p.z + m[3][0],
m[0][1] * p.x + m[1][1] * p.y + m[2][1] * p.z + m[3][1],
m[0][2] * p.x + m[1][2] * p.y + m[2][2] * p.z + m[3][2],
)
};
assert!(apply(face.point).z.abs() < 1e-12);
let c = apply(Vec3::new(3., 4., 5.));
assert!(c.x.abs() < 1e-12 && c.y.abs() < 1e-12 && c.z > 0., "{c:?}");
}
}
+13 -6
View File
@@ -74,7 +74,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("render.camera.get", "Inspect Render Camera", get_camera).icon("camera").noundo()
.params("No parameters. Returns the explicitly defined current view camera, or null if the session has no camera override. No camera is fitted, no job is started and no saved setup or engineering document is changed."),
CommandSpec::new("render.camera.fit", "Frame Render Subject", fit_camera).icon("camera").noundo()
.params("No parameters. Fits the current camera to the visible product bounds through the shared Camera::fit contract; changes only view state, not a saved setup, rendering job, stage or engineering geometry."),
.params("aspect?: the picture's width over its height, 0.000001 to 1000000 (default 1). Fits the current camera to the visible product bounds through the shared Camera::fit contract, moving back for a picture taller than wide so its sides hold the product too; changes only view state, not a saved setup, rendering job, stage or engineering geometry."),
CommandSpec::new("render.preset.resolve", "Review Native Studio", studio_presets::resolve).icon("render_scene").noundo()
.params("id: NATIVE-STUDIO-001..025; name; set?: typed GPU setup patch; expected_revision?:u64. Resolves original built-in HDR/rig/passive ground with conservative geometry clearance, returns all replacements and preserved fields without mutation. Unbound SCENE-* concepts are not accepted."),
CommandSpec::new("render.preset.apply", "Apply Native Studio", studio_presets::apply).icon("render_scene")
@@ -169,13 +169,20 @@ fn get_camera(s: &mut Session, params: &Value) -> Result<Value> {
}
fn fit_camera(s: &mut Session, params: &Value) -> Result<Value> {
if !params.as_object().is_some_and(|p| p.is_empty()) {
return Err(bad("render.camera.fit", "expected an empty parameter object"));
}
let aspect = match params.as_object() {
Some(p) if p.is_empty() => 1.0,
Some(p) if p.len() == 1 && p.contains_key("aspect") => match p["aspect"].as_f64() {
// The range a preview frame can show (`frame_rect` in the Render workspace).
Some(a) if (1e-6..=1e6).contains(&a) => a,
_ => return Err(bad("render.camera.fit", "aspect must be a number from 0.000001 to 1000000: the picture's width over its height")),
},
_ => return Err(bad("render.camera.fit", "expected an empty parameter object or {aspect}")),
};
let scene = crate::product_scene::extract(s)?;
let mut camera = s.camera.unwrap_or_default();
if !scene.bounds.is_empty() {
camera.fit(&scene.bounds);
// Publish only a valid fitted camera; the previous one stays otherwise.
if !camera.fit_aspect(&scene.bounds, aspect) && !scene.bounds.is_empty() {
return Err(bad("render.camera.fit", "the product cannot be framed at this aspect: the fitted camera is out of range"));
}
s.camera = Some(camera);
Ok(json!({"camera":camera,"empty":scene.bounds.is_empty(),"document_changed":false}))
+17
View File
@@ -1183,9 +1183,26 @@ fn registered_product_camera_fit_changes_only_view_and_keeps_orientation() {
assert_eq!(fitted["document_changed"], false);
let after = s.camera.unwrap();
assert_eq!((after.yaw, after.pitch, after.roll), (camera.yaw, camera.pitch, camera.roll));
// A picture taller than wide moves the camera back by the ratio, keeping the orientation
// and the target; a wide one is the plain fit.
let tall = s.execute("render.camera.fit", &json!({"aspect":0.5})).unwrap();
assert_eq!(tall["document_changed"], false);
let back = s.camera.unwrap();
assert!((back.distance - 2.0 * after.distance).abs() < 1e-9, "{} vs {}", back.distance, after.distance);
assert_eq!((back.yaw, back.pitch, back.roll, back.target), (after.yaw, after.pitch, after.roll, after.target));
s.execute("render.camera.fit", &json!({"aspect":2.0})).unwrap();
assert_eq!(s.camera.unwrap(), after);
assert_eq!(s.doc.to_json(), doc);
assert_eq!(s.undo.len(), undo);
// Hostile aspects are refused and leave the finite camera, the document and undo alone:
// a subnormal one would otherwise divide the fitted distance into infinity.
for aspect in [json!(0), json!(-1), json!(1e-320), json!(1e-7), json!(1e7), json!("tall")] {
assert!(s.execute("render.camera.fit", &json!({"aspect":aspect})).is_err(), "aspect {aspect}");
assert_eq!(s.camera.unwrap(), after);
}
assert!(s.execute("render.camera.fit", &json!({"scale":0})).is_err());
assert_eq!(s.doc.to_json(), doc);
assert_eq!(s.undo.len(), undo);
}
#[test]
+21 -4
View File
@@ -956,6 +956,20 @@ fn finish_sketch(s: &mut Session, _p: &Value) -> Result<Value> {
// ---------------------------------------------------------------------------------------------
// Drawing
/// The id a drawing command gives its geometry, checked against the names that geometry
/// will carry: a curve's id goes into the name of the faces the curve makes
/// (`F2:side:<id>`), and a name reads some characters as its own. An id holding one of
/// them would split every name it lands in, so it is refused here (#5858).
fn named_id(cmd: &str, id: Option<&str>) -> Result<Option<String>> {
match id {
Some(i) if !solidcraft_doc::persistent_name::text_fits_name(i) => {
Err(bad(cmd, format!("the id `{i}` cannot be written into a name whole; give an id without `|`, `,`, `~`, `#` or `]`")))
}
Some(i) => Ok(Some(i.to_string())),
None => Ok(None),
}
}
fn draw_line(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.line";
let p = &super::sketch_project::snap_points(s, p, cmd)?;
@@ -966,6 +980,9 @@ fn draw_line(s: &mut Session, p: &Value) -> Result<Value> {
let closed = bool_(p, "closed").unwrap_or(false);
let infer = bool_(p, "infer").unwrap_or(false);
let ids = string_list(cmd, p, "ids")?;
for id in &ids {
named_id(cmd, Some(id))?;
}
let ((curves, cons), info) = draw(s, p, cmd, |sk, doc| {
let args: Vec<PArg> = list.iter().map(|v| parg(sk, doc, v, cmd)).collect::<Result<_>>()?;
let n = args.len();
@@ -1308,7 +1325,7 @@ fn circle_center(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.circle.center";
let p = &super::sketch_project::snap_points(s, p, cmd)?;
let r = radius_arg(p, cmd)?;
let id = str_(p, "id").map(str::to_string);
let id = named_id(cmd, str_(p, "id"))?;
let (out, info) = draw(s, p, cmd, |sk, doc| {
let c = req_parg(sk, doc, p, "center", cmd)?;
// The origin is the sketch's frame, not a hold on the geometry: a circle centred there
@@ -1375,7 +1392,7 @@ fn circle_three(s: &mut Session, p: &Value) -> Result<Value> {
fn arc_three(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.arc.three_point";
let p = &super::sketch_project::snap_points(s, p, cmd)?;
let id = str_(p, "id").map(str::to_string);
let id = named_id(cmd, str_(p, "id"))?;
let (out, info) = draw(s, p, cmd, |sk, doc| {
let (a, b) = (req_parg(sk, doc, p, "start", cmd)?, req_parg(sk, doc, p, "end", cmd)?);
let t = req_parg(sk, doc, p, "through", cmd)?;
@@ -1402,7 +1419,7 @@ fn arc_three(s: &mut Session, p: &Value) -> Result<Value> {
fn arc_center(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.arc.center_point";
let p = &super::sketch_project::snap_points(s, p, cmd)?;
let id = str_(p, "id").map(str::to_string);
let id = named_id(cmd, str_(p, "id"))?;
let sweep = num(p, "sweep");
let (out, info) = draw(s, p, cmd, |sk, doc| {
let c = req_parg(sk, doc, p, "center", cmd)?;
@@ -1597,7 +1614,7 @@ fn slot_overall(s: &mut Session, p: &Value) -> Result<Value> {
fn draw_point(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "sketch.point";
let id = str_(p, "id").map(str::to_string);
let id = named_id(cmd, str_(p, "id"))?;
// A point snapped while drawing (`"mid:<line>"`, `"on:<curve>:x,y"`, a model vertex or edge):
// the snap makes it, held by its constraint, as it does for a line's end. Taken as bare
// coordinates it was free to move off the midpoint it was put on (#4691).
+2 -2
View File
@@ -116,7 +116,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.enabled(in_sketch)
.params("curves: [line ids], value?: bool (default toggles): infinite line type (#1359): construction geometry drawn across the whole view through the line's two points; off, a normal line"),
CommandSpec::new("sketch.text", "Text", text).at("SKETCH", "CREATE").icon("sketch_text").enabled(in_sketch).params(concat!(
"text: string (\\n breaks lines), or text_expression: the words built from text parameters (parameters.add with unit text) and quoted texts joined by + (\"Label + '-L'\"): the text keeps it, its words follow the parameters (a parameter edit or a configuration row relabels it) and `exprs` returns it as `text`; at: baseline start [x,y], height?: capital height mm (default 5), angle?: deg; at's x and y, height and angle each take a number or an expression over the design's parameters as a string (\"plate_width / 2\", \"label_size\"): the text keeps the expression, follows the parameters, and returns it in `exprs`; an expression naming no parameter is the number it comes to, a bare number in one is in the design's units, \
"text: string (\\n breaks lines), or text_expression: the words built from text parameters (parameters.add with unit text), number parameters and quoted texts joined by + (\"Label + '-L'\", \"'W' + Width\"; a number parameter shows its value in the design's units, \"85 mm\", an angle in degrees, \"30°\"; number(Width) is the number alone and number(Width, 2) has 2 decimals): the text keeps it, its words follow the parameters (a parameter edit or a configuration row relabels it) and `exprs` returns it as `text`; at: baseline start [x,y], height?: capital height mm (default 5), angle?: deg; at's x and y, height and angle each take a number or an expression over the design's parameters as a string (\"plate_width / 2\", \"label_size\"): the text keeps the expression, follows the parameters, and returns it in `exprs`; an expression naming no parameter is the number it comes to, a bare number in one is in the design's units, \
font?: an installed font's family or full name, or a .ttf/.otf file path (~ and %VAR% expand; default: the built-in Liberation Sans), ",
text_style_doc!(),
created_doc!()
@@ -222,7 +222,7 @@ fn text_source(doc: &solidcraft_doc::Document, p: &Value, cmd: &str, old: Option
t.clone()
}
(None, Some(Value::String(e))) => {
let words = doc.eval_words(e.trim()).map_err(|error| bad(cmd, format!("`text_expression`: {error}")))?;
let words = doc.eval_words(e.trim(), &doc.param_values().0).map_err(|error| bad(cmd, format!("`text_expression`: {error}")))?;
exprs.text = Some(e.trim().to_string());
words
}

Some files were not shown because too many files have changed in this diff Show More