diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index a9a38d33d1..7c58ea581c 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -52,6 +52,8 @@ Editable drawing code, native projects and shipped exports are collected under ` | docs/book/src/generated/enclosure.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/enclosure.json`; gitignored) | MIT OR Apache-2.0 | | docs/book/src/generated/revolve.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/revolve.json`; gitignored) | MIT OR Apache-2.0 | | docs/book/src/generated/sheet-metal.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/sheet-metal.json`; gitignored) | MIT OR Apache-2.0 | +| assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf | IBM Corp. (IBM Plex Mono) | https://github.com/IBM/plex (via google/fonts), unmodified; licence text in assets/fonts/ibm-plex-mono/OFL.txt; the interface monospace font | SIL OFL 1.1 | +| assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf | IBM Corp. (IBM Plex Sans, variable wdth and wght) | https://github.com/IBM/plex (via google/fonts), unmodified; licence text in assets/fonts/ibm-plex-sans/OFL.txt; the default interface font | SIL OFL 1.1 | | 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 | @@ -78,6 +80,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/fonts/biz-udp-gothic/OFL.txt | The BIZ UDGothic Project Authors | Unmodified accompanying font licence | 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 | | assets/icons/app/linux/exports/hicolor/128x128/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 | | assets/icons/app/linux/exports/hicolor/16x16/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 | @@ -137,7 +141,10 @@ Editable drawing code, native projects and shipped exports are collected under ` | assets/icons/ui/design/palette.toml | SolidCraft contributors | icon colour roles (light and dark) and accents, original SolidCraft data | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/bug_report.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/chevron.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/chevron_right.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/close.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/disclosure_closed.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/disclosure_open.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/fit.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/folder.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/help.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | @@ -232,7 +239,7 @@ Editable drawing code, native projects and shipped exports are collected under ` | assets/icons/ui/design/svg/model/export.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/extend.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/extrude.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | -| assets/icons/ui/design/svg/model/eye.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/eye.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/fastener.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/fillet.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/filter.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | @@ -285,6 +292,10 @@ Editable drawing code, native projects and shipped exports are collected under ` | assets/icons/ui/design/svg/model/silhouette.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/assistant.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/flat/agent_link.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/model/offset_two_sides.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/model/offset_symmetric.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/import_tray.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/export_tray.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/placeholder.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/plane.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/plane_angle.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | @@ -336,7 +347,7 @@ Editable drawing code, native projects and shipped exports are collected under ` | assets/icons/ui/design/svg/model/status_error.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/status_warning.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/stitch.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | -| assets/icons/ui/design/svg/model/suppress.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | +| assets/icons/ui/design/svg/flat/suppress.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/surface.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/svg.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | | assets/icons/ui/design/svg/model/sweep.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 | @@ -420,6 +431,7 @@ Editable drawing code, native projects and shipped exports are collected under ` | assets/icons/ui/design/tools/lint_icons.py | SolidCraft contributors | icon lint, status page or preview tool (standard-library Python), original SolidCraft code | MIT OR Apache-2.0 | | assets/icons/ui/design/tools/preview.py | SolidCraft contributors | icon lint, status page or preview tool (standard-library Python), original SolidCraft code | MIT OR Apache-2.0 | | assets/icons/ui/design/tools/review_state.py | SolidCraft contributors | icon review state helper (standard-library Python), original SolidCraft code | MIT OR Apache-2.0 | +| assets/icons/ui/design/requested_icons.txt | SolidCraft contributors | Runtime icon requests collected by the headless menu audit, original SolidCraft data | MIT OR Apache-2.0 | | assets/icons/ui/design/review.json | SolidCraft contributors | icon review state (accepted icons and notes), original SolidCraft data | MIT OR Apache-2.0 | | assets/icons/ui/design/tools/author/lib.py | SolidCraft contributors | icon authoring script (standard-library Python), original SolidCraft code | MIT OR Apache-2.0 | diff --git a/Cargo.lock b/Cargo.lock index f3891452ce..3860bdafc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4379,6 +4379,7 @@ dependencies = [ "solidcraft-engine", "solidcraft-gpu", "solidcraft-space-mouse", + "syn 2.0.119", "tiny-skia", "toml", "ttf-parser", diff --git a/assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf b/assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf new file mode 100644 index 0000000000..0c9770d518 Binary files /dev/null and b/assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf differ diff --git a/assets/fonts/ibm-plex-mono/OFL.txt b/assets/fonts/ibm-plex-mono/OFL.txt new file mode 100644 index 0000000000..c35c4c618f --- /dev/null +++ b/assets/fonts/ibm-plex-mono/OFL.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +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. diff --git a/assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf b/assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf new file mode 100644 index 0000000000..30f837e8b0 Binary files /dev/null and b/assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf differ diff --git a/assets/fonts/ibm-plex-sans/OFL.txt b/assets/fonts/ibm-plex-sans/OFL.txt new file mode 100644 index 0000000000..c35c4c618f --- /dev/null +++ b/assets/fonts/ibm-plex-sans/OFL.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +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. diff --git a/assets/icons/ui/AGENTS.md b/assets/icons/ui/AGENTS.md index 29f0973f72..e88a8d7dfd 100644 --- a/assets/icons/ui/AGENTS.md +++ b/assets/icons/ui/AGENTS.md @@ -29,3 +29,9 @@ request files under target, and do not retry them. The server never invokes a mo Canonical paths, launch command and export schema: [README.md](README.md). There are no numbered bulk authoring scripts or generated inventory files to maintain. + +Runtime discovery is also checked by `crates/ui-egui/tests/icon_audit.rs`. New menu +surfaces must extend that Harness traversal. Regenerate the committed +`design/requested_icons.txt` with `UPDATE_ICON_MANIFEST=1 cargo test -p +solidcraft-ui-egui --test icon_audit`; review the sorted id/placeholder diff. The +review tool reads this fixture in addition to source scans, masters and aliases. diff --git a/assets/icons/ui/README.md b/assets/icons/ui/README.md index 28cb8c8304..8e4aede022 100644 --- a/assets/icons/ui/README.md +++ b/assets/icons/ui/README.md @@ -18,8 +18,8 @@ artwork changes trigger the live revision check. The server binds only to loopba Every opening scans SVG masters, aliases and source references. New, unused, missing and changed icons are discoverable. **All discovered icons without a valid -acceptance are Open, including those with notes.** No inventory file or allowlist -is needed. Rescan sees additions immediately; a running page also polls revisions. +acceptance are Open, including those with notes.** Discovery also reads `design/requested_icons.txt`, the sorted runtime audit +manifest; it supplements source scans and never restricts discovery. Rescan sees additions immediately; a running page also polls revisions. The internal placeholder glyph is the only excluded card. Shared masters are shown separately even when an accepted alias already uses them; reopen that alias before requesting a redraw of its protected artwork. Unassigned commands @@ -82,6 +82,7 @@ Palette changes preserve accepted artwork and never update review hashes. | `design/palette.toml`, `aliases.toml` | Shared theme roles, weights and semantic aliases | | `design/review.json` | Owner acceptance hashes and current notes | | `design/references.toml` | Curated style references; does not control discovery | +| `design/requested_icons.txt` | Sorted runtime requests and placeholder flags, checked by the Harness audit | | `design/tools/author/{lib,bases}.py` | Reusable geometry and canonical cube/slab/arrow bases | | `design/tools/author/new_icon.py` | Safe new-master scaffold, never overwrites an id | | `design/tools/icon_status.py`, `review_state.py` | Discovery, review backend and state | @@ -104,5 +105,25 @@ python3 -B assets/icons/ui/design/tools/icon_status.py --csv ``` Reports, screenshots, requests, generated contact sheets and previews stay under -ignored `target/`. Do not commit generated inventories or historical authoring -batches. Every non-Markdown resource has an exact row in `ATTRIBUTION.md`. +ignored `target/`. The runtime request manifest is a committed regression fixture; other generated +inventories and historical authoring batches do not belong in the repository. Every non-Markdown resource has an exact row in `ATTRIBUTION.md`. + +## Runtime menu audit + +`cargo test -p solidcraft-ui-egui --test icon_audit` drives the real UI through the +headless scenario Harness and rejects placeholder requests and unexpected blank +context-menu icons. It compares sorted requested ids with +`design/requested_icons.txt` (`idplaceholder`, one per line). After deliberately +changing menu coverage or assignments, regenerate and review its diff: + +```sh +UPDATE_ICON_MANIFEST=1 cargo test -p solidcraft-ui-egui --test icon_audit +``` + +The review page includes every manifest id, even ids used only in tables or helper +arguments. A true placeholder flag, missing master or broken alias is reported as +missing; runtime collection never changes acceptance hashes. The live control +method `ui.icon_audit` can diagnose requests in a running UI (see +[control protocol](../../../docs/control-protocol.md)). Keep the Harness traversal +current when adding menus. A successful traversal covers only the contexts it +actually opens; document unreachable or untested contexts in the audit report. diff --git a/assets/icons/ui/STYLE_GUIDE.md b/assets/icons/ui/STYLE_GUIDE.md index 00fa94798e..33c91585af 100644 --- a/assets/icons/ui/STYLE_GUIDE.md +++ b/assets/icons/ui/STYLE_GUIDE.md @@ -207,3 +207,9 @@ All artwork must be original SolidCraft work or an explicitly licensed resource with provenance. Commercial CAD artwork, screenshots and extracted assets are forbidden even as hidden references. This guide and the local finished set provide all required visual context for future agents. + +Runtime discovery is also checked by `crates/ui-egui/tests/icon_audit.rs`. New menu +surfaces must extend that Harness traversal. Regenerate the committed +`design/requested_icons.txt` with `UPDATE_ICON_MANIFEST=1 cargo test -p +solidcraft-ui-egui --test icon_audit`; review the sorted id/placeholder diff. The +review tool reads this fixture in addition to source scans, masters and aliases. diff --git a/assets/icons/ui/design/aliases.toml b/assets/icons/ui/design/aliases.toml index f3443077c4..adb998138c 100644 --- a/assets/icons/ui/design/aliases.toml +++ b/assets/icons/ui/design/aliases.toml @@ -35,4 +35,12 @@ sketch_scale = { master = "scale", accent = "sketch" } sketch_text = { master = "text", accent = "sketch" } surface_fair = { master = "spline_cv", accent = "surface" } mesh_export = { master = "export", accent = "mesh" } + +# Legacy feature-history id for the same Split Body operation. +split = "split_body" + +# Sketch dialog choices share the corresponding finished tool artwork. +sketch_chamfer_equal = { master = "sketch_chamfer", accent = "sketch" } +sketch_chamfer_two = { master = "sketch_chamfer_two_distances", accent = "sketch" } +offset_one_side = { master = "offset", accent = "sketch" } bom = "list" diff --git a/assets/icons/ui/design/palette.toml b/assets/icons/ui/design/palette.toml index e648a81282..aaed95b248 100644 --- a/assets/icons/ui/design/palette.toml +++ b/assets/icons/ui/design/palette.toml @@ -10,7 +10,7 @@ [roles.ink] # outlines and details drawn over a face source = "#34465b" light = "#34465b" -dark = "#d8dfeb" +dark = "#1f2021" [roles.line] # line art that sits directly on the UI background (SVG: currentColor) source = "#34465b" light = "#34465b" @@ -66,13 +66,13 @@ parameters = 4.2 [accents.surface.active-1] light = "#f1d2af" -dark = "#ba8f6a" +dark = "#cc9362" [accents.surface.active-2] light = "#c06f00" -dark = "#b86f04" +dark = "#bd750d" [accents.surface.active-3] light = "#a45000" -dark = "#95682d" +dark = "#955a0e" [accents.form.active-1] light = "#decff8" @@ -85,41 +85,41 @@ light = "#7349cd" dark = "#7b63a6" [accents.mesh.active-1] -light = "#e5d99b" -dark = "#a9985f" +light = "#ffec8e" +dark = "#ffe282" [accents.mesh.active-2] -light = "#a28200" -dark = "#9a8100" +light = "#bb9600" +dark = "#ab8f00" [accents.mesh.active-3] -light = "#826700" -dark = "#857100" +light = "#e0b200" +dark = "#c6a800" [accents.sketch.active-1] light = "#bfe2cc" -dark = "#81a28e" +dark = "#7ab792" [accents.sketch.active-2] light = "#439a65" -dark = "#459665" +dark = "#3a965e" [accents.sketch.active-3] light = "#057f45" -dark = "#27864e" +dark = "#128d45" [accents.constraint.active-1] light = "#f0cfca" dark = "#b98c88" [accents.constraint.active-2] light = "#c1685f" -dark = "#b66b61" +dark = "#c27267" [accents.constraint.active-3] -light = "#af4038" -dark = "#a05c54" +light = "#ca473d" +dark = "#c16358" [accents.construction.active-1] light = "#ecd3b6" -dark = "#b19373" +dark = "#b99a78" [accents.construction.active-2] light = "#b8741f" -dark = "#b4711c" +dark = "#ad7023" [accents.construction.active-3] light = "#9c5600" dark = "#a75d00" @@ -134,12 +134,16 @@ dark = "#1d7f89" [variants.red-green.construction.active-3] light = "#b97300" dark = "#a75d00" +# The constraint red stays darker than the sketch green for a weak red sense. +[variants.red-green.constraint.active-3] +light = "#af4038" +dark = "#c16358" [variants.blue-yellow.construction.active-3] light = "#b97300" dark = "#c37d00" [variants.blue-yellow.constraint.active-3] light = "#af4038" -dark = "#986255" +dark = "#85554a" # Default accent by requested icon id. Explicit alias accents in aliases.toml win. # Deliberately not inherited from a master: ground reuses c_fix but is not a constraint. @@ -153,9 +157,9 @@ sketch = [ "arc3", "arc_center", "arc_slot", "arc_slot_center", "arc_tangent", "blend", "circle", "circle2", "circle3", "circle_tt", "circle_ttt", "conic", "constrainer", "dimension", "ellipse", - "extend", "finish", "fit_section", "include", "infinite", + "extend", "finish", "fit_section", "include", "intersect", "intersection_curve", "iso_curve", "line", "line_mid", - "offset", "point", "polygon", "polygon_circumscribed", "polygon_edge", + "offset", "offset_two_sides", "offset_symmetric", "point", "polygon", "polygon_circumscribed", "polygon_edge", "project", "project_surface", "rect", "rect3", "rect_center", "rect_rounded", "sketch", "sketch_chamfer", "sketch_chamfer_angle", "sketch_chamfer_two_distances", "sketch_fillet", "sketch_mirror", "slot", "slot_center", "slot_overall", @@ -168,5 +172,5 @@ constraint = [ "c_polygon", "c_smooth", "c_symmetry", "c_tangent", ] construction = [ - "centerline", "construction", + "centerline", "construction", "infinite", ] diff --git a/assets/icons/ui/design/requested_icons.txt b/assets/icons/ui/design/requested_icons.txt new file mode 100644 index 0000000000..b50b03259c --- /dev/null +++ b/assets/icons/ui/design/requested_icons.txt @@ -0,0 +1,282 @@ +accessibility false +activate false +align false +appearance false +arc3 false +arc_center false +arc_slot false +arc_slot_center false +arc_tangent false +as_built false +assistant false +auto_constrain false +axis false +axis_edge false +axis_normal_to_face false +axis_through_cylinder false +axis_two_planes false +axis_two_points false +bendtable false +blend false +body false +bom false +boss false +boundary_fill false +bounding_solid false +box false +break false +bug_report false +c_coincident false +c_collinear false +c_concentric false +c_equal false +c_fix false +c_hv false +c_midpoint false +c_parallel false +c_perpendicular false +c_polygon false +c_smooth false +c_symmetry false +c_tangent false +cam false +camera false +canvas false +capture false +center_of_mass false +centerline false +chain false +chamfer false +checkpoint false +chevron false +chevron_right false +circle false +circle2 false +circle3 false +circle_tt false +circle_ttt false +close false +coil false +combine false +compare false +component false +compute false +configuration_table false +conic false +constrainer false +construction false +contact_all false +contact_new false +contact_off false +contact_sets false +continuity false +convert_sheet false +copy false +curvature_comb false +curvature_map false +cut false +cylinder false +decal false +delete false +delete_face false +dimension false +draft false +draft_analysis false +drive false +dxf false +edit false +ellipse false +emboss false +environment_map false +error false +explode false +export false +export_tray false +extend false +extrude false +eye false +fastener false +fillet false +finish false +fit false +fit_section false +flange false +flat false +fold false +folder false +gear false +graph false +ground false +help false +hem false +hole false +home false +import false +import_tray false +include false +infinite false +inspect false +interference false +intersect false +intersection_curve false +iso_analysis false +iso_curve false +joint false +joint_origin false +library false +line false +line_mid false +lip false +list false +loft false +lookat false +material false +measure false +mesh_export false +min_radius false +mirror false +motion_link false +move false +new false +normal false +offset false +offset_face false +offset_one_side false +offset_symmetric false +offset_two_sides false +open false +orbit false +origin false +pan false +params false +paste false +pattern_center false +pattern_circ false +pattern_flip false +pattern_path false +pattern_rect false +perforate false +perspective false +pipe false +plane false +plane_along_path false +plane_angle false +plane_midplane false +plane_perpendicular false +plane_tangent false +plane_three_points false +plane_two_edges false +plastic_assign false +plastic_rule false +play false +plus false +point false +point_along_path false +point_center false +point_edge_plane false +point_three_planes false +point_two_edges false +point_vertex false +polygon false +polygon_circumscribed false +polygon_edge false +presspull false +project false +project_surface false +rack_pinion false +reach false +rect false +rect3 false +rect_center false +rect_rounded false +redo false +refold false +rename false +render_image false +render_scene false +reorder false +replace_face false +rest false +restore false +revert false +revolve false +rib false +rigid_group false +rollback false +save false +scale false +screw false +section false +select false +settings false +sheet_rules false +shell false +silhouette false +sketch false +sketch_break false +sketch_chamfer false +sketch_chamfer_angle false +sketch_chamfer_equal false +sketch_chamfer_two false +sketch_chamfer_two_distances false +sketch_circ_pattern false +sketch_dxf false +sketch_fillet false +sketch_mirror false +sketch_move false +sketch_rect_pattern false +sketch_scale false +sketch_text false +slot false +slot_center false +slot_overall false +snap false +snap_fit false +sphere false +spin false +spline false +spline_cv false +split false +split_body false +split_face false +spun false +star false +status_error false +style false +suppress false +surface_blend false +surface_boundary false +surface_extend false +surface_extrude false +surface_fair false +surface_fillet false +surface_loft false +surface_offset false +surface_patch false +surface_reverse_normal false +surface_revolve false +surface_ruled false +surface_stitch false +surface_sweep false +surface_thicken false +surface_trim false +surface_unstitch false +surface_untrim false +svg false +sweep false +text false +thread false +torus false +trace false +trim false +undo false +unfold false +update false +warning false +web false +ws_animate false +ws_design false +ws_drawing false +ws_render false +zebra false +zoom false diff --git a/assets/icons/ui/design/review.json b/assets/icons/ui/design/review.json index d3583be579..eb886ca08b 100644 --- a/assets/icons/ui/design/review.json +++ b/assets/icons/ui/design/review.json @@ -15,9 +15,9 @@ "as_built": "0f481932deca5586a8a8ea3d707efca0747f5c59559d53830b694d3458531013", "assistant": "8fac392b16b4f13c6a399c59962b97c1912282e0ec909976496959472f1d642d", "auto_constrain": "012e5da4ff83449f6a3a92ee2d9924998cbeea545281bdf97873710b7436183d", - "axis": "66ebabe43a7b1b73b59a3a03b7bd92ab54d899947d0cc2d93ad6e6aac4d76501", + "axis": "acdab0eb7b99b1579d64ae190082df48b65174e7b8ad3104967bcc59f6154206", "axis_edge": "5a65898bc38bdaaaf5b04f2c2f7556915b5b7b1fdc4de9708fcc30f64aa1ab18", - "axis_normal_to_face": "0824bb37e80928e9ac880af5ca5fdb3b52084af9e73db73a5a5a7bc8f1f899f7", + "axis_normal_to_face": "597b5b96ce787b70791133267108c5196aca09d5e3f94e7dc92199a63fe49ca6", "axis_through_cylinder": "804eb75631f4ee71ac8db51209237bb19d82bafd326abdc81b124b875c0874b4", "axis_two_planes": "80162aa79ca7baf6d60490414a4fb53d87e6f2b85e901c452875a5f3ed86d35a", "axis_two_points": "5064f7544ce41ae7c1f05a90c958222bbcee03723df67d34cd3e209d25a1f71f", @@ -30,36 +30,38 @@ "boundary_fill": "17ccb419a46c04a44ec864af968a32984dc81f6b3196a821bfff7f54141dfdd1", "bounding_solid": "e2deb6fff292f0f295022583ba7ddb86896eb79d9dd7268e653713b9c62f0580", "box": "72cd44c3c4fc5ae71242fca8c60af47371930201276df5b7db9d2f16a2739c31", - "break": "430582243311f304dbab397ce58c8c9aef9c78ed88d3e921a57e584ddb95d94c", + "break": "67a5fab332616e6d6a9aeae3bedc04ee0768179143ba34b795bdd98cea04bb24", "bug_report": "8cf7afbe0d3c34988fd2748de148f56a7e939fd8273ba01a79a976f879c74831", - "c_coincident": "32fb535b3dd7631dc2e9ea284f445ad54ea0e5c8b0e1bb27a9984c849746917d", + "c_coincident": "a93cc58acc5090c24c973cb558920bff4e35c8f99071135f16a115256d41bf98", "c_collinear": "3bcf1dc6b221a1fa9a4445ee8afc7d858dc28a5270d22d10fef86fa4997f1bdf", "c_concentric": "90e9b35b6186ea4fcb3debc24808f302c0bf7354b7afae86e339574ef8e5103a", "c_equal": "c0f50c02db7d15a6392d2178e28df5c100e67fab7732f8e8474b888d218d0673", - "c_fix": "b91c6af7ea9740efb8cf196409b11d16fad52c51b732a356e5f844bd4e2f08e0", + "c_fix": "23e2471a9d6872e83028e0e219e330cef1930a4d89456a0fefa1bda8a7e3eb81", "c_hv": "b5c44d60a4b03b9942fbcf3c706f797ce4dbddf9a67eb1c98d6aaf3234e9bc18", - "c_midpoint": "fd4efc26da1336f811a9016b02d7675af2a7c95cd1ffa2f091507b22434f5435", + "c_midpoint": "aaea530b8a04aa80efc99ed2cf42347a91c65b160286bae3e2882f5a0bd09a51", "c_parallel": "aaa80258502af091b596511662aca392546dd0d7bf69cec4db98b6e0ae7591f0", - "c_perpendicular": "692ba7a77cd77108c33c101386b60b7c957ce7d2202937d9696d18dcf3d4cf8e", - "c_polygon": "defac40b4c6ef323494f49acbb94b896502f78f6a99e750975693c602164a44b", - "c_smooth": "dccf5effdb8977ac04e6c437c280c83ed0f117d81b64c57fae03f5f7f7f2b9c0", + "c_perpendicular": "0ccf0383176ace2d4ab30c2efe853815ca1df20f2537020f369f496d8bf124b5", + "c_polygon": "02c76b288b42d6be8eb77f502af95bb840557f43fed3def2b1a381f6ac920375", + "c_smooth": "87aeb504aa330a191a8680d9eab3e86844f6d7be0a9caa10fdf0a657e76f45ee", "c_symmetry": "8646a7deb7447514bcc455562d8639f8b4c482df8c971fd2870c05965ce71f78", - "c_tangent": "a011f5d43d5e09f68de332c60c5acee737a3ebc94d107f521871225c3841a064", + "c_tangent": "ef8b1e502c2a939958b5e8de9ef2716442e32b2a394555eb7b51101c415b966e", "cam": "a35342e36ca4deae55e98c54df630b5c2da591c06d979eb2b8ce6a13f6db66d0", "camera": "fcd867877ea4984318dbff36a166b48682f26bb9d44e70383dde63d9a7ab87c8", "canvas": "512d7721f0b8b14a46f22b9588b437b0cd8c2c425330d31b3b156c0d1334bcef", "capture": "23ef84e50b45c06532b3e842b72f6da5498d0e6b8391e1bff55e84afa8359f37", "center_of_mass": "24401b1a19cb45ddb028de0acf3c9815b47abecbde9e133f473adac3d27ae346", - "centerline": "a1889b9092ef5a7aa9592df994b6541b53ea9afb03b2c56c7678a61df335f4d7", + "centerline": "850283497bad77e233f934e8effc0d0d7b01ccde83fa66f6abb539a33a8a2c80", "chain": "9cdc78486b5c4b5b2a378e09bbd41733eb167513f9f2bc4b1aad629dd6c4c753", "chamfer": "487c90ae65e87b74d7ddee7d9ce9f088b63e9b5b85c2a390364855a2649f17e8", "checkpoint": "a3bc0700a40b791b5f3bf176d820bdabd2cf94a526908609d50136c8d0918d6a", "chevron": "61887736af4b1e6bef857c4d2e3846657824e84f1c8026a56525ddba2d2a8083", + "chevron_right": "d28a52dcc51fd9eb988f8f83e89d74370a31100049c910a886732a48ec1342ca", "circle": "145544ff1cd133971f537dd247092126ef8d91a88fe31b795222e76840d0c4d0", "circle2": "f1b6e8e2157279cfe1e608f913b377e44a70f7555f5010298138de8a887bf586", "circle3": "aaf6b914e630688675abf4dcbf7d43096317881ced0e22520a4ef229013e0de2", "circle_tt": "94996320b671c8c48f239b8c48f1b2f1cd5cc33a7d8ea1daaa61aa9203803cae", "circle_ttt": "ac3c211a6fe17c3625060f695ecd93937730e2bc2dd57921bcbbcaa984b9badf", + "close": "5833954dfba3d70a9a3a7519f2796a7213899548b0345bd8fe8ea9a321d82be2", "coil": "4c549fd63506457165f53a7940cc0f3dcacdbe075e935ea299edbf4985104411", "combine": "f9d4211f0153779369c9da68b266c2c9a857d11bc033ff4a96ede794e470e38e", "compare": "c24b6c18f18413ae6f3344aaa6e04b42fae8b63e7cd1552a81398462eb68eb96", @@ -68,7 +70,7 @@ "configuration_table": "1a142e76b9e4cd47412a307cc0ad9b8c589bb97c8930fc445dd5bb8d74ecf707", "conic": "808445fd697ff76f9074e7bbf877146e634995c295dce045604991fa4cec26a0", "constrainer": "774cb158897eb91767541b5b1a25a46f3a731cbea0661006ce5472634ab6b0e3", - "construction": "ad962cbcd0aaef9167744abfd0bfe1d7ef55e48b4ff42f2b4b05f66204b58acb", + "construction": "f13e3851cdf7b30a7118802a1c17e8852fcb9893fd1d65c7098a5c3fc35c8396", "contact_all": "6ad4d245592815a3e38e70ae324ad3971ab614daba7ca3cf8fcb64cb8e2be077", "contact_new": "603410785fff408cc12dc72bef2f09885b694c5ddb2571c7819ae1301fb89dc6", "contact_off": "54dc3bd2267ebb084d8dc7efee85e9f21d8c59a57ad37b96d78095bc75e73fa0", @@ -83,8 +85,10 @@ "decal": "acc258e40b29ffb6b6007057f0b70f487995f573704c7ef4e4f08f209d52bddd", "delete": "d14203f06b8b1b87328e7f198c7aa41595e9a66b5d8a7fbd2a3ec2e35cd99afd", "delete_face": "dcbf8ab59bd37bd5a0cdd7a528b29c2d19daa5f3fc130ef5cff0c20034f061a4", - "dimension": "8246dde8b0a140d1f533c1e45f2e8b5af35c05d3504dccf50bd7e7726a93389e", - "dots": "aa38423c547594462f49cc355f3c9f5b41e28a7515f466a6a8cffda487ba1715", + "dimension": "a926b7c64bc8ca99d8d6dcbb81b95235bd3fc34fbed049aca8b01b4d1147c15d", + "disclosure_closed": "49a350c6f2efae78de0be96c524ebee1fd1291ea449a84224b88d37164a4e807", + "disclosure_open": "d13ce3f97e3e362a9904fa2bf3412f3f00e4136bc23538b92d41483961d280de", + "dots": "600f7ff209d93145fc938bc4f6a1e34473e6b9ee22a5b4718f133bc7da5ee58d", "draft": "9c0fc361cacd0ad95ccd54794f4825dfc9901a83a3de752a588faec365d0ee09", "draft_analysis": "eb39c0f90e9cf226da6307365a76015e900dd692dc3f0fdc8f02fe717226261f", "drive": "cf9e924acb2531efd5a1b90915d59f0cb532c844b754fd3a0e376ef6f706c47d", @@ -99,7 +103,7 @@ "export": "c8c2abf007e43e896479d78798e3976ba9eda809507c820bb38538f77db696ac", "extend": "28c63df352c77d02feffe44cf09fb0fc1d48f7905aaabbfa6dba601dbb70f8e8", "extrude": "9cc1538971c5d25467aaed23f739899d9d102fd07f86472ac3197c54ca2e1144", - "eye": "5d90a25f5d07e6e36a2a8faf5e2ed1425b2a787834ab2291b1b1c521057ec1c2", + "eye": "2449167c8410826dc1106b687c12a309d5bea8f7817b897943e80f182a163c2d", "fastener": "fcff4e4be50907f712e527b373771afafd92e50096fc7a4b8aba5d86448e1e42", "fillet": "11ec232dc111597211e3a4fe61b69616b5d28b4b1b2732052ddcd486cdfc6129", "filter": "4d456a56416089574bd3732fa44c0c1b27fd2478b369448808a27f0144d15d8a", @@ -111,15 +115,15 @@ "fold": "90344fb5e5799378117fbd79ef28a84f27fba5723af5414c4ea30a2ac3a4f21e", "folder": "c19f5230df45a1bda1e92e89de38bba21c91e6eca0517eb15847a7712012d1e5", "gear": "25a1ca48a587850da1104a46005a330873ed196246e3d04e12cf61f26ad2b0d5", - "graph": "25646bdf4d1a534c51e1897a8ae641a21f871ed70b24ff25652e1fe957c9fa00", - "ground": "b91c6af7ea9740efb8cf196409b11d16fad52c51b732a356e5f844bd4e2f08e0", + "graph": "7156caedd2dbfa2ae53da18b7a2204b8db66c45126859d36caf049a3fd4335a4", + "ground": "23e2471a9d6872e83028e0e219e330cef1930a4d89456a0fefa1bda8a7e3eb81", "help": "8ce4060577483dbdb510be147d2827de50ac20029354008fca55abc0c7edf05d", "hem": "f9b1aafbedcf3dc90c26be308814638e0095fbe31b0647092507343fa60af011", "hole": "f82d47751f826e08682db847a270897474661bfe9f35b6d3678f49a203172563", "home": "ab5f1c41d377687e4c5a808dfdccf2757b33dd43f934d44df43d6e9118176853", "import": "e343540eb49fdb1e53c4d54b9e60ae513b3103df6c9133048bed878e58067455", "include": "f5b80ad3e06e3b863fcfbe415ec912e4e09653dd519f9ac207819339fe34730f", - "infinite": "bff57a1c56d9b47131d7c8ff85f008a53c612c63706052e7e19d7aaea93cf5b1", + "infinite": "53fb781131078d2f8e71be23aa610d9f9d9c8bb79ba5f97fa397475e9ff8c3bd", "inspect": "d2b4644b2d5f1aa50cb576b9265e275fa91201e296c2bc79865f810067d538e9", "interference": "b3d729d0e822e8d95485c8ef50649847572def10d4c713fff0e073b8031c5f5d", "intersect": "9ab4f6551848a2be546f55502a77075bed2d3994f89d91b721eab92bcee61d61", @@ -127,7 +131,7 @@ "iso_analysis": "dacd34ac254ec4fbb6932350f62d4d92626d922a2bfc62d2caecdeb17303e896", "iso_curve": "015ff2e26346ccaf149f75cf6a0652d9201c1b23a14e6bb62625acfa599dc002", "joint": "72b71a608e713f6cf51a1cb84edb011d4ccbd8bfe41d917fa2a1d13345dc0b2b", - "joint_origin": "5b0761122eb4295ea220314e8db1efd1251e3482653d6cdcc47252d0268864a8", + "joint_origin": "e12529af1fce8c5ffdf746e71ce2f5db5eb170e53778469cea8fd30344291574", "library": "ac0b1798e4c94340f9c854daa3829371c1a9f3ffea7679d955e6cfa139cafdc8", "line": "cd1177ea659548f03d43c7fa7dcc209a0542b1ba4bd0153c5113576cb7a96a81", "line_mid": "edb8ebc463479ae9878e54b930c124153ca880270b7cf6c0e4edba6336ba2854", @@ -141,17 +145,20 @@ "min_radius": "92bd79b7d557aba6ddfccc76a4c6d0cfb579061b3c57172f9171d28af675711c", "mirror": "56637068a319dc2972c5552b25e4f43151aa9bd5055c2fef08204dd7cc0829cf", "moon": "3ba5adb92bf0f49e92d46682dbf6905e555aa6eebc8e2b3c5288f949cda29322", - "motion_link": "19698bbe15b7c2d2601208bbcdc26627d1aa7724aee017b7da8874ad87fb9d1f", + "motion_link": "27832f827146446557609336a9b9e48e649ed84e58c45bd9032a93abbd454104", "move": "4132e270ada8d0c79b477095c84d656d6d354e200c1748ce7fc425f36d0b7031", "nav_settings": "b93dd46d3a3d074e0d8834167f977dab417fbc1a721bc4db5e840732304e4193", "new": "c64c78fd8445bc7b2aa8765d0507b9adf23e35cbf52828d3f1987edccce0389f", - "normal": "1148cef2d60a858b69406a2e2527d4ea126f45846de7cc2fb3a96640e9ee226f", + "normal": "3c000d707837d4e9c865aefa43c1e610bfce5a06a38510f82e4db11fc13ab31f", "offset": "094607afcfdcb9c1410114d9f8d22ca367f8e32ab4c61f71affe44ad5d09adbe", "offset_face": "83d39308ec9f3ead78329c62841c8899bbc76ce08e17d539016d18c91d9d4ea7", + "offset_one_side": "094607afcfdcb9c1410114d9f8d22ca367f8e32ab4c61f71affe44ad5d09adbe", "offset_surface": "b501cfb56b8404cdb2275935d780ce7dbb67e87e29ade3782f0859c59070a2b1", + "offset_symmetric": "67f3489402af3afa055181bbdb2a32b9fe2e0d96dccbe0f28df087577978ac9c", + "offset_two_sides": "ef69f955c5bef15471e9d77a4498e9337bf938ad75dcbc1e95af2515b6fd13da", "open": "f1880d25489753f7e40390902037bbd3195fbd099a5e9dd175797d832abff727", "orbit": "4adcd400cdde909139386e78579433354f65f5a77a0594447bf6dda663b3b1b4", - "origin": "9b9360edc16ef9f059bff7d863f1bc0e3abac6acf559d76c4ea39dc374ab2844", + "origin": "636f9f41654a6da7a5a708bcddef85672f2d77bfec2267b0134e8c5a59768b40", "pan": "2465eda7a39d4458d7818f2eb2b033992a68f80d9505bff538b41eb4511bb632", "params": "d0d9d0b3e786880e59b985dc2e3a7d593cf3771484a8fd2924d69155195c230f", "paste": "e1b3c089adafacb6f7b78b86ec7aba0876b189c81d01031045dfee2c47ae8edc", @@ -166,22 +173,23 @@ "perspective": "5fa45ba9b75dbe9fdad9eae331ca7ff35c6ad018b8a1fc4c8ae7bcfc13ed0c3f", "pipe": "b9ecf1ae9f6790e2ae6c677f290446f70eba1773a10247fe33d5283b360b0403", "plane": "3e7fa0a9d57891f3c1ec03d7422bdb29353d3487deed12e8ec5737aeb33c2f3a", - "plane_along_path": "020e25969a7f4e7e3358003addee7df8b1991cde8401df0b4df8295b19497994", + "plane_along_path": "db4686ea02c3c3b8ad0d9eac203cc050c395cff6358358922a79c00e955e6852", "plane_angle": "2f0914955663b393a377fda080bc03dc5a2133d42c506c1908c53b2fd609b156", "plane_midplane": "66bd880821e7b122f130824313b7070d72d79168a52df6a126106b6eb5816474", - "plane_perpendicular": "90e1d9b8e75743821aa1082596d9c3f288ab3baddf087fbae3f2025481e58a6e", + "plane_perpendicular": "0055fb18ee39044bfc9f2ac53ba55fe73d8fe59e8f4cf225ee2c723a883d3085", "plane_tangent": "c3872b9f1d0f0b2493a5488dfdad50049c3422c29023250088eeda9bbcc6e795", "plane_three_points": "08e2dbaf606d165ef4f788869fc794a4fbbbb2ee86a60017b76e467af6da338a", - "plane_two_edges": "1588b8d4741382d9e8fec38212419ac1c05b1bc6036f276d3b5c62db66f8ebdb", + "plane_two_edges": "21de401ec5bab8ae37510da6fa0a6910325f858c17460da0041fd26897338d30", "plastic_assign": "bbfdb2d2c07629c047e8e671c7c032654668a67b4b8a934f74cbb3d7a00cc5fe", "plastic_rule": "51b8591d92907a3ffb76a47a0503d7ba86706d129dd990515a364dbf1ab8cb48", "play": "d1c17ebc16e9a4ee5ae2a8cc37b515ffcc8c53a2ab17117959e9b21278df8def", + "plus": "4f324157bb2348ac9f09346b669b08d66ed96cb1c694f33db6f21134f18904b5", "point": "fd19dbcd26e2a898267bab54b9d89779d97894209c1771317d7debef5a6f8f5d", - "point_along_path": "1fd58d45285fb5cabeddbd35bc8485af1b6d7f1414e302202b4e73de593d1b7a", + "point_along_path": "17abd16dec5f70e9f8449c927aa995cfed10fca552ec7cd8f51b209c52012595", "point_center": "0ad755896f7d0154a854bda16d3059da78d1fae57962e13a3bb1b31e284b7e79", - "point_edge_plane": "548a6c1c944caabe44ab0318e85ae82f39c11f74fbedda35f57bc6615755f249", + "point_edge_plane": "a277bc0b87f2dd39eb613835d0f961b3372483e22053e6a522a06c535ad530c4", "point_three_planes": "b33c7508ffa67b66a57b72c6ea7900e93ff16362a29d0b2688e0b1db9bfad041", - "point_two_edges": "35dcb67b36c8e41041cc836b01056731ab6f14c5e3b4bdd836fbee2a1dd897e3", + "point_two_edges": "da329241706e405b877cec20639dc0a78505ca7212c7c47ad1f1f34aa26f4666", "point_vertex": "649bb63caf65a3fbc00a1222cc642294832b5e5d8c4d33f812e09a70684c4abe", "polygon": "4919268cca7d21ce98dbf4ca01ee832350e793afe4213e257d3c46e94ea88ad1", "polygon_circumscribed": "7e4ad16cc1f42c599287b186f26f4ff3c108736877a571a5513bfc72d0cbb23f", @@ -215,17 +223,19 @@ "scale": "a66f69d9a1d46c8be30da0a15827bc7e38cba72d6271537763b888c2c7e2b3fa", "screw": "5e30570785360d6a591ea22862a82f1cf62228385c1e96382536db8a453ac791", "section": "cdd13f99f15f6d59bfa21c747810608d6afd00cb8207faf0337928c8521e7eae", - "select": "a0c716234ef1d599be9a7654d6b2911ebe4b2de479383ba33aeab14992443a2f", + "select": "fdf6431270006ca7409c60c37cfed59572de04229f104ec723e6010ffa2b7163", "settings": "9193dd3a1007c43660d0d47b6499cb2571338b67949e1a99df112057b9bf2b8a", "sheet": "774e30923ef6f37dd9c0b2bc3e45705e83b3a76700bf071cd41155a365d0daa4", - "sheet_rules": "b8c3a67c82be20fc00c109decc566e8755d70167e04e0a9fb92941f7b27aa993", + "sheet_rules": "5d3a2f37d3b4a4262b3ddbdae53d61f29e0f8e284dd54006da25a7acebfa1114", "shell": "a1959ea0d0441a479bf654b6e549e2e44a0bfdc9cda4eb46fe1fbfd884de7e38", "silhouette": "2a848aed2f247b1c0439f830a34a513c93bc5493c40044c821d493d863ad996e", "sketch": "b52e5862b1709a9a7e3ca00d7436d126fbfc01b38080006fa4e17dc581dfa103", - "sketch_break": "430582243311f304dbab397ce58c8c9aef9c78ed88d3e921a57e584ddb95d94c", + "sketch_break": "67a5fab332616e6d6a9aeae3bedc04ee0768179143ba34b795bdd98cea04bb24", "sketch_chamfer": "80ab27a9929f9cf0ab2f827757e007173d74ddca6c19af238931c40d4cd874b4", - "sketch_chamfer_angle": "b853f5e7c3b008c0d56ac445a9a28f713987c919ea284945113ad35e7665e2a7", - "sketch_chamfer_two_distances": "2b70d658e341f4388838f3c8c579ec5f4f3a7fddcfa2ce7a3f89fc3db6422d6e", + "sketch_chamfer_angle": "6750268d05e0b0fd74c2d0368bf4703b09da825def668dc3dfd05deef3ba9adb", + "sketch_chamfer_equal": "80ab27a9929f9cf0ab2f827757e007173d74ddca6c19af238931c40d4cd874b4", + "sketch_chamfer_two": "1f5ecf1283e2b021e067ed2e4e2e97aa4e1ad3c7c98d8609bb81c5f811988f78", + "sketch_chamfer_two_distances": "1f5ecf1283e2b021e067ed2e4e2e97aa4e1ad3c7c98d8609bb81c5f811988f78", "sketch_circ_pattern": "c5ff904237a3072f858f66c2777febc4facd2fee630bbf979047c5685d9a05ce", "sketch_dxf": "dddce44d4dd10f75a221208fcf431b36b3f0cafce6611fc695ac73afb4bf5789", "sketch_fillet": "e8fa170e5f6899abf75788b9384d49f2b9d77ba9b644f79309284a9e65629fe1", @@ -243,6 +253,7 @@ "spin": "a5bebeff8276dd12ab7e42ddf0051370b9063fb602607298acf22e77044434d1", "spline": "a0dcb25614e5fe1a8f17bc3bddbb11b61cf82b461dd46443ab28cddd6e4e32e3", "spline_cv": "353f6e411c0ce611f660cef2e4fc4f8f55edc2c82288de91948dc638cd2b5f15", + "split": "eb9619e74c6ee1b1989d35a2e31d2d33a7adac210c4cd424e5382902945ac68b", "split_body": "eb9619e74c6ee1b1989d35a2e31d2d33a7adac210c4cd424e5382902945ac68b", "split_face": "c8b89072118bb7a28b243d38e26b81a796781a631f30b3b1fc296ba23e4d908d", "spun": "b4cfa093ffb43611ee124f4a39fb44c609deb9cde5e05e4dfbaf893fe197c27b", @@ -251,7 +262,7 @@ "status_warning": "1ac4f0ccfac2c01f6f66cb42188f9ae053c4aef9c5bbd249f1fb09611eb4d825", "stitch": "6c3205d64007b771ed78d6f8055867578439fb25d591563b32dbd3343c98732d", "style": "6c2b04234b82ea4858c80190cc7208c60f549358253005afc35ab4e29506b578", - "suppress": "6c033a6672a3b6ca926e3d7a75a758c9db7e1144210cd360be043a9fdf999ede", + "suppress": "c54fbc01c12c0175d78cd63f692e8aaf73fc2cac79752b2a3ad821ac32734343", "surface": "ba0e839ce0ee03e7ff95a2389a5ecb20bd80c713064d9b1bd5f461f4a80d1921", "surface_blend": "0642f3b4af7d22f379a3c74f54c7b1c9eef0bdda19372ce4df73c0e56bdd7bbc", "surface_boundary": "fb8501bae192e2416118cc2e88f544e0e29197f6720bccc541c967ca0848d8d3", @@ -267,14 +278,14 @@ "surface_ruled": "c3f0202d3beaacef0368765ce771eb9e3b0e3e157f86564c72d81f0fad9a3cab", "surface_stitch": "6c3205d64007b771ed78d6f8055867578439fb25d591563b32dbd3343c98732d", "surface_sweep": "6d7f18c12b2e4c1dbe3632fe08239687f6f027b29b6dddbf8c8e2d9151c7baf3", - "surface_thicken": "676eb73a99d708214e8951ebe2c4d88d60de2d752c725d8106d12fbd812e461a", + "surface_thicken": "35aaa8e4fdbcd220a3a05165d5230a7ce576fbf5a6a5772d27162ab61bb71538", "surface_trim": "ae269721b386bd1264184b9838df3240b5b6ba02b99c9993384ef21200fb7398", "surface_unstitch": "bd8302fe95a89eb6f9bbf35ef0ee98c8a2186e160cff3d091f8cf2a0a75d4473", "surface_untrim": "24de5d558519e0629280d11919ba5661c92ccdccdce03f8f3d83f2d7867c2173", "svg": "7da77d53c84db18ce95737ad633c105d1009444b3192a5e0aae76873cfc9cc12", "sweep": "6d7f18c12b2e4c1dbe3632fe08239687f6f027b29b6dddbf8c8e2d9151c7baf3", "text": "9d31b72b16644681c8a86ac257511847ad69ce4351cf6960442222e94b168c4a", - "thicken": "676eb73a99d708214e8951ebe2c4d88d60de2d752c725d8106d12fbd812e461a", + "thicken": "35aaa8e4fdbcd220a3a05165d5230a7ce576fbf5a6a5772d27162ab61bb71538", "thread": "158728c3dcf6623bb1584d162398bb12c28d18770e0d47f5e6328355ce31b37c", "torus": "5ae13fc213582e0844118b910a3a26efd9e4b177d21500363fbae230803cb4fd", "trace": "5f2ecc61d4591d3c3b0ecccd35296b8613fce7beb2ae214fb906160c31d89187", @@ -286,7 +297,7 @@ "update": "c675e16df4c78589a00be4a34f98627500681672ee8a668e929aec50982da3cc", "warning": "1ac4f0ccfac2c01f6f66cb42188f9ae053c4aef9c5bbd249f1fb09611eb4d825", "web": "be5cd95bebcd0ebe257f9eb862112124429b381ef4a2f0849938fac7c9ccc619", - "ws_animate": "d36a0834ad989a6e16e2b0db493ad8867c590caedf0442c51eff19e934c887f8", + "ws_animate": "f34f187850789cc4f141b1ace114082be04edec8e7771d6730fec1cef19c7f64", "ws_design": "7b1dd678a2d018892b965a78932c047bbfff6b4439d7bd9fe85c09a995f97581", "ws_drawing": "61e99c7179f3448a31b8c76392334352ec3d5e39b0b92fe5244940146d0986cb", "ws_render": "1bfe474d1742623ad2142129e4d6a40015e674813eb1db401d2e88e55b1f2a03", @@ -294,6 +305,9 @@ "zoom": "89c121fa85df034d70bd9f06e20cbd54d163691d4df03b13a9ac5372e951aeda" }, "notes": { - "pattern_flip": "This feels sparse. Improve the composition" + "construction": "Fix the bottom dot's position", + "infinite": "Make the infinity symbol use the 'Line art' colour", + "pattern_flip": "This feels sparse. Improve the composition", + "sheet_rules": "Make the top line also blue" } } diff --git a/assets/icons/ui/design/svg/flat/chevron_right.svg b/assets/icons/ui/design/svg/flat/chevron_right.svg new file mode 100644 index 0000000000..e07c0d022f --- /dev/null +++ b/assets/icons/ui/design/svg/flat/chevron_right.svg @@ -0,0 +1,4 @@ + + Chevron right + + diff --git a/assets/icons/ui/design/svg/flat/disclosure_closed.svg b/assets/icons/ui/design/svg/flat/disclosure_closed.svg new file mode 100644 index 0000000000..33f723348a --- /dev/null +++ b/assets/icons/ui/design/svg/flat/disclosure_closed.svg @@ -0,0 +1,4 @@ + + Disclosure closed + + diff --git a/assets/icons/ui/design/svg/flat/disclosure_open.svg b/assets/icons/ui/design/svg/flat/disclosure_open.svg new file mode 100644 index 0000000000..ce48c1db7f --- /dev/null +++ b/assets/icons/ui/design/svg/flat/disclosure_open.svg @@ -0,0 +1,4 @@ + + Disclosure open + + diff --git a/assets/icons/ui/design/svg/flat/export_tray.svg b/assets/icons/ui/design/svg/flat/export_tray.svg new file mode 100644 index 0000000000..f37257fc51 --- /dev/null +++ b/assets/icons/ui/design/svg/flat/export_tray.svg @@ -0,0 +1,4 @@ + + Export Tray + + diff --git a/assets/icons/ui/design/svg/flat/eye.svg b/assets/icons/ui/design/svg/flat/eye.svg new file mode 100644 index 0000000000..4f6920ad49 --- /dev/null +++ b/assets/icons/ui/design/svg/flat/eye.svg @@ -0,0 +1,5 @@ + + Eye + + + diff --git a/assets/icons/ui/design/svg/flat/import_tray.svg b/assets/icons/ui/design/svg/flat/import_tray.svg new file mode 100644 index 0000000000..474cab3ff6 --- /dev/null +++ b/assets/icons/ui/design/svg/flat/import_tray.svg @@ -0,0 +1,4 @@ + + Import Tray + + diff --git a/assets/icons/ui/design/svg/flat/suppress.svg b/assets/icons/ui/design/svg/flat/suppress.svg new file mode 100644 index 0000000000..3a7b168577 --- /dev/null +++ b/assets/icons/ui/design/svg/flat/suppress.svg @@ -0,0 +1,12 @@ + + Suppress + + + + + + + + + + diff --git a/assets/icons/ui/design/svg/model/axis.svg b/assets/icons/ui/design/svg/model/axis.svg index 79434aeb90..6090eb91f6 100644 --- a/assets/icons/ui/design/svg/model/axis.svg +++ b/assets/icons/ui/design/svg/model/axis.svg @@ -4,5 +4,5 @@ - + diff --git a/assets/icons/ui/design/svg/model/axis_normal_to_face.svg b/assets/icons/ui/design/svg/model/axis_normal_to_face.svg index efddcd5677..aa60b7a8da 100644 --- a/assets/icons/ui/design/svg/model/axis_normal_to_face.svg +++ b/assets/icons/ui/design/svg/model/axis_normal_to_face.svg @@ -6,5 +6,5 @@ - + diff --git a/assets/icons/ui/design/svg/model/break.svg b/assets/icons/ui/design/svg/model/break.svg index 614bed30dc..43d5f0a88c 100644 --- a/assets/icons/ui/design/svg/model/break.svg +++ b/assets/icons/ui/design/svg/model/break.svg @@ -3,5 +3,5 @@ - + diff --git a/assets/icons/ui/design/svg/model/c_coincident.svg b/assets/icons/ui/design/svg/model/c_coincident.svg index 01bc385e4c..23712c129a 100644 --- a/assets/icons/ui/design/svg/model/c_coincident.svg +++ b/assets/icons/ui/design/svg/model/c_coincident.svg @@ -1,6 +1,6 @@ Coincident - - + + diff --git a/assets/icons/ui/design/svg/model/c_fix.svg b/assets/icons/ui/design/svg/model/c_fix.svg index 7de87c238d..fce9b3ce30 100644 --- a/assets/icons/ui/design/svg/model/c_fix.svg +++ b/assets/icons/ui/design/svg/model/c_fix.svg @@ -2,5 +2,5 @@ Fixed - + diff --git a/assets/icons/ui/design/svg/model/c_midpoint.svg b/assets/icons/ui/design/svg/model/c_midpoint.svg index e2a504a6ea..37e0989829 100644 --- a/assets/icons/ui/design/svg/model/c_midpoint.svg +++ b/assets/icons/ui/design/svg/model/c_midpoint.svg @@ -1,6 +1,6 @@ Midpoint - + diff --git a/assets/icons/ui/design/svg/model/c_perpendicular.svg b/assets/icons/ui/design/svg/model/c_perpendicular.svg index 56bfc50ce2..8b7280e040 100644 --- a/assets/icons/ui/design/svg/model/c_perpendicular.svg +++ b/assets/icons/ui/design/svg/model/c_perpendicular.svg @@ -2,5 +2,5 @@ Perpendicular - + diff --git a/assets/icons/ui/design/svg/model/c_polygon.svg b/assets/icons/ui/design/svg/model/c_polygon.svg index 918d59de22..fa36be4067 100644 --- a/assets/icons/ui/design/svg/model/c_polygon.svg +++ b/assets/icons/ui/design/svg/model/c_polygon.svg @@ -1,6 +1,6 @@ Regular polygon - - + + diff --git a/assets/icons/ui/design/svg/model/c_smooth.svg b/assets/icons/ui/design/svg/model/c_smooth.svg index 972a625e23..999f01d21d 100644 --- a/assets/icons/ui/design/svg/model/c_smooth.svg +++ b/assets/icons/ui/design/svg/model/c_smooth.svg @@ -1,6 +1,6 @@ Curvature - + - + diff --git a/assets/icons/ui/design/svg/model/c_tangent.svg b/assets/icons/ui/design/svg/model/c_tangent.svg index 8965660299..5bd8392e8c 100644 --- a/assets/icons/ui/design/svg/model/c_tangent.svg +++ b/assets/icons/ui/design/svg/model/c_tangent.svg @@ -1,5 +1,5 @@ Tangent - + diff --git a/assets/icons/ui/design/svg/model/centerline.svg b/assets/icons/ui/design/svg/model/centerline.svg index d839cff649..bd1b7b06d5 100644 --- a/assets/icons/ui/design/svg/model/centerline.svg +++ b/assets/icons/ui/design/svg/model/centerline.svg @@ -1,9 +1,9 @@ Centerline - + - - - - + + + + diff --git a/assets/icons/ui/design/svg/model/construction.svg b/assets/icons/ui/design/svg/model/construction.svg index 76d509885b..bae8ebc36b 100644 --- a/assets/icons/ui/design/svg/model/construction.svg +++ b/assets/icons/ui/design/svg/model/construction.svg @@ -1,7 +1,6 @@ Construction - - - - + + + diff --git a/assets/icons/ui/design/svg/model/dimension.svg b/assets/icons/ui/design/svg/model/dimension.svg index fb51381dbc..a54f43b823 100644 --- a/assets/icons/ui/design/svg/model/dimension.svg +++ b/assets/icons/ui/design/svg/model/dimension.svg @@ -1,6 +1,6 @@ Dimension - + diff --git a/assets/icons/ui/design/svg/model/dots.svg b/assets/icons/ui/design/svg/model/dots.svg index 0aa05f3105..3e8f0a1363 100644 --- a/assets/icons/ui/design/svg/model/dots.svg +++ b/assets/icons/ui/design/svg/model/dots.svg @@ -1,6 +1,6 @@ More - - - + + + diff --git a/assets/icons/ui/design/svg/model/eye.svg b/assets/icons/ui/design/svg/model/eye.svg deleted file mode 100644 index a7cc82f280..0000000000 --- a/assets/icons/ui/design/svg/model/eye.svg +++ /dev/null @@ -1,8 +0,0 @@ - - Eye - - - - - - diff --git a/assets/icons/ui/design/svg/model/graph.svg b/assets/icons/ui/design/svg/model/graph.svg index 215ce75530..2de6c208fd 100644 --- a/assets/icons/ui/design/svg/model/graph.svg +++ b/assets/icons/ui/design/svg/model/graph.svg @@ -1,6 +1,6 @@ Dependency graph - + diff --git a/assets/icons/ui/design/svg/model/infinite.svg b/assets/icons/ui/design/svg/model/infinite.svg index 55bca1e955..20619d8d79 100644 --- a/assets/icons/ui/design/svg/model/infinite.svg +++ b/assets/icons/ui/design/svg/model/infinite.svg @@ -1,9 +1,9 @@ Infinite line - - - - + + + + - + diff --git a/assets/icons/ui/design/svg/model/joint_origin.svg b/assets/icons/ui/design/svg/model/joint_origin.svg index aa8114d0c5..24799a9ef0 100644 --- a/assets/icons/ui/design/svg/model/joint_origin.svg +++ b/assets/icons/ui/design/svg/model/joint_origin.svg @@ -13,5 +13,5 @@ - + diff --git a/assets/icons/ui/design/svg/model/motion_link.svg b/assets/icons/ui/design/svg/model/motion_link.svg index 5fa9c62663..e48e5eaa01 100644 --- a/assets/icons/ui/design/svg/model/motion_link.svg +++ b/assets/icons/ui/design/svg/model/motion_link.svg @@ -14,6 +14,6 @@ - - + + diff --git a/assets/icons/ui/design/svg/model/normal.svg b/assets/icons/ui/design/svg/model/normal.svg index 97f3d7fb10..84d15407e1 100644 --- a/assets/icons/ui/design/svg/model/normal.svg +++ b/assets/icons/ui/design/svg/model/normal.svg @@ -1,4 +1,4 @@ Normal line - + diff --git a/assets/icons/ui/design/svg/model/offset_symmetric.svg b/assets/icons/ui/design/svg/model/offset_symmetric.svg new file mode 100644 index 0000000000..e767ce99d7 --- /dev/null +++ b/assets/icons/ui/design/svg/model/offset_symmetric.svg @@ -0,0 +1,6 @@ + + Symmetric offset + + + + diff --git a/assets/icons/ui/design/svg/model/offset_two_sides.svg b/assets/icons/ui/design/svg/model/offset_two_sides.svg new file mode 100644 index 0000000000..75f9555eeb --- /dev/null +++ b/assets/icons/ui/design/svg/model/offset_two_sides.svg @@ -0,0 +1,6 @@ + + Two independent offset sides + + + + diff --git a/assets/icons/ui/design/svg/model/origin.svg b/assets/icons/ui/design/svg/model/origin.svg index 1379189ce9..23af9e99ff 100644 --- a/assets/icons/ui/design/svg/model/origin.svg +++ b/assets/icons/ui/design/svg/model/origin.svg @@ -2,10 +2,10 @@ Origin - - - - + + + + diff --git a/assets/icons/ui/design/svg/model/plane_along_path.svg b/assets/icons/ui/design/svg/model/plane_along_path.svg index 193935e43c..36ea58973a 100644 --- a/assets/icons/ui/design/svg/model/plane_along_path.svg +++ b/assets/icons/ui/design/svg/model/plane_along_path.svg @@ -1,6 +1,6 @@ Plane along path - + - + diff --git a/assets/icons/ui/design/svg/model/plane_perpendicular.svg b/assets/icons/ui/design/svg/model/plane_perpendicular.svg index d205e87da5..1e9abb9f6b 100644 --- a/assets/icons/ui/design/svg/model/plane_perpendicular.svg +++ b/assets/icons/ui/design/svg/model/plane_perpendicular.svg @@ -3,5 +3,5 @@ - + diff --git a/assets/icons/ui/design/svg/model/plane_two_edges.svg b/assets/icons/ui/design/svg/model/plane_two_edges.svg index 89e80fe6d4..cd6c34b9d8 100644 --- a/assets/icons/ui/design/svg/model/plane_two_edges.svg +++ b/assets/icons/ui/design/svg/model/plane_two_edges.svg @@ -1,6 +1,6 @@ Plane through two edges - - + + diff --git a/assets/icons/ui/design/svg/model/point_along_path.svg b/assets/icons/ui/design/svg/model/point_along_path.svg index 37b4bd66ac..53acd98e45 100644 --- a/assets/icons/ui/design/svg/model/point_along_path.svg +++ b/assets/icons/ui/design/svg/model/point_along_path.svg @@ -1,6 +1,6 @@ Point along path - + diff --git a/assets/icons/ui/design/svg/model/point_edge_plane.svg b/assets/icons/ui/design/svg/model/point_edge_plane.svg index 8b8b93035b..ec94d02ecd 100644 --- a/assets/icons/ui/design/svg/model/point_edge_plane.svg +++ b/assets/icons/ui/design/svg/model/point_edge_plane.svg @@ -1,9 +1,9 @@ Point at edge and plane - + - + diff --git a/assets/icons/ui/design/svg/model/point_two_edges.svg b/assets/icons/ui/design/svg/model/point_two_edges.svg index b4844e8845..9f81d196f2 100644 --- a/assets/icons/ui/design/svg/model/point_two_edges.svg +++ b/assets/icons/ui/design/svg/model/point_two_edges.svg @@ -1,6 +1,6 @@ Point through two edges - + diff --git a/assets/icons/ui/design/svg/model/select.svg b/assets/icons/ui/design/svg/model/select.svg index 461b436e74..b9528a63d9 100644 --- a/assets/icons/ui/design/svg/model/select.svg +++ b/assets/icons/ui/design/svg/model/select.svg @@ -1,4 +1,4 @@ Select - + diff --git a/assets/icons/ui/design/svg/model/sheet_rules.svg b/assets/icons/ui/design/svg/model/sheet_rules.svg index bc73919939..b72d7bce35 100644 --- a/assets/icons/ui/design/svg/model/sheet_rules.svg +++ b/assets/icons/ui/design/svg/model/sheet_rules.svg @@ -6,7 +6,7 @@ - + diff --git a/assets/icons/ui/design/svg/model/sketch_chamfer_angle.svg b/assets/icons/ui/design/svg/model/sketch_chamfer_angle.svg index bcebd81aff..b38ccb1dfb 100644 --- a/assets/icons/ui/design/svg/model/sketch_chamfer_angle.svg +++ b/assets/icons/ui/design/svg/model/sketch_chamfer_angle.svg @@ -2,7 +2,7 @@ Distance and angle sketch chamfer - + diff --git a/assets/icons/ui/design/svg/model/sketch_chamfer_two_distances.svg b/assets/icons/ui/design/svg/model/sketch_chamfer_two_distances.svg index 323fbd9ea8..76d5f717c6 100644 --- a/assets/icons/ui/design/svg/model/sketch_chamfer_two_distances.svg +++ b/assets/icons/ui/design/svg/model/sketch_chamfer_two_distances.svg @@ -2,7 +2,7 @@ Two distance sketch chamfer - + diff --git a/assets/icons/ui/design/svg/model/suppress.svg b/assets/icons/ui/design/svg/model/suppress.svg deleted file mode 100644 index 9d369a54c6..0000000000 --- a/assets/icons/ui/design/svg/model/suppress.svg +++ /dev/null @@ -1,8 +0,0 @@ - - Suppress - - - - - - diff --git a/assets/icons/ui/design/svg/model/thicken.svg b/assets/icons/ui/design/svg/model/thicken.svg index e72ea9e498..089485534f 100644 --- a/assets/icons/ui/design/svg/model/thicken.svg +++ b/assets/icons/ui/design/svg/model/thicken.svg @@ -2,8 +2,8 @@ Thicken - - + + @@ -11,9 +11,9 @@ - + - - + + diff --git a/assets/icons/ui/design/svg/workspace/ws_animate.svg b/assets/icons/ui/design/svg/workspace/ws_animate.svg index d53cfbb81a..048485c9c2 100644 --- a/assets/icons/ui/design/svg/workspace/ws_animate.svg +++ b/assets/icons/ui/design/svg/workspace/ws_animate.svg @@ -1,9 +1,9 @@ Animate workspace - + - + diff --git a/assets/icons/ui/design/tools/icon_status.py b/assets/icons/ui/design/tools/icon_status.py index f0c9fbaab0..74a3ad6582 100644 --- a/assets/icons/ui/design/tools/icon_status.py +++ b/assets/icons/ui/design/tools/icon_status.py @@ -9,7 +9,7 @@ --out FILE Self-contained static review page (default target/icon-status.html) Read assets/icons/ui/STYLE_GUIDE.md before editing artwork. SVGs are authoritative. -Every discovered non-final icon is Open; no inventory file controls discovery. +Every discovered non-final icon is Open; artwork, source and runtime requests are discovery sources. """ import sys, os, re, glob, html, json, hashlib, collections, tomllib, uuid, datetime @@ -36,6 +36,7 @@ def art_rev(): paths += glob.glob(ROOT + '/crates/ui-egui/src/**/*.rs', recursive=True) paths += glob.glob(HERE + '/*.py') paths += [D + '/palette.toml', D + '/aliases.toml', __file__] + paths += glob.glob(D + '/requested_icons.txt') for path in sorted(paths): # Equal-length edits can share an mtime. Only the content identifies what # the reviewer saw; file metadata must never authorize stale acceptance. @@ -56,8 +57,31 @@ def ui_icon_names(source): if icon: names.add(icon.group(1)) for table in re.finditer(r'for\s*\(i,\s*\(label,\s*icon,\s*tip\)\)\s*in\s*\[(.*?)\]\s*\.iter\(', source, re.S): names.update(re.findall(r'\("[^"]*",\s*"([a-z][a-z0-9_]*)",\s*"[^"]*"\)', table.group(1))) + # System-window wrappers and declarative page lists own their icon arguments. + for call in re.finditer(r'\b(?:Window|Confirm)::new\([^;]*?,\s*"([a-z][a-z0-9_]*)"\s*,', source, re.S): + names.add(call.group(1)) + for page in re.finditer(r'\bWindowPage\s*\{[^}]*?\bicon:\s*"([a-z][a-z0-9_]*)"', source, re.S): + names.add(page.group(1)) return names +def requested_icons(): + """Runtime ids recorded by the Harness audit; flags describe its last actual render.""" + path = os.path.join(D, 'requested_icons.txt') + if not os.path.exists(path): + return {} + requested = {} + with open(path, encoding='utf-8') as manifest: + for number, line in enumerate(manifest, 1): + if not line.strip() or line.startswith('#'): + continue + fields = line.strip().split('\t') + if len(fields) != 2 or not re.fullmatch(r'[a-z][a-z0-9_]*', fields[0]) or fields[1] not in ('true', 'false'): + raise ValueError(f'{path}:{number}: expected icon_idtrue|false') + if fields[0] in requested: + raise ValueError(f'{path}:{number}: duplicate requested icon {fields[0]}') + requested[fields[0]] = fields[1] == 'true' + return requested + def scan(): """Everything derived from the repo: rows, summary.""" pal = tomllib.load(open(D + '/palette.toml', 'rb')) @@ -86,6 +110,9 @@ def scan(): t = open(f, encoding='utf-8').read() for name in ui_icon_names(t): uses[name].append('ui:' + os.path.relpath(f, ROOT + '/crates/ui-egui/src').replace(os.sep, '/')) + runtime = requested_icons() + for name in runtime: + uses[name].append('ui:runtime audit') mk = ROOT + '/assets/icons/ui/source/markers.rs' if os.path.exists(mk): for name in re.findall(r'=> "(c_[a-z_]+)"', open(mk, encoding='utf-8').read()): uses[name].append('ui:sketch constraint glyph') @@ -95,7 +122,7 @@ def scan(): rows = [] for n in sorted(set(uses) | set(masters) | set(alias_raw)): m, acc = alias_of(n) - if m in masters and n != 'placeholder': status = 'done' + if m in masters and n != 'placeholder' and not runtime.get(n, False): status = 'done' else: status, m = 'placeholder', 'placeholder' cmds = [u for u in uses.get(n, []) if not u.startswith('ui:')] rows.append(dict(id=n, status=status, master=m, accent=acc, commands=len(cmds), example=cmds[0] if cmds else '', diff --git a/assets/icons/ui/design/tools/lint_icons.py b/assets/icons/ui/design/tools/lint_icons.py index 6c7fe532cd..fd95ad704d 100644 --- a/assets/icons/ui/design/tools/lint_icons.py +++ b/assets/icons/ui/design/tools/lint_icons.py @@ -15,7 +15,7 @@ files = sorted(glob.glob(d + '/svg/*/*.svg')) for path in Path(d).rglob('*'): if not path.is_file(): continue rel = path.relative_to(d).as_posix() - allowed = (rel in {'palette.toml', 'aliases.toml', 'review.json', 'references.toml'} + allowed = (rel in {'palette.toml', 'aliases.toml', 'review.json', 'references.toml', 'requested_icons.txt'} or path.suffix == '.md' or (rel.startswith('svg/') and path.suffix == '.svg') or (path.parent in {Path(d) / 'tools', Path(d) / 'tools/author'} and path.suffix == '.py') @@ -79,6 +79,36 @@ if os.path.exists(references): if name not in names and name not in al: err(references, name + ': reference artwork is missing') for message in icon_palette.validation_errors(pal, names | set(al)): err(d + '/palette.toml', message) +# the review page lies its icons on the real app surfaces: its colours must be the app's tokens +def app_tokens(): + src = open(os.path.join(d, '..', '..', '..', 'styles', 'ui', 'palettes.rs'), encoding='utf-8').read() + lo, hi = src.index('pub const fn light'), src.index('pub const fn dark') + light_src, dark_src = src[lo:hi], src[hi:] + def pick(block, name, dark): + pat = (r'\bt\.%s = Color32::from_rgb\((\d+), (\d+), (\d+)\)' if dark else r'\b%s: Color32::from_rgb\((\d+), (\d+), (\d+)\)') % name + m = re.search(pat, block) + return None if not m else '#%02x%02x%02x' % tuple(int(x) for x in m.groups()) + out = {} + for name in ('toolbar', 'panel', 'border', 'text', 'text_dim', 'viewport_top'): + out[name] = (pick(light_src, name, False), pick(dark_src, name, True) or pick(light_src, name, False)) + return out +css_path = os.path.join(d, 'tools', 'review', 'review.css') +if os.path.exists(css_path): + try: + tok = app_tokens() + css = open(css_path, encoding='utf-8').read() + want = { + ':root{': {'--bg': tok['panel'][0], '--fg': tok['text'][0], '--card': tok['toolbar'][0], '--line': tok['border'][0], '--mut': tok['text_dim'][0]}, + 'body.dark{': {'--bg': tok['viewport_top'][1], '--fg': tok['text'][1], '--card': tok['toolbar'][1], '--line': tok['border'][1], '--mut': tok['text_dim'][1]}, + } + for block, values in want.items(): + line = next((l for l in css.split('\n') if l.startswith(block)), '') + for var, hexv in values.items(): + m = re.search(re.escape(var) + r':(#[0-9a-fA-F]{6})', line) + if not m or m.group(1).lower() != hexv: + err(css_path, '%s%s is %s, the app token is %s (palettes.rs)' % (block, var, m.group(1) if m else 'missing', hexv)) + except (OSError, ValueError) as e: + err(css_path, 'cannot compare with palettes.rs: %s' % e) # an icon accepted as final on the review page must not change (un-accept it there to work on it again) import json, review_state rp = d + '/review.json' diff --git a/assets/icons/ui/design/tools/preview.py b/assets/icons/ui/design/tools/preview.py index 400fe8df1b..82da8936f1 100644 --- a/assets/icons/ui/design/tools/preview.py +++ b/assets/icons/ui/design/tools/preview.py @@ -9,7 +9,7 @@ aliases = tomllib.load(open(d + '/aliases.toml', 'rb')).get('alias', {}) def themed(svg, theme, icon, family, size): _, accent = icon_palette.resolve(palette, aliases, icon) return icon_palette.themed(svg, palette, theme, accent, namespace='preview-' + family + '-' + icon + '-' + theme + '-' + str(size)) -h = '' +h = '' for theme in ('light', 'dark'): for fam in sorted(os.listdir(d + '/svg')): h += '

%s - %s

' % (theme, fam, theme) diff --git a/assets/icons/ui/design/tools/review/guide.html b/assets/icons/ui/design/tools/review/guide.html index 062199381c..7719b42f40 100644 --- a/assets/icons/ui/design/tools/review/guide.html +++ b/assets/icons/ui/design/tools/review/guide.html @@ -1,6 +1,6 @@ SolidCraft icon style references - +

Icon style references

Return to review. The binding specification is assets/icons/ui/STYLE_GUIDE.md. Read it before editing. These are original SolidCraft bases and finished visual references, shown directly from the current SVG masters.

64-unit grid · 2.55 outlines · 4.25 action strokes · round caps and joins · standard 30° isometric axes · top light, left medium, right dark.

diff --git a/assets/icons/ui/design/tools/review/review.css b/assets/icons/ui/design/tools/review/review.css index f6aa119e96..6c3fe03177 100644 --- a/assets/icons/ui/design/tools/review/review.css +++ b/assets/icons/ui/design/tools/review/review.css @@ -1,5 +1,6 @@ -:root{--bg:#f4f6f9;--fg:#1d2733;--card:#fff;--line:#d5dbe3;--mut:#6b7a8a;--sz:48px} -body.dark{--bg:#1e2530;--fg:#dbe4ee;--card:#262f3c;--line:#3a4657;--mut:#8a9db0} +/* App surfaces (keep in sync with assets/styles/ui/palettes.rs; lint_icons.py checks): --card is the ribbon surface `toolbar` the icons lie on, --bg the page (`panel` in light, `viewport_top` in dark), --line `border`, --fg `text`, --mut `text_dim`. */ +:root{--bg:#fafbfc;--fg:#262a32;--card:#f6f7f9;--line:#d0d4dc;--mut:#6e7480;--sz:48px} +body.dark{--bg:#2e3440;--fg:#dee2e8;--card:#272b34;--line:#3f4552;--mut:#949ba8} body{font:12px system-ui,sans-serif;margin:0;background:var(--bg);color:var(--fg)} header{position:sticky;top:0;background:var(--card);border-bottom:1px solid var(--line);padding:6px 12px;z-index:5;display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center} h1{font-size:14px;margin:0}.sum{color:var(--mut)} diff --git a/assets/icons/ui/source/glyphs.rs b/assets/icons/ui/source/glyphs.rs index 6650a38c0d..241bd18cbb 100644 --- a/assets/icons/ui/source/glyphs.rs +++ b/assets/icons/ui/source/glyphs.rs @@ -146,20 +146,3 @@ pub(crate) fn cross(p: &egui::Painter, r: Rect, s: Stroke) { p.line_segment([r.left_top(), r.right_bottom()], s); p.line_segment([r.right_top(), r.left_bottom()], s); } -pub(crate) fn radial_centre(p: &egui::Painter, c: Pos2, direction: Option<(egui::Vec2, bool)>, t: crate::theme::Tokens) { - p.circle(c, 15.0, t.panel.gamma_multiply(0.92), Stroke::new(1.0, t.border)); - if let Some((d, enabled)) = direction { - p.line_segment([c + d * 6.0, c + d * 15.0], Stroke::new(3.0, if enabled { t.accent } else { t.border })); - } else { - p.circle_filled(c, 3.0, t.text_dim); - } -} - -pub(crate) fn disclosure(p: &egui::Painter, ar: Rect, open: bool, ink: Color32) { - let pts = if open { - vec![pos2(ar.left() + 2.0, ar.top() + 4.0), pos2(ar.right() - 2.0, ar.top() + 4.0), pos2(ar.center().x, ar.bottom() - 3.0)] - } else { - vec![pos2(ar.left() + 4.0, ar.top() + 2.0), pos2(ar.right() - 3.0, ar.center().y), pos2(ar.left() + 4.0, ar.bottom() - 2.0)] - }; - p.add(egui::Shape::convex_polygon(pts, ink, Stroke::NONE)); -} diff --git a/assets/icons/ui/source/markers.rs b/assets/icons/ui/source/markers.rs index 1cf8ec8436..3560a31351 100644 --- a/assets/icons/ui/source/markers.rs +++ b/assets/icons/ui/source/markers.rs @@ -49,7 +49,7 @@ pub(crate) fn snap(painter: &egui::Painter, c: Pos2, tip: Option, kind: Sn // What it snaps to (hovered), or which component it is (picked): a small label. if let Some(tag) = tag { let t = Tokens::get(); - let font = egui::FontId::proportional(11.5); + let font = egui::FontId::proportional(crate::theme::Tokens::get().font_caption); let g = painter.layout_no_wrap(tag.to_string(), font, t.text); let at = c + vec2(12.0, 10.0); let r = egui::Rect::from_min_size(at, g.size()).expand2(vec2(5.0, 2.0)); diff --git a/assets/icons/ui/source/navigation.rs b/assets/icons/ui/source/navigation.rs index 54e6ca9802..611c3f558a 100644 --- a/assets/icons/ui/source/navigation.rs +++ b/assets/icons/ui/source/navigation.rs @@ -1,6 +1,6 @@ //! Navigation drawing definitions. Camera projection and picking remain in the UI widgets. use crate::theme::Tokens; -use egui::{Align2, Color32, FontId, Pos2, Rect, Shape, Stroke, pos2, vec2}; +use egui::{Color32, FontId, Pos2, Rect, Shape, Stroke, pos2, vec2}; use solidcraft_engine::geom::{Ring, Vec3}; pub(crate) struct Facet { pub(crate) direction: Vec3, @@ -120,7 +120,7 @@ pub(crate) fn paint_cube(painter: &egui::Painter, faces: &[Facet], hovered: Opti } if labels { for face in faces { - face_text(painter, face, t.cube_text, 14.0 * scale); + face_text(painter, face, t.cube_text, t.font_heading * scale); } } } @@ -148,7 +148,8 @@ pub(crate) fn curved_arrow(painter: &egui::Painter, at: Pos2, clockwise: bool, h arrow(painter, *tip, vec2(-end.sin(), end.cos()) * (sign * scale), hover, t); } } -pub(crate) const AXIS_RADIUS: f32 = 11.0; +/// The pointer target around a compass end: larger than the drawn disc, so it is easy to hit. +pub(crate) const AXIS_RADIUS: f32 = 15.0; pub(crate) fn axis_tip(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, at: Pos2, depth: f64, hovered: bool, t: Tokens) { let col = if d.x != 0.0 { t.triad[0] @@ -158,9 +159,57 @@ pub(crate) fn axis_tip(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, a t.triad[2] }; let positive = !name.starts_with('-'); - let ink = if depth < -0.01 { col.gamma_multiply(0.55) } else { col }; - painter.line_segment([c, at], Stroke::new(if positive { 2.0 } else { 1.0 }, ink)); - painter.circle_filled(at, AXIS_RADIUS, if hovered { t.accent_soft } else { t.overlay }); - painter.circle_stroke(at, AXIS_RADIUS, Stroke::new(if hovered { 2.0 } else { 1.0 }, if hovered { t.accent } else { ink })); - painter.text(at, Align2::CENTER_CENTER, name.to_uppercase(), FontId::proportional(11.0), ink); + let light = t.panel.r() > 128; + // Positive shafts are the full axis colour; negative ones are a dimmed tint (and any end + // pointing away from the viewer dims further). + let ink = if !positive || depth < -0.01 { col.gamma_multiply(0.55) } else { col }; + let radius = if positive { 11.0 } else { 4.8 }; + let direction = (at - c).normalized(); + let width = 3.6; + // A negative shaft stops at the outer edge of its ring; a positive one runs under its disc. + let end = if positive { at - direction * radius } else { at - direction * (radius + 1.2) }; + painter.line_segment([c, end], Stroke::new(width, ink)); + // The round cap sits at the origin; the other end is covered by the disc or ring. + painter.circle_filled(c, width / 2.0, ink); + if positive { + painter.circle_filled(at, radius, col); + } + // Negative tips are hollow: neither the axis shaft nor a fill covers their + // centres, so the translucent compass background shows through. A hovered tip of either + // kind turns fully white. + if hovered { + // Same outer size as the resting tip: a ring's outline is half its stroke outside. + painter.circle_filled(at, if positive { radius } else { radius + 1.2 }, t.on_accent); + // On the light theme a white disc vanishes into the compass, so it carries an outline in + // the axis colour, drawn inside the disc so it does not grow. + if light && positive { + painter.circle_stroke(at, radius - 1.0, Stroke::new(2.0, col)); + } + } + if positive { + // On a hovered (white) disc the letter takes the axis colour. + let letter = if hovered { col } else { t.on_accent }; + let label = name.to_uppercase(); + let galley = painter.layout_no_wrap(label, crate::fonts::bold(crate::theme::Tokens::get().font_compass), letter); + let origin = at - galley.size() / 2.0; + let ppp = painter.ctx().pixels_per_point().max(0.5); + let origin = pos2((origin.x * ppp).round() / ppp, (origin.y * ppp).round() / ppp); + painter.galley(origin, galley, letter); + } + if !positive { + painter.circle_stroke( + at, + radius, + Stroke::new( + 2.4, + if !hovered { + ink + } else if light { + col + } else { + t.on_accent + }, + ), + ); + } } diff --git a/assets/icons/ui/source/pointer.rs b/assets/icons/ui/source/pointer.rs index b637f75340..dd16e2d4da 100644 --- a/assets/icons/ui/source/pointer.rs +++ b/assets/icons/ui/source/pointer.rs @@ -11,7 +11,7 @@ pub(crate) fn paint(painter: &egui::Painter, p: Pos2) { [(0.0, 0.0), (0.0, 17.0), (4.5, 13.0), (7.5, 20.0), (10.5, 18.8), (7.5, 12.0), (13.0, 12.0)].iter().map(|(x, y)| p + vec2(*x, *y)).collect(); painter.add(Shape::convex_polygon(pts.clone(), ic.agent, Stroke::NONE)); painter.add(Shape::closed_line(pts, Stroke::new(1.5, ic.highlight))); - let label = painter.layout_no_wrap("Agent".into(), FontId::proportional(11.0), ic.highlight); + let label = painter.layout_no_wrap("Agent".into(), FontId::proportional(crate::theme::Tokens::get().font_small), ic.highlight); let r = egui::Rect::from_min_size(p + vec2(15.0, 18.0), label.size() + vec2(10.0, 4.0)); painter.rect_filled(r, 4.0, ic.agent); painter.galley(r.min + vec2(5.0, 2.0), label, ic.highlight); diff --git a/assets/locales/cs.json b/assets/locales/cs.json index 786444d524..463e080cd1 100644 --- a/assets/locales/cs.json +++ b/assets/locales/cs.json @@ -6,9 +6,9 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Hlášení odešlete na GitHubu tlačítkem Submit new issue.", "A new configuration, a copy of the active one": "Nová konfigurace, kopie aktivní", - "AI": "AI", "ARRANGE": "USPOŘÁDAT", "About SolidCraft": "O aplikaci SolidCraft", + "Actions": "Akce", "Active": "Aktivní", "Active rule": "Aktivní pravidlo", "Add": "Přidat", @@ -16,6 +16,7 @@ "Add Mid Radius": "Přidat střední poloměr", "Add Row": "Přidat řádek", "Add condition": "Přidat podmínku", + "Add or edit rule": "Přidat nebo upravit pravidlo", "Alignment": "Zarovnání", "All Sketch Points": "Všechny body náčrtu", "All open designs": "Všechny otevřené návrhy", @@ -27,6 +28,7 @@ "Applied Radius": "Použitý poloměr", "Apply To": "Použít na", "Apply to selection": "Použít na výběr", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Nejprve použijte vzhled. Exportuje přiřazené vzhledy a materiály vytvořené v tomto návrhu.", "Arc length": "Délka oblouku", "Area": "Plocha", "Attachment": "Připojení", @@ -56,14 +58,15 @@ "Check collisions": "Kontrolovat kolize", "Checked": "Zaškrtnuto", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Zvolte novou rovinu. Geometrie a kóty náčrtu zůstanou ve svých dvourozměrných souřadnicích beze změny.", + "Choose…": "Vybrat…", "Circumference": "Obvod", "Close draft": "Zavřít koncept", "Collisions": "Kolize", "Colour": "Barva", + "Comment": "Komentář", "Component1": "Komponenta1", "Configuration": "Konfigurace", "Configuration rules": "Pravidla konfigurace", - "Construction": "Konstrukční", "Contact": "Kontakt", "Continuity": "Spojitost", "Copy Error Message": "Kopírovat chybovou zprávu", @@ -83,11 +86,11 @@ "Curvature (G2)": "Křivost (G2)", "Curvature change (G3)": "Změna křivosti (G3)", "Customize Toolbar": "Přizpůsobit panel nástrojů", + "Delete": "Odstranit", "Delete all": "Smazat vše", "Delete rule": "Smazat pravidlo", "Delete the row": "Smazat řádek", "Delete the set": "Smazat sadu", - "Delete this parameter": "Smazat tento parametr", "Density": "Hustota", "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.", @@ -98,15 +101,19 @@ "Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Dvojklik prostředním tlačítkem v pohledu: Domů (výchozí pohled se zobrazením všeho), Přizpůsobit (zobrazí vše při zachování natočení) nebo nic", "Drive Joint": "Pohánět kloub", "Edges": "Hrany", + "Edit": "Upravit", "Edit Profile Sketch": "Upravit náčrt profilu", "Enable rule": "Povolit pravidlo", "Enabled": "Povoleno", "Explode": "Rozložit", "Export Animation…": "Exportovat animaci…", + "Export CSV…": "Exportovat CSV…", "Export Current View as DXF": "Exportovat aktuální pohled do DXF", "Export Current View as DXF…": "Exportovat aktuální pohled do DXF…", "Export DXF…": "Exportovat DXF…", + "Export JSON…": "Exportovat JSON…", "Export hidden edges as dashed lines": "Exportovat skryté hrany čárkovaně", + "Expression": "Výraz", "Extend to meet": "Prodloužit k protnutí", "Extents": "Rozsahy", "FILES": "SOUBORY", @@ -115,11 +122,12 @@ "Face attachment lost": "Připojení k ploše ztraceno", "Faces": "Plochy", "Faces / edges / vertices": "Plochy / hrany / vrcholy", + "Favorites": "Oblíbené", "Favourite": "Oblíbené", + "Feature": "Prvek", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Soubory z mnoha CAD programů mají osu Y nahoru, proto se zde otevřou ležící na zádech (Z nahoru).", "Filter": "Filtr", "Find in Browser": "Najít v prohlížeči", - "Flat": "Plochý", "Flat size": "Rozměr rozvinu", "Follow camera (Look At sketches)": "Sledovat kameru (náčrty natočené k pohledu)", "Follow the camera (Look At sketches)": "Sledovat kameru (náčrty natočené k pohledu)", @@ -134,6 +142,7 @@ "Holes (edges on them)": "Otvory (jejich hrany)", "Home view": "Výchozí pohled", "IMAGES": "OBRÁZKY", + "Import…": "Importovat…", "In This Design": "V tomto návrhu", "In place (surfaces)": "Na místě (plochy)", "In plane": "V rovině", @@ -154,7 +163,9 @@ "Length units": "Jednotky délky", "Letter Spacing": "Mezery mezi písmeny", "Library": "Knihovna", + "Library file": "Soubor knihovny", "Line Spacing": "Řádkování", + "Local library": "Místní knihovna", "Loop": "Smyčka", "MANAGE": "SPRÁVA", "MATERIALS": "MATERIÁLY", @@ -162,35 +173,43 @@ "MOTION": "POHYB", "Margin": "Okraj", "Mass": "Hmotnost", + "Matched restrictions check the final values.": "Odpovídající omezení kontrolují konečné hodnoty.", "Material": "Materiál", "Max": "Max", "Mid Radius": "Střední poloměr", "Middle double-click": "Dvojklik prostředním tlačítkem", "Min": "Min", "Mitre": "Pokos", + "Model Parameters": "Parametry modelu", "Modeled": "Modelovaný", "More toolbar panels": "Další panely nástrojů", "Motion": "Pohyb", "Move Type": "Typ posunu", + "Move down": "Posunout dolů", "Move earlier": "Posunout dříve", "Move later": "Posunout později", + "Move up": "Posunout nahoru", "Name": "Název", + "New": "Nový", + "New rule…": "Nové pravidlo…", "No crash reports.": "Žádná hlášení o pádech.", + "No matching appearance presets.": "Žádné odpovídající předvolby vzhledu.", "No matching appearances": "Žádné odpovídající vzhledy", "No visible bodies to export.": "Žádná viditelná tělesa k exportu.", "None": "Žádné", - "Normal to sketch": "Kolmo ke skice", "Not available yet": "Zatím není k dispozici", "Notch": "Zářez", "OUTPUT": "VÝSTUP", "Opacity": "Neprůhlednost", "Open GitHub draft": "Otevřít koncept na GitHubu", + "Open local libraries in the desktop app. You can export this design's library here.": "Místní knihovny otevřete v desktopové aplikaci. Zde můžete exportovat knihovnu tohoto návrhu.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Open-source parametrický 3D CAD v jazyce Rust. Licence MIT OR Apache-2.0.", + "Operator": "Operátor", "Other side": "Druhá strana", "Other surface (mutual trim)": "Druhá plocha (vzájemné oříznutí)", "Outside": "Vně", "Overall length": "Celková délka", - "Parameter values": "Hodnoty parametrů", + "Parameter": "Parametr", "Pass through": "Procházet skrz", "Path": "Cesta", "Per Edge": "Pro každou hranu", @@ -205,7 +224,6 @@ "Preview Row": "Náhled řádku", "Preview leaves the current design unchanged.": "Náhled aktuální návrh nemění.", "Profiles/Curves": "Profily/křivky", - "Projection": "Promítání", "RECENT": "NEDÁVNÉ", "REFERENCE": "REFERENCE", "RULED SURFACE": "PŘÍMKOVÁ PLOCHA", @@ -241,6 +259,8 @@ "Roll History Marker Here": "Přesunout značku historie sem", "Round": "Zaoblený", "Rule": "Pravidlo", + "Rule name": "Název pravidla", + "Rules run from top to bottom. Matched drives change effective values.": "Pravidla se spouštějí shora dolů. Odpovídající řídicí pravidla mění účinné hodnoty.", "SECTION": "ŘEZ", "SURFACE QUALITY": "KVALITA POVRCHU", "Save Rule": "Uložit pravidlo", @@ -249,6 +269,7 @@ "Scale factor": "Měřítko", "Search": "Hledat", "Search commands": "Hledat příkazy", + "Search this library": "Prohledat tuto knihovnu", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vybrat jiné: podržte levé tlačítko na modelu (nebo pravé tlačítko › Vybrat jiné…) a zobrazí se vše pod kurzorem; dalším kliknutím na stejné místo vyberete další prvek. Dvojklikem prostředním tlačítkem přejdete na výchozí pohled (Předvolby › Navigace).", "Select bodies (a face picks its body)": "Vyberte tělesa (plocha vybere své těleso)", "Select bodies or faces instead": "Vyberte místo toho tělesa nebo plochy", @@ -303,17 +324,18 @@ "Turn off section analysis before exporting the view.": "Před exportem pohledu vypněte analýzu řezu.", "Type": "Typ", "UNTRIM": "ZRUŠIT OŘEZ", + "Unit": "Jednotka", "Updating the preview": "Aktualizace náhledu", "Use active author placement": "Použít aktivní umístění autora", "Use fixed plane": "Použít pevnou rovinu", "Use saved fixed plane": "Použít uloženou pevnou rovinu", "Use selected face as fixed plane": "Použít vybranou plochu jako pevnou rovinu", + "User Parameters": "Uživatelské parametry", "VIEW": "POHLED", "VIEWS": "POHLEDY", "Value": "Hodnota", "Vertices": "Vrcholy", "View navigator": "Navigátor pohledu", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Pouze viditelná tělesa. Křivky jsou nahrazeny úsečkami (tolerance 0,01 mm).", "Volume": "Objem", "Wall": "Stěna", "Wall Location": "Umístění stěny", @@ -321,6 +343,12 @@ "When": "Když", "Width": "Šířka", "Will fail": "Selže", + "X angle": "Úhel X", + "X distance": "Vzdálenost X", + "Y angle": "Úhel Y", + "Y distance": "Vzdálenost Y", + "Z angle": "Úhel Z", + "Z distance": "Vzdálenost Z", "angle, height": "úhel, výška", "appearance.dielectric_ior": "Index lomu dielektrika", "appearance.metallic": "Kovovost", @@ -329,7 +357,11 @@ "appearance.roughness": "Drsnost", "appearance.surface_response": "Odezva povrchu", "browser.body_name": "Těleso{number}", + "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í.", + "browser.capture_one": "Komponenta byla přesunuta, ale její poloha není zachycena.", "browser.component_name": "Komponenta{number}", + "browser.move_title": "Přesunout: {name}", "browser.parameters": "Parametry ({count})", "browser.precision": "Přesnost: {precision}", "browser.sketch_name": "Náčrt{number}", @@ -651,7 +683,8 @@ "cmd.view_navigate": "Navigovat pohled", "cmd.view_rename": "Přejmenovat pojmenovaný pohled", "cmd.view_save": "Nový pojmenovaný pohled", - "command name": "název příkazu", + "contact.components_count": "Počet komponent: {count}", + "contact.enabled_label": "povoleno", "coupling.gear_1": "Kolo 1", "coupling.gear_1_pick": "Kolo 1 (volitelné)", "coupling.gear_2": "Kolo 2", @@ -668,8 +701,11 @@ "coupling.rack": "Hřeben", "coupling.radius_1": "Poloměr 1", "coupling.radius_2": "Poloměr 2", + "delete.and_more": "… a další ({count})", "dialog.computing": "Výpočet… {seconds} s", "dialog.custom_axis": "↖ Vlastní osa", + "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.select": "↖ Vybrat", "dialog.selected_count": "↖ Vybráno: {count}", @@ -693,9 +729,17 @@ "dxf.progress.write": "Zápis DXF (5/5)", "dxf.tolerance": "Tolerance křivek", "dxf.tolerance_help": "Pouze viditelná tělesa. Křivky jsou nahrazeny úsečkami. Menší tolerance dává jemnější křivky, ale zabere více času a paměti.", - "expression, e.g. 25 mm": "výraz, např. 25 mm", "fits the face, e.g. M8": "odpovídá ploše, např. M8", "from the rule": "z pravidla", + "help.apache": "Licence Apache 2.0", + "help.attributions": "Uvedení autorství (ATTRIBUTION.md)", + "help.build": "Verze {version} · commit {commit} · sestaveno {date}", + "help.font_details": "Rozhraní používá vestavěná písma egui (Ubuntu Light a Hack pod licencemi Ubuntu Font Licence a MIT/Bitstream Vera; Noto Emoji a písmo ikon emoji pod licencemi OFL a MIT). Text náčrtu používá Liberation Sans (SIL OFL 1.1). Volitelný balíček písem craft-fonts od The Crafting Apps (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1) není součástí sestavení SolidCraft.", + "help.fonts": "Písma", + "help.licences": "Licence", + "help.mit": "Licence MIT", + "help.notice": "NOTICE", + "help.third_party": "Licence třetích stran", "home.documentation": "Dokumentace", "home.in_this_browser": "v tomto prohlížeči", "home.missing": "chybí", @@ -724,6 +768,18 @@ "joint.from_bodies_hint": "Každé vybrané těleso se přesune do vlastní nové komponenty a zůstane na místě.", "joint.one_body": "Oba výběry jsou na {body}. Druhý vyberte na jiném dílu.", "joint.one_component": "Tyto díly jsou tělesy jedné komponenty: {bodies}. Kloub pohybuje jednou komponentou vůči druhé, takže každý díl potřebuje vlastní komponentu.", + "keymap.command_id": "ID příkazu", + "keymap.empty": "Žádné odpovídající příkazy", + "keymap.use_anyway": "Přesto zde použít {key}", + "manage.appearance_hint": "Vyberte vzhled pro výběr nebo přetáhněte vzorek na těleso či plochu.", + "manage.assign_hint": "Vyberte tělesa a zvolte jejich pravidlo návrhu plastových dílů.", + "manage.config_hint": "Upravte řádky, sloupce a pravidla konfigurace a poté řádek aktivujte nebo zobrazte jeho náhled.", + "manage.empty": "Žádné odpovídající řádky.", + "manage.material_hint": "Vyberte tělesa a zvolte jejich fyzikální materiál.", + "manage.parameters_hint": "Upravte výrazy a jednotky. Změny se použijí po stisknutí Enter nebo opuštění pole.", + "manage.rules_hint": "Vyberte pravidlo k úpravě nebo vytvořte nové. Potvrzením pravidlo uložíte.", + "measure.at": "V bodě", + "measure.item": "Položka {number}", "messages.ago_h": "před {n} h", "messages.ago_min": "před {n} min", "messages.ago_s": "před {n} s", @@ -733,6 +789,14 @@ "messages.menu": "Zprávy…", "messages.none": "Zatím žádné zprávy.", "messages.title": "Zprávy", + "motion.delete_key": "Odstranit klíčový snímek", + "motion.key_here": "Vložit klíčový snímek sem", + "motion.key_here_hint": "Přidat v tomto kroku klíčový snímek s aktuální hodnotou kloubu", + "motion.step_back": "Krok zpět", + "motion.step_forward": "Krok vpřed", + "motion.to_end": "Na konec", + "motion.to_start": "Na začátek", + "motion.track_name": "Stopa {number}", "nav.touchpad.help": "Podržením Shift a pohybem ukazatele posouváte; podržením Alt/Option a pohybem otáčíte. Tlačítko myši není potřeba. Svislé posouvání přibližuje.", "nav.touchpad.label": "Touchpad", "nav.touchpad.orbit": "Otáčení (podržte Alt/Option a pohybujte; bez tlačítka myši)", @@ -741,11 +805,12 @@ "nav.touchpad.zoom": "Přiblížení (svislé posouvání nebo sevření prstů)", "needs two joints": "vyžaduje dva klouby", "new configuration": "nová konfigurace", - "new name": "nový název", "new parameter": "nový parametr", "no interference": "bez kolize", "no joint that moves yet": "zatím žádný pohyblivý kloub", "no picture": "bez obrázku", + "none": "žádné", + "parts.automatic_name": "automaticky", "pattern.alignment_2": "Zarovnání 2", "pattern.alignment_hint": "Počty zahrnují originál. Na střed rozloží kopie rovnoměrně; Převrátit volí stranu navíc u sudých počtů.", "pattern.direction_1": "Směr 1", @@ -753,12 +818,13 @@ "pattern.flip_direction": "Převrátit směr", "pick a hole to seat it, or OK places it at the origin": "vyberte otvor pro usazení, nebo OK umístí do počátku", "pick one or two vertices, edges or faces": "vyberte jeden nebo dva vrcholy, hrany či plochy", + "prefs.cpu": "CPU (softwarově)", + "prefs.minutes_suffix": " min", + "properties.title": "Vlastnosti: {name}", "render.background": "Pozadí scény", "render.fit": "Přizpůsobit produktu", - "render.image_cancel": "Zrušit render", "render.image_cancelled": "Render zrušen", "render.image_cancelling": "Rušení", - "render.image_check": "Zkontrolovat požadavek na render", "render.image_completed": "Obrázek připraven k exportu", "render.image_encoding": "Kódování PNG", "render.image_export": "Exportovat PNG", @@ -815,15 +881,28 @@ "settings.reset_intro": "Tato nastavení na tomto počítači nefungují a bránila by spuštění SolidCraftu, proto byla změněna:", "settings.reset_title": "Nastavení byla změněna", "settings.supersampling_note": "Vykresluje pohled ve vyšším rozlišení a poté jej zmenšuje: hladší hrany, čáry a mřížka na obrazovkách pod 4K. Stojí výkon GPU: asi 2,25× více pixelů při 1,5× a 4× při 2×.", + "shortcut.json_error": "parametry musí být ve formátu JSON: {error}", + "shortcut.no_command": "příkaz `{id}` neexistuje; text před JSON musí být ID příkazu", + "shortcut.pin": "Připnout nahoru do pole S", + "shortcut.unpin": "Odepnout", "sketch.degrees_of_freedom": "Stupně volnosti: {count}", + "sketch.dof_left": "Zbývající stupně volnosti: {count}", + "sketch.dof_left_one": "Zbývá {count} stupeň volnosti", "sketch.draw_curves": "Kreslit jako {linetype}", "sketch.make_selection": "Převést výběr na {linetype}", + "sketch_panel.attached": "Připojeno k {body} / {path}", + "sketch_panel.author": "Umístění autora: {path}", + "sketch_panel.dimension_hint": "Zadejte hodnotu nebo výraz a poté stiskněte OK.", + "sketch_panel.move_hint": "Zadejte vzdálenosti a úhel a poté stiskněte OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Připojeno k rovinné ploše plechového tělesa; při změně plechu sleduje tento panel.", + "sketch_panel.text_hint": "Upravte text a jeho umístění a poté stiskněte OK.", + "sketch_panel.use_plane": "Použít rovinu {plane}", "sketch_text.align_center": "Na střed", "sketch_text.align_left": "Vlevo", "sketch_text.align_right": "Vpravo", "sketch_text.bold": "Tučné", "sketch_text.builtin_font": "{family} (vestavěné)", - "sketch_text.font_search": "Hledat písma", "sketch_text.italic": "Kurzíva", "sketch_text.loading_fonts": "Hledání nainstalovaných písem…", "sketch_text.on_path": "Text sleduje svou cestu: umístění určuje křivka", @@ -838,6 +917,7 @@ "timeline.reorder_many": "Změnit pořadí prvků", "timeline.ungroup": "Odebrat složky prvků", "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.", "toolbar.image": "OBRÁZEK", "toolbar.render": "RENDER", "toolbar.render_scene": "SCÉNA", @@ -846,9 +926,7 @@ "trace.stopped_by_loop": "Končí ve vzorku {sample}: smyčku přes {joint} už nelze dále uzavřít", "ui.1_degree_of_freedom": "1 stupeň volnosti", "ui.3d_mouse_speed": "Rychlost 3D myši", - "ui.3d_mouse_windows": "3D myš", "ui.3d_sketch": "3D náčrt", - "ui.about": "O aplikaci", "ui.activate": "Aktivovat", "ui.active_component": "Aktivní komponenta", "ui.agent_chat": "Chat s agentem", @@ -890,7 +968,7 @@ "ui.agent_chat_model_tip": "Model, který agent používá. Vaše volba se pro tohoto agenta zapamatuje.", "ui.agent_chat_new": "Nový chat", "ui.agent_chat_new_confirm": "Začít nový chat? Aktuální bude vymazán.", - "ui.agent_chat_new_yes": "Nový chat", + "ui.agent_chat_next_picture": "Další obrázek", "ui.agent_chat_no_cli": "Vedle aplikace nebyl nalezen solidcraft-cli, takže agent nemá přístup k návrhu.", "ui.agent_chat_no_control": "Endpoint chatu s agentem se nespustil, takže agent nemá přístup k návrhu.", "ui.agent_chat_no_images": "Tento agent nepřijímá obrázky", @@ -902,6 +980,7 @@ "ui.agent_chat_pick": "Agent", "ui.agent_chat_placeholder": "Požádejte agenta o změnu návrhu…", "ui.agent_chat_placeholder_busy": "Zařadit zprávu, až agent skončí (Ctrl+Enter: odeslat hned)", + "ui.agent_chat_previous_picture": "Předchozí obrázek", "ui.agent_chat_privacy": "Nic se nikam neodesílá, jen agentovi, kterého spouštíte pod vlastním účtem.", "ui.agent_chat_privacy_note": "SolidCraft nic nikam neodesílá: agent, kterého spouštíte, komunikuje se svou službou pod vaším účtem. SolidCraft nikdy nevidí ani neukládá klíč či heslo.", "ui.agent_chat_queue": "Zařadit", @@ -955,6 +1034,7 @@ "ui.ai_bad_url": "Zadejte URL začínající http:// nebo https://", "ui.ai_base_url": "Základní URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "kopie", "ui.ai_custom_url": "Vlastní URL", "ui.ai_default": "Výchozí", @@ -975,8 +1055,10 @@ "ui.ai_mode_edits_note": "Modelovací příkazy běží bez ptaní; ukládání, otevírání, export a další souborové či destruktivní akce se stále ptají.", "ui.ai_mode_global": "Podle nastavení výše", "ui.ai_model": "Model", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Tato nastavení se týkají nástrojů SolidCraftu. Vlastní nástroje agenta (jeho shell, úpravy souborů) se řídí jeho vlastním režimem, který zvolíte v záhlaví panelu chatu, pokud agent režimy má.", "ui.ai_name": "Název", + "ui.ai_name_hint": "Můj agent", "ui.ai_need_command": "Zadejte příkaz, který spouští agenta", "ui.ai_need_model": "Zadejte název modelu", "ui.ai_no_answer": "Agent neodpověděl včas", @@ -1007,6 +1089,7 @@ "ui.ai_toolbar_button": "Zobrazit tlačítko na panelu nástrojů", "ui.ai_toolbar_button_note": "Tlačítko AI na pravém konci panelu nástrojů. Ctrl+Shift+A a nabídka Zobrazit panel otevřou i tak.", "ui.ai_undo_tip": "Vrátit jedním krokem vše, co agent u této zprávy změnil", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Vše", "ui.analysis": "Analýza", "ui.angle": "Úhel", @@ -1018,7 +1101,6 @@ "ui.anti_aliasing": "Vyhlazování hran", "ui.apply": "Použít", "ui.arc": "Oblouk", - "ui.assemble": "Sestavit", "ui.autosave_every": "Automaticky ukládat každých", "ui.axes": "Osy", "ui.axis": "Osa", @@ -1028,6 +1110,11 @@ "ui.bodies": "Tělesa", "ui.box": "Kvádr", "ui.break": "Přerušit", + "ui.browser_bodies_one": "{count} těleso", + "ui.browser_bodies_other": "{count} těles", + "ui.browser_collapse": "Sbalit prohlížeč", + "ui.browser_expand": "Rozbalit prohlížeč", + "ui.browser_title": "Prohlížeč", "ui.calibrate": "Kalibrovat", "ui.cancel": "Zrušit", "ui.canvases": "Plátna", @@ -1037,7 +1124,6 @@ "ui.chamfer": "Sražení", "ui.chamfer_type": "Typ sražení", "ui.change_workspace": "Změnit pracovní prostředí", - "ui.chord_length": "Délka tětivy", "ui.circle": "Kružnice", "ui.circular_pattern": "Kruhové pole", "ui.clear": "Vymazat", @@ -1070,11 +1156,9 @@ "ui.command_done": "{command} hotovo", "ui.command_palette_hint": "Zadejte příkaz, např. {example} — nebo stiskněte S pro hledání", "ui.concentric": "Soustřednost", - "ui.configurations": "Konfigurace", "ui.conic_curve": "Kuželosečka", "ui.constant": "Konstantní", "ui.constraints_conflict": "Konflikt vazeb", - "ui.construct": "Konstrukce", "ui.construction": "Konstrukční", "ui.context_menu": "Místní nabídka", "ui.continuity_gap": "Mezera", @@ -1097,7 +1181,6 @@ "ui.cylinder": "Válec", "ui.dark": "Tmavý", "ui.default_modelling_orientation": "Výchozí orientace modelování", - "ui.degrees_of_freedom": "stupňů volnosti", "ui.delete": "Smazat", "ui.depth": "Hloubka", "ui.design": "Návrh", @@ -1139,7 +1222,6 @@ "ui.equivalent_radius": "Ekvivalentní poloměr", "ui.expand": "Rozbalit", "ui.exportDxf": "Exportovat DXF…", - "ui.export_ellipsis": "Exportovat...", "ui.export_ellipsis_keymap": "Exportovat…", "ui.export_keymap_tip": "Klávesy, oblíbené z vyhledávání S a panel nástrojů jako soubor", "ui.extend": "Prodloužit", @@ -1221,7 +1303,6 @@ "ui.heading_sheet_metal": "PLECH", "ui.heading_shell": "SKOŘEPINA", "ui.heading_sketch": "NÁČRT", - "ui.heading_sketch_palette": "PALETA NÁČRTU", "ui.heading_sketch_scale": "MĚŘÍTKO NÁČRTU", "ui.heading_solid": "TĚLESO", "ui.heading_sphere": "KOULE", @@ -1240,6 +1321,7 @@ "ui.hideOrigin": "Skrýt počátek", "ui.hidePlanes": "Skrýt roviny", "ui.hideProfile": "Skrýt profil", + "ui.history_title": "Historie", "ui.hole": "Otvor", "ui.hole_tap_type": "Typ závitu otvoru", "ui.hole_type": "Typ otvoru", @@ -1259,7 +1341,6 @@ "ui.length_decimals": "Desetinná místa délky", "ui.light": "Světlý", "ui.line": "Úsečka", - "ui.linetype": "Typ čáry", "ui.lock": "Zamknout", "ui.loft": "Spojení profilů", "ui.look_at": "Pohled kolmo", @@ -1291,10 +1372,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF se značkami ploch", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Střed", "ui.mirror": "Zrcadlit", "ui.mirror_line": "Osa zrcadlení", - "ui.modify": "Upravit", "ui.move": "Posunout", "ui.multiple": "Více", "ui.named_views": "Pojmenované pohledy", @@ -1314,7 +1393,6 @@ "ui.offset_plane": "Odsazená rovina", "ui.one_side": "Jedna strana", "ui.open": "Otevřít", - "ui.open_ellipsis": "Otevřít...", "ui.open_preferences": "Otevřít předvolby", "ui.open_recent": "Otevřít nedávné", "ui.operation": "Operace", @@ -1323,8 +1401,9 @@ "ui.orbit_around": "Otáčet kolem", "ui.orientation": "Orientace", "ui.origin": "Počátek", - "ui.palette_points": "Body", - "ui.palette_profile": "Profil", + "ui.palette_construction_mode": "Konstrukční režim (X)", + "ui.palette_more": "Další", + "ui.palette_sketch_options": "Možnosti náčrtu", "ui.pan": "Posun", "ui.pan_zoom_orbit": "Posun, přiblížení, otáčení", "ui.parallel": "Rovnoběžnost", @@ -1358,7 +1437,6 @@ "ui.quit": "Ukončit", "ui.radial_context_menu": "Kruhová místní nabídka", "ui.radius": "Poloměr", - "ui.radius_type": "Typ poloměru", "ui.reachable": "Dosažitelné", "ui.rectangle": "Obdélník", "ui.rectangular_pattern": "Obdélníkové pole", @@ -1381,7 +1459,6 @@ "ui.right": "Zprava", "ui.save": "Uložit", "ui.save_as": "Uložit jako", - "ui.save_as_ellipsis": "Uložit jako...", "ui.save_mesh_no_slicer": "Nebyl nalezen žádný slicer: zvolte Vlastní… a vyberte svůj slicer, nebo vypněte Odeslat do sliceru", "ui.save_mesh_pick_bodies": "Vyberte tělesa k uložení", "ui.save_mesh_temp_failed": "Nelze vytvořit {folder}: {error}", @@ -1393,7 +1470,6 @@ "ui.select": "Vybrat", "ui.selectOther": "Vybrat jiné…", "ui.selectTangentChain": "Vybrat tečný řetězec", - "ui.select_other_hint": "Vybrat jiné: podržte levé tlačítko na modelu (nebo pravé tlačítko › Vybrat jiné…) a zobrazí se vše pod kurzorem; dalším kliknutím na stejné místo vyberete další prvek.", "ui.selection": "Výběr", "ui.send": "Odeslat", "ui.send_to_slicer": "Odeslat do sliceru", @@ -1410,7 +1486,6 @@ "ui.show_constraints": "Vazby", "ui.show_construction_geometries": "Konstrukční geometrie", "ui.show_dimensions": "Kóty", - "ui.show_points": "Zobrazit body", "ui.show_profile": "Zobrazit profil", "ui.show_projected_geometries": "Promítnuté geometrie", "ui.simple": "Jednoduchý", @@ -1420,7 +1495,6 @@ "ui.sketch_dimension": "Kóta náčrtu", "ui.sketch_grid": "Mřížka náčrtu", "ui.sketch_scale": "Měřítko náčrtu", - "ui.sketch_status": "Stav náčrtu", "ui.sketches": "Náčrty", "ui.slice": "Řez", "ui.slicer": "Slicer", @@ -1466,7 +1540,6 @@ "ui.stand_up_tip": "Když se soubor STEP nebo IGES otevře s osou Z nahoru, nabídnout otočení o čtvrt otáčky kolem X", "ui.start": "Start", "ui.start_over": "Začít znovu", - "ui.start_radius": "Počáteční poloměr", "ui.supersampling": "Supersampling (SSAA)", "ui.supersampling_adaptive": "Jen když je pohled v klidu", "ui.supersampling_off": "Vypnuto (1×)", @@ -1510,7 +1583,6 @@ "ui.untitled": "Bez názvu", "ui.up_axis_note": "Která osa v pohledech míří nahoru (pohled podél osy, výchozí pohled, pohled kolmo). Návrhy se nemění.", "ui.updateToCurrentView": "Aktualizovat na aktuální pohled", - "ui.variable": "Proměnný", "ui.view_centre": "Střed pohledu", "ui.visual_shaded_hidden_edges": "Stínovaný se skrytými hranami", "ui.visual_shaded_with_edges": "Stínovaný s hranami", @@ -1526,7 +1598,6 @@ "ui.zoom_to_the_cursor": "Přibližovat ke kurzoru", "update.available": "Je k dispozici SolidCraft {version}", "update.button": "Aktualizovat", - "update.button_tip": "Je k dispozici SolidCraft {version}", "update.button_version": "Aktualizovat na {version}", "update.check_automatically": "Automaticky kontrolovat aktualizace", "update.check_now": "Zkontrolovat nyní", @@ -1558,5 +1629,6 @@ "update.title": "Je k dispozici aktualizace", "update.up_to_date": "SolidCraft je aktuální.", "update.update_now": "Aktualizovat nyní", - "update.you_have": "Máte {version}." + "update.you_have": "Máte {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ vybrat · Enter / Tab doplnit · Esc zavřít" } diff --git a/assets/locales/da.json b/assets/locales/da.json index 5ffc6bb2f9..5e1d068a8b 100644 --- a/assets/locales/da.json +++ b/assets/locales/da.json @@ -5,7 +5,6 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Vedhæft dit skærmbillede: Kopiér og indsæt det, eller gem og upload PNG-filen.", "3. Click Submit new issue on GitHub to send your report.": "3. Klik på Submit new issue på GitHub for at sende rapporten.", "A new configuration, a copy of the active one": "Opret en ny konfiguration som en kopi af den aktive", - "AI": "AI", "About SolidCraft": "Om SolidCraft", "Acceleration": "Acceleration", "Active": "Aktiv", @@ -60,7 +59,6 @@ "Component1": "Komponent1", "Configuration": "Konfiguration", "Configuration rules": "Konfigurationsregler", - "Construction": "Konstruktionsgeometri", "Contact": "Kontakt", "Contact sets": "Kontaktsæt", "Continuity": "Kontinuitet", @@ -86,7 +84,6 @@ "Delete rule": "Slet regel", "Delete the row": "Slet rækken", "Delete the set": "Slet sættet", - "Delete this parameter": "Slet denne parameter", "Density": "Densitet", "Designs you open or save appear here.": "Designs, du åbner eller gemmer, vises her.", "Determined by To Object. Choose Angle to edit the angular extent.": "Bestemmes af Til objekt. Vælg Vinkel for at ændre vinkeludstrækningen.", @@ -121,7 +118,6 @@ "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Filer fra mange CAD-programmer har Y opad og åbnes derfor liggende på ryggen her (Z opad).", "Filter": "Filter", "Find in Browser": "Find i modeltræ", - "Flat": "Flad", "Flat size": "Bredde mellem flader", "Follow camera (Look At sketches)": "Følg kamera (Se på skitser)", "Follow the camera (Look At sketches)": "Følg kameraet (Se på skitser)", @@ -174,7 +170,6 @@ "No matching appearances": "Ingen matchende udseender", "No visible bodies to export.": "Der er ingen synlige emner at eksportere.", "None": "Ingen", - "Normal to sketch": "Vinkelret på skitse", "Not available yet": "Endnu ikke tilgængelig", "Notch": "Udskæring", "Opacity": "Opacitet", @@ -184,7 +179,6 @@ "Other surface (mutual trim)": "Anden overflade (trim begge)", "Outside": "Udvendigt", "Overall length": "Samlet længde", - "Parameter values": "Parameterværdier", "Pass through": "Gå igennem", "Path": "Bane", "Per Edge": "Pr. kant", @@ -202,7 +196,6 @@ "Preview Row": "Forhåndsvis række", "Preview leaves the current design unchanged.": "Forhåndsvisning ændrer ikke det aktuelle design.", "Profiles/Curves": "Profiler/kurver", - "Projection": "Projektion", "Quantity": "Antal", "RECENT": "SENESTE", "RULED SURFACE": "REGELFLADE", @@ -311,7 +304,6 @@ "Velocity": "Hastighed", "Vertices": "Hjørnepunkter", "View navigator": "Visningsnavigator", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Kun synlige emner. Kurver består af linjestykker (tolerance: 0,01 mm).", "Volume": "Volumen", "Wall": "Væg", "Wall Location": "Vægplacering", @@ -663,7 +655,6 @@ "cmd.view_navigate": "Navigér i visning", "cmd.view_rename": "Omdøb navngiven visning", "cmd.view_save": "Ny navngiven visning", - "command name": "kommandonavn", "coupling.gear_1": "Tandhjul 1", "coupling.gear_1_pick": "Tandhjul 1 (valgfrit)", "coupling.gear_2": "Tandhjul 2", @@ -710,7 +701,6 @@ "dxf.progress.write": "Skriver DXF (5/5)", "dxf.tolerance": "Kurvetolerance", "dxf.tolerance_help": "Kun synlige emner. Kurver består af rette linjestykker. En mindre tolerance giver finere kurver og kræver mere tid og hukommelse.", - "expression, e.g. 25 mm": "udtryk, f.eks. 25 mm", "fits the face, e.g. M8": "passer til fladen, f.eks. M8", "from the rule": "fra reglen", "home.documentation": "Dokumentation", @@ -762,7 +752,6 @@ "nav.touchpad.zoom": "Zoom (lodret rulning eller knibebevægelse)", "needs two joints": "kræver to led", "new configuration": "ny konfiguration", - "new name": "nyt navn", "new parameter": "ny parameter", "no hole": "intet hul", "no interference": "intet overlap", @@ -778,10 +767,8 @@ "pick one or two vertices, edges or faces": "vælg et eller to hjørnepunkter, kanter eller flader", "render.background": "Scenebaggrund", "render.fit": "Tilpas til produkt", - "render.image_cancel": "Annullér rendering", "render.image_cancelled": "Rendering annulleret", "render.image_cancelling": "Annullerer", - "render.image_check": "Kontrollér, om billedet kan renderes", "render.image_completed": "Billedet er klar til eksport", "render.image_encoding": "Gemmer som PNG", "render.image_export": "Eksportér PNG", @@ -848,7 +835,6 @@ "sketch_text.align_right": "Højre", "sketch_text.bold": "Fed", "sketch_text.builtin_font": "{family} (indbygget)", - "sketch_text.font_search": "Søg efter skrifttyper", "sketch_text.italic": "Kursiv", "sketch_text.loading_fonts": "Søger efter installerede skrifttyper…", "sketch_text.on_path": "Teksten følger sin bane: Kurven bestemmer placeringen", @@ -877,9 +863,7 @@ "trace.stopped_by_loop": "Slutter ved prøve {sample}: Løkken gennem {joint} kan ikke lukkes yderligere", "ui.1_degree_of_freedom": "1 frihedsgrad", "ui.3d_mouse_speed": "3D-musens hastighed", - "ui.3d_mouse_windows": "3D-mus", "ui.3d_sketch": "3D-skitse", - "ui.about": "Om", "ui.activate": "Aktivér", "ui.active_component": "Aktiv komponent", "ui.agent_chat": "Agentchat", @@ -945,7 +929,6 @@ "ui.agent_chat_model_tip": "Modellen, som agenten bruger. Dit valg huskes for denne agent.", "ui.agent_chat_new": "Ny chat", "ui.agent_chat_new_confirm": "Start en ny chat? Den aktuelle chat ryddes.", - "ui.agent_chat_new_yes": "Ny chat", "ui.agent_chat_no_cli": "solidcraft-cli blev ikke fundet ved siden af appen, så agenten kan ikke få adgang til designet.", "ui.agent_chat_no_control": "Agentchattens endepunkt startede ikke, så agenten kan ikke få adgang til designet.", "ui.agent_chat_no_images": "Denne agent kan ikke modtage billeder", @@ -1081,7 +1064,6 @@ "ui.anti_aliasing": "Kantudjævning", "ui.apply": "Anvend", "ui.arc": "Bue", - "ui.assemble": "Samling", "ui.autosave_every": "Gem automatisk hver", "ui.axes": "Akser", "ui.axis": "Akse", @@ -1101,7 +1083,6 @@ "ui.chamfer": "Affasning", "ui.chamfer_type": "Affasningstype", "ui.change_workspace": "Skift arbejdsområde", - "ui.chord_length": "Kordelængde", "ui.circle": "Cirkel", "ui.circular_pattern": "Cirkulært mønster", "ui.clear": "Ryd", @@ -1134,11 +1115,9 @@ "ui.command_done": "{command} udført", "ui.command_palette_hint": "Skriv en kommando, f.eks. {example} — eller tryk S for at søge", "ui.concentric": "Koncentrisk", - "ui.configurations": "Konfigurationer", "ui.conic_curve": "Keglesnitskurve", "ui.constant": "Konstant", "ui.constraints_conflict": "Bindinger er i konflikt", - "ui.construct": "Konstruktion", "ui.construction": "Konstruktionsgeometri", "ui.context_menu": "Genvejsmenu", "ui.continuity_g0": "G0-knæk", @@ -1164,7 +1143,6 @@ "ui.cylinder": "Cylinder", "ui.dark": "Mørk", "ui.default_modelling_orientation": "Standardorientering for modellering", - "ui.degrees_of_freedom": "frihedsgrader", "ui.delete": "Slet", "ui.depth": "Dybde", "ui.design": "Design", @@ -1206,7 +1184,6 @@ "ui.equivalent_radius": "Ækvivalent radius", "ui.expand": "Fold ud", "ui.exportDxf": "Eksportér DXF…", - "ui.export_ellipsis": "Eksportér...", "ui.export_ellipsis_keymap": "Eksportér…", "ui.export_keymap_tip": "Gem tastaturgenveje, favoritter i S-menuen og værktøjslinjen i en fil", "ui.extend": "Forlæng", @@ -1296,7 +1273,6 @@ "ui.heading_sheet_metal": "PLADE", "ui.heading_shell": "UDHULNING", "ui.heading_sketch": "SKITSE", - "ui.heading_sketch_palette": "SKITSEPALETTE", "ui.heading_sketch_scale": "SKITSESKALERING", "ui.heading_solid": "MASSIVT EMNE", "ui.heading_sphere": "KUGLE", @@ -1334,7 +1310,6 @@ "ui.length_decimals": "Længdedecimaler", "ui.light": "Lys", "ui.line": "Linje", - "ui.linetype": "Linjetype", "ui.lock": "Lås", "ui.loft": "Loft", "ui.look_at": "Se på", @@ -1368,10 +1343,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF med flademærker", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Midtpunkt", "ui.mirror": "Spejling", "ui.mirror_line": "Spejlingslinje", - "ui.modify": "Redigér", "ui.move": "Flyt", "ui.multiple": "Flere", "ui.named_views": "Navngivne visninger", @@ -1392,7 +1365,6 @@ "ui.offset_plane": "Forskudt plan", "ui.one_side": "Én side", "ui.open": "Åbn", - "ui.open_ellipsis": "Åbn...", "ui.open_ends_tip": "Berører intet", "ui.open_preferences": "Åbn indstillinger", "ui.open_recent": "Åbn seneste", @@ -1402,8 +1374,6 @@ "ui.orbit_around": "Rotér omkring", "ui.orientation": "Orientering", "ui.origin": "Nulpunkt", - "ui.palette_points": "Punkter", - "ui.palette_profile": "Profil", "ui.pan": "Panorér", "ui.pan_zoom_orbit": "Panorér, zoom, rotér visning", "ui.parallel": "Parallel", @@ -1444,7 +1414,6 @@ "ui.quit": "Afslut", "ui.radial_context_menu": "Radial genvejsmenu", "ui.radius": "Radius", - "ui.radius_type": "Radiustype", "ui.rail": "Styrekurve", "ui.rail_fit": "Tilpasning til styrekurve", "ui.reachable": "Kan nås", @@ -1474,7 +1443,6 @@ "ui.right": "Fra højre", "ui.save": "Gem", "ui.save_as": "Gem som", - "ui.save_as_ellipsis": "Gem som...", "ui.save_mesh_no_slicer": "Ingen slicer fundet: Vælg Brugerdefineret…, og vælg dit slicerprogram, eller slå Send til slicer fra", "ui.save_mesh_pick_bodies": "Vælg de emner, der skal gemmes", "ui.save_mesh_temp_failed": "Kunne ikke oprette {folder}: {error}", @@ -1487,7 +1455,6 @@ "ui.select": "Vælg", "ui.selectOther": "Vælg andet…", "ui.selectTangentChain": "Vælg tangentkæde", - "ui.select_other_hint": "Vælg andet: Hold venstre museknap nede uden at flytte markøren på modellen (eller højreklik › Vælg andet…) for at vise alt under markøren. Klik igen samme sted for at vælge næste objekt.", "ui.selection": "Markering", "ui.selection_modifiers": "Taster ved markering", "ui.selection_shift_add_only": "Shift+klik tilføjer uden at fjerne", @@ -1507,7 +1474,6 @@ "ui.show_constraints": "Bindinger", "ui.show_construction_geometries": "Konstruktionsgeometri", "ui.show_dimensions": "Mål", - "ui.show_points": "Vis punkter", "ui.show_profile": "Vis profil", "ui.show_projected_geometries": "Projiceret geometri", "ui.simple": "Enkel", @@ -1518,7 +1484,6 @@ "ui.sketch_grid": "Skitsegitter", "ui.sketch_modifiers": "Taster i skitser", "ui.sketch_scale": "Skitseskalering", - "ui.sketch_status": "Skitsestatus", "ui.sketches": "Skitser", "ui.slice": "Lagdeling", "ui.slicer": "Slicer", @@ -1564,7 +1529,6 @@ "ui.stand_up_tip": "Når en STEP- eller IGES-fil åbnes med Z opad, tilbydes en kvart omdrejning om X", "ui.start": "Start", "ui.start_over": "Start forfra", - "ui.start_radius": "Startradius", "ui.stretch": "Stræk", "ui.supersampling": "Supersampling (SSAA)", "ui.supersampling_adaptive": "Kun når visningen står stille", @@ -1614,7 +1578,6 @@ "ui.untitled": "Uden titel", "ui.up_axis_note": "Hvilken akse der peger opad i visningerne (aksevisning, Hjem, Se på). Designs ændres ikke.", "ui.updateToCurrentView": "Opdatér til aktuel visning", - "ui.variable": "Variabel", "ui.view_centre": "Visningens centrum", "ui.visual_shaded_hidden_edges": "Skyggelagt med skjulte kanter", "ui.visual_shaded_with_edges": "Skyggelagt med kanter", @@ -1630,7 +1593,6 @@ "ui.zoom_to_the_cursor": "Zoom mod markøren", "update.available": "SolidCraft {version} er tilgængelig", "update.button": "Opdatér", - "update.button_tip": "SolidCraft {version} er tilgængelig", "update.button_version": "Opdatér til {version}", "update.check_automatically": "Søg automatisk efter opdateringer", "update.check_now": "Søg nu", diff --git a/assets/locales/de.json b/assets/locales/de.json index a440878745..f8d8432ac0 100644 --- a/assets/locales/de.json +++ b/assets/locales/de.json @@ -4,12 +4,14 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Screenshot anhängen: kopieren und einfügen oder als PNG speichern und hochladen.", "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Auf GitHub auf Submit new issue klicken, um den Bericht zu senden.", - "AI": "KI", "ARRANGE": "ANORDNEN", "Acceleration": "Beschleunigung", + "Actions": "Aktionen", + "Add or edit rule": "Regel hinzufügen oder bearbeiten", "Against": "Gegen", "Alignment": "Ausrichtung", "All components": "Alle Komponenten", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Wenden Sie zuerst Ihr Erscheinungsbild an. Exportiert zugewiesene Erscheinungsbilder und in diesem Design erstellte Materialien.", "Arc length": "Bogenlänge", "Area": "Fläche", "BROWSER": "BROWSER", @@ -21,11 +23,14 @@ "CONSTRAIN": "ABHÄNGIGKEITEN", "CONTACT": "KONTAKT", "CONVERT": "KONVERTIEREN", + "CSV or JSON: updates existing parameters, adds new ones": "CSV or JSON: updates existing parameters, adds new ones", "Centre to centre": "Mitte zu Mitte", "Check collisions": "Kollisionen prüfen", + "Choose…": "Auswählen…", "Circumference": "Umfang", "Collisions": "Kollisionen", - "Construction": "Konstruktionslinie", + "Comment": "Kommentar", + "Configuration rules": "Configuration rules", "Contact sets": "Kontaktsätze", "Continuity": "Stetigkeit", "Copy Error Message": "Fehlermeldung kopieren", @@ -35,32 +40,42 @@ "Current perspective view": "Aktuelle perspektivische Ansicht", "Curvature (G2)": "Krümmung (G2)", "Curvature change (G3)": "Krümmungsänderung (G3)", + "Delete": "Löschen", "Direction": "Richtung", "Do not export hidden edges": "Verdeckte Kanten nicht exportieren", "Don't ask again": "Nicht mehr fragen", + "Double-click to rename": "Double-click to rename", "Drive Joint": "Gelenk antreiben", "Duration": "Dauer", "Edges": "Kanten", + "Edit": "Bearbeiten", "Edit Profile Sketch": "Profile-Skizze bearbeiten", "Explode Text": "Text auflösen", "Export Animation…": "Animation exportieren…", + "Export CSV…": "CSV exportieren…", "Export Current View as DXF": "Aktuelle Ansicht als DXF exportieren", "Export Current View as DXF…": "Aktuelle Ansicht als DXF exportieren…", "Export DXF…": "DXF exportieren…", + "Export JSON…": "JSON exportieren…", "Export hidden edges as dashed lines": "Verdeckte Kanten gestrichelt exportieren", + "Expression": "Ausdruck", "Extend to meet": "Verlängern, bis sie sich treffen", "FILES": "DATEIEN", "FLAT": "ABWICKLUNG", "Face": "Fläche", "Faces": "Flächen", + "Favorites": "Favoriten", + "Favourite": "Favourite", + "Feature": "Funktion", + "Filter": "Filter", "Find in Browser": "Im Browser suchen", - "Flat": "Flach", "From edges": "Ausgangskanten", "HISTORY": "VERLAUF", "Hidden edges": "Verdeckte Kanten", "Holes": "Löcher", "Holes (edges on them)": "Löcher (ihre Kanten)", "IMAGES": "BILDER", + "Import…": "Importieren…", "In place (surfaces)": "An Ort und Stelle (Flächen)", "In plane": "In der Ebene", "Inside": "Innen", @@ -70,22 +85,35 @@ "LIBRARY": "BIBLIOTHEK", "LINKS": "VERKNÜPFUNGEN", "Length": "Länge", + "Length units": "Length units", + "Library file": "Bibliotheksdatei", + "Local library": "Lokale Bibliothek", "MANAGE": "VERWALTEN", "MATERIALS": "MATERIALIEN", "MODEL": "MODELL", "MOTION": "BEWEGUNG", + "Matched restrictions check the final values.": "Zutreffende Einschränkungen prüfen die endgültigen Werte.", "Max": "Max", "Min": "Min", "Mitre": "Gehrung", + "Model Parameters": "Modellparameter", + "Move down": "Nach unten", + "Move up": "Nach oben", + "Name": "Name", + "New": "Neu", + "New rule…": "Neue Regel…", "New study": "Neue Untersuchung", + "No matching appearance presets.": "Keine passenden Erscheinungsbildvorlagen.", "No visible bodies to export.": "Keine sichtbaren Körper zum Exportieren.", "None": "Keine", - "Normal to sketch": "Normal zur Skizze", "Notch": "Ausklinkung", "OUTPUT": "AUSGABE", + "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.", + "Operator": "Operator", "Other surface (mutual trim)": "Anderer Flächenkörper (gegenseitig trimmen)", "Outside": "Außen", "Overall length": "Gesamtlänge", + "Parameter": "Parameter", "Pass through": "Durch Punkte", "Picked": "Ausgewählte", "Picked point": "Gewählter Punkt", @@ -93,7 +121,6 @@ "Position (G0)": "Position (G0)", "Preferences › Design turns the offer back on": "Unter Einstellungen › Design lässt sich das Angebot wieder einschalten", "Profiles/Curves": "Profile/Kurven", - "Projection": "Projektion", "Quantity": "Größe", "REFERENCE": "REFERENZ", "RULED SURFACE": "REGELFLÄCHE", @@ -105,9 +132,13 @@ "Restore outer boundary": "Äußere Begrenzung wiederherstellen", "Revert Position": "Position zurücksetzen", "Round": "Rund", + "Rule": "Rule", + "Rule name": "Regelname", + "Rules run from top to bottom. Matched drives change effective values.": "Regeln werden von oben nach unten ausgeführt. Zutreffende Steuerungen ändern die wirksamen Werte.", "SECTION": "SCHNITT", "SURFACE QUALITY": "OBERFLÄCHENQUALITÄT", "Save the study as an animated GIF, seen as the viewport shows it": "Die Studie als animiertes GIF speichern, so wie der Ansichtsbereich sie zeigt", + "Search this library": "Diese Bibliothek durchsuchen", "Select bodies (a face picks its body)": "Körper auswählen (eine Fläche wählt ihren Körper)", "Selection filter": "Auswahlfilter", "Send your report on GitHub": "Bericht auf GitHub senden", @@ -124,6 +155,7 @@ "TRIM": "TRIMMEN", "Tangent (G1)": "Tangente (G1)", "Tear": "Reißen", + "Then": "Then", "Thickness Outside": "Dicke außen", "Thin": "Dünnwandig", "Thin Extrude": "Dünnwandig extrudieren", @@ -132,12 +164,21 @@ "Turn off section analysis before exporting the view.": "Deaktivieren Sie die Schnittanalyse, bevor Sie die Ansicht exportieren.", "Type": "Typ", "UNTRIM": "TRIMMEN AUFHEBEN", + "Unit": "Einheit", + "User Parameters": "Benutzerparameter", "VIEWS": "ANSICHTEN", + "Value": "Value", "Velocity": "Geschwindigkeit", "Vertices": "Scheitelpunkte", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Nur sichtbare Körper. Kurven werden durch Liniensegmente angenähert (Toleranz: 0,01 mm).", "Wall Location": "Wandlage", "Wall Thickness": "Wandstärke", + "When": "When", + "X angle": "X-Winkel", + "X distance": "X-Abstand", + "Y angle": "Y-Winkel", + "Y distance": "Y-Abstand", + "Z angle": "Z-Winkel", + "Z distance": "Z-Abstand", "appearance.dielectric_ior": "Brechungsindex des Dielektrikums", "appearance.metallic": "Metallanteil", "appearance.pbr_help": "Diese Oberfläche in Render oder einem Produkt-PNG ansehen. Keine Brechung oder Transmission.", @@ -145,7 +186,11 @@ "appearance.roughness": "Rauheit", "appearance.surface_response": "Oberflächenverhalten", "browser.body_name": "Körper {number}", + "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.", + "browser.capture_one": "Eine Komponente wurde verschoben, ihre Position ist aber nicht erfasst.", "browser.component_name": "Komponente {number}", + "browser.move_title": "Verschieben: {name}", "browser.parameters": "Parameter ({count})", "browser.precision": "Genauigkeit: {precision}", "browser.sketch_name": "Skizze {number}", @@ -471,6 +516,8 @@ "cmd.view_navigate": "Ansicht navigieren", "cmd.view_rename": "Benannte Ansicht umbenennen", "cmd.view_save": "Neue benannte Ansicht", + "contact.components_count": "{count} Komponenten", + "contact.enabled_label": "aktiviert", "coupling.gear_1": "Zahnrad 1", "coupling.gear_1_pick": "Zahnrad 1 (optional)", "coupling.gear_2": "Zahnrad 2", @@ -487,6 +534,7 @@ "coupling.rack": "Zahnstange", "coupling.radius_1": "Radius 1", "coupling.radius_2": "Radius 2", + "delete.and_more": "… und {count} weitere", "dialog.bodies_count": "{count} Körper", "dialog.clearance_invalid": "der Abstand muss eine Länge von 0 oder mehr sein", "dialog.compute": "Berechnen", @@ -494,6 +542,8 @@ "dialog.computing": "Wird berechnet… {seconds} s", "dialog.could_not_be_checked": "nicht prüfbar", "dialog.custom_axis": "↖ Eigene Achse", + "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.select": "↖ Auswählen", "dialog.selected_count": "↖ {count} ausgewählt", @@ -517,6 +567,15 @@ "dxf.progress.write": "DXF schreiben (5/5)", "dxf.tolerance": "Kurventoleranz", "dxf.tolerance_help": "Nur sichtbare Körper. Kurven werden durch gerade Segmente angenähert. Eine kleinere Toleranz liefert feinere Kurven und benötigt mehr Zeit und Speicher.", + "help.apache": "Apache-Lizenz 2.0", + "help.attributions": "Quellenangaben (ATTRIBUTION.md)", + "help.build": "Version {version} · Commit {commit} · erstellt am {date}", + "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.mit": "MIT-Lizenz", + "help.notice": "NOTICE", + "help.third_party": "Drittanbieterlizenzen", "home.documentation": "Dokumentation", "home.in_this_browser": "in diesem Browser", "home.missing": "fehlt", @@ -545,27 +604,51 @@ "joint.from_bodies_hint": "Jeder ausgewählte Körper kommt in eine eigene neue Komponente und bleibt, wo er ist.", "joint.one_body": "Beide Auswahlen liegen auf {body}. Wählen Sie die zweite auf einem anderen Teil.", "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.", + "keymap.command_id": "Befehls-ID", + "keymap.empty": "Keine passenden Befehle", + "keymap.use_anyway": "{key} hier trotzdem verwenden", + "manage.appearance_hint": "Wählen Sie ein Erscheinungsbild für die Auswahl oder ziehen Sie ein Farbmuster auf einen Körper oder eine Fläche.", + "manage.assign_hint": "Wählen Sie Körper und deren Kunststoff-Konstruktionsregel aus.", + "manage.config_hint": "Bearbeiten Sie Konfigurationszeilen, Spalten und Regeln und aktivieren Sie dann eine Zeile oder zeigen Sie deren Vorschau an.", + "manage.empty": "Keine passenden Zeilen.", + "manage.material_hint": "Wählen Sie Körper und deren physikalisches Material aus.", + "manage.parameters_hint": "Bearbeiten Sie Ausdrücke und Einheiten. Änderungen werden mit der Eingabetaste oder beim Verlassen eines Feldes übernommen.", + "manage.rules_hint": "Wählen Sie eine Regel zum Bearbeiten oder erstellen Sie eine neue. Bestätigen Sie, um die Regel zu speichern.", + "measure.at": "Bei", + "measure.item": "Element {number}", "motion.checked_to": "Nur bis Schritt {step} geprüft: der Prüfaufwand ist erschöpft", "motion.collision": "{a} · {b}: Schritte {steps}", + "motion.delete_key": "Schlüsselbild löschen", "motion.duration_hint": "Sekunden (leer: pro Schritt)", + "motion.key_here": "Schlüsselbild hier setzen", + "motion.key_here_hint": "Ein Schlüsselbild bei diesem Schritt mit dem aktuellen Gelenkwert hinzufügen", "motion.no_collisions": "Keine Kollisionen in der Untersuchung", + "motion.step_back": "Schritt zurück", + "motion.step_forward": "Schritt vor", + "motion.to_end": "Zum Ende", + "motion.to_start": "Zum Anfang", + "motion.track_name": "Spur {number}", "nav.touchpad.help": "Umschalt gedrückt halten und den Zeiger zum Verschieben bewegen; mit Alt/Option drehen. Keine Maustaste nötig. Vertikales Scrollen zoomt.", "nav.touchpad.label": "Touchpad", "nav.touchpad.orbit": "Drehen (Alt/Option halten und bewegen; ohne Maustaste)", "nav.touchpad.pan": "Verschieben (Umschalt halten und bewegen; ohne Maustaste)", "nav.touchpad.scroll": "Touchpad verwendet vertikales Scrollen zum Zoomen. Die Zwei-Finger-Verschiebung bleibt für andere Vorgaben gespeichert.", "nav.touchpad.zoom": "Zoom (vertikal scrollen oder Finger spreizen/zusammenziehen)", + "new parameter": "new parameter", + "none": "keine", + "parts.automatic_name": "automatisch", "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", "pattern.direction_2": "Richtung 2", "pattern.flip_direction": "Richtung umkehren", + "prefs.cpu": "CPU (Software)", + "prefs.minutes_suffix": " min", + "properties.title": "Eigenschaften: {name}", "render.background": "Szenenhintergrund", "render.fit": "Produkt einpassen", - "render.image_cancel": "Rendering abbrechen", "render.image_cancelled": "Rendering abgebrochen", "render.image_cancelling": "Wird abgebrochen", - "render.image_check": "Renderanfrage prüfen", "render.image_completed": "Bild bereit zum Export", "render.image_encoding": "PNG wird kodiert", "render.image_export": "PNG exportieren", @@ -622,15 +705,28 @@ "settings.reset_intro": "Diese Einstellungen funktionieren auf diesem Computer nicht und würden den Start von SolidCraft verhindern. Sie wurden daher geändert:", "settings.reset_title": "Einstellungen wurden geändert", "settings.supersampling_note": "Zeichnet die Ansicht in höherer Auflösung und rechnet sie herunter: glattere Kanten, Linien und Raster auf Bildschirmen unter 4K. Kostet GPU-Zeit: etwa 2,25-mal so viele Pixel bei 1,5× und 4-mal bei 2×.", + "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", + "shortcut.unpin": "Loslösen", "sketch.degrees_of_freedom": "{count} Freiheitsgrade", + "sketch.dof_left": "{count} Freiheitsgrade übrig", + "sketch.dof_left_one": "{count} Freiheitsgrad übrig", "sketch.draw_curves": "Als {linetype} zeichnen", "sketch.make_selection": "Auswahl als {linetype} markieren", + "sketch_panel.attached": "Angehängt an {body} / {path}", + "sketch_panel.author": "Autorenplatzierung: {path}", + "sketch_panel.dimension_hint": "Einen Wert oder Ausdruck eingeben, dann OK drücken.", + "sketch_panel.move_hint": "Abstände und einen Winkel eingeben, dann OK drücken.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "An eine ebene Fläche eines Blechkörpers angehängt; folgt diesem Bereich bei Änderungen am Blech.", + "sketch_panel.text_hint": "Text und Platzierung bearbeiten, dann OK drücken.", + "sketch_panel.use_plane": "Ebene {plane} verwenden", "sketch_text.align_center": "Mitte", "sketch_text.align_left": "Links", "sketch_text.align_right": "Rechts", "sketch_text.bold": "Fett", "sketch_text.builtin_font": "{family} (integriert)", - "sketch_text.font_search": "Schriftarten suchen", "sketch_text.italic": "Kursiv", "sketch_text.loading_fonts": "Installierte Schriftarten werden gesucht…", "sketch_text.on_path": "Der Text folgt seiner Kurve: sie bestimmt seine Lage", @@ -643,6 +739,7 @@ "timeline.rename_group": "Merkmalsordner umbenennen", "timeline.reorder_many": "Merkmale neu ordnen", "timeline.ungroup": "Merkmalsordner entfernen", + "toolbar.customize_note": "Registerkarte {tab}. Markieren Sie einen Befehl, um ihm eine Schaltfläche in der Werkzeugleiste zu geben. Sie können auch eine Zeile aus dem Aufklappmenü eines Panels auf die Werkzeugleiste ziehen, eine Schaltfläche zum Entfernen aus der Werkzeugleiste ziehen oder die Stecknadel einer Menüzeile verwenden.", "toolbar.image": "BILD", "toolbar.render": "RENDERN", "toolbar.render_scene": "SZENE", @@ -651,7 +748,6 @@ "trace.stopped_by_loop": "Endet bei Probe {sample}: die Schleife über {joint} lässt sich nicht weiter schließen", "ui.1_degree_of_freedom": "1 Freiheitsgrad", "ui.3d_sketch": "3D-Skizze", - "ui.about": "Über", "ui.activate": "Aktivieren", "ui.active_component": "Aktive Komponente", "ui.agent_chat": "Agenten-Chat", @@ -689,6 +785,7 @@ "ui.agent_chat_left": "Links", "ui.agent_chat_limit": "Der Agent hat die Grenze von {limit} Änderungen für eine Nachricht erreicht. Senden Sie eine weitere Nachricht, damit er weitermacht.", "ui.agent_chat_minimize": "Zu einer Leiste minimieren", + "ui.agent_chat_next_picture": "Nächstes Bild", "ui.agent_chat_no_cli": "solidcraft-cli wurde neben der App nicht gefunden, daher kann der Agent das Design nicht erreichen.", "ui.agent_chat_no_control": "Der Endpunkt des Agenten-Chats ist nicht gestartet, daher kann der Agent das Design nicht erreichen.", "ui.agent_chat_no_images": "Dieser Agent nimmt keine Bilder an", @@ -699,6 +796,7 @@ "ui.agent_chat_permission": "Der Agent möchte ausführen: {title}", "ui.agent_chat_pick": "Agent", "ui.agent_chat_placeholder": "Bitten Sie den Agenten, das Design zu ändern…", + "ui.agent_chat_previous_picture": "Vorheriges Bild", "ui.agent_chat_privacy": "Nichts geht irgendwohin außer an den Agenten, den Sie unter Ihrem eigenen Konto ausführen.", "ui.agent_chat_privacy_note": "SolidCraft sendet nichts irgendwohin: Der Agent, den Sie starten, spricht unter Ihrem eigenen Konto mit seinem eigenen Dienst. SolidCraft sieht und speichert nie einen Schlüssel oder ein Passwort.", "ui.agent_chat_refresh": "Aktualisieren", @@ -743,6 +841,7 @@ "ui.ai_bad_url": "Geben Sie eine URL ein, die mit http:// oder https:// beginnt", "ui.ai_base_url": "Basis-URL", "ui.ai_button": "KI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "Kopie", "ui.ai_custom_url": "Eigene URL", "ui.ai_default": "Standard", @@ -763,8 +862,10 @@ "ui.ai_mode_edits_note": "Modellierbefehle laufen ohne Nachfrage; Speichern, Öffnen, Exportieren und andere Datei- oder destruktive Aktionen fragen weiterhin.", "ui.ai_mode_global": "Wie oben eingestellt", "ui.ai_model": "Modell", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Diese Einstellungen gelten für die Werkzeuge von SolidCraft. Die eigenen Werkzeuge eines Agenten (seine Shell, seine Dateiänderungen) folgen seinem eigenen Modus, den Sie im Kopf des Chat-Panels wählen, wenn der Agent Modi hat.", "ui.ai_name": "Name", + "ui.ai_name_hint": "Mein Agent", "ui.ai_need_command": "Geben Sie den Befehl ein, der den Agenten startet", "ui.ai_need_model": "Geben Sie den Modellnamen ein", "ui.ai_no_answer": "Der Agent hat nicht rechtzeitig geantwortet", @@ -795,6 +896,7 @@ "ui.ai_toolbar_button": "Schaltfläche in der Werkzeugleiste anzeigen", "ui.ai_toolbar_button_note": "Die KI-Schaltfläche am rechten Ende der Werkzeugleiste. Strg+Umschalt+A und das Ansicht-Menü öffnen das Panel weiterhin.", "ui.ai_undo_tip": "Alles, was der Agent für diese Nachricht geändert hat, in einem Schritt rückgängig machen", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Alle", "ui.ambient_occlusion": "Umgebungsverdeckung", "ui.ambient_occlusion_help": "Sanfte Schattierung an Kontaktstellen und in Vertiefungen undurchsichtiger Flächen. Erfordert die GPU-Ansicht.", @@ -808,7 +910,6 @@ "ui.anti_aliasing": "Kantenglättung", "ui.apply": "Anwenden", "ui.arc": "Kreisbogen", - "ui.assemble": "Zusammenbau", "ui.autosave_every": "Autospeichern alle", "ui.axes": "Achsen", "ui.axis": "Achse", @@ -819,6 +920,11 @@ "ui.bodies_all_if_none": "Körper (alle, wenn keine)", "ui.box": "Block", "ui.break": "Unterbrechen", + "ui.browser_bodies_one": "{count} Körper", + "ui.browser_bodies_other": "{count} Körper", + "ui.browser_collapse": "Browser einklappen", + "ui.browser_expand": "Browser ausklappen", + "ui.browser_title": "Browser", "ui.calibrate": "Kalibrieren", "ui.cancel": "Abbrechen", "ui.canvases": "Kanvas", @@ -828,7 +934,6 @@ "ui.chamfer": "Abschrägung", "ui.chamfer_type": "Fasentyp", "ui.change_workspace": "Arbeitsbereich wechseln", - "ui.chord_length": "Sehnenlänge", "ui.circle": "Kreis", "ui.circular_pattern": "Radiales Muster", "ui.clear_recent": "Zuletzt verwendet leeren", @@ -858,11 +963,9 @@ "ui.command_done": "{command} fertig", "ui.command_palette_hint": "Befehl eingeben, z. B. {example} — oder S drücken, um zu suchen", "ui.concentric": "Konzentrisch", - "ui.configurations": "Konfigurationen", "ui.conic_curve": "Kegelschnitt", "ui.constant": "Konstant", "ui.constraints_conflict": "Bedingungen konfliktreich", - "ui.construct": "Konstruktion", "ui.construction": "Konstruktionslinie", "ui.continuity_g0": "G0 Knick", "ui.continuity_g1": "G1 Tangente", @@ -887,7 +990,6 @@ "ui.cylinder": "Zylinder", "ui.dark": "Dunkel", "ui.default_modelling_orientation": "Standard-Modellierungsorientierung", - "ui.degrees_of_freedom": "Freiheitsgrade", "ui.delete": "Löschen", "ui.depth": "Tiefe", "ui.design": "Design", @@ -929,7 +1031,6 @@ "ui.equivalent_radius": "Äquivalenter Radius", "ui.expand": "Aufklappen", "ui.exportDxf": "Als DXF exportieren…", - "ui.export_ellipsis": "Exportieren...", "ui.export_ellipsis_keymap": "Exportieren…", "ui.extend": "Verlängern", "ui.extent_type": "Ausdehnungstyp", @@ -1011,7 +1112,6 @@ "ui.heading_sheet_metal": "BLECH", "ui.heading_shell": "HÜLLE", "ui.heading_sketch": "SKIZZE", - "ui.heading_sketch_palette": "SKIZZENPALETTE", "ui.heading_sketch_scale": "SKIZZE SKALIEREN", "ui.heading_solid": "KÖRPER", "ui.heading_sphere": "SPHÄRE", @@ -1029,6 +1129,7 @@ "ui.hideOrigin": "Ursprung ausblenden", "ui.hidePlanes": "Ebenen ausblenden", "ui.hideProfile": "Profil ausblenden", + "ui.history_title": "Verlauf", "ui.hole": "Bohrung", "ui.hole_tap_type": "Gewindetyp", "ui.hole_type": "Bohrungstyp", @@ -1047,7 +1148,6 @@ "ui.length_decimals": "Längen-Dezimalstellen", "ui.light": "Hell", "ui.line": "Linie", - "ui.linetype": "Linientyp", "ui.lock": "Sperren", "ui.loft": "Loft", "ui.look_at": "Ansehen", @@ -1078,10 +1178,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF mit Flächenmarken", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Mittelpunkt", "ui.mirror": "Spiegeln", "ui.mirror_line": "Spiegelachse", - "ui.modify": "Ändern", "ui.move": "Verschieben", "ui.multiple": "Mehrfach", "ui.named_views": "Benannte Ansichten", @@ -1101,7 +1199,6 @@ "ui.offset_plane": "Abstandsebene", "ui.one_side": "Einseitig", "ui.open": "Öffnen", - "ui.open_ellipsis": "Öffnen...", "ui.open_preferences": "Einstellungen öffnen", "ui.open_recent": "Zuletzt verwendet", "ui.operation": "Operation", @@ -1110,8 +1207,9 @@ "ui.orbit_around": "Umkreisen um", "ui.orientation": "Ausrichtung", "ui.origin": "Ursprung", - "ui.palette_points": "Punkte", - "ui.palette_profile": "Profil", + "ui.palette_construction_mode": "Konstruktionsmodus (X)", + "ui.palette_more": "Mehr", + "ui.palette_sketch_options": "Skizzenoptionen", "ui.pan": "Verschieben", "ui.pan_zoom_orbit": "Verschieben, zoomen, umkreisen", "ui.parallel": "Parallel", @@ -1141,7 +1239,6 @@ "ui.quantity_2": "Anzahl 2", "ui.quit": "Beenden", "ui.radius": "Radius", - "ui.radius_type": "Radius-Typ", "ui.rail": "Leitkurve", "ui.rail_fit": "Anpassung an Leitkurve", "ui.reachable": "Erreichbar", @@ -1163,7 +1260,6 @@ "ui.right": "Rechts", "ui.save": "Speichern", "ui.save_as": "Speichern unter", - "ui.save_as_ellipsis": "Speichern unter...", "ui.save_mesh_no_slicer": "Kein Slicer gefunden: „Benutzerdefiniert…“ wählen und das Slicer-Programm angeben, oder „An Slicer senden“ ausschalten", "ui.save_mesh_pick_bodies": "Wähle die Körper zum Speichern aus", "ui.save_mesh_temp_failed": "{folder} konnte nicht angelegt werden: {error}", @@ -1192,7 +1288,6 @@ "ui.show_constraints": "Bedingungen", "ui.show_construction_geometries": "Konstruktionsgeometrien", "ui.show_dimensions": "Maße", - "ui.show_points": "Punkte anzeigen", "ui.show_profile": "Profil anzeigen", "ui.show_projected_geometries": "Projizierte Geometrien", "ui.simple": "Einfach", @@ -1202,7 +1297,6 @@ "ui.sketch_dimension": "Skizzenmaß", "ui.sketch_grid": "Skizzenraster", "ui.sketch_scale": "Skizzenskalierung", - "ui.sketch_status": "Skizzenstatus", "ui.sketches": "Skizzen", "ui.slice": "Scheibe", "ui.slicer": "Slicer", @@ -1243,7 +1337,6 @@ "ui.split_body": "Körper trennen", "ui.start": "Start", "ui.start_over": "Von vorn beginnen", - "ui.start_radius": "Startradius", "ui.stretch": "Strecken", "ui.supersampling": "Supersampling (SSAA)", "ui.supersampling_adaptive": "Nur bei ruhender Ansicht", @@ -1285,7 +1378,6 @@ "ui.unsuppress": "Unterdrückung aufheben", "ui.untitled": "Ohne Titel", "ui.updateToCurrentView": "Auf aktuelle Ansicht aktualisieren", - "ui.variable": "Variabel", "ui.view_centre": "Ansichtszentrum", "ui.visual_style": "Ansichtsstil", "ui.workspace": "Arbeitsbereich", @@ -1295,7 +1387,6 @@ "ui.zoom_to_the_cursor": "Zum Cursor zoomen", "update.available": "SolidCraft {version} ist verfügbar", "update.button": "Update", - "update.button_tip": "SolidCraft {version} ist verfügbar", "update.button_version": "Auf {version} aktualisieren", "update.check_automatically": "Automatisch nach Updates suchen", "update.check_now": "Jetzt suchen", @@ -1327,5 +1418,6 @@ "update.title": "Update verfügbar", "update.up_to_date": "SolidCraft ist aktuell.", "update.update_now": "Jetzt aktualisieren", - "update.you_have": "Installiert ist {version}." + "update.you_have": "Installiert ist {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ auswählen · Eingabe / Tab vervollständigen · Esc schließen" } diff --git a/assets/locales/en.json b/assets/locales/en.json index c04b52ad38..b5d51025cd 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -6,11 +6,11 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Click Submit new issue on GitHub to send your report.", "A new configuration, a copy of the active one": "A new configuration, a copy of the active one", - "AI": "AI", "ARRANGE": "ARRANGE", "About SolidCraft": "About SolidCraft", "Acceleration": "Acceleration", "Access": "Access", + "Actions": "Actions", "Active": "Active", "Active rule": "Active rule", "Add": "Add", @@ -18,6 +18,7 @@ "Add Mid Radius": "Add Mid Radius", "Add Row": "Add Row", "Add condition": "Add condition", + "Add or edit rule": "Add or edit rule", "Against": "Against", "Alignment": "Alignment", "All Sketch Points": "All Sketch Points", @@ -31,6 +32,7 @@ "Applied Radius": "Applied Radius", "Apply To": "Apply To", "Apply to selection": "Apply to selection", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Apply your appearance first. Exports assigned appearances and materials created in this design.", "Arc length": "Arc length", "Area": "Area", "As-Built": "As-Built", @@ -67,16 +69,17 @@ "Check collisions": "Check collisions", "Checked": "Checked", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.", + "Choose…": "Choose…", "Circular": "Circular", "Circumference": "Circumference", "Close draft": "Close draft", "Collisions": "Collisions", "Colour": "Colour", + "Comment": "Comment", "Component1": "Component1", "Config Table": "Config Table", "Configuration": "Configuration", "Configuration rules": "Configuration rules", - "Construction": "Construction", "Contact": "Contact", "Contact Sets": "Contact Sets", "Contact sets": "Contact sets", @@ -100,11 +103,11 @@ "Curvature (G2)": "Curvature (G2)", "Curvature change (G3)": "Curvature change (G3)", "Customize Toolbar": "Customize Toolbar", + "Delete": "Delete", "Delete all": "Delete all", "Delete rule": "Delete rule", "Delete the row": "Delete the row", "Delete the set": "Delete the set", - "Delete this parameter": "Delete this parameter", "Density": "Density", "Dependents": "Dependents", "Designs you open or save appear here.": "Designs you open or save appear here.", @@ -118,6 +121,7 @@ "Drive Joint": "Drive Joint", "Duration": "Duration", "Edges": "Edges", + "Edit": "Edit", "Edit Profile Sketch": "Edit Profile Sketch", "Enable rule": "Enable rule", "Enabled": "Enabled", @@ -126,11 +130,14 @@ "Explode": "Explode", "Explode Text": "Explode Text", "Export Animation…": "Export Animation…", + "Export CSV…": "Export CSV…", "Export Current View as DXF": "Export Current View as DXF", "Export Current View as DXF…": "Export Current View as DXF…", "Export DXF": "Export DXF", "Export DXF…": "Export DXF…", + "Export JSON…": "Export JSON…", "Export hidden edges as dashed lines": "Export hidden edges as dashed lines", + "Expression": "Expression", "Extend to meet": "Extend to meet", "Extents": "Extents", "FILES": "FILES", @@ -140,12 +147,13 @@ "Faces": "Faces", "Faces / edges / vertices": "Faces / edges / vertices", "Fastener": "Fastener", + "Favorites": "Favorites", "Favourite": "Favourite", + "Feature": "Feature", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Files from many CAD programs have Y up, so they open lying on their back here (Z up).", "Filter": "Filter", "Find in Browser": "Find in Browser", "Finish": "Finish", - "Flat": "Flat", "Flat Pattern": "Flat Pattern", "Flat size": "Flat size", "Follow camera (Look At sketches)": "Follow camera (Look At sketches)", @@ -164,6 +172,7 @@ "Home view": "Home view", "Horiz./Vert.": "Horiz./Vert.", "IMAGES": "IMAGES", + "Import…": "Import…", "In This Design": "In This Design", "In place (surfaces)": "In place (surfaces)", "In plane": "In plane", @@ -185,7 +194,9 @@ "Length units": "Length units", "Letter Spacing": "Letter Spacing", "Library": "Library", + "Library file": "Library file", "Line Spacing": "Line Spacing", + "Local library": "Local library", "Loop": "Loop", "MANAGE": "MANAGE", "MATERIALS": "MATERIALS", @@ -194,41 +205,49 @@ "Margin": "Margin", "Mass": "Mass", "Mass Center": "Mass Center", + "Matched restrictions check the final values.": "Matched restrictions check the final values.", "Material": "Material", "Max": "Max", "Mid Radius": "Mid Radius", "Middle double-click": "Middle double-click", "Min": "Min", "Mitre": "Mitre", + "Model Parameters": "Model Parameters", "Modeled": "Modeled", "More toolbar panels": "More toolbar panels", "Motion": "Motion", "Move / Copy": "Move / Copy", "Move Type": "Move Type", + "Move down": "Move down", "Move earlier": "Move earlier", "Move later": "Move later", + "Move up": "Move up", "Name": "Name", "Nearest": "Nearest", + "New": "New", "New Comp.": "New Comp.", "New Sketch": "New Sketch", + "New rule…": "New rule…", "New study": "New study", "No crash reports.": "No crash reports.", + "No matching appearance presets.": "No matching appearance presets.", "No matching appearances": "No matching appearances", "No visible bodies to export.": "No visible bodies to export.", "None": "None", - "Normal to sketch": "Normal to sketch", "Not available yet": "Not available yet", "Notch": "Notch", "OUTPUT": "OUTPUT", "Opacity": "Opacity", "Open GitHub draft": "Open GitHub draft", + "Open local libraries in the desktop app. You can export this design's library here.": "Open local libraries in the desktop app. You can export this design's library here.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.", + "Operator": "Operator", "Other side": "Other side", "Other surface (mutual trim)": "Other surface (mutual trim)", "Outside": "Outside", "Overall length": "Overall length", "Param. Graph": "Param. Graph", - "Parameter values": "Parameter values", + "Parameter": "Parameter", "Parts": "Parts", "Pass through": "Pass through", "Path": "Path", @@ -249,7 +268,6 @@ "Preview Row": "Preview Row", "Preview leaves the current design unchanged.": "Preview leaves the current design unchanged.", "Profiles/Curves": "Profiles/Curves", - "Projection": "Projection", "Quantity": "Quantity", "RECENT": "RECENT", "REFERENCE": "REFERENCE", @@ -289,6 +307,8 @@ "Roll History Marker Here": "Roll History Marker Here", "Round": "Round", "Rule": "Rule", + "Rule name": "Rule name", + "Rules run from top to bottom. Matched drives change effective values.": "Rules run from top to bottom. Matched drives change effective values.", "SECTION": "SECTION", "SURFACE QUALITY": "SURFACE QUALITY", "Save Rule": "Save Rule", @@ -299,6 +319,7 @@ "Scene": "Scene", "Search": "Search", "Search commands": "Search commands", + "Search this library": "Search this library", "Section": "Section", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).", "Select bodies (a face picks its body)": "Select bodies (a face picks its body)", @@ -360,18 +381,19 @@ "Turn off section analysis before exporting the view.": "Turn off section analysis before exporting the view.", "Type": "Type", "UNTRIM": "UNTRIM", + "Unit": "Unit", "Updating the preview": "Updating the preview", "Use active author placement": "Use active author placement", "Use fixed plane": "Use fixed plane", "Use saved fixed plane": "Use saved fixed plane", "Use selected face as fixed plane": "Use selected face as fixed plane", + "User Parameters": "User Parameters", "VIEW": "VIEW", "VIEWS": "VIEWS", "Value": "Value", "Velocity": "Velocity", "Vertices": "Vertices", "View navigator": "View navigator", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Visible bodies only. Curves use line segments (0.01 mm tolerance).", "Volume": "Volume", "Wall": "Wall", "Wall Location": "Wall Location", @@ -379,6 +401,12 @@ "When": "When", "Width": "Width", "Will fail": "Will fail", + "X angle": "X angle", + "X distance": "X distance", + "Y angle": "Y angle", + "Y distance": "Y distance", + "Z angle": "Z angle", + "Z distance": "Z distance", "Zebra": "Zebra", "angle, height": "angle, height", "appearance.dielectric_ior": "Dielectric IOR", @@ -388,7 +416,13 @@ "appearance.roughness": "Roughness", "appearance.surface_response": "Surface response", "browser.body_name": "Body{number}", + "browser.calibrate_title": "Calibrate: {name}", + "browser.capture_many": "{count} components were moved but their positions aren't captured.", + "browser.capture_note": "Capture keeps the new position; Revert puts it back.", + "browser.capture_one": "A component was moved but its position isn't captured.", "browser.component_name": "Component{number}", + "browser.edit_canvas_title": "Edit Canvas: {name}", + "browser.move_title": "Move: {name}", "browser.parameters": "Parameters ({count})", "browser.precision": "Precision: {precision}", "browser.sketch_name": "Sketch{number}", @@ -726,7 +760,15 @@ "cmd.view_navigate": "Navigate View", "cmd.view_rename": "Rename Named View", "cmd.view_save": "New Named View", - "command name": "command name", + "color.copy": "Click to copy color values", + "color.edit": "Click to edit color", + "color.gamma": "Showing color values in 0-255 gamma space", + "color.hue": "Hue", + "color.linear": "Showing color values in 0-1 linear space", + "color.saturation_value": "Saturation and value", + "color.selected": "Selected color", + "contact.components_count": "{count} components", + "contact.enabled_label": "enabled", "coupling.gear_1": "Gear 1", "coupling.gear_1_pick": "Gear 1 (optional)", "coupling.gear_2": "Gear 2", @@ -743,9 +785,21 @@ "coupling.rack": "Rack", "coupling.radius_1": "Radius 1", "coupling.radius_2": "Radius 2", + "crash.announce": "SolidCraft closed unexpectedly or hit an internal error last time. A crash report was saved (Help > Crash Reports…).", + "crash.copy_folder": "Copy folder path", + "crash.copy_path": "Copy path", + "crash.deleted": "Deleted {count} report(s).", "crash.detected_time": "Detected {time} UTC", + "crash.intro": "Crash reports stay on this computer and are never sent anywhere. Report… opens a draft you review before anything is shared.", + "crash.kind_command": "Internal error in a command", + "crash.kind_internal": "Internal error", + "crash.kind_kernel": "Kernel error (recovered)", + "crash.kind_unclean": "Closed unexpectedly", "crash.previous_session": "An earlier SolidCraft session ended unexpectedly or hit an internal error. Reports from unclean exits are detected at startup. See Help > Crash Reports… for details.", + "crash.replayable": "replayable", + "crash.report": "Report…", "crash.unclean_explanation": "Detected on a later launch; the exact exit time was not recorded. A system shutdown or forced stop can also cause an unclean exit.", + "delete.and_more": "… and {count} more", "dialog.bodies_count": "{count} bodies", "dialog.clearance_invalid": "the clearance must be a length of 0 or more", "dialog.compute": "Compute", @@ -753,6 +807,8 @@ "dialog.computing": "Computing… {seconds} s", "dialog.could_not_be_checked": "could not be checked", "dialog.custom_axis": "↖ Custom Axis", + "dialog.dimension_insert_hint": "Click another driving dimension to insert its name", + "dialog.dimension_name": "Dimension: {name}", "dialog.edit_title": "EDIT {title}", "dialog.select": "↖ Select", "dialog.selected_count": "↖ {count} selected", @@ -776,9 +832,30 @@ "dxf.progress.write": "Writing DXF (5/5)", "dxf.tolerance": "Curve tolerance", "dxf.tolerance_help": "Visible bodies only. Curves use straight segments. Smaller tolerance gives finer curves and uses more time and memory.", - "expression, e.g. 25 mm": "expression, e.g. 25 mm", + "file_load.built": "Built so far: {built} of {total} features.", + "file_load.converting": "Converting solid {done} of {total}", + "file_load.inserting": "Inserting geometry", + "file_load.opening": "Opening design", + "file_load.preparing_thumbnail": "Preparing the thumbnail...", + "file_load.preparing_view": "Preparing the view...", + "file_load.preview_note": "Saved preview. Geometry is being checked before editing.", + "file_load.reading": "Reading the file...", + "file_load.reading_percent": "Reading the file... {percent}%", + "file_load.rebuilding": "Rebuilding feature {done} of {total}", + "file_load.recovering": "Recovering design", + "file_load.stopping": "Waiting for the previous operation to stop...", + "file_load.waiting": "Waiting for file operation", "fits the face, e.g. M8": "fits the face, e.g. M8", "from the rule": "from the rule", + "help.apache": "Apache licence 2.0", + "help.attributions": "Attributions (ATTRIBUTION.md)", + "help.build": "Version {version} · commit {commit} · built {date}", + "help.font_details": "The interface uses egui's built-in fonts (Ubuntu Light and Hack, under the Ubuntu Font Licence and the MIT/Bitstream Vera licence; Noto Emoji and the emoji icon font, under the OFL and MIT). Sketch text uses Liberation Sans (SIL OFL 1.1). The Crafting Apps' optional font pack, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), is not bundled in SolidCraft builds.", + "help.fonts": "Fonts", + "help.licences": "Licences", + "help.mit": "MIT licence", + "help.notice": "NOTICE", + "help.third_party": "Third-party licences", "home.documentation": "Documentation", "home.in_this_browser": "in this browser", "home.missing": "missing", @@ -804,9 +881,30 @@ "install.menu": "Install app…", "install.steps_intro": "Safari installs web apps from its own menus:", "install.title": "Install SolidCraft", + "issue.body_label": "Report text", + "issue.note_image_copied": "Paste the image into the GitHub draft, or save the PNG if image copying is unavailable.", + "issue.note_opened": "Review the GitHub draft and attach the screenshot. GitHub's Submit new issue button publishes it.", + "issue.note_opened_copied": "Report copied. Paste into the GitHub draft, attach the screenshot, then click Submit new issue on GitHub.", + "issue.note_removed": "Screenshot removed from this draft.", + "issue.note_report_copied": "Report copied. Paste it into the GitHub draft if needed.", + "issue.note_save_failed": "Could not save screenshot: {error}", + "issue.note_saved": "Screenshot saved. Attach the PNG in the GitHub draft.", + "issue.save_screenshot": "Save screenshot…", "joint.from_bodies_hint": "Each picked body moves into a new component of its own and stays where it is.", "joint.one_body": "Both picks are on {body}. Pick the second one on another part.", "joint.one_component": "These parts are bodies of one component: {bodies}. A joint moves one component against another, so each part needs a component of its own.", + "keymap.command_id": "Command ID", + "keymap.empty": "No matching commands", + "keymap.use_anyway": "Use {key} here anyway", + "manage.appearance_hint": "Choose a look for the selection, or drag a swatch onto a body or face.", + "manage.assign_hint": "Select bodies and choose their plastic design rule.", + "manage.config_hint": "Edit configuration rows, columns and rules, then activate or preview a row.", + "manage.empty": "No matching rows.", + "manage.material_hint": "Select bodies and choose their physical material.", + "manage.parameters_hint": "Edit expressions and units. Changes apply on Enter or when leaving a field.", + "manage.rules_hint": "Choose a rule to edit, or create a new rule. Confirm to save the rule.", + "measure.at": "At", + "measure.item": "Item {number}", "messages.ago_h": "{n} h ago", "messages.ago_min": "{n} min ago", "messages.ago_s": "{n} s ago", @@ -818,8 +916,16 @@ "messages.title": "Messages", "motion.checked_to": "Checked to step {step} only: the check ran out of its work budget", "motion.collision": "{a} · {b}: steps {steps}", + "motion.delete_key": "Delete key", "motion.duration_hint": "seconds (empty: per step)", + "motion.key_here": "Key here", + "motion.key_here_hint": "Add a key at this step with the joint's value now", "motion.no_collisions": "No collisions over the study", + "motion.step_back": "Step back", + "motion.step_forward": "Step on", + "motion.to_end": "To the end", + "motion.to_start": "To the start", + "motion.track_name": "Track {number}", "nav.touchpad.help": "Hold Shift and move the pointer to pan; hold Alt/Option and move it to orbit. No mouse button is needed. Vertical scroll zooms.", "nav.touchpad.label": "Touchpad", "nav.touchpad.orbit": "Orbit (hold Alt/Option and move; no mouse button)", @@ -828,13 +934,13 @@ "nav.touchpad.zoom": "Zoom (vertical scroll or pinch)", "needs two joints": "needs two joints", "new configuration": "new configuration", - "new name": "new name", "new parameter": "new parameter", "no hole": "no hole", "no interference": "no interference", "no joint that moves yet": "no joint that moves yet", "no picture": "no picture", "none": "none", + "parts.automatic_name": "automatic", "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", @@ -842,12 +948,19 @@ "pattern.flip_direction": "Flip direction", "pick a hole to seat it, or OK places it at the origin": "pick a hole to seat it, or OK places it at the origin", "pick one or two vertices, edges or faces": "pick one or two vertices, edges or faces", + "prefs.cpu": "CPU (software)", + "prefs.minutes_suffix": " min", + "properties.title": "Properties: {name}", + "recovery.days_ago": "{count} days ago", + "recovery.detail": "{file} · {features} features · autosaved {when}", + "recovery.hours_ago": "{count} h ago", + "recovery.just_now": "just now", + "recovery.min_ago": "{count} min ago", + "recovery.never_saved": "never saved", "render.background": "Scene background", "render.fit": "Fit Product", - "render.image_cancel": "Cancel Render", "render.image_cancelled": "Render cancelled", "render.image_cancelling": "Cancelling", - "render.image_check": "Check Render Request", "render.image_completed": "Image ready to export", "render.image_encoding": "Encoding PNG", "render.image_export": "Export PNG", @@ -894,6 +1007,19 @@ "sample.plate.name": "Sample Plate", "sample.vents.about": "Slots in a rectangular pattern: rows, columns and pitch are parameters", "sample.vents.name": "Vent Plate", + "search.command_keys": "Enter run Shift+Enter run and keep open Esc close", + "search.commands": "Commands", + "search.design": "In this design", + "search.feature": "Feature", + "search.invalid": "Not valid", + "search.keys": "↑ ↓ choose Enter run Tab fill name Esc close", + "search.no_match": "No command matches '{query}'", + "search.parameter": "Parameter", + "search.recent": "Recent", + "search.run": "Run command", + "search.suggest": "Did you mean {label}?", + "search.try": "Type a command name, a feature in this design, or a parameter. To run a command with values, type its id and a JSON object: {example}", + "search.valid": "Valid", "settings.display_quality_note": "Finer levels draw curved faces and edges smoother and take longer to mesh. Only the viewport changes, never the design or exports.", "settings.frame_rate_note": "Shows frames per second in the view's bottom-right corner. While it is on, the view redraws continuously.", "settings.measure_axis_distances": "Show X/Y/Z distances", @@ -904,17 +1030,30 @@ "settings.reset_intro": "These settings don't work on this computer and would keep SolidCraft from starting, so they were changed:", "settings.reset_title": "Settings were changed", "settings.supersampling_note": "Draws the view at a higher resolution and filters it down: smoother edges, lines and grid on screens below 4K. It costs GPU time: about 2.25× the pixels at 1.5× and 4× at 2×.", + "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", + "shortcut.unpin": "Unpin", "sketch.degrees_of_freedom": "{count} degrees of freedom", + "sketch.dof_left": "{count} degrees of freedom left", + "sketch.dof_left_one": "{count} degree of freedom left", "sketch.draw_curves": "Draw as {linetype}", "sketch.make_selection": "Convert the selection to {linetype}", "sketch.open_ends": "Open {count}", "sketch.untied_ends": "Untied {count}", + "sketch_panel.attached": "Attached to {body} / {path}", + "sketch_panel.author": "Author placement: {path}", + "sketch_panel.dimension_hint": "Enter a value or expression, then press OK.", + "sketch_panel.move_hint": "Enter distances and an angle, then press OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Attached to a flat face of a sheet metal body; it follows that panel when the sheet changes.", + "sketch_panel.text_hint": "Edit the text and its placement, then press OK.", + "sketch_panel.use_plane": "Use {plane} plane", "sketch_text.align_center": "Center", "sketch_text.align_left": "Left", "sketch_text.align_right": "Right", "sketch_text.bold": "Bold", "sketch_text.builtin_font": "{family} (built-in)", - "sketch_text.font_search": "Search fonts", "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", @@ -935,6 +1074,7 @@ "timeline.ungroup": "Remove Feature Folders", "to the face": "to the face", "to the next face": "to the next face", + "toolbar.customize_note": "{tab} tab. Tick a command to give it a toolbar button. You can also drag a row of a panel's drop-down onto the toolbar, drag a button off the toolbar to remove it, or use the pin on a drop-down row.", "toolbar.image": "IMAGE", "toolbar.render": "RENDER", "toolbar.render_scene": "SCENE", @@ -943,11 +1083,11 @@ "trace.stopped_by_loop": "Ends at sample {sample}: the loop through {joint} can't close any further", "ui.1_degree_of_freedom": "1 degree of freedom", "ui.3d_mouse_speed": "3D mouse speed", - "ui.3d_mouse_windows": "3D mouse", "ui.3d_sketch": "3D Sketch", - "ui.about": "About", "ui.activate": "Activate", "ui.active_component": "Active component", + "ui.add_configuration": "Add configuration", + "ui.add_parameter": "Add parameter", "ui.agent_chat": "Agent chat", "ui.agent_chat_add": "Attach files", "ui.agent_chat_add_file": "Choose a file…", @@ -989,6 +1129,7 @@ "ui.agent_chat_file_error": "{name}: {error}", "ui.agent_chat_file_heic": "{name}: HEIC pictures can't be read here; export it as JPEG or PNG first", "ui.agent_chat_file_importing": "Importing into the design…", + "ui.agent_chat_file_kinds": "{name}: pictures (PNG, JPEG, GIF, WebP, BMP, TIFF), text files (SVG too) and CAD files (STEP, IGES, FreeCAD, Rhino, STL, 3MF, OBJ, glTF) can be attached", "ui.agent_chat_file_loading": "Reading the file…", "ui.agent_chat_file_not_a_file": "{name}: not a file", "ui.agent_chat_file_not_a_picture": "{name}: not a picture", @@ -1011,7 +1152,7 @@ "ui.agent_chat_model_tip": "The model the agent uses. Your choice is remembered for this agent.", "ui.agent_chat_new": "New chat", "ui.agent_chat_new_confirm": "Start a new chat? The current one is cleared.", - "ui.agent_chat_new_yes": "New chat", + "ui.agent_chat_next_picture": "Next picture", "ui.agent_chat_no_cli": "solidcraft-cli was not found next to the app, so the agent can't reach the design.", "ui.agent_chat_no_control": "The agent chat's endpoint did not start, so the agent can't reach the design.", "ui.agent_chat_no_images": "This agent doesn't take pictures", @@ -1027,6 +1168,7 @@ "ui.agent_chat_placeholder": "Ask the agent to change the design…", "ui.agent_chat_placeholder_busy": "Queue a message for when the agent is done (Ctrl+Enter: send now)", "ui.agent_chat_pop_out": "Open in its own window, which you can move to another screen", + "ui.agent_chat_previous_picture": "Previous picture", "ui.agent_chat_privacy": "Nothing goes anywhere except to the agent you run, under your own account.", "ui.agent_chat_privacy_note": "SolidCraft sends nothing anywhere: the agent you run talks to its own service under your own account. SolidCraft never sees or stores a key or password.", "ui.agent_chat_queue": "Queue", @@ -1082,6 +1224,7 @@ "ui.ai_bad_url": "Enter a URL that starts with http:// or https://", "ui.ai_base_url": "Base URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "copy", "ui.ai_custom_url": "Custom URL", "ui.ai_default": "Default", @@ -1102,8 +1245,10 @@ "ui.ai_mode_edits_note": "Modelling commands run without asking; saving, opening, exporting and other file or destructive actions still ask.", "ui.ai_mode_global": "As set above", "ui.ai_model": "Model", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "These settings cover SolidCraft's tools. An agent's own tools (its shell, its file edits) follow the agent's own mode, which you pick in the chat panel's header when the agent has modes.", "ui.ai_name": "Name", + "ui.ai_name_hint": "My agent", "ui.ai_need_command": "Enter the command that starts the agent", "ui.ai_need_model": "Enter the model name", "ui.ai_no_answer": "The agent did not answer in time", @@ -1134,6 +1279,7 @@ "ui.ai_toolbar_button": "Show Assistant button in application bar", "ui.ai_toolbar_button_note": "The Assistant button in the application bar. Ctrl+Shift+A and the View menu still open the panel.", "ui.ai_undo_tip": "Undo everything the agent changed for this message, as one step", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "All", "ui.ambient_occlusion": "Ambient occlusion", "ui.ambient_occlusion_help": "Soft shading where opaque surfaces meet. Requires the GPU viewport.", @@ -1147,7 +1293,6 @@ "ui.anti_aliasing": "Anti-aliasing", "ui.apply": "Apply", "ui.arc": "Arc", - "ui.assemble": "Assemble", "ui.assistant": "Assistant", "ui.assistant_waiting": "{count} approvals waiting", "ui.autosave_every": "Autosave every", @@ -1160,6 +1305,11 @@ "ui.bodies_all_if_none": "Bodies (all if none)", "ui.box": "Box", "ui.break": "Break", + "ui.browser_bodies_one": "{count} body", + "ui.browser_bodies_other": "{count} bodies", + "ui.browser_collapse": "Collapse the browser", + "ui.browser_expand": "Expand the browser", + "ui.browser_title": "Browser", "ui.calibrate": "Calibrate", "ui.cancel": "Cancel", "ui.canvases": "Canvases", @@ -1169,7 +1319,6 @@ "ui.chamfer": "Chamfer", "ui.chamfer_type": "Chamfer Type", "ui.change_workspace": "Change workspace", - "ui.chord_length": "Chord Length", "ui.circle": "Circle", "ui.circular_pattern": "Circular Pattern", "ui.clear": "Clear", @@ -1202,11 +1351,9 @@ "ui.command_done": "{command} done", "ui.command_palette_hint": "Type a command, e.g. {example} — or press S to search", "ui.concentric": "Concentric", - "ui.configurations": "Configurations", "ui.conic_curve": "Conic Curve", "ui.constant": "Constant", "ui.constraints_conflict": "Constraints conflict", - "ui.construct": "Construct", "ui.construction": "Construction", "ui.context_menu": "Context menu", "ui.continuity_g0": "G0 crease", @@ -1232,7 +1379,6 @@ "ui.cylinder": "Cylinder", "ui.dark": "Dark", "ui.default_modelling_orientation": "Default modelling orientation", - "ui.degrees_of_freedom": "degrees of freedom", "ui.delete": "Delete", "ui.depth": "Depth", "ui.design": "Design", @@ -1274,7 +1420,6 @@ "ui.equivalent_radius": "Equivalent Radius", "ui.expand": "Expand", "ui.exportDxf": "Export DXF…", - "ui.export_ellipsis": "Export...", "ui.export_ellipsis_keymap": "Export…", "ui.export_keymap_tip": "Keys, S box favourites and the toolbar, as a file", "ui.extend": "Extend", @@ -1298,6 +1443,7 @@ "ui.frame_rate": "Frame rate", "ui.front": "Front", "ui.fully_constrained": "Fully constrained", + "ui.function": "Function", "ui.general": "General", "ui.graphics": "Graphics", "ui.graphics_custom": "Custom", @@ -1364,7 +1510,6 @@ "ui.heading_sheet_metal": "SHEET METAL", "ui.heading_shell": "SHELL", "ui.heading_sketch": "SKETCH", - "ui.heading_sketch_palette": "SKETCH PALETTE", "ui.heading_sketch_scale": "SKETCH SCALE", "ui.heading_solid": "SOLID", "ui.heading_sphere": "SPHERE", @@ -1383,6 +1528,7 @@ "ui.hideOrigin": "Hide Origin", "ui.hidePlanes": "Hide Planes", "ui.hideProfile": "Hide Profile", + "ui.history_title": "History", "ui.hole": "Hole", "ui.hole_tap_type": "Hole Tap Type", "ui.hole_type": "Hole Type", @@ -1402,7 +1548,6 @@ "ui.length_decimals": "Length decimals", "ui.light": "Light", "ui.line": "Line", - "ui.linetype": "Linetype", "ui.lock": "Lock", "ui.loft": "Loft", "ui.look_at": "Look At", @@ -1436,10 +1581,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF with face tags", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Midpoint", "ui.mirror": "Mirror", "ui.mirror_line": "Mirror line", - "ui.modify": "Modify", "ui.move": "Move", "ui.multiple": "Multiple", "ui.named_views": "Named Views", @@ -1460,7 +1603,6 @@ "ui.offset_plane": "Offset Plane", "ui.one_side": "One Side", "ui.open": "Open", - "ui.open_ellipsis": "Open...", "ui.open_ends_tip": "Touches nothing", "ui.open_preferences": "Open Preferences", "ui.open_recent": "Open Recent", @@ -1470,8 +1612,9 @@ "ui.orbit_around": "Orbit around", "ui.orientation": "Orientation", "ui.origin": "Origin", - "ui.palette_points": "Points", - "ui.palette_profile": "Profile", + "ui.palette_construction_mode": "Construction mode (X)", + "ui.palette_more": "More", + "ui.palette_sketch_options": "Sketch options", "ui.pan": "Pan", "ui.pan_zoom_orbit": "Pan, zoom, orbit", "ui.parallel": "Parallel", @@ -1512,7 +1655,6 @@ "ui.quit": "Quit", "ui.radial_context_menu": "Radial context menu", "ui.radius": "Radius", - "ui.radius_type": "Radius Type", "ui.rail": "Rail", "ui.rail_fit": "Rail Fit", "ui.reachable": "Reachable", @@ -1545,7 +1687,6 @@ "ui.right": "Right", "ui.save": "Save", "ui.save_as": "Save As", - "ui.save_as_ellipsis": "Save As...", "ui.save_mesh_no_slicer": "No slicer found: choose Custom… and pick your slicer program, or turn off Send to slicer", "ui.save_mesh_pick_bodies": "Select the bodies to save", "ui.save_mesh_temp_failed": "Couldn't make {folder}: {error}", @@ -1558,7 +1699,6 @@ "ui.select": "Select", "ui.selectOther": "Select Other…", "ui.selectTangentChain": "Select Tangent Chain", - "ui.select_other_hint": "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there.", "ui.selection": "Selection", "ui.selection_modifiers": "Selection modifiers", "ui.selection_shift_add_only": "Shift+click adds without removing", @@ -1578,7 +1718,6 @@ "ui.show_constraints": "Constraints", "ui.show_construction_geometries": "Construction Geometries", "ui.show_dimensions": "Dimensions", - "ui.show_points": "Show Points", "ui.show_profile": "Show Profile", "ui.show_projected_geometries": "Projected Geometries", "ui.simple": "Simple", @@ -1589,7 +1728,6 @@ "ui.sketch_grid": "Sketch Grid", "ui.sketch_modifiers": "Sketch modifiers", "ui.sketch_scale": "Sketch Scale", - "ui.sketch_status": "Sketch Status", "ui.sketches": "Sketches", "ui.slice": "Slice", "ui.slicer": "Slicer", @@ -1636,7 +1774,6 @@ "ui.start": "Start", "ui.start_over": "Start over", "ui.start_page": "Start page", - "ui.start_radius": "Start Radius", "ui.stretch": "Stretch", "ui.supersampling": "Supersampling (SSAA)", "ui.supersampling_adaptive": "Only when the view is still", @@ -1686,7 +1823,6 @@ "ui.untitled": "Untitled", "ui.up_axis_note": "Which axis points up in the views (axis view, Home, Look At). Designs are unchanged.", "ui.updateToCurrentView": "Update to Current View", - "ui.variable": "Variable", "ui.view_centre": "View centre", "ui.visual_shaded_hidden_edges": "Shaded with hidden edges", "ui.visual_shaded_with_edges": "Shaded with edges", @@ -1714,7 +1850,6 @@ "ui.zoom_to_the_cursor": "Zoom to the cursor", "update.available": "SolidCraft {version} is available", "update.button": "Update", - "update.button_tip": "SolidCraft {version} is available", "update.button_version": "Update to {version}", "update.check_automatically": "Check for updates automatically", "update.check_now": "Check now", @@ -1746,5 +1881,6 @@ "update.title": "Update available", "update.up_to_date": "SolidCraft is up to date.", "update.update_now": "Update now", - "update.you_have": "You have {version}." + "update.you_have": "You have {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ choose · Enter / Tab complete · Esc dismiss" } diff --git a/assets/locales/es.json b/assets/locales/es.json index bfe8938f9e..a729e80e7b 100644 --- a/assets/locales/es.json +++ b/assets/locales/es.json @@ -6,10 +6,10 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Haz clic en «Submit new issue» en GitHub para enviar el informe.", "A new configuration, a copy of the active one": "Una configuración nueva, copia de la activa", - "AI": "AI", "ARRANGE": "ORGANIZAR", "About SolidCraft": "Acerca de SolidCraft", "Acceleration": "Aceleración", + "Actions": "Acciones", "Active": "Activo", "Active rule": "Regla activa", "Add": "Añadir", @@ -17,6 +17,7 @@ "Add Mid Radius": "Añadir radio intermedio", "Add Row": "Añadir fila", "Add condition": "Añadir condición", + "Add or edit rule": "Añadir o editar regla", "Against": "Contra", "Alignment": "Alineación", "All Sketch Points": "Todos los puntos del boceto", @@ -30,6 +31,7 @@ "Applied Radius": "Radio aplicado", "Apply To": "Aplicar a", "Apply to selection": "Aplicar a la selección", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Aplique primero la apariencia. Exporta las apariencias asignadas y los materiales creados en este diseño.", "Arc length": "Longitud del arco", "Area": "Área", "Attachment": "Vinculación", @@ -59,14 +61,15 @@ "Check collisions": "Comprobar colisiones", "Checked": "Comprobado", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Elige un plano nuevo. La geometría y las cotas del boceto se mantienen en sus coordenadas bidimensionales.", + "Choose…": "Elegir…", "Circumference": "Circunferencia", "Close draft": "Cerrar borrador", "Collisions": "Colisiones", "Colour": "Color", + "Comment": "Comentario", "Component1": "Componente1", "Configuration": "Configuración", "Configuration rules": "Reglas de configuración", - "Construction": "Construcción", "Contact": "Contacto", "Contact sets": "Conjuntos de contacto", "Continuity": "Continuidad", @@ -87,11 +90,11 @@ "Curvature (G2)": "Curvatura (G2)", "Curvature change (G3)": "Cambio de curvatura (G3)", "Customize Toolbar": "Personalizar barra de herramientas", + "Delete": "Eliminar", "Delete all": "Eliminar todo", "Delete rule": "Eliminar regla", "Delete the row": "Eliminar la fila", "Delete the set": "Eliminar el conjunto", - "Delete this parameter": "Eliminar este parámetro", "Density": "Densidad", "Designs you open or save appear here.": "Aquí aparecen los diseños que abras o guardes.", "Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por «Hasta objeto». Elige «Ángulo» para modificar la extensión angular.", @@ -104,16 +107,20 @@ "Drive Joint": "Accionar unión", "Duration": "Duración", "Edges": "Aristas", + "Edit": "Editar", "Edit Profile Sketch": "Editar boceto del perfil", "Enable rule": "Activar regla", "Enabled": "Activado", "Explode": "Explosionar", "Explode Text": "Descomponer texto", "Export Animation…": "Exportar animación…", + "Export CSV…": "Exportar CSV…", "Export Current View as DXF": "Exportar vista actual como DXF", "Export Current View as DXF…": "Exportar vista actual como DXF…", "Export DXF…": "Exportar DXF…", + "Export JSON…": "Exportar JSON…", "Export hidden edges as dashed lines": "Exportar aristas ocultas como líneas discontinuas", + "Expression": "Expresión", "Extend to meet": "Extender hasta encontrarla", "Extents": "Extensión", "FILES": "ARCHIVOS", @@ -122,11 +129,12 @@ "Face attachment lost": "Se ha perdido la vinculación de la cara", "Faces": "Caras", "Faces / edges / vertices": "Caras / aristas / vértices", + "Favorites": "Favoritos", "Favourite": "Favorito", + "Feature": "Operación", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Los archivos de muchos programas CAD usan Y como eje vertical, por eso aquí se abren tumbados (con Z hacia arriba).", "Filter": "Filtrar", "Find in Browser": "Buscar en el explorador", - "Flat": "Plano", "Flat size": "Tamaño del desarrollo", "Follow camera (Look At sketches)": "Seguir cámara (orientar hacia los bocetos)", "Follow the camera (Look At sketches)": "Seguir la cámara (orientar hacia los bocetos)", @@ -141,6 +149,7 @@ "Holes (edges on them)": "Agujeros (aristas que contienen)", "Home view": "Vista de inicio", "IMAGES": "IMÁGENES", + "Import…": "Importar…", "In This Design": "En este diseño", "In place (surfaces)": "En su posición (superficies)", "In plane": "En el plano", @@ -161,7 +170,9 @@ "Length units": "Unidades de longitud", "Letter Spacing": "Espaciado entre letras", "Library": "Biblioteca", + "Library file": "Archivo de biblioteca", "Line Spacing": "Interlineado", + "Local library": "Biblioteca local", "Loop": "Bucle", "MANAGE": "GESTIONAR", "MATERIALS": "MATERIALES", @@ -169,37 +180,45 @@ "MOTION": "MOVIMIENTO", "Margin": "Margen", "Mass": "Masa", + "Matched restrictions check the final values.": "Las restricciones que se cumplen comprueban los valores finales.", "Material": "Material", "Max": "Máx.", "Mid Radius": "Radio intermedio", "Middle double-click": "Doble clic central", "Min": "Mín.", "Mitre": "Inglete", + "Model Parameters": "Parámetros del modelo", "Modeled": "Modelado", "More toolbar panels": "Más paneles de la barra de herramientas", "Motion": "Movimiento", "Move Type": "Tipo de movimiento", + "Move down": "Mover abajo", "Move earlier": "Mover antes", "Move later": "Mover después", + "Move up": "Mover arriba", "Name": "Nombre", "Nearest": "Más cercano", + "New": "Nuevo", + "New rule…": "Nueva regla…", "New study": "Nuevo estudio", "No crash reports.": "No hay informes de fallos.", + "No matching appearance presets.": "No hay preajustes de apariencia coincidentes.", "No matching appearances": "No hay apariencias coincidentes", "No visible bodies to export.": "No hay cuerpos visibles para exportar.", "None": "Ninguno", - "Normal to sketch": "Normal al boceto", "Not available yet": "Todavía no disponible", "Notch": "Muesca", "OUTPUT": "SALIDA", "Opacity": "Opacidad", "Open GitHub draft": "Abrir borrador de GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Abra las bibliotecas locales en la aplicación de escritorio. Aquí puede exportar la biblioteca de este diseño.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "CAD 3D paramétrico de código abierto desarrollado en Rust. Licencia MIT O Apache-2.0.", + "Operator": "Operador", "Other side": "Lado opuesto", "Other surface (mutual trim)": "Otra superficie (recorte mutuo)", "Outside": "Exterior", "Overall length": "Longitud total", - "Parameter values": "Valores de parámetros", + "Parameter": "Parámetro", "Pass through": "Pasante", "Path": "Trayectoria", "Per Edge": "Por arista", @@ -217,7 +236,6 @@ "Preview Row": "Previsualizar fila", "Preview leaves the current design unchanged.": "La vista previa no modifica el diseño actual.", "Profiles/Curves": "Perfiles/curvas", - "Projection": "Proyección", "Quantity": "Cantidad", "RECENT": "RECIENTES", "REFERENCE": "REFERENCIA", @@ -254,6 +272,8 @@ "Roll History Marker Here": "Desplazar marcador de la línea de tiempo hasta aquí", "Round": "Redondo", "Rule": "Regla", + "Rule name": "Nombre de la regla", + "Rules run from top to bottom. Matched drives change effective values.": "Las reglas se ejecutan de arriba abajo. Las reglas de control que se cumplen modifican los valores efectivos.", "SECTION": "SECCIÓN", "SURFACE QUALITY": "CALIDAD DE SUPERFICIE", "Save Rule": "Guardar regla", @@ -262,6 +282,7 @@ "Scale factor": "Factor de escala", "Search": "Buscar", "Search commands": "Buscar comandos", + "Search this library": "Buscar en esta biblioteca", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleccionar otro: mantén pulsado el botón izquierdo sobre el modelo (o haz clic derecho › Seleccionar otro…) para ver todo lo que hay bajo el cursor; vuelve a pulsar en el mismo lugar para seleccionar el siguiente elemento. Haz doble clic con el botón central para volver a Inicio (Preferencias › Navegación).", "Select bodies (a face picks its body)": "Selecciona cuerpos (al seleccionar una cara se selecciona su cuerpo)", "Select bodies or faces instead": "Selecciona cuerpos o caras en su lugar", @@ -318,18 +339,19 @@ "Turn off section analysis before exporting the view.": "Desactiva el análisis de sección antes de exportar la vista.", "Type": "Tipo", "UNTRIM": "ANULAR RECORTE", + "Unit": "Unidad", "Updating the preview": "Actualizando vista previa", "Use active author placement": "Usar posición de autor activa", "Use fixed plane": "Usar plano fijo", "Use saved fixed plane": "Usar plano fijo guardado", "Use selected face as fixed plane": "Usar la cara seleccionada como plano fijo", + "User Parameters": "Parámetros de usuario", "VIEW": "VISTA", "VIEWS": "VISTAS", "Value": "Valor", "Velocity": "Velocidad", "Vertices": "Vértices", "View navigator": "Navegador de vistas", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Solo cuerpos visibles. Las curvas utilizan segmentos rectos (tolerancia de 0,01 mm).", "Volume": "Volumen", "Wall": "Pared", "Wall Location": "Posición de la pared", @@ -337,6 +359,12 @@ "When": "Cuando", "Width": "Anchura", "Will fail": "Fallará", + "X angle": "Ángulo X", + "X distance": "Distancia X", + "Y angle": "Ángulo Y", + "Y distance": "Distancia Y", + "Z angle": "Ángulo Z", + "Z distance": "Distancia Z", "angle, height": "ángulo, altura", "appearance.dielectric_ior": "Índice de refracción dieléctrico", "appearance.metallic": "Metalicidad", @@ -345,7 +373,11 @@ "appearance.roughness": "Rugosidad", "appearance.surface_response": "Respuesta de la superficie", "browser.body_name": "Cuerpo{number}", + "browser.capture_many": "Se han movido {count} componentes, pero sus posiciones no se han capturado.", + "browser.capture_note": "Capturar posición conserva la nueva posición; Revertir posición restaura la anterior.", + "browser.capture_one": "Se ha movido un componente, pero su posición no se ha capturado.", "browser.component_name": "Componente{number}", + "browser.move_title": "Mover: {name}", "browser.parameters": "Parámetros ({count})", "browser.precision": "Precisión: {precision}", "browser.sketch_name": "Boceto{number}", @@ -671,7 +703,8 @@ "cmd.view_navigate": "Navegar por la vista", "cmd.view_rename": "Cambiar nombre de vista guardada", "cmd.view_save": "Nueva vista guardada", - "command name": "nombre del comando", + "contact.components_count": "{count} componentes", + "contact.enabled_label": "activado", "coupling.gear_1": "Engranaje 1", "coupling.gear_1_pick": "Engranaje 1 (opcional)", "coupling.gear_2": "Engranaje 2", @@ -688,8 +721,11 @@ "coupling.rack": "Cremallera", "coupling.radius_1": "Radio 1", "coupling.radius_2": "Radio 2", + "delete.and_more": "… y {count} más", "dialog.computing": "Calculando… {seconds} s", "dialog.custom_axis": "↖ Eje personalizado", + "dialog.dimension_insert_hint": "Haga doble clic en otra cota de control para insertar su nombre", + "dialog.dimension_name": "Cota: {name}", "dialog.edit_title": "EDITAR {title}", "dialog.select": "↖ Seleccionar", "dialog.selected_count": "↖ {count} seleccionados", @@ -713,9 +749,17 @@ "dxf.progress.write": "Escribiendo DXF (5/5)", "dxf.tolerance": "Tolerancia de curva", "dxf.tolerance_help": "Solo cuerpos visibles. Las curvas usan segmentos rectos. Una tolerancia menor da curvas más finas y consume más tiempo y memoria.", - "expression, e.g. 25 mm": "expresión, p. ej., 25 mm", "fits the face, e.g. M8": "se ajusta a la cara, p. ej., M8", "from the rule": "de la regla", + "help.apache": "Licencia Apache 2.0", + "help.attributions": "Atribuciones (ATTRIBUTION.md)", + "help.build": "Versión {version} · commit {commit} · compilada el {date}", + "help.font_details": "La interfaz utiliza las fuentes integradas de egui (Ubuntu Light y Hack, bajo las licencias Ubuntu Font Licence y MIT/Bitstream Vera; Noto Emoji y la fuente de iconos emoji, bajo OFL y MIT). El texto de los bocetos utiliza Liberation Sans (SIL OFL 1.1). El paquete opcional de fuentes de The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), no se incluye en las compilaciones de SolidCraft.", + "help.fonts": "Fuentes", + "help.licences": "Licencias", + "help.mit": "Licencia MIT", + "help.notice": "NOTICE", + "help.third_party": "Licencias de terceros", "home.documentation": "Documentación", "home.in_this_browser": "en este navegador", "home.missing": "no disponible", @@ -744,10 +788,30 @@ "joint.from_bodies_hint": "Cada cuerpo seleccionado pasa a un componente nuevo propio y conserva su posición.", "joint.one_body": "Las dos selecciones pertenecen a {body}. Selecciona la segunda en otra pieza.", "joint.one_component": "Estas piezas son cuerpos de un mismo componente: {bodies}. Una unión mueve un componente respecto a otro, por lo que cada pieza debe tener su propio componente.", + "keymap.command_id": "ID del comando", + "keymap.empty": "No hay comandos coincidentes", + "keymap.use_anyway": "Usar {key} aquí de todos modos", + "manage.appearance_hint": "Elija una apariencia para la selección o arrastre una muestra a un cuerpo o una cara.", + "manage.assign_hint": "Seleccione cuerpos y elija su regla de diseño de piezas de plástico.", + "manage.config_hint": "Edite las filas, columnas y reglas de configuración y luego active una fila o muestre su vista previa.", + "manage.empty": "No hay filas coincidentes.", + "manage.material_hint": "Seleccione cuerpos y elija su material físico.", + "manage.parameters_hint": "Edite expresiones y unidades. Los cambios se aplican al pulsar Intro o salir de un campo.", + "manage.rules_hint": "Elija una regla para editarla o cree una nueva. Confirme para guardar la regla.", + "measure.at": "En", + "measure.item": "Elemento {number}", "motion.checked_to": "Comprobado solo hasta el paso {step}: la comprobación agotó su presupuesto de trabajo", "motion.collision": "{a} · {b}: pasos {steps}", + "motion.delete_key": "Eliminar fotograma clave", "motion.duration_hint": "segundos (vacío: por paso)", + "motion.key_here": "Insertar fotograma clave aquí", + "motion.key_here_hint": "Añadir un fotograma clave en este paso con el valor actual de la unión", "motion.no_collisions": "Sin colisiones durante el estudio", + "motion.step_back": "Paso atrás", + "motion.step_forward": "Paso adelante", + "motion.to_end": "Al final", + "motion.to_start": "Al inicio", + "motion.track_name": "Pista {number}", "nav.touchpad.help": "Mantén pulsado Mayús y mueve el puntero para encuadrar; mantén Alt/Opción y muévelo para orbitar. No hace falta pulsar ningún botón del ratón. El desplazamiento vertical hace zoom.", "nav.touchpad.label": "Panel táctil", "nav.touchpad.orbit": "Orbitar (mantén Alt/Opción y mueve el puntero; sin pulsar botones)", @@ -756,11 +820,12 @@ "nav.touchpad.zoom": "Zoom (desplazamiento vertical o gesto de pellizco)", "needs two joints": "necesita dos uniones", "new configuration": "nueva configuración", - "new name": "nuevo nombre", "new parameter": "nuevo parámetro", "no interference": "sin interferencias", "no joint that moves yet": "todavía no hay ninguna unión móvil", "no picture": "sin imagen", + "none": "ninguno", + "parts.automatic_name": "automático", "pattern.alignment_2": "Alineación 2", "pattern.alignment_hint": "Los recuentos incluyen el original. Centrar equilibra las copias; Invertir elige el lado adicional cuando el total es par.", "pattern.direction_1": "Dirección 1", @@ -768,12 +833,13 @@ "pattern.flip_direction": "Invertir dirección", "pick a hole to seat it, or OK places it at the origin": "selecciona un agujero para colocarlo o pulsa Aceptar para situarlo en el origen", "pick one or two vertices, edges or faces": "selecciona uno o dos vértices, aristas o caras", + "prefs.cpu": "CPU (software)", + "prefs.minutes_suffix": " min", + "properties.title": "Propiedades: {name}", "render.background": "Fondo de la escena", "render.fit": "Ajustar producto", - "render.image_cancel": "Cancelar renderizado", "render.image_cancelled": "Renderizado cancelado", "render.image_cancelling": "Cancelando", - "render.image_check": "Comprobar solicitud de renderizado", "render.image_completed": "Imagen lista para exportar", "render.image_encoding": "Codificando PNG", "render.image_export": "Exportar PNG", @@ -830,15 +896,28 @@ "settings.reset_intro": "Estos ajustes no funcionan en este ordenador e impedirían iniciar SolidCraft, por lo que se han cambiado:", "settings.reset_title": "Se han cambiado los ajustes", "settings.supersampling_note": "Dibuja la vista a mayor resolución y después la reduce: bordes, líneas y cuadrícula más suaves en pantallas inferiores a 4K. Consume recursos de GPU: unos 2,25× píxeles a 1,5× y 4× a 2×.", + "shortcut.json_error": "los parámetros deben estar en formato JSON: {error}", + "shortcut.no_command": "no existe el comando `{id}`; el texto anterior al JSON debe ser un ID de comando", + "shortcut.pin": "Fijar en la parte superior del cuadro S", + "shortcut.unpin": "Desfijar", "sketch.degrees_of_freedom": "{count} grados de libertad", + "sketch.dof_left": "Quedan {count} grados de libertad", + "sketch.dof_left_one": "Queda {count} grado de libertad", "sketch.draw_curves": "Dibujar como {linetype}", "sketch.make_selection": "Convertir la selección a {linetype}", + "sketch_panel.attached": "Vinculado a {body} / {path}", + "sketch_panel.author": "Ubicación de autoría: {path}", + "sketch_panel.dimension_hint": "Introduzca un valor o una expresión y pulse Aceptar.", + "sketch_panel.move_hint": "Introduzca las distancias y un ángulo y pulse Aceptar.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Vinculado a una cara plana de un cuerpo de chapa; sigue ese panel cuando cambia la chapa.", + "sketch_panel.text_hint": "Edite el texto y su posición y pulse Aceptar.", + "sketch_panel.use_plane": "Usar plano {plane}", "sketch_text.align_center": "Centrar", "sketch_text.align_left": "Alinear a la izquierda", "sketch_text.align_right": "Alinear a la derecha", "sketch_text.bold": "Negrita", "sketch_text.builtin_font": "{family} (integrada)", - "sketch_text.font_search": "Buscar fuentes", "sketch_text.italic": "Cursiva", "sketch_text.loading_fonts": "Buscando fuentes instaladas…", "sketch_text.on_path": "El texto sigue su trayectoria: la curva determina su posición", @@ -857,6 +936,7 @@ "timeline.reorder_many": "Reordenar operaciones", "timeline.ungroup": "Quitar carpetas de operaciones", "to the next face": "hasta la siguiente cara", + "toolbar.customize_note": "Pestaña {tab}. Marque un comando para añadir su botón a la barra de herramientas. También puede arrastrar una fila del desplegable de un panel a la barra, arrastrar un botón fuera de la barra para quitarlo o usar la chincheta de una fila del desplegable.", "toolbar.image": "IMAGEN", "toolbar.render": "RENDERIZAR", "toolbar.render_scene": "ESCENA", @@ -865,9 +945,7 @@ "trace.stopped_by_loop": "Termina en la muestra {sample}: el bucle que pasa por {joint} no puede cerrarse más", "ui.1_degree_of_freedom": "1 grado de libertad", "ui.3d_mouse_speed": "Velocidad del ratón 3D", - "ui.3d_mouse_windows": "Ratón 3D", "ui.3d_sketch": "Boceto 3D", - "ui.about": "Acerca de", "ui.activate": "Activar", "ui.active_component": "Componente activo", "ui.agent_chat": "Chat del agente", @@ -909,7 +987,7 @@ "ui.agent_chat_model_tip": "Modelo que utiliza el agente. Se recuerda tu elección para este agente.", "ui.agent_chat_new": "Nuevo chat", "ui.agent_chat_new_confirm": "¿Iniciar un nuevo chat? Se borrará la conversación actual.", - "ui.agent_chat_new_yes": "Nuevo chat", + "ui.agent_chat_next_picture": "Imagen siguiente", "ui.agent_chat_no_cli": "No se ha encontrado solidcraft-cli junto a la aplicación, por lo que el agente no puede acceder al diseño.", "ui.agent_chat_no_control": "No se ha iniciado el punto de conexión del chat del agente, por lo que no puede acceder al diseño.", "ui.agent_chat_no_images": "Este agente no admite imágenes", @@ -921,6 +999,7 @@ "ui.agent_chat_pick": "Agente", "ui.agent_chat_placeholder": "Pide al agente que modifique el diseño…", "ui.agent_chat_placeholder_busy": "Pon un mensaje en cola para cuando termine el agente (Ctrl+Intro: enviar ahora)", + "ui.agent_chat_previous_picture": "Imagen anterior", "ui.agent_chat_privacy": "Solo se envían datos al agente que ejecutes, con tu propia cuenta.", "ui.agent_chat_privacy_note": "SolidCraft no envía nada a otros destinos: el agente que ejecutas se comunica con su propio servicio utilizando tu cuenta. SolidCraft nunca ve ni almacena claves o contraseñas.", "ui.agent_chat_queue": "Poner en cola", @@ -974,6 +1053,7 @@ "ui.ai_bad_url": "Introduce una URL que empiece por http:// o https://", "ui.ai_base_url": "URL base", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "copia", "ui.ai_custom_url": "URL personalizada", "ui.ai_default": "Predeterminado", @@ -994,8 +1074,10 @@ "ui.ai_mode_edits_note": "Los comandos de modelado se ejecutan sin preguntar; guardar, abrir, exportar y otras acciones sobre archivos o destructivas siguen requiriendo permiso.", "ui.ai_mode_global": "Según la configuración anterior", "ui.ai_model": "Modelo", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Estos ajustes se aplican a las herramientas de SolidCraft. Las herramientas propias del agente (terminal, edición de archivos) siguen el modo del agente, que puedes elegir en la cabecera del panel de chat si dispone de modos.", "ui.ai_name": "Nombre", + "ui.ai_name_hint": "Mi agente", "ui.ai_need_command": "Introduce el comando que inicia el agente", "ui.ai_need_model": "Introduce el nombre del modelo", "ui.ai_no_answer": "El agente no ha respondido a tiempo", @@ -1026,6 +1108,7 @@ "ui.ai_toolbar_button": "Mostrar botón en la barra de herramientas", "ui.ai_toolbar_button_note": "El botón IA situado al extremo derecho de la barra. Ctrl+Mayús+A y el menú Ver también abren el panel.", "ui.ai_undo_tip": "Deshacer todos los cambios del agente en este mensaje, en un solo paso", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Todo", "ui.analysis": "Análisis", "ui.angle": "Ángulo", @@ -1037,7 +1120,6 @@ "ui.anti_aliasing": "Suavizado de bordes", "ui.apply": "Aplicar", "ui.arc": "Arco", - "ui.assemble": "Ensamblar", "ui.autosave_every": "Guardar automáticamente cada", "ui.axes": "Ejes", "ui.axis": "Eje", @@ -1047,6 +1129,11 @@ "ui.bodies": "Cuerpos", "ui.box": "Caja", "ui.break": "Romper", + "ui.browser_bodies_one": "{count} cuerpo", + "ui.browser_bodies_other": "{count} cuerpos", + "ui.browser_collapse": "Contraer el navegador", + "ui.browser_expand": "Expandir el navegador", + "ui.browser_title": "Navegador", "ui.calibrate": "Calibrar", "ui.cancel": "Cancelar", "ui.canvases": "Lienzos", @@ -1056,7 +1143,6 @@ "ui.chamfer": "Chaflán", "ui.chamfer_type": "Tipo de chaflán", "ui.change_workspace": "Cambiar espacio de trabajo", - "ui.chord_length": "Longitud de cuerda", "ui.circle": "Círculo", "ui.circular_pattern": "Patrón circular", "ui.clear": "Borrar", @@ -1089,11 +1175,9 @@ "ui.command_done": "{command} completado", "ui.command_palette_hint": "Escribe un comando, p. ej., {example}, o pulsa S para buscar", "ui.concentric": "Concéntrico", - "ui.configurations": "Configuraciones", "ui.conic_curve": "Curva cónica", "ui.constant": "Constante", "ui.constraints_conflict": "Conflicto entre restricciones", - "ui.construct": "Construir", "ui.construction": "Construcción", "ui.context_menu": "Menú contextual", "ui.continuity_g0": "G0 arista", @@ -1119,7 +1203,6 @@ "ui.cylinder": "Cilindro", "ui.dark": "Oscuro", "ui.default_modelling_orientation": "Orientación de modelado predeterminada", - "ui.degrees_of_freedom": "grados de libertad", "ui.delete": "Eliminar", "ui.depth": "Profundidad", "ui.design": "Diseño", @@ -1161,7 +1244,6 @@ "ui.equivalent_radius": "Radio equivalente", "ui.expand": "Expandir", "ui.exportDxf": "Exportar DXF…", - "ui.export_ellipsis": "Exportar…", "ui.export_ellipsis_keymap": "Exportar…", "ui.export_keymap_tip": "Exportar las teclas, los favoritos de la búsqueda S y la barra de herramientas como archivo", "ui.extend": "Extender", @@ -1245,7 +1327,6 @@ "ui.heading_sheet_metal": "CHAPA", "ui.heading_shell": "VACIADO", "ui.heading_sketch": "BOCETO", - "ui.heading_sketch_palette": "PALETA DE BOCETOS", "ui.heading_sketch_scale": "ESCALA DEL BOCETO", "ui.heading_solid": "SÓLIDO", "ui.heading_sphere": "ESFERA", @@ -1264,6 +1345,7 @@ "ui.hideOrigin": "Ocultar origen", "ui.hidePlanes": "Ocultar planos", "ui.hideProfile": "Ocultar perfil", + "ui.history_title": "Historial", "ui.hole": "Agujero", "ui.hole_tap_type": "Tipo de roscado del agujero", "ui.hole_type": "Tipo de agujero", @@ -1283,7 +1365,6 @@ "ui.length_decimals": "Decimales de longitud", "ui.light": "Claro", "ui.line": "Línea", - "ui.linetype": "Tipo de línea", "ui.lock": "Bloquear", "ui.loft": "Solevación", "ui.look_at": "Mirar hacia", @@ -1315,10 +1396,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF con etiquetas de cara", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Punto medio", "ui.mirror": "Simetría", "ui.mirror_line": "Línea de simetría", - "ui.modify": "Modificar", "ui.move": "Mover", "ui.multiple": "Múltiple", "ui.named_views": "Vistas guardadas", @@ -1338,7 +1417,6 @@ "ui.offset_plane": "Plano desfasado", "ui.one_side": "Un lado", "ui.open": "Abrir", - "ui.open_ellipsis": "Abrir…", "ui.open_preferences": "Abrir preferencias", "ui.open_recent": "Abrir reciente", "ui.operation": "Operación", @@ -1347,8 +1425,9 @@ "ui.orbit_around": "Orbitar alrededor de", "ui.orientation": "Orientación", "ui.origin": "Origen", - "ui.palette_points": "Puntos", - "ui.palette_profile": "Perfil", + "ui.palette_construction_mode": "Modo de construcción (X)", + "ui.palette_more": "Más", + "ui.palette_sketch_options": "Opciones de croquis", "ui.pan": "Desplazar", "ui.pan_zoom_orbit": "Desplazar, zoom y orbitar", "ui.parallel": "Paralelo", @@ -1382,7 +1461,6 @@ "ui.quit": "Salir", "ui.radial_context_menu": "Menú contextual radial", "ui.radius": "Radio", - "ui.radius_type": "Tipo de radio", "ui.rail": "Línea guía", "ui.rail_fit": "Ajuste a la guía", "ui.reachable": "Alcanzable", @@ -1407,7 +1485,6 @@ "ui.right": "Derecha", "ui.save": "Guardar", "ui.save_as": "Guardar como", - "ui.save_as_ellipsis": "Guardar como…", "ui.save_mesh_no_slicer": "No se ha encontrado ningún laminador: elige Personalizado… y selecciona tu laminador, o desactiva Enviar al laminador", "ui.save_mesh_pick_bodies": "Selecciona los cuerpos que se van a guardar", "ui.save_mesh_temp_failed": "No se ha podido crear {folder}: {error}", @@ -1420,7 +1497,6 @@ "ui.select": "Seleccionar", "ui.selectOther": "Seleccionar otro…", "ui.selectTangentChain": "Seleccionar cadena tangente", - "ui.select_other_hint": "Seleccionar otro: mantén pulsado el botón izquierdo sobre el modelo (o haz clic derecho › Seleccionar otro…) para ver todos los elementos bajo el cursor; vuelve a pulsar en el mismo punto para seleccionar el siguiente.", "ui.selection": "Selección", "ui.send": "Enviar", "ui.send_to_slicer": "Enviar al laminador", @@ -1437,7 +1513,6 @@ "ui.show_constraints": "Restricciones", "ui.show_construction_geometries": "Geometrías de construcción", "ui.show_dimensions": "Cotas", - "ui.show_points": "Mostrar puntos", "ui.show_profile": "Mostrar perfil", "ui.show_projected_geometries": "Geometrías proyectadas", "ui.simple": "Sencillo", @@ -1447,7 +1522,6 @@ "ui.sketch_dimension": "Cota de boceto", "ui.sketch_grid": "Cuadrícula de boceto", "ui.sketch_scale": "Escala del boceto", - "ui.sketch_status": "Estado del boceto", "ui.sketches": "Bocetos", "ui.slice": "Laminar", "ui.slicer": "Laminador", @@ -1493,7 +1567,6 @@ "ui.stand_up_tip": "Tras abrir un archivo STEP o IGES con Z hacia arriba, ofrecer un cuarto de vuelta alrededor de X", "ui.start": "Iniciar", "ui.start_over": "Volver a empezar", - "ui.start_radius": "Radio inicial", "ui.stretch": "Estirar", "ui.supersampling": "Supermuestreo (SSAA)", "ui.supersampling_adaptive": "Solo cuando la vista está inmóvil", @@ -1538,7 +1611,6 @@ "ui.untitled": "Sin título", "ui.up_axis_note": "Eje que apunta hacia arriba en las vistas (vista de eje, Inicio, Mirar hacia). Los diseños no cambian.", "ui.updateToCurrentView": "Actualizar a vista actual", - "ui.variable": "Variable", "ui.view_centre": "Centro de la vista", "ui.visual_shaded_hidden_edges": "Sombreado con aristas ocultas", "ui.visual_shaded_with_edges": "Sombreado con aristas", @@ -1554,7 +1626,6 @@ "ui.zoom_to_the_cursor": "Zoom hacia el cursor", "update.available": "SolidCraft {version} está disponible", "update.button": "Actualizar", - "update.button_tip": "SolidCraft {version} está disponible", "update.button_version": "Actualizar a {version}", "update.check_automatically": "Buscar actualizaciones automáticamente", "update.check_now": "Comprobar ahora", @@ -1586,5 +1657,6 @@ "update.title": "Actualización disponible", "update.up_to_date": "SolidCraft está actualizado.", "update.update_now": "Actualizar ahora", - "update.you_have": "Tienes la versión {version}." + "update.you_have": "Tienes la versión {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ elegir · Intro / Tab completar · Esc cerrar" } diff --git a/assets/locales/fr.json b/assets/locales/fr.json index 9387ef3f9f..36c3af0ce5 100644 --- a/assets/locales/fr.json +++ b/assets/locales/fr.json @@ -6,10 +6,10 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Cliquer sur Submit new issue sur GitHub pour envoyer votre rapport.", "A new configuration, a copy of the active one": "Nouvelle configuration, copie de la configuration active", - "AI": "IA", "ARRANGE": "DISPOSER", "About SolidCraft": "À propos de SolidCraft", "Acceleration": "Accélération", + "Actions": "Actions", "Active": "Actif", "Active rule": "Règle active", "Add": "Ajouter", @@ -17,6 +17,7 @@ "Add Mid Radius": "Ajouter un rayon intermédiaire", "Add Row": "Ajouter une ligne", "Add condition": "Ajouter une condition", + "Add or edit rule": "Ajouter ou modifier une règle", "Against": "Contre", "Alignment": "Alignement", "All Sketch Points": "Tous les points d'esquisse", @@ -30,6 +31,7 @@ "Applied Radius": "Rayon appliqué", "Apply To": "Appliquer à", "Apply to selection": "Appliquer à la sélection", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Appliquez d’abord votre apparence. Exporte les apparences attribuées et les matériaux créés dans cette conception.", "Arc length": "Longueur d'arc", "Area": "Aire", "Attachment": "Rattachement", @@ -60,14 +62,15 @@ "Check collisions": "Vérifier les collisions", "Checked": "Vérifié", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Choisir un nouveau plan. La géométrie et les cotes de l'esquisse restent inchangées dans ses coordonnées bidimensionnelles.", + "Choose…": "Choisir…", "Circumference": "Circonférence", "Close draft": "Fermer le brouillon", "Collisions": "Collisions", "Colour": "Couleur", + "Comment": "Commentaire", "Component1": "Composant1", "Configuration": "Configuration", "Configuration rules": "Règles de configuration", - "Construction": "Construction", "Contact": "Contact", "Contact sets": "Jeux de contact", "Continuity": "Continuité", @@ -89,11 +92,11 @@ "Curvature (G2)": "Courbure (G2)", "Curvature change (G3)": "Variation de courbure (G3)", "Customize Toolbar": "Personnaliser la barre d'outils", + "Delete": "Supprimer", "Delete all": "Tout supprimer", "Delete rule": "Supprimer la règle", "Delete the row": "Supprimer la ligne", "Delete the set": "Supprimer le jeu", - "Delete this parameter": "Supprimer ce paramètre", "Density": "Masse volumique", "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.", @@ -106,16 +109,20 @@ "Drive Joint": "Piloter la liaison", "Duration": "Durée", "Edges": "Arêtes", + "Edit": "Modifier", "Edit Profile Sketch": "Modifier l'esquisse du profil", "Enable rule": "Activer la règle", "Enabled": "Activé", "Explode": "Éclater", "Explode Text": "Décomposer le texte", "Export Animation…": "Exporter l'animation…", + "Export CSV…": "Exporter CSV…", "Export Current View as DXF": "Exporter la vue actuelle en DXF", "Export Current View as DXF…": "Exporter la vue actuelle en DXF…", "Export DXF…": "Exporter en DXF…", + "Export JSON…": "Exporter JSON…", "Export hidden edges as dashed lines": "Exporter les arêtes cachées en tirets", + "Expression": "Expression", "Extend to meet": "Prolonger jusqu'à la rencontre", "Extents": "Étendues", "FILES": "FICHIERS", @@ -124,11 +131,12 @@ "Face attachment lost": "Rattachement à la face perdu", "Faces": "Faces", "Faces / edges / vertices": "Faces / arêtes / sommets", + "Favorites": "Favoris", "Favourite": "Favori", + "Feature": "Fonction", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Les fichiers de nombreux logiciels de CAO ont Y vers le haut : ils s'ouvrent donc couchés ici (Z vers le haut).", "Filter": "Filtrer", "Find in Browser": "Trouver dans le navigateur", - "Flat": "Plat", "Flat size": "Dimensions à plat", "Follow camera (Look At sketches)": "Suivre la caméra (vue normale aux esquisses)", "Follow the camera (Look At sketches)": "Suivre la caméra (vue normale aux esquisses)", @@ -143,6 +151,7 @@ "Holes (edges on them)": "Trous (par leurs arêtes)", "Home view": "Vue d'accueil", "IMAGES": "IMAGES", + "Import…": "Importer…", "In This Design": "Dans cette conception", "In place (surfaces)": "En place (surfaces)", "In plane": "Dans le plan", @@ -163,7 +172,9 @@ "Length units": "Unités de longueur", "Letter Spacing": "Espacement des lettres", "Library": "Bibliothèque", + "Library file": "Fichier de bibliothèque", "Line Spacing": "Interligne", + "Local library": "Bibliothèque locale", "Loop": "Boucle", "MANAGE": "GÉRER", "MATERIALS": "MATÉRIAUX", @@ -171,37 +182,45 @@ "MOTION": "MOUVEMENT", "Margin": "Marge", "Mass": "Masse", + "Matched restrictions check the final values.": "Les restrictions dont les conditions sont remplies vérifient les valeurs finales.", "Material": "Matériau", "Max": "Max", "Mid Radius": "Rayon intermédiaire", "Middle double-click": "Double-clic milieu", "Min": "Min", "Mitre": "Onglet", + "Model Parameters": "Paramètres du modèle", "Modeled": "Modélisé", "More toolbar panels": "Autres groupes de la barre d'outils", "Motion": "Mouvement", "Move Type": "Type de déplacement", + "Move down": "Déplacer vers le bas", "Move earlier": "Déplacer plus tôt", "Move later": "Déplacer plus tard", + "Move up": "Déplacer vers le haut", "Name": "Nom", "Nearest": "Corps le plus proche", + "New": "Nouveau", + "New rule…": "Nouvelle règle…", "New study": "Nouvelle étude", "No crash reports.": "Aucun rapport de plantage.", + "No matching appearance presets.": "Aucun préréglage d’apparence correspondant.", "No matching appearances": "Aucune apparence correspondante", "No visible bodies to export.": "Aucun corps visible à exporter.", "None": "Aucun", - "Normal to sketch": "Normale à l'esquisse", "Not available yet": "Pas encore disponible", "Notch": "Encoche", "OUTPUT": "SORTIE", "Opacity": "Opacité", "Open GitHub draft": "Ouvrir le brouillon GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Ouvrez les bibliothèques locales dans l’application de bureau. Vous pouvez exporter ici la bibliothèque de cette conception.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "CAO 3D paramétrique open source en Rust. Sous licence MIT OR Apache-2.0.", + "Operator": "Opérateur", "Other side": "Autre côté", "Other surface (mutual trim)": "Autre surface (ajustement mutuel)", "Outside": "Extérieur", "Overall length": "Longueur hors tout", - "Parameter values": "Valeurs des paramètres", + "Parameter": "Paramètre", "Pass through": "Passer par", "Path": "Trajectoire", "Per Edge": "Par arête", @@ -219,7 +238,6 @@ "Preview Row": "Aperçu de la ligne", "Preview leaves the current design unchanged.": "L'aperçu ne modifie pas la conception actuelle.", "Profiles/Curves": "Profils/Courbes", - "Projection": "Projection", "Quantity": "Grandeur", "RECENT": "RÉCENTS", "REFERENCE": "RÉFÉRENCE", @@ -256,6 +274,8 @@ "Roll History Marker Here": "Placer le marqueur d'historique ici", "Round": "Arrondi", "Rule": "Règle", + "Rule name": "Nom de la règle", + "Rules run from top to bottom. Matched drives change effective values.": "Les règles s’exécutent de haut en bas. Les règles de pilotage dont les conditions sont remplies modifient les valeurs effectives.", "SECTION": "COUPE", "SURFACE QUALITY": "QUALITÉ DE SURFACE", "Save Rule": "Enregistrer la règle", @@ -264,6 +284,7 @@ "Scale factor": "Facteur d'échelle", "Search": "Rechercher", "Search commands": "Rechercher des commandes", + "Search this library": "Rechercher dans cette bibliothèque", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Sélectionner un autre élément : maintenir le bouton gauche immobile sur le modèle (ou clic droit › Sélectionner un autre…) pour lister tout ce qui se trouve sous le curseur ; cliquer de nouveau au même endroit pour sélectionner l'élément suivant. Double-cliquer avec le bouton du milieu pour revenir à l'accueil (Préférences › Navigation).", "Select bodies (a face picks its body)": "Sélectionner des corps (une face sélectionne son corps)", "Select bodies or faces instead": "Sélectionner plutôt des corps ou des faces", @@ -320,18 +341,19 @@ "Turn off section analysis before exporting the view.": "Désactivez l'analyse de coupe avant d'exporter la vue.", "Type": "Type", "UNTRIM": "ANNULER L'AJUSTEMENT", + "Unit": "Unité", "Updating the preview": "Mise à jour de l'aperçu", "Use active author placement": "Utiliser le placement d'auteur actif", "Use fixed plane": "Utiliser un plan fixe", "Use saved fixed plane": "Utiliser le plan fixe enregistré", "Use selected face as fixed plane": "Utiliser la face sélectionnée comme plan fixe", + "User Parameters": "Paramètres utilisateur", "VIEW": "VUE", "VIEWS": "VUES", "Value": "Valeur", "Velocity": "Vitesse", "Vertices": "Sommets", "View navigator": "Repère de navigation", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Corps visibles uniquement. Les courbes sont approchées par des segments de droite (tolérance de 0,01 mm).", "Volume": "Volume", "Wall": "Paroi", "Wall Location": "Position de la paroi", @@ -339,6 +361,12 @@ "When": "Quand", "Width": "Largeur", "Will fail": "Échouera", + "X angle": "Angle X", + "X distance": "Distance X", + "Y angle": "Angle Y", + "Y distance": "Distance Y", + "Z angle": "Angle Z", + "Z distance": "Distance Z", "angle, height": "angle, hauteur", "appearance.dielectric_ior": "Indice de réfraction (diélectrique)", "appearance.metallic": "Métallique", @@ -347,7 +375,11 @@ "appearance.roughness": "Rugosité", "appearance.surface_response": "Réponse de la surface", "browser.body_name": "Corps{number}", + "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.", + "browser.capture_one": "Un composant a été déplacé, mais sa position n’est pas capturée.", "browser.component_name": "Composant{number}", + "browser.move_title": "Déplacer : {name}", "browser.parameters": "Paramètres ({count})", "browser.precision": "Précision : {precision}", "browser.sketch_name": "Esquisse{number}", @@ -678,7 +710,8 @@ "cmd.view_navigate": "Naviguer dans la vue", "cmd.view_rename": "Renommer la vue nommée", "cmd.view_save": "Nouvelle vue nommée", - "command name": "nom de la commande", + "contact.components_count": "{count} composants", + "contact.enabled_label": "activé", "coupling.gear_1": "Engrenage 1", "coupling.gear_1_pick": "Engrenage 1 (facultatif)", "coupling.gear_2": "Engrenage 2", @@ -695,6 +728,7 @@ "coupling.rack": "Crémaillère", "coupling.radius_1": "Rayon 1", "coupling.radius_2": "Rayon 2", + "delete.and_more": "… et {count} de plus", "dialog.bodies_count": "{count} corps", "dialog.clearance_invalid": "le jeu doit être une longueur positive ou nulle", "dialog.compute": "Calculer", @@ -702,6 +736,8 @@ "dialog.computing": "Calcul… {seconds} s", "dialog.could_not_be_checked": "non vérifiable", "dialog.custom_axis": "↖ Axe personnalisé", + "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.select": "↖ Sélectionner", "dialog.selected_count": "↖ {count} sélectionné(s)", @@ -725,9 +761,17 @@ "dxf.progress.write": "Écriture du DXF (5/5)", "dxf.tolerance": "Tolérance des courbes", "dxf.tolerance_help": "Corps visibles uniquement. Les courbes sont approchées par des segments de droite. Une tolérance plus faible donne des courbes plus fines, au prix de plus de temps et de mémoire.", - "expression, e.g. 25 mm": "expression, par ex. 25 mm", "fits the face, e.g. M8": "adapté à la face, par ex. M8", "from the rule": "selon la règle", + "help.apache": "Licence Apache 2.0", + "help.attributions": "Attributions (ATTRIBUTION.md)", + "help.build": "Version {version} · commit {commit} · compilée le {date}", + "help.font_details": "L’interface utilise les polices intégrées d’egui (Ubuntu Light et Hack, sous les licences Ubuntu Font Licence et MIT/Bitstream Vera ; Noto Emoji et la police d’icônes emoji, sous OFL et MIT). Le texte des esquisses utilise Liberation Sans (SIL OFL 1.1). Le pack de polices facultatif de The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic ; SIL OFL 1.1), n’est pas inclus dans les versions de SolidCraft.", + "help.fonts": "Polices", + "help.licences": "Licences", + "help.mit": "Licence MIT", + "help.notice": "NOTICE", + "help.third_party": "Licences tierces", "home.documentation": "Documentation", "home.in_this_browser": "dans ce navigateur", "home.missing": "introuvable", @@ -756,10 +800,30 @@ "joint.from_bodies_hint": "Chaque corps sélectionné passe dans un nouveau composant à lui et reste où il est.", "joint.one_body": "Les deux sélections sont sur {body}. Choisissez la seconde sur une autre pièce.", "joint.one_component": "Ces pièces sont des corps d'un même composant : {bodies}. Une liaison déplace un composant par rapport à un autre, chaque pièce a donc besoin de son propre composant.", + "keymap.command_id": "ID de commande", + "keymap.empty": "Aucune commande correspondante", + "keymap.use_anyway": "Utiliser quand même {key} ici", + "manage.appearance_hint": "Choisissez une apparence pour la sélection ou faites glisser un échantillon sur un corps ou une face.", + "manage.assign_hint": "Sélectionnez des corps et choisissez leur règle de conception plastique.", + "manage.config_hint": "Modifiez les lignes, colonnes et règles de configuration, puis activez une ligne ou affichez son aperçu.", + "manage.empty": "Aucune ligne correspondante.", + "manage.material_hint": "Sélectionnez des corps et choisissez leur matériau physique.", + "manage.parameters_hint": "Modifiez les expressions et les unités. Les modifications s’appliquent avec Entrée ou lorsque vous quittez un champ.", + "manage.rules_hint": "Choisissez une règle à modifier ou créez-en une. Confirmez pour enregistrer la règle.", + "measure.at": "À", + "measure.item": "Élément {number}", "motion.checked_to": "Vérifié jusqu'à l'étape {step} seulement : la vérification a épuisé son budget de calcul", "motion.collision": "{a} · {b} : étapes {steps}", + "motion.delete_key": "Supprimer l’image clé", "motion.duration_hint": "secondes (vide : par étape)", + "motion.key_here": "Placer une image clé ici", + "motion.key_here_hint": "Ajouter une image clé à cette étape avec la valeur actuelle de la liaison", "motion.no_collisions": "Aucune collision au cours de l'étude", + "motion.step_back": "Étape précédente", + "motion.step_forward": "Étape suivante", + "motion.to_end": "À la fin", + "motion.to_start": "Au début", + "motion.track_name": "Piste {number}", "nav.touchpad.help": "Maintenir Maj et déplacer le pointeur pour faire un panoramique ; maintenir Alt/Option et le déplacer pour orbiter. Aucun bouton de souris n'est nécessaire. Le défilement vertical zoome.", "nav.touchpad.label": "Pavé tactile", "nav.touchpad.orbit": "Orbite (maintenir Alt/Option et déplacer ; sans bouton de souris)", @@ -768,13 +832,13 @@ "nav.touchpad.zoom": "Zoom (défilement vertical ou pincement)", "needs two joints": "nécessite deux liaisons", "new configuration": "nouvelle configuration", - "new name": "nouveau nom", "new parameter": "nouveau paramètre", "no hole": "sans perçage", "no interference": "aucune interférence", "no joint that moves yet": "aucune liaison mobile pour l'instant", "no picture": "aucune image", "none": "aucun", + "parts.automatic_name": "automatique", "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", @@ -782,12 +846,13 @@ "pattern.flip_direction": "Inverser la direction", "pick a hole to seat it, or OK places it at the origin": "choisir un trou pour la loger, ou OK la place à l'origine", "pick one or two vertices, edges or faces": "choisir un ou deux sommets, arêtes ou faces", + "prefs.cpu": "CPU (logiciel)", + "prefs.minutes_suffix": " min", + "properties.title": "Propriétés : {name}", "render.background": "Arrière-plan de la scène", "render.fit": "Cadrer le produit", - "render.image_cancel": "Annuler le rendu", "render.image_cancelled": "Rendu annulé", "render.image_cancelling": "Annulation", - "render.image_check": "Vérifier la demande de rendu", "render.image_completed": "Image prête à exporter", "render.image_encoding": "Encodage du PNG", "render.image_export": "Exporter en PNG", @@ -844,17 +909,30 @@ "settings.reset_intro": "Ces réglages ne fonctionnent pas sur cet ordinateur et empêcheraient SolidCraft de démarrer ; ils ont donc été modifiés :", "settings.reset_title": "Des réglages ont été modifiés", "settings.supersampling_note": "Dessine la vue à une résolution plus élevée puis la réduit : arêtes, lignes et grille plus lisses sur les écrans de définition inférieure à la 4K. Cela coûte du temps GPU : environ 2,25× plus de pixels à 1,5× et 4× plus à 2×.", + "shortcut.json_error": "les paramètres doivent être au format JSON : {error}", + "shortcut.no_command": "la commande `{id}` n’existe pas ; le texte avant le JSON doit être un ID de commande", + "shortcut.pin": "Épingler en haut de la boîte S", + "shortcut.unpin": "Désépingler", "sketch.degrees_of_freedom": "{count} degrés de liberté", + "sketch.dof_left": "{count} degrés de liberté restants", + "sketch.dof_left_one": "{count} degré de liberté restant", "sketch.draw_curves": "Dessiner en {linetype}", "sketch.make_selection": "Convertir la sélection en {linetype}", "sketch.open_ends": "{count} ouvertes", "sketch.untied_ends": "{count} sans coïncidence", + "sketch_panel.attached": "Attaché à {body} / {path}", + "sketch_panel.author": "Placement d’origine : {path}", + "sketch_panel.dimension_hint": "Saisissez une valeur ou une expression, puis appuyez sur OK.", + "sketch_panel.move_hint": "Saisissez les distances et un angle, puis appuyez sur OK.", + "sketch_panel.redefine_title": "{command} : {name}", + "sketch_panel.sheet_attachment": "Attaché à une face plane d’un corps de tôlerie ; suit ce panneau lorsque la tôle change.", + "sketch_panel.text_hint": "Modifiez le texte et son placement, puis appuyez sur OK.", + "sketch_panel.use_plane": "Utiliser le plan {plane}", "sketch_text.align_center": "Centre", "sketch_text.align_left": "Gauche", "sketch_text.align_right": "Droite", "sketch_text.bold": "Gras", "sketch_text.builtin_font": "{family} (intégrée)", - "sketch_text.font_search": "Rechercher des polices", "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", @@ -875,6 +953,7 @@ "timeline.ungroup": "Supprimer les dossiers de 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.", "toolbar.image": "IMAGE", "toolbar.render": "RENDU", "toolbar.render_scene": "SCÈNE", @@ -883,9 +962,7 @@ "trace.stopped_by_loop": "Se termine à l'échantillon {sample} : la boucle passant par {joint} ne peut plus se fermer", "ui.1_degree_of_freedom": "1 degré de liberté", "ui.3d_mouse_speed": "Vitesse de la souris 3D", - "ui.3d_mouse_windows": "Souris 3D", "ui.3d_sketch": "Esquisse 3D", - "ui.about": "À propos", "ui.activate": "Activer", "ui.active_component": "Composant actif", "ui.agent_chat": "Chat de l'agent", @@ -951,7 +1028,7 @@ "ui.agent_chat_model_tip": "Le modèle utilisé par l'agent. Votre choix est mémorisé pour cet agent.", "ui.agent_chat_new": "Nouveau chat", "ui.agent_chat_new_confirm": "Démarrer un nouveau chat ? Le chat actuel sera effacé.", - "ui.agent_chat_new_yes": "Nouveau chat", + "ui.agent_chat_next_picture": "Image suivante", "ui.agent_chat_no_cli": "solidcraft-cli est introuvable à côté de l'application : l'agent ne peut donc pas accéder à la conception.", "ui.agent_chat_no_control": "Le point d'accès du chat de l'agent n'a pas démarré : l'agent ne peut donc pas accéder à la conception.", "ui.agent_chat_no_images": "Cet agent n'accepte pas les images", @@ -967,6 +1044,7 @@ "ui.agent_chat_placeholder": "Demander à l'agent de modifier la conception…", "ui.agent_chat_placeholder_busy": "Mettre un message en file d'attente pour quand l'agent aura terminé (Ctrl+Enter : envoyer maintenant)", "ui.agent_chat_pop_out": "Ouvrir dans une fenêtre séparée, déplaçable vers un autre écran", + "ui.agent_chat_previous_picture": "Image précédente", "ui.agent_chat_privacy": "Rien n'est envoyé ailleurs qu'à l'agent que vous lancez, sous votre propre compte.", "ui.agent_chat_privacy_note": "SolidCraft n'envoie rien nulle part : l'agent que vous lancez dialogue avec son propre service, sous votre propre compte. SolidCraft ne voit ni ne stocke jamais de clé ni de mot de passe.", "ui.agent_chat_queue": "Mettre en file", @@ -1022,6 +1100,7 @@ "ui.ai_bad_url": "Saisir une URL commençant par http:// ou https://", "ui.ai_base_url": "URL de base", "ui.ai_button": "IA", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "copie", "ui.ai_custom_url": "URL personnalisée", "ui.ai_default": "Par défaut", @@ -1042,8 +1121,10 @@ "ui.ai_mode_edits_note": "Les commandes de modélisation s'exécutent sans demander ; l'enregistrement, l'ouverture, l'exportation et les autres actions sur les fichiers ou destructrices demandent toujours.", "ui.ai_mode_global": "Comme défini ci-dessus", "ui.ai_model": "Modèle", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Ces réglages concernent les outils de SolidCraft. Les outils propres à un agent (son shell, ses modifications de fichiers) suivent le mode de l'agent, que vous choisissez dans l'en-tête du panneau de chat lorsque l'agent propose des modes.", "ui.ai_name": "Nom", + "ui.ai_name_hint": "Mon agent", "ui.ai_need_command": "Saisir la commande qui lance l'agent", "ui.ai_need_model": "Saisir le nom du modèle", "ui.ai_no_answer": "L'agent n'a pas répondu à temps", @@ -1074,6 +1155,7 @@ "ui.ai_toolbar_button": "Afficher le bouton dans la barre d'outils", "ui.ai_toolbar_button_note": "Le bouton IA à l'extrémité droite de la barre d'outils. Ctrl+Shift+A et le menu Vue ouvrent toujours le panneau.", "ui.ai_undo_tip": "Annuler tout ce que l'agent a modifié pour ce message, en une seule étape", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Tout", "ui.ambient_occlusion": "Occlusion ambiante", "ui.ambient_occlusion_help": "Ombrage doux aux jonctions des surfaces opaques. Nécessite la vue GPU.", @@ -1087,7 +1169,6 @@ "ui.anti_aliasing": "Anticrénelage", "ui.apply": "Appliquer", "ui.arc": "Arc", - "ui.assemble": "Assembler", "ui.autosave_every": "Enregistrement automatique toutes les", "ui.axes": "Axes", "ui.axis": "Axe", @@ -1098,6 +1179,11 @@ "ui.bodies_all_if_none": "Corps (tous si aucun)", "ui.box": "Boîte", "ui.break": "Fractionner", + "ui.browser_bodies_one": "{count} corps", + "ui.browser_bodies_other": "{count} corps", + "ui.browser_collapse": "Réduire le navigateur", + "ui.browser_expand": "Développer le navigateur", + "ui.browser_title": "Navigateur", "ui.calibrate": "Étalonner", "ui.cancel": "Annuler", "ui.canvases": "Canevas", @@ -1107,7 +1193,6 @@ "ui.chamfer": "Chanfrein", "ui.chamfer_type": "Type de chanfrein", "ui.change_workspace": "Changer d'espace de travail", - "ui.chord_length": "Longueur de corde", "ui.circle": "Cercle", "ui.circular_pattern": "Réseau circulaire", "ui.clear": "Effacer", @@ -1140,11 +1225,9 @@ "ui.command_done": "{command} terminé", "ui.command_palette_hint": "Tapez une commande, ex. {example} — ou S pour chercher", "ui.concentric": "Concentricité", - "ui.configurations": "Configurations", "ui.conic_curve": "Courbe conique", "ui.constant": "Constant", "ui.constraints_conflict": "Contraintes en conflit", - "ui.construct": "Construire", "ui.construction": "Construction", "ui.context_menu": "Menu contextuel", "ui.continuity_g0": "G0 angle vif", @@ -1170,7 +1253,6 @@ "ui.cylinder": "Cylindre", "ui.dark": "Sombre", "ui.default_modelling_orientation": "Orientation de modélisation par défaut", - "ui.degrees_of_freedom": "degrés de liberté", "ui.delete": "Supprimer", "ui.depth": "Profondeur", "ui.design": "Conception", @@ -1212,7 +1294,6 @@ "ui.equivalent_radius": "Rayon équivalent", "ui.expand": "Développer", "ui.exportDxf": "Exporter en DXF…", - "ui.export_ellipsis": "Exporter...", "ui.export_ellipsis_keymap": "Exporter…", "ui.export_keymap_tip": "Raccourcis, favoris de la boîte S et barre d'outils, dans un fichier", "ui.extend": "Prolonger", @@ -1302,7 +1383,6 @@ "ui.heading_sheet_metal": "TÔLERIE", "ui.heading_shell": "COQUE", "ui.heading_sketch": "ESQUISSE", - "ui.heading_sketch_palette": "PALETTE D'ESQUISSE", "ui.heading_sketch_scale": "ÉCHELLE D'ESQUISSE", "ui.heading_solid": "SOLIDE", "ui.heading_sphere": "SPHÈRE", @@ -1321,6 +1401,7 @@ "ui.hideOrigin": "Masquer l'origine", "ui.hidePlanes": "Masquer les plans", "ui.hideProfile": "Masquer le profil", + "ui.history_title": "Historique", "ui.hole": "Perçage", "ui.hole_tap_type": "Type de taraudage", "ui.hole_type": "Type de perçage", @@ -1340,7 +1421,6 @@ "ui.length_decimals": "Décimales des longueurs", "ui.light": "Clair", "ui.line": "Ligne", - "ui.linetype": "Type de ligne", "ui.lock": "Verrouiller", "ui.loft": "Lissage", "ui.look_at": "Vue normale", @@ -1372,10 +1452,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF avec étiquettes de faces", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Point milieu", "ui.mirror": "Symétrie", "ui.mirror_line": "Ligne de symétrie", - "ui.modify": "Modifier", "ui.move": "Déplacer", "ui.multiple": "Multiple", "ui.named_views": "Vues nommées", @@ -1396,7 +1474,6 @@ "ui.offset_plane": "Plan décalé", "ui.one_side": "Un côté", "ui.open": "Ouvrir", - "ui.open_ellipsis": "Ouvrir...", "ui.open_ends_tip": "Ne touche rien", "ui.open_preferences": "Ouvrir les préférences", "ui.open_recent": "Ouvrir un fichier récent", @@ -1406,8 +1483,9 @@ "ui.orbit_around": "Orbite autour de", "ui.orientation": "Orientation", "ui.origin": "Origine", - "ui.palette_points": "Points", - "ui.palette_profile": "Profil", + "ui.palette_construction_mode": "Mode construction (X)", + "ui.palette_more": "Plus", + "ui.palette_sketch_options": "Options d’esquisse", "ui.pan": "Panoramique", "ui.pan_zoom_orbit": "Panoramique, zoom, orbite", "ui.parallel": "Parallélisme", @@ -1441,7 +1519,6 @@ "ui.quit": "Quitter", "ui.radial_context_menu": "Menu contextuel radial", "ui.radius": "Rayon", - "ui.radius_type": "Type de rayon", "ui.rail": "Rail", "ui.rail_fit": "Ajustement au rail", "ui.reachable": "Accessible", @@ -1468,7 +1545,6 @@ "ui.right": "Droite", "ui.save": "Enregistrer", "ui.save_as": "Enregistrer sous", - "ui.save_as_ellipsis": "Enregistrer sous...", "ui.save_mesh_no_slicer": "Aucun slicer trouvé : choisissez Personnalisé… et sélectionnez votre slicer, ou désactivez Envoyer au slicer", "ui.save_mesh_pick_bodies": "Sélectionner les corps à enregistrer", "ui.save_mesh_temp_failed": "Impossible de créer {folder} : {error}", @@ -1481,7 +1557,6 @@ "ui.select": "Sélectionner", "ui.selectOther": "Sélectionner un autre…", "ui.selectTangentChain": "Sélectionner la chaîne tangente", - "ui.select_other_hint": "Sélectionner un autre élément : maintenir le bouton gauche immobile sur le modèle (ou clic droit › Sélectionner un autre…) pour lister tout ce qui se trouve sous le curseur ; cliquer de nouveau au même endroit pour sélectionner l'élément suivant.", "ui.selection": "Sélection", "ui.send": "Envoyer", "ui.send_to_slicer": "Envoyer au slicer", @@ -1498,7 +1573,6 @@ "ui.show_constraints": "Contraintes", "ui.show_construction_geometries": "Géométries de construction", "ui.show_dimensions": "Cotes", - "ui.show_points": "Afficher les points", "ui.show_profile": "Afficher le profil", "ui.show_projected_geometries": "Géométries projetées", "ui.simple": "Simple", @@ -1508,7 +1582,6 @@ "ui.sketch_dimension": "Cote d'esquisse", "ui.sketch_grid": "Grille d'esquisse", "ui.sketch_scale": "Échelle d'esquisse", - "ui.sketch_status": "État de l'esquisse", "ui.sketches": "Esquisses", "ui.slice": "Trancher", "ui.slicer": "Slicer", @@ -1554,7 +1627,6 @@ "ui.stand_up_tip": "Après l'ouverture d'un fichier STEP ou IGES avec Z vers le haut, proposer un quart de tour autour de X", "ui.start": "Début", "ui.start_over": "Recommencer", - "ui.start_radius": "Rayon initial", "ui.stretch": "Étirer", "ui.supersampling": "Suréchantillonnage (SSAA)", "ui.supersampling_adaptive": "Seulement quand la vue est immobile", @@ -1602,7 +1674,6 @@ "ui.untitled": "Sans titre", "ui.up_axis_note": "Axe orienté vers le haut dans les vues (vue d'axe, Accueil, Vue normale). Les conceptions ne sont pas modifiées.", "ui.updateToCurrentView": "Mettre à jour avec la vue actuelle", - "ui.variable": "Variable", "ui.view_centre": "Centre de la vue", "ui.visual_shaded_hidden_edges": "Ombré avec arêtes cachées", "ui.visual_shaded_with_edges": "Ombré avec arêtes", @@ -1618,7 +1689,6 @@ "ui.zoom_to_the_cursor": "Zoom vers le curseur", "update.available": "SolidCraft {version} est disponible", "update.button": "Mettre à jour", - "update.button_tip": "SolidCraft {version} est disponible", "update.button_version": "Mettre à jour vers {version}", "update.check_automatically": "Rechercher automatiquement les mises à jour", "update.check_now": "Vérifier maintenant", @@ -1650,5 +1720,6 @@ "update.title": "Mise à jour disponible", "update.up_to_date": "SolidCraft est à jour.", "update.update_now": "Mettre à jour maintenant", - "update.you_have": "Vous avez la version {version}." + "update.you_have": "Vous avez la version {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ choisir · Entrée / Tab compléter · Échap fermer" } diff --git a/assets/locales/gl.json b/assets/locales/gl.json index fd4ec7c6bc..7bafe6e065 100644 --- a/assets/locales/gl.json +++ b/assets/locales/gl.json @@ -6,9 +6,9 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Preme Submit new issue en GitHub para enviar o informe.", "A new configuration, a copy of the active one": "Unha configuración nova, copia da activa", - "AI": "IA", "ARRANGE": "ORGANIZAR", "About SolidCraft": "Acerca de SolidCraft", + "Actions": "Accións", "Active": "Activa", "Active rule": "Regra activa", "Add": "Engadir", @@ -16,6 +16,7 @@ "Add Mid Radius": "Engadir raio medio", "Add Row": "Engadir fila", "Add condition": "Engadir condición", + "Add or edit rule": "Engadir ou editar unha regra", "Alignment": "Aliñamento", "All Sketch Points": "Todos os puntos do bosquexo", "All open designs": "Todos os deseños abertos", @@ -27,6 +28,7 @@ "Applied Radius": "Raio aplicado", "Apply To": "Aplicar a", "Apply to selection": "Aplicar á selección", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Aplique primeiro a aparencia. Exporta as aparencias asignadas e os materiais creados neste deseño.", "Arc length": "Lonxitude do arco", "Area": "Área", "Attachment": "Ancoraxe", @@ -56,14 +58,15 @@ "Check collisions": "Comprobar colisións", "Checked": "Marcado", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Escolle un plano novo. A xeometría e as cotas do bosquexo mantéñense nas súas coordenadas bidimensionais.", + "Choose…": "Escoller…", "Circumference": "Circunferencia", "Close draft": "Pechar o borrador", "Collisions": "Colisións", "Colour": "Cor", + "Comment": "Comentario", "Component1": "Compoñente1", "Configuration": "Configuración", "Configuration rules": "Regras de configuración", - "Construction": "Construción", "Contact": "Contacto", "Continuity": "Continuidade", "Copy Error Message": "Copiar a mensaxe de erro", @@ -83,11 +86,11 @@ "Curvature (G2)": "Curvatura (G2)", "Curvature change (G3)": "Cambio de curvatura (G3)", "Customize Toolbar": "Personalizar a barra de ferramentas", + "Delete": "Eliminar", "Delete all": "Eliminar todo", "Delete rule": "Eliminar a regra", "Delete the row": "Eliminar a fila", "Delete the set": "Eliminar o conxunto", - "Delete this parameter": "Eliminar este parámetro", "Density": "Densidade", "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.", @@ -98,15 +101,19 @@ "Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Dobre clic co botón central na vista: Inicio (a vista de inicio, encadrando o que se mostra), Axustar (encadrar o que se mostra mantendo a orientación) ou nada", "Drive Joint": "Accionar a unión", "Edges": "Arestas", + "Edit": "Editar", "Edit Profile Sketch": "Editar o bosquexo do perfil", "Enable rule": "Activar a regra", "Enabled": "Activado", "Explode": "Explosionar", "Export Animation…": "Exportar animación…", + "Export CSV…": "Exportar CSV…", "Export Current View as DXF": "Exportar a vista actual como DXF", "Export Current View as DXF…": "Exportar a vista actual como DXF…", "Export DXF…": "Exportar DXF…", + "Export JSON…": "Exportar JSON…", "Export hidden edges as dashed lines": "Exportar as arestas ocultas como liñas descontinuas", + "Expression": "Expresión", "Extend to meet": "Estender ata atopala", "Extents": "Extensións", "FILES": "FICHEIROS", @@ -115,11 +122,12 @@ "Face attachment lost": "Perdeuse a ancoraxe á cara", "Faces": "Caras", "Faces / edges / vertices": "Caras / arestas / vértices", + "Favorites": "Favoritos", "Favourite": "Favorito", + "Feature": "Operación", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Os ficheiros de moitos programas CAD teñen o Y cara arriba, así que aquí ábrense deitados (Z cara arriba).", "Filter": "Filtro", "Find in Browser": "Buscar no navegador", - "Flat": "Plano", "Flat size": "Tamaño desenvolvido", "Follow camera (Look At sketches)": "Seguir a cámara (Mirar cara aos bosquexos)", "Follow the camera (Look At sketches)": "Seguir a cámara (Mirar cara aos bosquexos)", @@ -134,6 +142,7 @@ "Holes (edges on them)": "Furados (arestas sobre eles)", "Home view": "Vista de inicio", "IMAGES": "IMAXES", + "Import…": "Importar…", "In This Design": "Neste deseño", "In place (surfaces)": "No sitio (superficies)", "In plane": "No plano", @@ -154,7 +163,9 @@ "Length units": "Unidades de lonxitude", "Letter Spacing": "Espazado entre letras", "Library": "Biblioteca", + "Library file": "Ficheiro de biblioteca", "Line Spacing": "Espazado entre liñas", + "Local library": "Biblioteca local", "Loop": "Bucle", "MANAGE": "XESTIONAR", "MATERIALS": "MATERIAIS", @@ -162,35 +173,43 @@ "MOTION": "MOVEMENTO", "Margin": "Marxe", "Mass": "Masa", + "Matched restrictions check the final values.": "As restricións que se cumpren comproban os valores finais.", "Material": "Material", "Max": "Máx.", "Mid Radius": "Raio medio", "Middle double-click": "Dobre clic central", "Min": "Mín.", "Mitre": "Inglete", + "Model Parameters": "Parámetros do modelo", "Modeled": "Modelado", "More toolbar panels": "Máis paneis da barra de ferramentas", "Motion": "Movemento", "Move Type": "Tipo de movemento", + "Move down": "Mover abaixo", "Move earlier": "Mover antes", "Move later": "Mover despois", + "Move up": "Mover arriba", "Name": "Nome", + "New": "Novo", + "New rule…": "Nova regra…", "No crash reports.": "Non hai informes de fallos.", + "No matching appearance presets.": "Non hai preaxustes de aparencia coincidentes.", "No matching appearances": "Non hai aparencias coincidentes", "No visible bodies to export.": "Non hai corpos visibles para exportar.", "None": "Ningún", - "Normal to sketch": "Normal ao bosquexo", "Not available yet": "Aínda non dispoñible", "Notch": "Entalle", "OUTPUT": "SAÍDA", "Opacity": "Opacidade", "Open GitHub draft": "Abrir o borrador de GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Abra as bibliotecas locais na aplicación de escritorio. Aquí pode exportar a biblioteca deste deseño.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "CAD 3D paramétrico de código aberto en Rust. Con licenza MIT OR Apache-2.0.", + "Operator": "Operador", "Other side": "Outro lado", "Other surface (mutual trim)": "Outra superficie (recorte mutuo)", "Outside": "Fóra", "Overall length": "Lonxitude total", - "Parameter values": "Valores dos parámetros", + "Parameter": "Parámetro", "Pass through": "Atravesar", "Path": "Traxectoria", "Per Edge": "Por aresta", @@ -205,7 +224,6 @@ "Preview Row": "Previsualizar a fila", "Preview leaves the current design unchanged.": "A previsualización non cambia o deseño actual.", "Profiles/Curves": "Perfís/curvas", - "Projection": "Proxección", "RECENT": "RECENTES", "REFERENCE": "REFERENCIA", "RULED SURFACE": "SUPERFICIE REGRADA", @@ -241,6 +259,8 @@ "Roll History Marker Here": "Levar o marcador do historial aquí", "Round": "Redondo", "Rule": "Regra", + "Rule name": "Nome da regra", + "Rules run from top to bottom. Matched drives change effective values.": "As regras execútanse de arriba abaixo. As regras de control que se cumpren modifican os valores efectivos.", "SECTION": "SECCIÓN", "SURFACE QUALITY": "CALIDADE DA SUPERFICIE", "Save Rule": "Gardar a regra", @@ -249,6 +269,7 @@ "Scale factor": "Factor de escala", "Search": "Buscar", "Search commands": "Buscar ordes", + "Search this library": "Buscar nesta biblioteca", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleccionar outro: mantén o botón esquerdo quieto sobre o modelo (ou clic dereito › Seleccionar outro…) para listar todo o que hai baixo o cursor; fai clic de novo no mesmo punto para seleccionar o seguinte. Fai dobre clic co botón central para ir ao Inicio (Preferencias › Navegación).", "Select bodies (a face picks its body)": "Selecciona corpos (unha cara escolle o seu corpo)", "Select bodies or faces instead": "Seleccionar corpos ou caras no seu lugar", @@ -303,17 +324,18 @@ "Turn off section analysis before exporting the view.": "Desactiva a análise de sección antes de exportar a vista.", "Type": "Tipo", "UNTRIM": "DESRECORTAR", + "Unit": "Unidade", "Updating the preview": "Actualizando a previsualización", "Use active author placement": "Usar a colocación de autor activa", "Use fixed plane": "Usar un plano fixo", "Use saved fixed plane": "Usar o plano fixo gardado", "Use selected face as fixed plane": "Usar a cara seleccionada como plano fixo", + "User Parameters": "Parámetros de usuario", "VIEW": "VISTA", "VIEWS": "VISTAS", "Value": "Valor", "Vertices": "Vértices", "View navigator": "Navegador de vistas", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Só os corpos visibles. As curvas usan segmentos de liña (tolerancia de 0,01 mm).", "Volume": "Volume", "Wall": "Parede", "Wall Location": "Situación da parede", @@ -321,6 +343,12 @@ "When": "Cando", "Width": "Largura", "Will fail": "Fallará", + "X angle": "Ángulo X", + "X distance": "Distancia X", + "Y angle": "Ángulo Y", + "Y distance": "Distancia Y", + "Z angle": "Ángulo Z", + "Z distance": "Distancia Z", "angle, height": "ángulo, altura", "appearance.dielectric_ior": "IOR dieléctrico", "appearance.metallic": "Metálico", @@ -329,7 +357,11 @@ "appearance.roughness": "Rugosidade", "appearance.surface_response": "Resposta da superficie", "browser.body_name": "Corpo{number}", + "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.", + "browser.capture_one": "Moveuse un compoñente, pero a súa posición non se capturou.", "browser.component_name": "Compoñente{number}", + "browser.move_title": "Mover: {name}", "browser.parameters": "Parámetros ({count})", "browser.precision": "Precisión: {precision}", "browser.sketch_name": "Bosquexo{number}", @@ -651,7 +683,8 @@ "cmd.view_navigate": "Navegar pola vista", "cmd.view_rename": "Renomear a vista con nome", "cmd.view_save": "Vista con nome nova", - "command name": "nome da orde", + "contact.components_count": "{count} compoñentes", + "contact.enabled_label": "activado", "coupling.gear_1": "Engrenaxe 1", "coupling.gear_1_pick": "Engrenaxe 1 (opcional)", "coupling.gear_2": "Engrenaxe 2", @@ -668,8 +701,11 @@ "coupling.rack": "Cremalleira", "coupling.radius_1": "Raio 1", "coupling.radius_2": "Raio 2", + "delete.and_more": "… e {count} máis", "dialog.computing": "Calculando… {seconds} s", "dialog.custom_axis": "↖ Eixe personalizado", + "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.select": "↖ Seleccionar", "dialog.selected_count": "↖ {count} seleccionados", @@ -693,9 +729,17 @@ "dxf.progress.write": "Escribindo o DXF (5/5)", "dxf.tolerance": "Tolerancia da curva", "dxf.tolerance_help": "Só os corpos visibles. As curvas usan segmentos rectos. Unha tolerancia menor dá curvas máis finas e usa máis tempo e memoria.", - "expression, e.g. 25 mm": "expresión, p. ex. 25 mm", "fits the face, e.g. M8": "encaixa na cara, p. ex. M8", "from the rule": "da regra", + "help.apache": "Licenza Apache 2.0", + "help.attributions": "Atribucións (ATTRIBUTION.md)", + "help.build": "Versión {version} · commit {commit} · compilada o {date}", + "help.font_details": "A interface usa as fontes integradas de egui (Ubuntu Light e Hack, baixo as licenzas Ubuntu Font Licence e MIT/Bitstream Vera; Noto Emoji e a fonte de iconas emoji, baixo OFL e MIT). O texto dos bosquexos usa Liberation Sans (SIL OFL 1.1). O paquete opcional de fontes de The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), non se inclúe nas compilacións de SolidCraft.", + "help.fonts": "Fontes", + "help.licences": "Licenzas", + "help.mit": "Licenza MIT", + "help.notice": "NOTICE", + "help.third_party": "Licenzas de terceiros", "home.documentation": "Documentación", "home.in_this_browser": "neste navegador", "home.missing": "non atopado", @@ -724,6 +768,26 @@ "joint.from_bodies_hint": "Cada corpo escollido pasa a un compoñente novo propio e queda onde está.", "joint.one_body": "As dúas seleccións están en {body}. Escolle a segunda noutra peza.", "joint.one_component": "Estas pezas son corpos dun mesmo compoñente: {bodies}. Unha unión move un compoñente respecto doutro, así que cada peza precisa o seu propio compoñente.", + "keymap.command_id": "ID do comando", + "keymap.empty": "Non hai comandos coincidentes", + "keymap.use_anyway": "Usar {key} aquí de todos os xeitos", + "manage.appearance_hint": "Escolla unha aparencia para a selección ou arrastre unha mostra a un corpo ou unha cara.", + "manage.assign_hint": "Seleccione corpos e escolla a súa regra de deseño de pezas de plástico.", + "manage.config_hint": "Edite as filas, columnas e regras de configuración e despois active unha fila ou amose a súa vista previa.", + "manage.empty": "Non hai filas coincidentes.", + "manage.material_hint": "Seleccione corpos e escolla o seu material físico.", + "manage.parameters_hint": "Edite expresións e unidades. Os cambios aplícanse ao premer Intro ou saír dun campo.", + "manage.rules_hint": "Escolla unha regra para editar ou cree unha nova. Confirme para gardar a regra.", + "measure.at": "En", + "measure.item": "Elemento {number}", + "motion.delete_key": "Eliminar fotograma clave", + "motion.key_here": "Inserir fotograma clave aquí", + "motion.key_here_hint": "Engadir un fotograma clave neste paso co valor actual da unión", + "motion.step_back": "Paso atrás", + "motion.step_forward": "Paso adiante", + "motion.to_end": "Ao final", + "motion.to_start": "Ao inicio", + "motion.track_name": "Pista {number}", "nav.touchpad.help": "Mantén Maiús e move o punteiro para desprazar; mantén Alt/Opción e móveo para orbitar. Non fai falta ningún botón do rato. O desprazamento vertical fai zoom.", "nav.touchpad.label": "Panel táctil", "nav.touchpad.orbit": "Orbitar (mantén Alt/Opción e move; sen botón do rato)", @@ -732,11 +796,12 @@ "nav.touchpad.zoom": "Zoom (desprazamento vertical ou pinza)", "needs two joints": "precisa dúas unións", "new configuration": "configuración nova", - "new name": "nome novo", "new parameter": "parámetro novo", "no interference": "sen interferencias", "no joint that moves yet": "aínda non hai ningunha unión que se mova", "no picture": "sen imaxe", + "none": "ningún", + "parts.automatic_name": "automático", "pattern.alignment_2": "Aliñamento 2", "pattern.alignment_hint": "O número inclúe o orixinal. Centrar equilibra as copias; Inverter escolle o lado da copia extra cando o total é par.", "pattern.direction_1": "Dirección 1", @@ -744,12 +809,13 @@ "pattern.flip_direction": "Inverter a dirección", "pick a hole to seat it, or OK places it at the origin": "escolle un furado onde asentala, ou Aceptar colócaa na orixe", "pick one or two vertices, edges or faces": "escolle un ou dous vértices, arestas ou caras", + "prefs.cpu": "CPU (software)", + "prefs.minutes_suffix": " min", + "properties.title": "Propiedades: {name}", "render.background": "Fondo da escena", "render.fit": "Encadrar o produto", - "render.image_cancel": "Cancelar o render", "render.image_cancelled": "Render cancelado", "render.image_cancelling": "Cancelando", - "render.image_check": "Comprobar a solicitude de render", "render.image_completed": "Imaxe lista para exportar", "render.image_encoding": "Codificando o PNG", "render.image_export": "Exportar PNG", @@ -806,15 +872,28 @@ "settings.reset_intro": "Estes axustes non funcionan neste ordenador e impedirían que SolidCraft se iniciase, así que se cambiaron:", "settings.reset_title": "Cambiáronse os axustes", "settings.supersampling_note": "Debuxa a vista a unha resolución maior e fíltraa cara abaixo: arestas, liñas e grella máis suaves en pantallas por debaixo de 4K. Consome tempo de GPU: uns 2,25× píxeles a 1,5× e 4× a 2×.", + "shortcut.json_error": "os parámetros deben estar en formato JSON: {error}", + "shortcut.no_command": "non existe o comando `{id}`; o texto anterior ao JSON debe ser un ID de comando", + "shortcut.pin": "Fixar na parte superior da caixa S", + "shortcut.unpin": "Desafixar", "sketch.degrees_of_freedom": "{count} graos de liberdade", + "sketch.dof_left": "Quedan {count} graos de liberdade", + "sketch.dof_left_one": "Queda {count} grao de liberdade", "sketch.draw_curves": "Debuxar como {linetype}", "sketch.make_selection": "Converter a selección en {linetype}", + "sketch_panel.attached": "Ligado a {body} / {path}", + "sketch_panel.author": "Localización de autoría: {path}", + "sketch_panel.dimension_hint": "Introduza un valor ou unha expresión e prema Aceptar.", + "sketch_panel.move_hint": "Introduza as distancias e un ángulo e prema Aceptar.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Ligado a unha cara plana dun corpo de chapa; segue ese panel cando cambia a chapa.", + "sketch_panel.text_hint": "Edite o texto e a súa posición e prema Aceptar.", + "sketch_panel.use_plane": "Usar plano {plane}", "sketch_text.align_center": "Centro", "sketch_text.align_left": "Esquerda", "sketch_text.align_right": "Dereita", "sketch_text.bold": "Negra", "sketch_text.builtin_font": "{family} (integrado)", - "sketch_text.font_search": "Buscar tipos de letra", "sketch_text.italic": "Cursiva", "sketch_text.loading_fonts": "Buscando os tipos de letra instalados…", "sketch_text.on_path": "O texto segue a súa traxectoria: a curva colócao", @@ -829,6 +908,7 @@ "timeline.reorder_many": "Reordenar operacións", "timeline.ungroup": "Quitar os cartafoles de operacións", "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.", "toolbar.image": "IMAXE", "toolbar.render": "RENDER", "toolbar.render_scene": "ESCENA", @@ -837,9 +917,7 @@ "trace.stopped_by_loop": "Remata na mostra {sample}: o bucle a través de {joint} non pode pecharse máis", "ui.1_degree_of_freedom": "1 grao de liberdade", "ui.3d_mouse_speed": "Velocidade do rato 3D", - "ui.3d_mouse_windows": "Rato 3D", "ui.3d_sketch": "Bosquexo 3D", - "ui.about": "Acerca de", "ui.activate": "Activar", "ui.active_component": "Compoñente activo", "ui.agent_chat": "Chat co axente", @@ -881,7 +959,7 @@ "ui.agent_chat_model_tip": "O modelo que usa o axente. A túa escolla lémbrase para este axente.", "ui.agent_chat_new": "Chat novo", "ui.agent_chat_new_confirm": "Comezar un chat novo? Borrarase o actual.", - "ui.agent_chat_new_yes": "Chat novo", + "ui.agent_chat_next_picture": "Imaxe seguinte", "ui.agent_chat_no_cli": "Non se atopou solidcraft-cli xunto á aplicación, así que o axente non pode chegar ao deseño.", "ui.agent_chat_no_control": "O punto de acceso do chat co axente non se iniciou, así que o axente non pode chegar ao deseño.", "ui.agent_chat_no_images": "Este axente non admite imaxes", @@ -893,6 +971,7 @@ "ui.agent_chat_pick": "Axente", "ui.agent_chat_placeholder": "Pídelle ao axente que cambie o deseño…", "ui.agent_chat_placeholder_busy": "Pon unha mensaxe na cola para cando remate o axente (Ctrl+Intro: enviar agora)", + "ui.agent_chat_previous_picture": "Imaxe anterior", "ui.agent_chat_privacy": "Nada vai a ningún sitio agás ao axente que executas, coa túa propia conta.", "ui.agent_chat_privacy_note": "SolidCraft non envía nada a ningún sitio: o axente que executas fala co seu propio servizo coa túa propia conta. SolidCraft nunca ve nin garda ningunha clave nin contrasinal.", "ui.agent_chat_queue": "Pór na cola", @@ -946,6 +1025,7 @@ "ui.ai_bad_url": "Introduce un URL que comece por http:// ou https://", "ui.ai_base_url": "URL base", "ui.ai_button": "IA", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "copia", "ui.ai_custom_url": "URL personalizado", "ui.ai_default": "Predeterminado", @@ -966,8 +1046,10 @@ "ui.ai_mode_edits_note": "As ordes de modelado execútanse sen preguntar; gardar, abrir, exportar e outras accións con ficheiros ou destrutivas seguen preguntando.", "ui.ai_mode_global": "Como se definiu arriba", "ui.ai_model": "Modelo", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Estes axustes cobren as ferramentas de SolidCraft. As ferramentas propias dun axente (o seu shell, as súas edicións de ficheiros) seguen o modo propio do axente, que escolles na cabeceira do panel de chat cando o axente ten modos.", "ui.ai_name": "Nome", + "ui.ai_name_hint": "O meu axente", "ui.ai_need_command": "Introduce a orde que inicia o axente", "ui.ai_need_model": "Introduce o nome do modelo", "ui.ai_no_answer": "O axente non respondeu a tempo", @@ -998,6 +1080,7 @@ "ui.ai_toolbar_button": "Mostrar o botón na barra de ferramentas", "ui.ai_toolbar_button_note": "O botón de IA no extremo dereito da barra de ferramentas. Ctrl+Maiús+A e o menú Vista seguen abrindo o panel.", "ui.ai_undo_tip": "Desfai nun só paso todo o que cambiou o axente para esta mensaxe", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Todo", "ui.analysis": "Análise", "ui.angle": "Ángulo", @@ -1009,7 +1092,6 @@ "ui.anti_aliasing": "Antialiasing", "ui.apply": "Aplicar", "ui.arc": "Arco", - "ui.assemble": "Ensamblar", "ui.autosave_every": "Gardar automaticamente cada", "ui.axes": "Eixes", "ui.axis": "Eixe", @@ -1019,6 +1101,11 @@ "ui.bodies": "Corpos", "ui.box": "Caixa", "ui.break": "Romper", + "ui.browser_bodies_one": "{count} corpo", + "ui.browser_bodies_other": "{count} corpos", + "ui.browser_collapse": "Contraer o navegador", + "ui.browser_expand": "Expandir o navegador", + "ui.browser_title": "Navegador", "ui.calibrate": "Calibrar", "ui.cancel": "Cancelar", "ui.canvases": "Lenzos", @@ -1028,7 +1115,6 @@ "ui.chamfer": "Chafrán", "ui.chamfer_type": "Tipo de chafrán", "ui.change_workspace": "Cambiar de espazo de traballo", - "ui.chord_length": "Lonxitude da corda", "ui.circle": "Círculo", "ui.circular_pattern": "Patrón circular", "ui.clear": "Borrar", @@ -1061,11 +1147,9 @@ "ui.command_done": "{command} feito", "ui.command_palette_hint": "Escribe unha orde, p. ex. {example} — ou preme S para buscar", "ui.concentric": "Concéntrica", - "ui.configurations": "Configuracións", "ui.conic_curve": "Curva cónica", "ui.constant": "Constante", "ui.constraints_conflict": "Conflito de restricións", - "ui.construct": "Construír", "ui.construction": "Construción", "ui.context_menu": "Menú contextual", "ui.continuity_gap": "Separación", @@ -1088,7 +1172,6 @@ "ui.cylinder": "Cilindro", "ui.dark": "Escuro", "ui.default_modelling_orientation": "Orientación de modelado predeterminada", - "ui.degrees_of_freedom": "graos de liberdade", "ui.delete": "Eliminar", "ui.depth": "Profundidade", "ui.design": "Deseño", @@ -1130,7 +1213,6 @@ "ui.equivalent_radius": "Raio equivalente", "ui.expand": "Expandir", "ui.exportDxf": "Exportar DXF…", - "ui.export_ellipsis": "Exportar...", "ui.export_ellipsis_keymap": "Exportar…", "ui.export_keymap_tip": "Teclas, favoritos da caixa S e a barra de ferramentas, como un ficheiro", "ui.extend": "Estender", @@ -1212,7 +1294,6 @@ "ui.heading_sheet_metal": "CHAPA", "ui.heading_shell": "BALEIRADO", "ui.heading_sketch": "BOSQUEXO", - "ui.heading_sketch_palette": "PALETA DO BOSQUEXO", "ui.heading_sketch_scale": "ESCALA DO BOSQUEXO", "ui.heading_solid": "SÓLIDO", "ui.heading_sphere": "ESFERA", @@ -1231,6 +1312,7 @@ "ui.hideOrigin": "Ocultar a orixe", "ui.hidePlanes": "Ocultar os planos", "ui.hideProfile": "Ocultar o perfil", + "ui.history_title": "Historial", "ui.hole": "Furado", "ui.hole_tap_type": "Tipo de roscado do furado", "ui.hole_type": "Tipo de furado", @@ -1250,7 +1332,6 @@ "ui.length_decimals": "Decimais das lonxitudes", "ui.light": "Claro", "ui.line": "Liña", - "ui.linetype": "Tipo de liña", "ui.lock": "Bloquear", "ui.loft": "Transición", "ui.look_at": "Mirar cara a", @@ -1282,10 +1363,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF con etiquetas de cara", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Punto medio", "ui.mirror": "Simetría", "ui.mirror_line": "Liña de simetría", - "ui.modify": "Modificar", "ui.move": "Mover", "ui.multiple": "Múltiple", "ui.named_views": "Vistas con nome", @@ -1305,7 +1384,6 @@ "ui.offset_plane": "Plano desprazado", "ui.one_side": "Un lado", "ui.open": "Abrir", - "ui.open_ellipsis": "Abrir...", "ui.open_preferences": "Abrir as preferencias", "ui.open_recent": "Abrir recentes", "ui.operation": "Operación", @@ -1314,8 +1392,9 @@ "ui.orbit_around": "Orbitar arredor de", "ui.orientation": "Orientación", "ui.origin": "Orixe", - "ui.palette_points": "Puntos", - "ui.palette_profile": "Perfil", + "ui.palette_construction_mode": "Modo de construción (X)", + "ui.palette_more": "Máis", + "ui.palette_sketch_options": "Opcións do esbozo", "ui.pan": "Desprazar", "ui.pan_zoom_orbit": "Desprazar, zoom, orbitar", "ui.parallel": "Paralela", @@ -1349,7 +1428,6 @@ "ui.quit": "Saír", "ui.radial_context_menu": "Menú contextual radial", "ui.radius": "Raio", - "ui.radius_type": "Tipo de raio", "ui.reachable": "Alcanzable", "ui.rectangle": "Rectángulo", "ui.rectangular_pattern": "Patrón rectangular", @@ -1372,7 +1450,6 @@ "ui.right": "Dereita", "ui.save": "Gardar", "ui.save_as": "Gardar como", - "ui.save_as_ellipsis": "Gardar como...", "ui.save_mesh_no_slicer": "Non se atopou ningún laminador: escolle Personalizado… e selecciona o teu programa laminador, ou desactiva Enviar ao laminador", "ui.save_mesh_pick_bodies": "Selecciona os corpos que queres gardar", "ui.save_mesh_temp_failed": "Non se puido crear {folder}: {error}", @@ -1384,7 +1461,6 @@ "ui.select": "Seleccionar", "ui.selectOther": "Seleccionar outro…", "ui.selectTangentChain": "Seleccionar cadea tanxente", - "ui.select_other_hint": "Seleccionar outro: mantén o botón esquerdo quieto sobre o modelo (ou clic dereito › Seleccionar outro…) para listar todo o que hai baixo o cursor; fai clic de novo no mesmo punto para seleccionar o seguinte.", "ui.selection": "Selección", "ui.send": "Enviar", "ui.send_to_slicer": "Enviar ao laminador", @@ -1401,7 +1477,6 @@ "ui.show_constraints": "Restricións", "ui.show_construction_geometries": "Xeometrías de construción", "ui.show_dimensions": "Cotas", - "ui.show_points": "Mostrar os puntos", "ui.show_profile": "Mostrar o perfil", "ui.show_projected_geometries": "Xeometrías proxectadas", "ui.simple": "Simple", @@ -1411,7 +1486,6 @@ "ui.sketch_dimension": "Cota do bosquexo", "ui.sketch_grid": "Grella do bosquexo", "ui.sketch_scale": "Escala do bosquexo", - "ui.sketch_status": "Estado do bosquexo", "ui.sketches": "Bosquexos", "ui.slice": "Laminar", "ui.slicer": "Laminador", @@ -1457,7 +1531,6 @@ "ui.stand_up_tip": "Despois de abrir un ficheiro STEP ou IGES con Z cara arriba, ofrecer un cuarto de volta arredor de X", "ui.start": "Inicio", "ui.start_over": "Volver comezar", - "ui.start_radius": "Raio inicial", "ui.supersampling": "Supermostraxe (SSAA)", "ui.supersampling_adaptive": "Só cando a vista está quieta", "ui.supersampling_off": "Desactivado (1×)", @@ -1501,7 +1574,6 @@ "ui.untitled": "Sen título", "ui.up_axis_note": "Que eixe apunta cara arriba nas vistas (vista de eixe, Inicio, Mirar cara a). Os deseños non cambian.", "ui.updateToCurrentView": "Actualizar á vista actual", - "ui.variable": "Variable", "ui.view_centre": "Centro da vista", "ui.visual_shaded_hidden_edges": "Sombreado con arestas ocultas", "ui.visual_shaded_with_edges": "Sombreado con arestas", @@ -1517,7 +1589,6 @@ "ui.zoom_to_the_cursor": "Zoom cara ao cursor", "update.available": "Está dispoñible SolidCraft {version}", "update.button": "Actualizar", - "update.button_tip": "Está dispoñible SolidCraft {version}", "update.button_version": "Actualizar a {version}", "update.check_automatically": "Buscar actualizacións automaticamente", "update.check_now": "Comprobar agora", @@ -1549,5 +1620,6 @@ "update.title": "Actualización dispoñible", "update.up_to_date": "SolidCraft está actualizado.", "update.update_now": "Actualizar agora", - "update.you_have": "Tes a versión {version}." + "update.you_have": "Tes a versión {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ escoller · Intro / Tab completar · Esc pechar" } diff --git a/assets/locales/it.json b/assets/locales/it.json index b2e7bfd7e7..b15f550f30 100644 --- a/assets/locales/it.json +++ b/assets/locales/it.json @@ -5,9 +5,9 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Allega la schermata: copiala e incollala, oppure salva e carica il PNG.", "3. Click Submit new issue on GitHub to send your report.": "3. Fai clic su Submit new issue su GitHub per inviare la segnalazione.", "A new configuration, a copy of the active one": "Una nuova configurazione, copia di quella attiva", - "AI": "IA", "About SolidCraft": "Informazioni su SolidCraft", "Acceleration": "Accelerazione", + "Actions": "Azioni", "Active": "Attivo", "Active rule": "Regola attiva", "Add": "Aggiungi", @@ -15,6 +15,7 @@ "Add Mid Radius": "Aggiungi raggio intermedio", "Add Row": "Aggiungi riga", "Add condition": "Aggiungi condizione", + "Add or edit rule": "Aggiungi o modifica regola", "Against": "Rispetto a", "Alignment": "Allineamento", "All Sketch Points": "Tutti i punti dello schizzo", @@ -28,6 +29,7 @@ "Applied Radius": "Raggio applicato", "Apply To": "Applica a", "Apply to selection": "Applica alla selezione", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Applica prima l’aspetto. Esporta gli aspetti assegnati e i materiali creati in questo progetto.", "Arc length": "Lunghezza dell'arco", "Area": "Area", "Attachment": "Collegamento", @@ -52,14 +54,15 @@ "Check collisions": "Controlla collisioni", "Checked": "Controllato", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Scegli un nuovo piano. La geometria e le quote dello schizzo restano invariate nelle sue coordinate bidimensionali.", + "Choose…": "Scegli…", "Circumference": "Circonferenza", "Close draft": "Chiudi bozza", "Collisions": "Collisioni", "Colour": "Colore", + "Comment": "Commento", "Component1": "Componente1", "Configuration": "Configurazione", "Configuration rules": "Regole di configurazione", - "Construction": "Costruzione", "Contact": "Contatto", "Contact sets": "Gruppi di contatto", "Continuity": "Continuità", @@ -81,11 +84,11 @@ "Curvature (G2)": "Curvatura (G2)", "Curvature change (G3)": "Variazione di curvatura (G3)", "Customize Toolbar": "Personalizza barra degli strumenti", + "Delete": "Elimina", "Delete all": "Elimina tutto", "Delete rule": "Elimina regola", "Delete the row": "Elimina la riga", "Delete the set": "Elimina il gruppo", - "Delete this parameter": "Elimina questo parametro", "Density": "Densità", "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.", @@ -98,26 +101,31 @@ "Drive Joint": "Aziona giunto", "Duration": "Durata", "Edges": "Spigoli", + "Edit": "Modifica", "Edit Profile Sketch": "Modifica schizzo del profilo", "Enable rule": "Abilita regola", "Enabled": "Abilitato", "Explode": "Esplodi", "Explode Text": "Esplodi testo", "Export Animation…": "Esporta animazione…", + "Export CSV…": "Esporta CSV…", "Export Current View as DXF": "Esporta vista corrente come DXF", "Export Current View as DXF…": "Esporta vista corrente come DXF…", "Export DXF…": "Esporta DXF…", + "Export JSON…": "Esporta JSON…", "Export hidden edges as dashed lines": "Esporta gli spigoli nascosti come linee tratteggiate", + "Expression": "Espressione", "Extents": "Estensioni", "Face": "Faccia", "Face attachment lost": "Collegamento alla faccia perso", "Faces": "Facce", "Faces / edges / vertices": "Facce / spigoli / vertici", + "Favorites": "Preferiti", "Favourite": "Preferito", + "Feature": "Lavorazione", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Molti programmi CAD usano Y come asse verticale: qui, dove l'asse verticale è Z, i loro file si aprono coricati.", "Filter": "Filtro", "Find in Browser": "Trova nella struttura", - "Flat": "Piatto", "Flat size": "Dimensioni dello sviluppo", "Follow camera (Look At sketches)": "Segui la camera (Vista normale agli schizzi)", "Follow the camera (Look At sketches)": "Segui la camera (Vista normale agli schizzi)", @@ -130,6 +138,7 @@ "Holes": "Fori", "Holes (edges on them)": "Fori (spigoli dei fori)", "Home view": "Vista iniziale", + "Import…": "Importa…", "In This Design": "In questo progetto", "In place (surfaces)": "In posizione (superfici)", "In plane": "Nel piano", @@ -145,39 +154,49 @@ "Length units": "Unità di lunghezza", "Letter Spacing": "Spaziatura dei caratteri", "Library": "Libreria", + "Library file": "File di libreria", "Line Spacing": "Interlinea", + "Local library": "Libreria locale", "Loop": "Ciclo", "Margin": "Margine", "Mass": "Massa", + "Matched restrictions check the final values.": "Le restrizioni le cui condizioni sono soddisfatte verificano i valori finali.", "Material": "Materiale", "Max": "Max", "Mid Radius": "Raggio intermedio", "Middle double-click": "Doppio clic centrale", "Min": "Min", "Mitre": "Giunzione ad angolo", + "Model Parameters": "Parametri del modello", "Modeled": "Modellato", "More toolbar panels": "Altri pannelli della barra degli strumenti", "Motion": "Movimento", "Move Type": "Tipo di spostamento", + "Move down": "Sposta giù", "Move earlier": "Sposta prima", "Move later": "Sposta dopo", + "Move up": "Sposta su", "Name": "Nome", "Nearest": "Più vicini", + "New": "Nuovo", + "New rule…": "Nuova regola…", "New study": "Nuovo studio", "No crash reports.": "Nessun rapporto di arresto anomalo.", + "No matching appearance presets.": "Nessun predefinito di aspetto corrispondente.", "No matching appearances": "Nessun aspetto corrispondente", "No visible bodies to export.": "Nessun corpo visibile da esportare.", "None": "Nessuno", - "Normal to sketch": "Normale allo schizzo", "Not available yet": "Non ancora disponibile", "Notch": "Intaglio", "Opacity": "Opacità", "Open GitHub draft": "Apri bozza su GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Apri le librerie locali nell’applicazione desktop. Qui puoi esportare la libreria di questo progetto.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "CAD 3D parametrico open source in Rust. Licenza MIT OR Apache-2.0.", + "Operator": "Operatore", "Other side": "Lato opposto", "Outside": "Esterno", "Overall length": "Lunghezza complessiva", - "Parameter values": "Valori dei parametri", + "Parameter": "Parametro", "Pass through": "Passante", "Path": "Percorso", "Per Edge": "Per spigolo", @@ -195,7 +214,6 @@ "Preview Row": "Anteprima della riga", "Preview leaves the current design unchanged.": "L'anteprima lascia invariato il progetto corrente.", "Profiles/Curves": "Profili/Curve", - "Projection": "Proiezione", "Quantity": "Quantità", "RECENT": "RECENTI", "RULED SURFACE": "SUPERFICIE RIGATA", @@ -230,12 +248,15 @@ "Roll History Marker Here": "Sposta qui il marcatore della cronologia", "Round": "Arrotondato", "Rule": "Regola", + "Rule name": "Nome della regola", + "Rules run from top to bottom. Matched drives change effective values.": "Le regole vengono eseguite dall’alto verso il basso. Le regole di controllo le cui condizioni sono soddisfatte modificano i valori effettivi.", "Save Rule": "Salva regola", "Save the study as an animated GIF, seen as the viewport shows it": "Salva lo studio come GIF animata, così come appare nella vista", "Saved preview. Geometry is being checked before editing.": "Anteprima salvata. Verifica della geometria in corso prima della modifica.", "Scale factor": "Fattore di scala", "Search": "Cerca", "Search commands": "Cerca comandi", + "Search this library": "Cerca in questa libreria", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleziona altro: tieni premuto il pulsante sinistro senza muovere il puntatore sul modello (o fai clic destro › Seleziona altro…) per elencare gli oggetti sotto il cursore; fai di nuovo clic nello stesso punto per selezionare l'oggetto successivo. Fai doppio clic con il pulsante centrale per tornare alla vista iniziale (Preferenze › Navigazione).", "Select bodies (a face picks its body)": "Seleziona corpi (una faccia seleziona il corpo a cui appartiene)", "Select bodies or faces instead": "Seleziona invece corpi o facce", @@ -288,17 +309,18 @@ "Turn off section analysis before exporting the view.": "Disattiva l'analisi in sezione prima di esportare la vista.", "Type": "Tipo", "UNTRIM": "RIPRISTINA SUPERFICIE", + "Unit": "Unità", "Updating the preview": "Aggiornamento dell'anteprima", "Use active author placement": "Usa posizionamento di creazione attivo", "Use fixed plane": "Usa piano fisso", "Use saved fixed plane": "Usa piano fisso salvato", "Use selected face as fixed plane": "Usa faccia selezionata come piano fisso", + "User Parameters": "Parametri utente", "VIEW": "VISTA", "Value": "Valore", "Velocity": "Velocità", "Vertices": "Vertici", "View navigator": "Navigatore della vista", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Solo corpi visibili. Le curve usano segmenti di retta (tolleranza 0.01 mm).", "Volume": "Volume", "Wall": "Parete", "Wall Location": "Posizione della parete", @@ -306,6 +328,12 @@ "When": "Quando", "Width": "Larghezza", "Will fail": "Non riuscirà", + "X angle": "Angolo X", + "X distance": "Distanza X", + "Y angle": "Angolo Y", + "Y distance": "Distanza Y", + "Z angle": "Angolo Z", + "Z distance": "Distanza Z", "angle, height": "angolo, altezza", "appearance.dielectric_ior": "Indice di rifrazione dielettrico", "appearance.metallic": "Metallicità", @@ -314,7 +342,11 @@ "appearance.roughness": "Rugosità", "appearance.surface_response": "Risposta della superficie", "browser.body_name": "Corpo{number}", + "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.", + "browser.capture_one": "Un componente è stato spostato, ma la sua posizione non è stata acquisita.", "browser.component_name": "Componente{number}", + "browser.move_title": "Sposta: {name}", "browser.parameters": "Parametri ({count})", "browser.precision": "Precisione: {precision}", "browser.sketch_name": "Schizzo{number}", @@ -643,7 +675,8 @@ "cmd.view_navigate": "Naviga nella vista", "cmd.view_rename": "Rinomina vista con nome", "cmd.view_save": "Nuova vista con nome", - "command name": "nome del comando", + "contact.components_count": "{count} componenti", + "contact.enabled_label": "abilitato", "coupling.gear_1": "Ruota dentata 1", "coupling.gear_1_pick": "Ruota dentata 1 (facoltativa)", "coupling.gear_2": "Ruota dentata 2", @@ -660,8 +693,11 @@ "coupling.rack": "Cremagliera", "coupling.radius_1": "Raggio 1", "coupling.radius_2": "Raggio 2", + "delete.and_more": "… e altri {count}", "dialog.computing": "Calcolo in corso… {seconds} s", "dialog.custom_axis": "↖ Asse personalizzato", + "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.select": "↖ Seleziona", "dialog.selected_count": "↖ Selezionati: {count}", @@ -685,9 +721,17 @@ "dxf.progress.write": "Scrittura DXF (5/5)", "dxf.tolerance": "Tolleranza delle curve", "dxf.tolerance_help": "Solo corpi visibili. Le curve usano segmenti rettilinei. Una tolleranza minore produce curve più precise e richiede più tempo e memoria.", - "expression, e.g. 25 mm": "espressione, ad es. 25 mm", "fits the face, e.g. M8": "adatta alla faccia, ad es. M8", "from the rule": "dalla regola", + "help.apache": "Licenza Apache 2.0", + "help.attributions": "Attribuzioni (ATTRIBUTION.md)", + "help.build": "Versione {version} · commit {commit} · compilata il {date}", + "help.font_details": "L’interfaccia usa i caratteri integrati di egui (Ubuntu Light e Hack, con licenze Ubuntu Font Licence e MIT/Bitstream Vera; Noto Emoji e il carattere per icone emoji, con licenze OFL e MIT). Il testo degli schizzi usa Liberation Sans (SIL OFL 1.1). Il pacchetto di caratteri facoltativo di The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), non è incluso nelle build di SolidCraft.", + "help.fonts": "Caratteri", + "help.licences": "Licenze", + "help.mit": "Licenza MIT", + "help.notice": "NOTICE", + "help.third_party": "Licenze di terze parti", "home.documentation": "Documentazione", "home.in_this_browser": "in questo browser", "home.missing": "non presente", @@ -716,6 +760,18 @@ "joint.from_bodies_hint": "Ogni corpo selezionato viene spostato in un nuovo componente distinto, mantenendo la sua posizione.", "joint.one_body": "Entrambe le selezioni sono su {body}. Seleziona il secondo elemento su un'altra parte.", "joint.one_component": "Queste parti sono corpi di un unico componente: {bodies}. Un giunto muove un componente rispetto a un altro, quindi ogni parte richiede un componente distinto.", + "keymap.command_id": "ID comando", + "keymap.empty": "Nessun comando corrispondente", + "keymap.use_anyway": "Usa comunque {key} qui", + "manage.appearance_hint": "Scegli un aspetto per la selezione o trascina un campione su un corpo o una faccia.", + "manage.assign_hint": "Seleziona i corpi e scegli la loro regola di progettazione per parti in plastica.", + "manage.config_hint": "Modifica righe, colonne e regole di configurazione, poi attiva una riga o visualizzane l’anteprima.", + "manage.empty": "Nessuna riga corrispondente.", + "manage.material_hint": "Seleziona i corpi e scegli il loro materiale fisico.", + "manage.parameters_hint": "Modifica espressioni e unità. Le modifiche vengono applicate premendo Invio o uscendo da un campo.", + "manage.rules_hint": "Scegli una regola da modificare o creane una nuova. Conferma per salvare la regola.", + "measure.at": "In", + "measure.item": "Elemento {number}", "messages.ago_h": "{n} h fa", "messages.ago_min": "{n} min fa", "messages.ago_s": "{n} s fa", @@ -727,8 +783,16 @@ "messages.title": "Messaggi", "motion.checked_to": "Controllato solo fino al passo {step}: il controllo ha esaurito il limite di calcolo", "motion.collision": "{a} · {b}: passi {steps}", + "motion.delete_key": "Elimina fotogramma chiave", "motion.duration_hint": "secondi (vuoto: per passo)", + "motion.key_here": "Inserisci fotogramma chiave qui", + "motion.key_here_hint": "Aggiungi un fotogramma chiave in questo passo con il valore attuale del giunto", "motion.no_collisions": "Nessuna collisione durante lo studio", + "motion.step_back": "Passo indietro", + "motion.step_forward": "Passo avanti", + "motion.to_end": "Alla fine", + "motion.to_start": "All’inizio", + "motion.track_name": "Traccia {number}", "nav.touchpad.help": "Tieni premuto Shift e muovi il puntatore per eseguire una panoramica; tieni premuto Alt/Option e muovilo per orbitare. Non serve premere pulsanti del mouse. Lo scorrimento verticale regola lo zoom.", "nav.touchpad.label": "Touchpad", "nav.touchpad.orbit": "Orbita (tieni premuto Alt/Option e muovi il puntatore; senza pulsanti del mouse)", @@ -737,11 +801,12 @@ "nav.touchpad.zoom": "Zoom (scorrimento verticale o gesto di pizzicamento)", "needs two joints": "richiede due giunti", "new configuration": "nuova configurazione", - "new name": "nuovo nome", "new parameter": "nuovo parametro", "no interference": "nessuna interferenza", "no joint that moves yet": "nessun giunto mobile ancora presente", "no picture": "nessuna immagine", + "none": "nessuno", + "parts.automatic_name": "automatico", "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", @@ -749,12 +814,13 @@ "pattern.flip_direction": "Inverti direzione", "pick a hole to seat it, or OK places it at the origin": "seleziona un foro in cui inserirlo, oppure premi OK per posizionarlo nell'origine", "pick one or two vertices, edges or faces": "seleziona uno o due vertici, spigoli o facce", + "prefs.cpu": "CPU (software)", + "prefs.minutes_suffix": " min", + "properties.title": "Proprietà: {name}", "render.background": "Sfondo della scena", "render.fit": "Inquadra prodotto", - "render.image_cancel": "Annulla rendering", "render.image_cancelled": "Rendering annullato", "render.image_cancelling": "Annullamento", - "render.image_check": "Verifica richiesta di rendering", "render.image_completed": "Immagine pronta per l'esportazione", "render.image_encoding": "Codifica PNG", "render.image_export": "Esporta PNG", @@ -811,17 +877,30 @@ "settings.reset_intro": "Queste impostazioni non funzionano su questo computer e impedirebbero l'avvio di SolidCraft, quindi sono state modificate:", "settings.reset_title": "Le impostazioni sono state modificate", "settings.supersampling_note": "Disegna la vista a una risoluzione maggiore e la riduce tramite filtro: spigoli, linee e griglia più uniformi sugli schermi sotto il 4K. Richiede tempo GPU: circa 2.25× i pixel a 1.5× e 4× a 2×.", + "shortcut.json_error": "i parametri devono essere in formato JSON: {error}", + "shortcut.no_command": "nessun comando `{id}`; il testo prima del JSON deve essere un ID comando", + "shortcut.pin": "Fissa in cima al riquadro S", + "shortcut.unpin": "Rimuovi fissaggio", "sketch.degrees_of_freedom": "{count} gradi di libertà", + "sketch.dof_left": "{count} gradi di libertà rimanenti", + "sketch.dof_left_one": "{count} grado di libertà rimanente", "sketch.draw_curves": "Disegna come {linetype}", "sketch.make_selection": "Converti la selezione in {linetype}", "sketch.open_ends": "Aperte {count}", "sketch.untied_ends": "Non vincolate {count}", + "sketch_panel.attached": "Collegato a {body} / {path}", + "sketch_panel.author": "Posizionamento originale: {path}", + "sketch_panel.dimension_hint": "Inserisci un valore o un’espressione, poi premi OK.", + "sketch_panel.move_hint": "Inserisci distanze e angolo, poi premi OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Collegato a una faccia piana di un corpo in lamiera; segue il pannello quando la lamiera cambia.", + "sketch_panel.text_hint": "Modifica il testo e la sua posizione, poi premi OK.", + "sketch_panel.use_plane": "Usa il piano {plane}", "sketch_text.align_center": "Centro", "sketch_text.align_left": "Sinistra", "sketch_text.align_right": "Destra", "sketch_text.bold": "Grassetto", "sketch_text.builtin_font": "{family} (integrato)", - "sketch_text.font_search": "Cerca caratteri", "sketch_text.italic": "Corsivo", "sketch_text.loading_fonts": "Ricerca dei caratteri installati…", "sketch_text.on_path": "Il testo segue il percorso: la curva ne determina la posizione", @@ -840,6 +919,7 @@ "timeline.reorder_many": "Riordina lavorazioni", "timeline.ungroup": "Rimuovi cartelle di lavorazioni", "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.", "toolbar.image": "IMMAGINE", "toolbar.render": "RENDERING", "toolbar.render_scene": "SCENA", @@ -848,9 +928,7 @@ "trace.stopped_by_loop": "Termina al campione {sample}: la catena chiusa che passa per {joint} non può chiudersi ulteriormente", "ui.1_degree_of_freedom": "1 grado di libertà", "ui.3d_mouse_speed": "Velocità del mouse 3D", - "ui.3d_mouse_windows": "Mouse 3D", "ui.3d_sketch": "Schizzo 3D", - "ui.about": "Informazioni", "ui.activate": "Attiva", "ui.active_component": "Componente attivo", "ui.agent_chat": "Chat con l'agente", @@ -892,7 +970,7 @@ "ui.agent_chat_model_tip": "Il modello usato dall'agente. La scelta viene memorizzata per questo agente.", "ui.agent_chat_new": "Nuova chat", "ui.agent_chat_new_confirm": "Avviare una nuova chat? Quella corrente verrà cancellata.", - "ui.agent_chat_new_yes": "Nuova chat", + "ui.agent_chat_next_picture": "Immagine successiva", "ui.agent_chat_no_cli": "solidcraft-cli non è stato trovato accanto all'applicazione, quindi l'agente non può accedere al progetto.", "ui.agent_chat_no_control": "L'endpoint della chat con l'agente non si è avviato, quindi l'agente non può accedere al progetto.", "ui.agent_chat_no_images": "Questo agente non accetta immagini", @@ -904,6 +982,7 @@ "ui.agent_chat_pick": "Agente", "ui.agent_chat_placeholder": "Chiedi all'agente di modificare il progetto…", "ui.agent_chat_placeholder_busy": "Metti in coda un messaggio per quando l'agente avrà terminato (Ctrl+Enter: invia subito)", + "ui.agent_chat_previous_picture": "Immagine precedente", "ui.agent_chat_privacy": "I dati vengono inviati solo all'agente che esegui, con il tuo account.", "ui.agent_chat_privacy_note": "SolidCraft non invia dati ad alcun servizio: l'agente che esegui comunica con il proprio servizio usando il tuo account. SolidCraft non vede né memorizza mai chiavi o password.", "ui.agent_chat_queue": "Metti in coda", @@ -957,6 +1036,7 @@ "ui.ai_bad_url": "Inserisci un URL che inizi con http:// o https://", "ui.ai_base_url": "URL di base", "ui.ai_button": "IA", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "copia", "ui.ai_custom_url": "URL personalizzato", "ui.ai_default": "Predefinito", @@ -977,8 +1057,10 @@ "ui.ai_mode_edits_note": "I comandi di modellazione vengono eseguiti senza chiedere; salvataggio, apertura, esportazione e altre azioni sui file o distruttive richiedono comunque conferma.", "ui.ai_mode_global": "Come impostato sopra", "ui.ai_model": "Modello", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Queste impostazioni riguardano gli strumenti di SolidCraft. Gli strumenti dell'agente (shell, modifiche ai file) seguono la modalità dell'agente, selezionabile nell'intestazione del pannello chat quando l'agente supporta le modalità.", "ui.ai_name": "Nome", + "ui.ai_name_hint": "Il mio agente", "ui.ai_need_command": "Inserisci il comando che avvia l'agente", "ui.ai_need_model": "Inserisci il nome del modello", "ui.ai_no_answer": "L'agente non ha risposto in tempo", @@ -1009,6 +1091,7 @@ "ui.ai_toolbar_button": "Mostra pulsante nella barra degli strumenti", "ui.ai_toolbar_button_note": "Il pulsante IA all'estremità destra della barra degli strumenti. Ctrl+Shift+A e il menu Vista aprono comunque il pannello.", "ui.ai_undo_tip": "Annulla in un solo passo tutte le modifiche dell'agente per questo messaggio", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Tutti", "ui.analysis": "Analisi", "ui.angle": "Angolo", @@ -1020,7 +1103,6 @@ "ui.anti_aliasing": "Antialiasing", "ui.apply": "Applica", "ui.arc": "Arco", - "ui.assemble": "Assembla", "ui.autosave_every": "Salvataggio automatico ogni", "ui.axes": "Assi", "ui.axis": "Asse", @@ -1030,6 +1112,11 @@ "ui.bodies": "Corpi", "ui.box": "Parallelepipedo", "ui.break": "Spezza", + "ui.browser_bodies_one": "{count} corpo", + "ui.browser_bodies_other": "{count} corpi", + "ui.browser_collapse": "Comprimi la struttura", + "ui.browser_expand": "Espandi la struttura", + "ui.browser_title": "Struttura", "ui.calibrate": "Calibra", "ui.cancel": "Annulla", "ui.canvases": "Immagini di riferimento", @@ -1039,7 +1126,6 @@ "ui.chamfer": "Smusso", "ui.chamfer_type": "Tipo di smusso", "ui.change_workspace": "Cambia area di lavoro", - "ui.chord_length": "Lunghezza della corda", "ui.circle": "Cerchio", "ui.circular_pattern": "Ripetizione circolare", "ui.clear": "Cancella", @@ -1072,11 +1158,9 @@ "ui.command_done": "{command} completato", "ui.command_palette_hint": "Digita un comando, ad es. {example} — oppure premi S per cercare", "ui.concentric": "Concentrico", - "ui.configurations": "Configurazioni", "ui.conic_curve": "Curva conica", "ui.constant": "Costante", "ui.constraints_conflict": "Vincoli in conflitto", - "ui.construct": "Costruisci", "ui.construction": "Costruzione", "ui.context_menu": "Menu contestuale", "ui.continuity_g0": "G0 spigolo vivo", @@ -1102,7 +1186,6 @@ "ui.cylinder": "Cilindro", "ui.dark": "Scuro", "ui.default_modelling_orientation": "Orientamento di modellazione predefinito", - "ui.degrees_of_freedom": "gradi di libertà", "ui.delete": "Elimina", "ui.depth": "Profondità", "ui.design": "Progettazione", @@ -1144,7 +1227,6 @@ "ui.equivalent_radius": "Raggio equivalente", "ui.expand": "Espandi", "ui.exportDxf": "Esporta DXF…", - "ui.export_ellipsis": "Esporta...", "ui.export_ellipsis_keymap": "Esporta…", "ui.export_keymap_tip": "Tasti, preferiti del riquadro S e barra degli strumenti, in un file", "ui.extend": "Estendi", @@ -1228,7 +1310,6 @@ "ui.heading_sheet_metal": "LAMIERA", "ui.heading_shell": "SVUOTA", "ui.heading_sketch": "SCHIZZO", - "ui.heading_sketch_palette": "TAVOLOZZA SCHIZZO", "ui.heading_sketch_scale": "SCALA SCHIZZO", "ui.heading_solid": "SOLIDO", "ui.heading_sphere": "SFERA", @@ -1247,6 +1328,7 @@ "ui.hideOrigin": "Nascondi origine", "ui.hidePlanes": "Nascondi piani", "ui.hideProfile": "Nascondi profilo", + "ui.history_title": "Cronologia", "ui.hole": "Foro", "ui.hole_tap_type": "Tipo di maschiatura del foro", "ui.hole_type": "Tipo di foro", @@ -1266,7 +1348,6 @@ "ui.length_decimals": "Decimali delle lunghezze", "ui.light": "Chiaro", "ui.line": "Linea", - "ui.linetype": "Tipo di linea", "ui.lock": "Blocca", "ui.loft": "Loft", "ui.look_at": "Vista normale", @@ -1298,10 +1379,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF con etichette delle facce", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Punto medio", "ui.mirror": "Specchia", "ui.mirror_line": "Asse di simmetria", - "ui.modify": "Modifica", "ui.move": "Sposta", "ui.multiple": "Multiplo", "ui.named_views": "Viste con nome", @@ -1321,7 +1400,6 @@ "ui.offset_plane": "Piano offset", "ui.one_side": "Un lato", "ui.open": "Apri", - "ui.open_ellipsis": "Apri...", "ui.open_ends_tip": "Non tocca altre curve", "ui.open_preferences": "Apri preferenze", "ui.open_recent": "Apri recenti", @@ -1331,8 +1409,9 @@ "ui.orbit_around": "Orbita attorno a", "ui.orientation": "Orientamento", "ui.origin": "Origine", - "ui.palette_points": "Punti", - "ui.palette_profile": "Profilo", + "ui.palette_construction_mode": "Modalità costruzione (X)", + "ui.palette_more": "Altro", + "ui.palette_sketch_options": "Opzioni schizzo", "ui.pan": "Panoramica", "ui.pan_zoom_orbit": "Panoramica, zoom, orbita", "ui.parallel": "Parallelo", @@ -1367,7 +1446,6 @@ "ui.quit": "Esci", "ui.radial_context_menu": "Menu contestuale radiale", "ui.radius": "Raggio", - "ui.radius_type": "Tipo di raggio", "ui.rail": "Guida", "ui.rail_fit": "Adattamento alla guida", "ui.reachable": "Raggiungibile", @@ -1397,7 +1475,6 @@ "ui.right": "Destra", "ui.save": "Salva", "ui.save_as": "Salva con nome", - "ui.save_as_ellipsis": "Salva con nome...", "ui.save_mesh_no_slicer": "Nessuno slicer trovato: scegli Personalizzato… e seleziona lo slicer, oppure disattiva Invia allo slicer", "ui.save_mesh_pick_bodies": "Seleziona i corpi da salvare", "ui.save_mesh_temp_failed": "Impossibile creare {folder}: {error}", @@ -1410,7 +1487,6 @@ "ui.select": "Seleziona", "ui.selectOther": "Seleziona altro…", "ui.selectTangentChain": "Seleziona catena tangente", - "ui.select_other_hint": "Seleziona altro: tieni premuto il pulsante sinistro senza muovere il puntatore sul modello (o fai clic destro › Seleziona altro…) per elencare gli oggetti sotto il cursore; fai di nuovo clic nello stesso punto per selezionare l'oggetto successivo.", "ui.selection": "Selezione", "ui.send": "Invia", "ui.send_to_slicer": "Invia allo slicer", @@ -1427,7 +1503,6 @@ "ui.show_constraints": "Vincoli", "ui.show_construction_geometries": "Geometrie di costruzione", "ui.show_dimensions": "Quote", - "ui.show_points": "Mostra punti", "ui.show_profile": "Mostra profilo", "ui.show_projected_geometries": "Geometrie proiettate", "ui.simple": "Semplice", @@ -1437,7 +1512,6 @@ "ui.sketch_dimension": "Quota dello schizzo", "ui.sketch_grid": "Griglia dello schizzo", "ui.sketch_scale": "Scala schizzo", - "ui.sketch_status": "Stato dello schizzo", "ui.sketches": "Schizzi", "ui.slice": "Genera sezioni", "ui.slicer": "Slicer", @@ -1483,7 +1557,6 @@ "ui.stand_up_tip": "Dopo l'apertura di un file STEP o IGES con asse Z verticale, proponi un quarto di giro attorno a X", "ui.start": "Inizio", "ui.start_over": "Ricomincia", - "ui.start_radius": "Raggio iniziale", "ui.stretch": "Stira", "ui.supersampling": "Sovracampionamento (SSAA)", "ui.supersampling_adaptive": "Solo quando la vista è ferma", @@ -1531,7 +1604,6 @@ "ui.untitled": "Senza titolo", "ui.up_axis_note": "L'asse verticale nelle viste (viste degli assi, Vista iniziale, Vista normale). I progetti restano invariati.", "ui.updateToCurrentView": "Aggiorna alla vista corrente", - "ui.variable": "Variabile", "ui.view_centre": "Centro della vista", "ui.visual_shaded_hidden_edges": "Ombreggiato con spigoli nascosti", "ui.visual_shaded_with_edges": "Ombreggiato con spigoli", @@ -1547,7 +1619,6 @@ "ui.zoom_to_the_cursor": "Zoom verso il cursore", "update.available": "È disponibile SolidCraft {version}", "update.button": "Aggiorna", - "update.button_tip": "È disponibile SolidCraft {version}", "update.button_version": "Aggiorna a {version}", "update.check_automatically": "Controlla automaticamente gli aggiornamenti", "update.check_now": "Controlla ora", @@ -1579,5 +1650,6 @@ "update.title": "Aggiornamento disponibile", "update.up_to_date": "SolidCraft è aggiornato.", "update.update_now": "Aggiorna ora", - "update.you_have": "La tua versione è {version}." + "update.you_have": "La tua versione è {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ scegli · Invio / Tab completa · Esc chiudi" } diff --git a/assets/locales/ja.json b/assets/locales/ja.json index 14bcd45b07..8efc6fb851 100644 --- a/assets/locales/ja.json +++ b/assets/locales/ja.json @@ -6,11 +6,11 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. GitHubでSubmit new issueをクリックしてレポートを送信します。", "A new configuration, a copy of the active one": "アクティブな構成をコピーして新しい構成を作成", - "AI": "AI", "ARRANGE": "配置", "About SolidCraft": "SolidCraft について", "Acceleration": "加速度", "Access": "アクセス性", + "Actions": "操作", "Active": "アクティブ", "Active rule": "有効なルール", "Add": "追加", @@ -18,6 +18,7 @@ "Add Mid Radius": "中間半径を追加", "Add Row": "行を追加", "Add condition": "条件を追加", + "Add or edit rule": "ルールを追加または編集", "Against": "相手", "Alignment": "配置", "All Sketch Points": "すべてのスケッチ点", @@ -31,6 +32,7 @@ "Applied Radius": "適用半径", "Apply To": "適用先", "Apply to selection": "選択項目に適用", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "先に外観を適用してください。割り当てた外観と、このデザインで作成したマテリアルを書き出します。", "Arc length": "円弧の長さ", "Area": "表面積", "As-Built": "現状位置", @@ -67,16 +69,17 @@ "Check collisions": "衝突チェック", "Checked": "チェック済み", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "新しい平面を選択してください。スケッチジオメトリと寸法は2次元座標系のまま維持されます。", + "Choose…": "選択…", "Circular": "円形", "Circumference": "円周", "Close draft": "下書きを閉じる", "Collisions": "衝突", "Colour": "色", + "Comment": "コメント", "Component1": "コンポーネント1", "Config Table": "構成テーブル", "Configuration": "構成", "Configuration rules": "構成ルール", - "Construction": "コンストラクション", "Contact": "接触", "Contact Sets": "接触セット", "Contact sets": "接触セット", @@ -100,11 +103,11 @@ "Curvature (G2)": "曲率 (G2)", "Curvature change (G3)": "曲率変化 (G3)", "Customize Toolbar": "ツールバーのカスタマイズ", + "Delete": "削除", "Delete all": "すべて削除", "Delete rule": "ルールを削除", "Delete the row": "行を削除", "Delete the set": "セットを削除", - "Delete this parameter": "このパラメータを削除", "Density": "密度", "Dependents": "依存先", "Designs you open or save appear here.": "開いたデザインや保存したデザインがここに表示されます。", @@ -118,6 +121,7 @@ "Drive Joint": "ジョイントを駆動", "Duration": "時間", "Edges": "エッジ", + "Edit": "編集", "Edit Profile Sketch": "プロファイルスケッチを編集", "Enable rule": "ルールを有効化", "Enabled": "有効", @@ -126,11 +130,14 @@ "Explode": "分解", "Explode Text": "テキストを分解", "Export Animation…": "アニメーションをエクスポート…", + "Export CSV…": "CSVを書き出し…", "Export Current View as DXF": "現在の表示をDXFでエクスポート", "Export Current View as DXF…": "現在の表示をDXFでエクスポート…", "Export DXF": "DXF出力", "Export DXF…": "DXFをエクスポート…", + "Export JSON…": "JSONを書き出し…", "Export hidden edges as dashed lines": "点線で出力", + "Expression": "式", "Extend to meet": "交わるまで延長", "Extents": "範囲", "FILES": "ファイル", @@ -140,12 +147,13 @@ "Faces": "面", "Faces / edges / vertices": "面 / エッジ / 頂点", "Fastener": "締結部品", + "Favorites": "お気に入り", "Favourite": "お気に入り", + "Feature": "フィーチャー", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "多くのCADではY軸を上向きとしているため、Z軸を上向きとするSolidCraftでは、読み込んだモデルが横倒しの向きで表示されることがあります。", "Filter": "フィルター", "Find in Browser": "ブラウザで検索", "Finish": "終了", - "Flat": "展開", "Flat Pattern": "フラットパターン", "Flat size": "展開サイズ", "Follow camera (Look At sketches)": "カメラ追従(スケッチを正面視)", @@ -164,6 +172,7 @@ "Home view": "ホームビュー", "Horiz./Vert.": "水平 / 垂直", "IMAGES": "画像", + "Import…": "読み込み…", "In This Design": "このデザイン内", "In place (surfaces)": "その場で(サーフェス)", "In plane": "平面内", @@ -185,7 +194,9 @@ "Length units": "長さの単位", "Letter Spacing": "文字間隔", "Library": "ライブラリ", + "Library file": "ライブラリファイル", "Line Spacing": "行間", + "Local library": "ローカルライブラリ", "Loop": "ループ", "MANAGE": "管理", "MATERIALS": "材料", @@ -194,41 +205,49 @@ "Margin": "マージン", "Mass": "質量", "Mass Center": "重心", + "Matched restrictions check the final values.": "条件に一致する制限ルールが最終値を確認します。", "Material": "材料", "Max": "最大", "Mid Radius": "中間半径", "Middle double-click": "中ボタンのダブルクリック", "Min": "最小", "Mitre": "留め継ぎ", + "Model Parameters": "モデルパラメータ", "Modeled": "モデル化済み", "More toolbar panels": "その他のツールバーパネル", "Motion": "モーション", "Move / Copy": "移動 / コピー", "Move Type": "移動タイプ", + "Move down": "下へ移動", "Move earlier": "前へ移動", "Move later": "後へ移動", + "Move up": "上へ移動", "Name": "名前", "Nearest": "最も近い", + "New": "新規", "New Comp.": "新規コンポーネント", "New Sketch": "新規スケッチ", + "New rule…": "新規ルール…", "New study": "新規スタディ", "No crash reports.": "クラッシュレポートはありません。", + "No matching appearance presets.": "一致する外観プリセットはありません。", "No matching appearances": "一致する外観がありません", "No visible bodies to export.": "出力する表示中のボディがありません。", "None": "なし", - "Normal to sketch": "スケッチに垂直", "Not available yet": "まだ利用できません", "Notch": "ノッチ", "OUTPUT": "出力", "Opacity": "不透明度", "Open GitHub draft": "GitHubの下書きを開く", + "Open local libraries in the desktop app. You can export this design's library here.": "ローカルライブラリはデスクトップアプリで開いてください。このデザインのライブラリはここで書き出せます。", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Rust製オープンソース・パラメトリック3D CAD。ライセンス: MIT または Apache-2.0。", + "Operator": "演算子", "Other side": "反対側", "Other surface (mutual trim)": "相手のサーフェス(相互トリム)", "Outside": "外側", "Overall length": "全長", "Param. Graph": "パラメータ図", - "Parameter values": "パラメータ値", + "Parameter": "パラメータ", "Parts": "部品", "Pass through": "通過点", "Path": "パス", @@ -249,7 +268,6 @@ "Preview Row": "行をプレビュー", "Preview leaves the current design unchanged.": "プレビューは現在のデザインを変更しません。", "Profiles/Curves": "プロファイル/曲線", - "Projection": "投影", "Quantity": "量", "RECENT": "最近使用した項目", "REFERENCE": "参照", @@ -289,6 +307,8 @@ "Roll History Marker Here": "履歴マーカーをここに移動", "Round": "丸め", "Rule": "ルール", + "Rule name": "ルール名", + "Rules run from top to bottom. Matched drives change effective values.": "ルールは上から順に実行されます。条件に一致する駆動ルールが有効値を変更します。", "SECTION": "断面", "SURFACE QUALITY": "サーフェス品質", "Save Rule": "ルールを保存", @@ -299,6 +319,7 @@ "Scene": "シーン", "Search": "検索", "Search commands": "コマンドを検索", + "Search this library": "このライブラリを検索", "Section": "断面", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "その他を選択: モデル上で左ボタンを長押し(または右クリック › その他を選択…)するとカーソル下の要素が一覧表示されます。同じ場所を再度クリックすると次の要素が選択されます。中ボタンをダブルクリックするとホームビューに戻ります(環境設定 › ナビゲーション)。", "Select bodies (a face picks its body)": "ボディを選択(面をクリックするとそのボディが選択されます)", @@ -360,18 +381,19 @@ "Turn off section analysis before exporting the view.": "断面解析をオフにしてからエクスポートしてください。", "Type": "タイプ", "UNTRIM": "トリム解除", + "Unit": "単位", "Updating the preview": "プレビューを更新中", "Use active author placement": "アクティブな作成元配置を使用", "Use fixed plane": "固定平面を使用", "Use saved fixed plane": "保存された固定平面を使用", "Use selected face as fixed plane": "選択した面を固定平面として使用", + "User Parameters": "ユーザーパラメータ", "VIEW": "表示", "VIEWS": "ビュー", "Value": "値", "Velocity": "速度", "Vertices": "頂点", "View navigator": "ビューナビゲーター", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "表示中のボディを出力します。曲線は線分で近似します(許容誤差0.01 mm)。", "Volume": "体積", "Wall": "肉厚", "Wall Location": "壁の位置", @@ -379,6 +401,12 @@ "When": "条件 (When)", "Width": "幅", "Will fail": "失敗します", + "X angle": "X角度", + "X distance": "X距離", + "Y angle": "Y角度", + "Y distance": "Y距離", + "Z angle": "Z角度", + "Z distance": "Z距離", "Zebra": "ゼブラ", "angle, height": "角度, 高さ", "appearance.dielectric_ior": "誘電体の屈折率", @@ -388,7 +416,13 @@ "appearance.roughness": "粗さ", "appearance.surface_response": "表面の応答", "browser.body_name": "ボディ{number}", + "browser.calibrate_title": "尺度を設定: {name}", + "browser.capture_many": "{count}個のコンポーネントが移動されましたが、位置がキャプチャされていません。", + "browser.capture_note": "「位置をキャプチャ」で新しい位置を保持し、「位置を元に戻す」で以前の位置に戻します。", + "browser.capture_one": "コンポーネントが移動されましたが、位置がキャプチャされていません。", "browser.component_name": "コンポーネント{number}", + "browser.edit_canvas_title": "キャンバスを編集: {name}", + "browser.move_title": "移動: {name}", "browser.parameters": "パラメータ ({count})", "browser.precision": "精度: {precision}", "browser.sketch_name": "スケッチ{number}", @@ -726,7 +760,15 @@ "cmd.view_navigate": "ビューナビゲーション", "cmd.view_rename": "名前付きビューの名前を変更", "cmd.view_save": "新規名前付きビュー", - "command name": "コマンド名", + "color.copy": "クリックして色の値をコピー", + "color.edit": "クリックして色を編集", + "color.gamma": "0〜255のガンマ空間で色の値を表示", + "color.hue": "色相", + "color.linear": "0〜1の線形空間で色の値を表示", + "color.saturation_value": "彩度と明度", + "color.selected": "選択した色", + "contact.components_count": "{count}個のコンポーネント", + "contact.enabled_label": "有効", "coupling.gear_1": "ギア 1", "coupling.gear_1_pick": "ギア 1 (オプション)", "coupling.gear_2": "ギア 2", @@ -743,9 +785,21 @@ "coupling.rack": "ラック", "coupling.radius_1": "半径 1", "coupling.radius_2": "半径 2", + "crash.announce": "前回、SolidCraftが予期せず終了したか、内部エラーが発生しました。クラッシュレポートが保存されています(ヘルプ > クラッシュレポート…)。", + "crash.copy_folder": "フォルダのパスをコピー", + "crash.copy_path": "パスをコピー", + "crash.deleted": "{count}件のレポートを削除しました。", "crash.detected_time": "検出日時: {time} UTC", + "crash.intro": "クラッシュレポートはこのコンピュータ内に保存され、外部に送信されることはありません。「報告…」で下書きが開き、共有前に内容を確認できます。", + "crash.kind_command": "コマンド内の内部エラー", + "crash.kind_internal": "内部エラー", + "crash.kind_kernel": "カーネルエラー(復旧済み)", + "crash.kind_unclean": "予期しない終了", "crash.previous_session": "以前の SolidCraft セッションが予期せず終了したか、内部エラーが発生しました。正常終了が記録されていないセッションは起動時に検出されます。詳細は ヘルプ > クラッシュレポート… を参照してください。", + "crash.replayable": "再現可能", + "crash.report": "報告…", "crash.unclean_explanation": "次回以降の起動時に検出されました。正確な終了時刻は記録されていません。システムのシャットダウンや強制停止によっても、正常終了が記録されない場合があります。", + "delete.and_more": "…ほか{count}件", "dialog.bodies_count": "{count} ボディ", "dialog.clearance_invalid": "クリアランスは 0 以上の長さにしてください", "dialog.compute": "計算", @@ -753,6 +807,8 @@ "dialog.computing": "計算中… {seconds} 秒", "dialog.could_not_be_checked": "チェックできません", "dialog.custom_axis": "↖ カスタム軸", + "dialog.dimension_insert_hint": "別の駆動寸法をクリックして名前を挿入", + "dialog.dimension_name": "寸法: {name}", "dialog.edit_title": "{title}を編集", "dialog.select": "↖ 選択", "dialog.selected_count": "↖ {count} 個選択中", @@ -776,9 +832,30 @@ "dxf.progress.write": "DXFを書き込み中 (5/5)", "dxf.tolerance": "曲線の許容誤差", "dxf.tolerance_help": "表示中のボディを出力し、曲線は線分で近似します。許容誤差を小さくすると曲線が滑らかになりますが、処理時間とメモリ使用量が増えます。", - "expression, e.g. 25 mm": "計算式 (例: 25 mm)", + "file_load.built": "構築済み: 全{total}フィーチャー中{built}件。", + "file_load.converting": "ソリッドを変換中: {done}/{total}", + "file_load.inserting": "ジオメトリを挿入中", + "file_load.opening": "デザインを開いています", + "file_load.preparing_thumbnail": "サムネイルを準備中...", + "file_load.preparing_view": "ビューを準備中...", + "file_load.preview_note": "保存済みのプレビューです。編集前にジオメトリを確認しています。", + "file_load.reading": "ファイルを読み込み中...", + "file_load.reading_percent": "ファイルを読み込み中... {percent}%", + "file_load.rebuilding": "フィーチャーを再構築中: {done}/{total}", + "file_load.recovering": "デザインを復元中", + "file_load.stopping": "前の操作の停止を待っています...", + "file_load.waiting": "ファイル操作を待っています", "fits the face, e.g. M8": "面に適合するサイズ (例: M8)", "from the rule": "ルールから", + "help.apache": "Apacheライセンス2.0", + "help.attributions": "帰属表示 (ATTRIBUTION.md)", + "help.build": "バージョン {version} · コミット {commit} · ビルド日 {date}", + "help.font_details": "インターフェースにはegui組み込みのフォントを使用しています(Ubuntu Font LicenceおよびMIT/Bitstream VeraライセンスのUbuntu LightとHack、OFLおよびMITのNoto Emojiと絵文字アイコンフォント)。スケッチのテキストにはLiberation Sans(SIL OFL 1.1)を使用しています。The Crafting Appsのオプションフォントパックcraft-fonts(BIZ UDPGothic、Shippori Mincho、BIZ UDMincho、Noto Sans CJK SC、Noto Sans Arabic、SIL OFL 1.1)はSolidCraftのビルドに同梱されていません。", + "help.fonts": "フォント", + "help.licences": "ライセンス", + "help.mit": "MITライセンス", + "help.notice": "NOTICE", + "help.third_party": "サードパーティライセンス", "home.documentation": "ドキュメント", "home.in_this_browser": "このブラウザ内", "home.missing": "見つかりません", @@ -804,9 +881,30 @@ "install.menu": "アプリをインストール…", "install.steps_intro": "Safari では、Web アプリを Safari 自身のメニューからインストールします:", "install.title": "SolidCraft をインストール", + "issue.body_label": "レポート本文", + "issue.note_image_copied": "GitHubの下書きに画像を貼り付けてください。画像をコピーできない場合はPNGを保存してください。", + "issue.note_opened": "GitHubの下書きを確認し、スクリーンショットを添付してください。GitHubのSubmit new issueボタンで公開されます。", + "issue.note_opened_copied": "レポートをコピーしました。GitHubの下書きに貼り付け、スクリーンショットを添付してから、GitHubでSubmit new issueをクリックしてください。", + "issue.note_removed": "この下書きからスクリーンショットを削除しました。", + "issue.note_report_copied": "レポートをコピーしました。必要に応じてGitHubの下書きに貼り付けてください。", + "issue.note_save_failed": "スクリーンショットを保存できませんでした: {error}", + "issue.note_saved": "スクリーンショットを保存しました。GitHubの下書きにPNGを添付してください。", + "issue.save_screenshot": "スクリーンショットを保存…", "joint.from_bodies_hint": "選択した各ボディを、位置はそのままで専用の新しいコンポーネントに移動します。", "joint.one_body": "両方の選択が {body} 上にあります。2 つ目は別の部品で選択してください。", "joint.one_component": "これらの部品は 1 つのコンポーネントのボディです: {bodies}。ジョイントはコンポーネント同士を動かすため、部品ごとに専用のコンポーネントが必要です。", + "keymap.command_id": "コマンドID", + "keymap.empty": "一致するコマンドはありません", + "keymap.use_anyway": "ここでは{key}をそのまま使用", + "manage.appearance_hint": "選択対象の外観を選ぶか、スウォッチをボディまたは面にドラッグしてください。", + "manage.assign_hint": "ボディを選択し、プラスチック設計ルールを指定してください。", + "manage.config_hint": "構成の行、列、ルールを編集し、行を有効にするかプレビューしてください。", + "manage.empty": "一致する行はありません。", + "manage.material_hint": "ボディを選択し、物理マテリアルを指定してください。", + "manage.parameters_hint": "式と単位を編集してください。Enterを押すか、フィールドから移動すると変更が適用されます。", + "manage.rules_hint": "編集するルールを選ぶか、新規ルールを作成してください。確定するとルールが保存されます。", + "measure.at": "位置", + "measure.item": "項目 {number}", "messages.ago_h": "{n} 時間前", "messages.ago_min": "{n} 分前", "messages.ago_s": "{n} 秒前", @@ -818,8 +916,16 @@ "messages.title": "メッセージ", "motion.checked_to": "ステップ {step} までしか確認していません: 確認の計算量の上限に達しました", "motion.collision": "{a} · {b}: ステップ {steps}", + "motion.delete_key": "キーフレームを削除", "motion.duration_hint": "秒 (空欄: ステップごと)", + "motion.key_here": "ここにキーフレームを追加", + "motion.key_here_hint": "このステップにジョイントの現在値でキーフレームを追加", "motion.no_collisions": "スタディ全体で衝突はありません", + "motion.step_back": "前のステップ", + "motion.step_forward": "次のステップ", + "motion.to_end": "最後へ", + "motion.to_start": "最初へ", + "motion.track_name": "トラック {number}", "nav.touchpad.help": "Shift を押しながらポインターを動かすとパン、Alt/Option を押しながら動かすと回転します。マウスボタンは不要です。縦スクロールでズームします。", "nav.touchpad.label": "タッチパッド", "nav.touchpad.orbit": "回転(Alt/Option を押しながら移動、マウスボタン不要)", @@ -828,13 +934,13 @@ "nav.touchpad.zoom": "ズーム(縦スクロールまたはピンチ)", "needs two joints": "2つのジョイントが必要", "new configuration": "新規構成", - "new name": "新しい名前", "new parameter": "新規パラメータ", "no hole": "穴なし", "no interference": "干渉なし", "no joint that moves yet": "可動ジョイントがまだありません", "no picture": "画像なし", "none": "なし", + "parts.automatic_name": "自動", "pattern.alignment_2": "配置 2", "pattern.alignment_hint": "個数には元の図形が含まれます。中央配置ではコピーを両側に配置し、反転で偶数個の場合に多く配置する側を切り替えます。", "pattern.direction_1": "方向 1", @@ -842,12 +948,19 @@ "pattern.flip_direction": "方向を反転", "pick a hole to seat it, or OK places it at the origin": "配置先の穴を選択するか、[OK]を押すと原点に配置します。", "pick one or two vertices, edges or faces": "1つまたは2つの頂点、エッジ、面を選択", + "prefs.cpu": "CPU(ソフトウェア)", + "prefs.minutes_suffix": " min", + "properties.title": "プロパティ: {name}", + "recovery.days_ago": "{count}日前", + "recovery.detail": "{file} · {features}フィーチャー · 自動保存: {when}", + "recovery.hours_ago": "{count}時間前", + "recovery.just_now": "たった今", + "recovery.min_ago": "{count}分前", + "recovery.never_saved": "未保存", "render.background": "シーンの背景", "render.fit": "製品を画面に合わせる", - "render.image_cancel": "レンダリングをキャンセル", "render.image_cancelled": "レンダリングをキャンセルしました", "render.image_cancelling": "キャンセル中", - "render.image_check": "レンダリング要求を確認", "render.image_completed": "画像をエクスポートできます", "render.image_encoding": "PNGをエンコード中", "render.image_export": "PNGをエクスポート", @@ -894,6 +1007,19 @@ "sample.plate.name": "サンプル プレート", "sample.vents.about": "矩形パターンに並んだスロット: 行数、列数、ピッチはパラメータ", "sample.vents.name": "通気プレート", + "search.command_keys": "Enter 実行 Shift+Enter 実行して開いたままにする Esc 閉じる", + "search.commands": "コマンド", + "search.design": "このデザイン内", + "search.feature": "フィーチャー", + "search.invalid": "無効", + "search.keys": "↑ ↓ 選択 Enter 実行 Tab 名前を補完 Esc 閉じる", + "search.no_match": "「{query}」に一致するコマンドはありません", + "search.parameter": "パラメーター", + "search.recent": "最近使った項目", + "search.run": "コマンドを実行", + "search.suggest": "{label} ですか?", + "search.try": "コマンド名、このデザインのフィーチャー、またはパラメーターを入力します。値を指定して実行するには、IDとJSONオブジェクトを入力します:{example}", + "search.valid": "有効", "settings.display_quality_note": "細かいほど曲面とエッジが滑らかに表示されますが、メッシュの生成に時間がかかります。変わるのはビューポートだけで、設計やエクスポートは変わりません。", "settings.frame_rate_note": "ビューの右下に 1 秒あたりのフレーム数を表示します。オンの間、ビューは常に再描画されます。", "settings.measure_axis_distances": "計測のX・Y・Z距離を表示", @@ -904,17 +1030,30 @@ "settings.reset_intro": "次の設定はこのコンピューターでは使用できず、SolidCraft が起動できなくなるため変更されました:", "settings.reset_title": "設定が変更されました", "settings.supersampling_note": "ビューを高い解像度で描画してから縮小します。4K 未満の画面でエッジ、線、グリッドが滑らかになります。GPU の負荷が増えます(1.5× で約 2.25 倍、2× で 4 倍のピクセル)。", + "shortcut.json_error": "パラメータはJSON形式で指定してください: {error}", + "shortcut.no_command": "コマンド`{id}`がありません。JSONの前のテキストはコマンドIDである必要があります", + "shortcut.pin": "Sボックスの先頭にピン留め", + "shortcut.unpin": "ピン留めを解除", "sketch.degrees_of_freedom": "自由度 {count}", + "sketch.dof_left": "残り自由度 {count}", + "sketch.dof_left_one": "残り自由度 {count}", "sketch.draw_curves": "{linetype}として描く", "sketch.make_selection": "選択を{linetype}にする", "sketch.open_ends": "開放 {count}", "sketch.untied_ends": "未拘束 {count}", + "sketch_panel.attached": "接続先: {body} / {path}", + "sketch_panel.author": "作成時の配置: {path}", + "sketch_panel.dimension_hint": "値または式を入力し、OK を押してください。", + "sketch_panel.move_hint": "距離と角度を入力し、OK を押してください。", + "sketch_panel.redefine_title": "{command}:{name}", + "sketch_panel.sheet_attachment": "板金ボディの平面に接続されています。板金が変更されると、そのパネルに追従します。", + "sketch_panel.text_hint": "テキストと配置を編集し、OK を押してください。", + "sketch_panel.use_plane": "{plane} 平面を使用", "sketch_text.align_center": "中央", "sketch_text.align_left": "左", "sketch_text.align_right": "右", "sketch_text.bold": "太字", "sketch_text.builtin_font": "{family}(内蔵)", - "sketch_text.font_search": "フォントを検索", "sketch_text.italic": "斜体", "sketch_text.loading_fonts": "インストール済みフォントを検索中…", "sketch_text.on_path": "文字はパスに沿います: 位置は曲線で決まります", @@ -935,6 +1074,7 @@ "timeline.ungroup": "フィーチャーフォルダを削除", "to the face": "面まで", "to the next face": "次の面まで", + "toolbar.customize_note": "{tab}タブ。コマンドにチェックを入れると、ツールバーにボタンが追加されます。パネルのドロップダウンの行をツールバーにドラッグしたり、ボタンをツールバーの外にドラッグして削除したり、ドロップダウンの行のピンを使うこともできます。", "toolbar.image": "画像", "toolbar.render": "レンダリング", "toolbar.render_scene": "シーン", @@ -943,11 +1083,11 @@ "trace.stopped_by_loop": "サンプル {sample} で終了: {joint} を通るループはこれ以上閉じられません", "ui.1_degree_of_freedom": "自由度 1", "ui.3d_mouse_speed": "3D マウス速度", - "ui.3d_mouse_windows": "3D マウス", "ui.3d_sketch": "3D スケッチ", - "ui.about": "SolidCraft について", "ui.activate": "アクティブ化", "ui.active_component": "アクティブなコンポーネント", + "ui.add_configuration": "構成を追加", + "ui.add_parameter": "パラメーターを追加", "ui.agent_chat": "エージェントチャット", "ui.agent_chat_add": "ファイルを添付", "ui.agent_chat_add_file": "ファイルを選択…", @@ -989,6 +1129,7 @@ "ui.agent_chat_file_error": "{name}: {error}", "ui.agent_chat_file_heic": "{name}: HEIC 画像はここでは読めません。先に JPEG か PNG に書き出してください", "ui.agent_chat_file_importing": "デザインにインポート中…", + "ui.agent_chat_file_kinds": "{name}: 添付できるのは画像 (PNG、JPEG、GIF、WebP、BMP、TIFF)、テキストファイル (SVG も)、CAD ファイル (STEP、IGES、FreeCAD、Rhino、STL、3MF、OBJ、glTF) です", "ui.agent_chat_file_loading": "ファイルを読み込み中…", "ui.agent_chat_file_not_a_file": "{name}: ファイルではありません", "ui.agent_chat_file_not_a_picture": "{name}: 画像ではありません", @@ -1011,7 +1152,7 @@ "ui.agent_chat_model_tip": "エージェントが使うモデル。選択はこのエージェントに対して記憶されます。", "ui.agent_chat_new": "新しいチャット", "ui.agent_chat_new_confirm": "新しいチャットを始めますか? 現在のチャットは消去されます。", - "ui.agent_chat_new_yes": "新しいチャット", + "ui.agent_chat_next_picture": "次の画像", "ui.agent_chat_no_cli": "アプリの隣に solidcraft-cli が見つからないため、エージェントはデザインにアクセスできません。", "ui.agent_chat_no_control": "エージェントチャットのエンドポイントが起動しなかったため、エージェントはデザインにアクセスできません。", "ui.agent_chat_no_images": "このエージェントは画像を受け付けません", @@ -1027,6 +1168,7 @@ "ui.agent_chat_placeholder": "デザインの変更をエージェントに依頼…", "ui.agent_chat_placeholder_busy": "エージェントの作業が終わったら送るメッセージを入力(Ctrl+Enter: 今すぐ送信)", "ui.agent_chat_pop_out": "独立したウィンドウで開く(別の画面に移動できます)", + "ui.agent_chat_previous_picture": "前の画像", "ui.agent_chat_privacy": "あなたのアカウントで実行するエージェント以外には、何も送信されません。", "ui.agent_chat_privacy_note": "SolidCraft はどこにも何も送信しません。あなたが実行するエージェントが、あなた自身のアカウントでそのサービスと通信します。SolidCraft がキーやパスワードを見たり保存したりすることはありません。", "ui.agent_chat_queue": "待機に追加", @@ -1082,6 +1224,7 @@ "ui.ai_bad_url": "http:// または https:// で始まる URL を入力してください", "ui.ai_base_url": "ベース URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "コピー", "ui.ai_custom_url": "カスタム URL", "ui.ai_default": "既定", @@ -1102,8 +1245,10 @@ "ui.ai_mode_edits_note": "モデリングコマンドは確認なしで実行されます。保存・開く・エクスポートなどのファイル操作や破壊的な操作は引き続き確認します。", "ui.ai_mode_global": "上の設定に従う", "ui.ai_model": "モデル", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "これらの設定は SolidCraft のツールに適用されます。エージェント自身のツール(シェル、ファイル編集)は、エージェントにモードがある場合にチャットパネルのヘッダーで選ぶエージェント自身のモードに従います。", "ui.ai_name": "名前", + "ui.ai_name_hint": "マイエージェント", "ui.ai_need_command": "エージェントを起動するコマンドを入力してください", "ui.ai_need_model": "モデル名を入力してください", "ui.ai_no_answer": "エージェントが時間内に応答しませんでした", @@ -1134,6 +1279,7 @@ "ui.ai_toolbar_button": "アプリケーションバーにアシスタントボタンを表示", "ui.ai_toolbar_button_note": "アプリケーションバーのアシスタントボタン。Ctrl+Shift+A と表示メニューからもパネルを開けます。", "ui.ai_undo_tip": "このメッセージでエージェントが行った変更を、1回でまとめて元に戻します", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "すべて", "ui.ambient_occlusion": "アンビエントオクルージョン", "ui.ambient_occlusion_help": "不透明な面が接する部分に柔らかい陰影を加えます。GPUビューポートが必要です。", @@ -1147,7 +1293,6 @@ "ui.anti_aliasing": "アンチエイリアス", "ui.apply": "適用", "ui.arc": "円弧", - "ui.assemble": "アセンブリ", "ui.assistant": "アシスタント", "ui.assistant_waiting": "{count} 件の承認待ち", "ui.autosave_every": "自動保存の間隔", @@ -1160,6 +1305,11 @@ "ui.bodies_all_if_none": "ボディ(未選択ならすべて)", "ui.box": "直方体", "ui.break": "分割", + "ui.browser_bodies_one": "{count} ボディ", + "ui.browser_bodies_other": "{count} ボディ", + "ui.browser_collapse": "ブラウザを折りたたむ", + "ui.browser_expand": "ブラウザを展開", + "ui.browser_title": "ブラウザ", "ui.calibrate": "校正", "ui.cancel": "キャンセル", "ui.canvases": "キャンバス", @@ -1169,7 +1319,6 @@ "ui.chamfer": "面取り", "ui.chamfer_type": "面取りタイプ", "ui.change_workspace": "ワークスペースの変更", - "ui.chord_length": "弦長", "ui.circle": "円", "ui.circular_pattern": "円形パターン", "ui.clear": "クリア", @@ -1202,11 +1351,9 @@ "ui.command_done": "{command} 完了", "ui.command_palette_hint": "コマンドを入力(例: {example})— または S キーで検索", "ui.concentric": "同心", - "ui.configurations": "構成", "ui.conic_curve": "円錐曲線", "ui.constant": "一定", "ui.constraints_conflict": "拘束が競合しています", - "ui.construct": "構築", "ui.construction": "コンストラクション", "ui.context_menu": "コンテキスト メニュー", "ui.continuity_g0": "G0 折れ", @@ -1232,7 +1379,6 @@ "ui.cylinder": "円柱", "ui.dark": "ダーク", "ui.default_modelling_orientation": "モデリングのデフォルト方向", - "ui.degrees_of_freedom": "自由度", "ui.delete": "削除", "ui.depth": "深さ", "ui.design": "デザイン", @@ -1274,7 +1420,6 @@ "ui.equivalent_radius": "等価半径", "ui.expand": "展開", "ui.exportDxf": "DXF をエクスポート…", - "ui.export_ellipsis": "エクスポート…", "ui.export_ellipsis_keymap": "エクスポート…", "ui.export_keymap_tip": "ショートカット、Sボックスのお気に入り、ツールバー設定をファイルに保存", "ui.extend": "延長", @@ -1298,6 +1443,7 @@ "ui.frame_rate": "フレームレート", "ui.front": "正面", "ui.fully_constrained": "完全拘束", + "ui.function": "関数", "ui.general": "一般", "ui.graphics": "グラフィックス", "ui.graphics_custom": "カスタム", @@ -1364,7 +1510,6 @@ "ui.heading_sheet_metal": "板金", "ui.heading_shell": "シェル", "ui.heading_sketch": "スケッチ", - "ui.heading_sketch_palette": "スケッチ パレット", "ui.heading_sketch_scale": "スケッチの尺度", "ui.heading_solid": "ソリッド", "ui.heading_sphere": "球", @@ -1383,6 +1528,7 @@ "ui.hideOrigin": "原点を非表示", "ui.hidePlanes": "平面を非表示", "ui.hideProfile": "プロファイルを非表示", + "ui.history_title": "履歴", "ui.hole": "穴", "ui.hole_tap_type": "ねじ穴タイプ", "ui.hole_type": "穴タイプ", @@ -1402,7 +1548,6 @@ "ui.length_decimals": "長さの小数点以下の桁数", "ui.light": "ライト", "ui.line": "線分", - "ui.linetype": "線種", "ui.lock": "ロック", "ui.loft": "ロフト", "ui.look_at": "正面視", @@ -1436,10 +1581,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "面タグ付き 3MF", "ui.mesh_format_stl": "STL", - "ui.midpoint": "中点", "ui.mirror": "ミラー", "ui.mirror_line": "ミラー線", - "ui.modify": "修正", "ui.move": "移動", "ui.multiple": "複数", "ui.named_views": "名前付きビュー", @@ -1460,7 +1603,6 @@ "ui.offset_plane": "オフセット平面", "ui.one_side": "片側", "ui.open": "開く", - "ui.open_ellipsis": "開く…", "ui.open_ends_tip": "どこにも接していない", "ui.open_preferences": "環境設定を開く", "ui.open_recent": "最近使った項目を開く", @@ -1470,8 +1612,9 @@ "ui.orbit_around": "回転中心", "ui.orientation": "向き", "ui.origin": "原点", - "ui.palette_points": "点", - "ui.palette_profile": "プロファイル", + "ui.palette_construction_mode": "補助線モード (X)", + "ui.palette_more": "その他", + "ui.palette_sketch_options": "スケッチオプション", "ui.pan": "パン", "ui.pan_zoom_orbit": "パン、ズーム、オービット", "ui.parallel": "平行", @@ -1512,7 +1655,6 @@ "ui.quit": "終了", "ui.radial_context_menu": "パイ メニュー", "ui.radius": "半径", - "ui.radius_type": "半径タイプ", "ui.rail": "レール", "ui.rail_fit": "レールの合わせ方", "ui.reachable": "到達可能", @@ -1545,7 +1687,6 @@ "ui.right": "右側面", "ui.save": "保存", "ui.save_as": "名前を付けて保存", - "ui.save_as_ellipsis": "名前を付けて保存…", "ui.save_mesh_no_slicer": "スライサーが見つかりません。「カスタム…」でスライサーのプログラムを選ぶか、「スライサーに送る」をオフにしてください", "ui.save_mesh_pick_bodies": "保存するボディを選択してください", "ui.save_mesh_temp_failed": "{folder} を作成できません: {error}", @@ -1558,7 +1699,6 @@ "ui.select": "選択", "ui.selectOther": "その他を選択…", "ui.selectTangentChain": "接線チェーンを選択", - "ui.select_other_hint": "その他を選択: モデル上で左ボタンを長押し(または右クリック › その他を選択…)するとカーソル下の要素が一覧表示されます。同じ場所を再度クリックすると次の要素が選択されます。", "ui.selection": "選択", "ui.selection_modifiers": "選択修飾キー", "ui.selection_shift_add_only": "Shift+クリックで追加のみ(解除なし)", @@ -1578,7 +1718,6 @@ "ui.show_constraints": "拘束", "ui.show_construction_geometries": "構築ジオメトリ", "ui.show_dimensions": "寸法", - "ui.show_points": "点を表示", "ui.show_profile": "プロファイルを表示", "ui.show_projected_geometries": "投影ジオメトリ", "ui.simple": "単純", @@ -1589,7 +1728,6 @@ "ui.sketch_grid": "スケッチ グリッド", "ui.sketch_modifiers": "スケッチ修飾キー", "ui.sketch_scale": "スケッチ尺度", - "ui.sketch_status": "スケッチの状態", "ui.sketches": "スケッチ", "ui.slice": "スライス", "ui.slicer": "スライサー", @@ -1636,7 +1774,6 @@ "ui.start": "開始", "ui.start_over": "最初からやり直す", "ui.start_page": "スタートページ", - "ui.start_radius": "開始半径", "ui.stretch": "伸縮", "ui.supersampling": "スーパーサンプリング (SSAA)", "ui.supersampling_adaptive": "ビューが静止しているときのみ", @@ -1686,7 +1823,6 @@ "ui.untitled": "無題", "ui.up_axis_note": "ビュー(軸ビュー、ホーム、正面視)で上向きにする軸を指定します。デザインデータ自体は変更されません。", "ui.updateToCurrentView": "現在のビューに更新", - "ui.variable": "可変", "ui.view_centre": "ビュー中心", "ui.visual_shaded_hidden_edges": "隠線付きシェーディング", "ui.visual_shaded_with_edges": "エッジ付きシェーディング", @@ -1714,7 +1850,6 @@ "ui.zoom_to_the_cursor": "カーソル位置へズーム", "update.available": "SolidCraft {version} が利用できます", "update.button": "アップデート", - "update.button_tip": "SolidCraft {version} が利用できます", "update.button_version": "{version} にアップデート", "update.check_automatically": "アップデートを自動的に確認", "update.check_now": "今すぐ確認", @@ -1746,5 +1881,6 @@ "update.title": "アップデートがあります", "update.up_to_date": "SolidCraft は最新です。", "update.update_now": "今すぐ更新", - "update.you_have": "現在のバージョンは {version} です。" + "update.you_have": "現在のバージョンは {version} です。", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ 選択 · Enter / Tab 補完 · Esc 閉じる" } diff --git a/assets/locales/ko.json b/assets/locales/ko.json index 6bcb068c03..e18fefdee4 100644 --- a/assets/locales/ko.json +++ b/assets/locales/ko.json @@ -4,12 +4,14 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. 스크린샷을 첨부하세요. 복사하여 붙여넣거나 PNG로 저장한 뒤 업로드하세요.", "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. GitHub에서 Submit new issue를 클릭하여 보고서를 보내세요.", - "AI": "AI", "ARRANGE": "배치", "Acceleration": "가속도", + "Actions": "작업", + "Add or edit rule": "규칙 추가 또는 편집", "Against": "상대", "Alignment": "정렬", "All components": "모든 구성요소", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "먼저 외관을 적용하세요. 지정된 외관과 이 디자인에서 만든 재질을 내보냅니다.", "Arc length": "호 길이", "Area": "면적", "BROWSER": "브라우저", @@ -21,11 +23,14 @@ "CONSTRAIN": "구속", "CONTACT": "접촉", "CONVERT": "변환", + "CSV or JSON: updates existing parameters, adds new ones": "CSV or JSON: updates existing parameters, adds new ones", "Centre to centre": "중심 간", "Check collisions": "충돌 확인", + "Choose…": "선택…", "Circumference": "원주", "Collisions": "충돌", - "Construction": "구성선", + "Comment": "설명", + "Configuration rules": "Configuration rules", "Contact sets": "접촉 집합", "Continuity": "연속성", "Copy Error Message": "오류 메시지 복사", @@ -35,32 +40,42 @@ "Current perspective view": "현재 원근 투영 뷰", "Curvature (G2)": "곡률 (G2)", "Curvature change (G3)": "곡률 변화 (G3)", + "Delete": "삭제", "Direction": "방향", "Do not export hidden edges": "숨겨진 모서리 내보내지 않기", "Don't ask again": "다시 묻지 않기", + "Double-click to rename": "Double-click to rename", "Drive Joint": "조인트 구동", "Duration": "지속 시간", "Edges": "모서리", + "Edit": "편집", "Edit Profile Sketch": "윤곽 스케치 편집", "Explode Text": "텍스트 분해", "Export Animation…": "애니메이션 내보내기…", + "Export CSV…": "CSV 내보내기…", "Export Current View as DXF": "현재 뷰를 DXF로 내보내기", "Export Current View as DXF…": "현재 뷰를 DXF로 내보내기…", "Export DXF…": "DXF 내보내기…", + "Export JSON…": "JSON 내보내기…", "Export hidden edges as dashed lines": "숨겨진 모서리를 파선으로 내보내기", + "Expression": "표현식", "Extend to meet": "만날 때까지 연장", "FILES": "파일", "FLAT": "전개", "Face": "면", "Faces": "면", + "Favorites": "즐겨찾기", + "Favourite": "Favourite", + "Feature": "피처", + "Filter": "Filter", "Find in Browser": "브라우저에서 찾기", - "Flat": "플랫", "From edges": "시작 모서리", "HISTORY": "이력", "Hidden edges": "숨겨진 모서리", "Holes": "구멍", "Holes (edges on them)": "구멍 (구멍의 모서리)", "IMAGES": "이미지", + "Import…": "가져오기…", "In place (surfaces)": "제자리에서 (서피스)", "In plane": "평면 내", "Inside": "안쪽", @@ -70,22 +85,35 @@ "LIBRARY": "라이브러리", "LINKS": "링크", "Length": "길이", + "Length units": "Length units", + "Library file": "라이브러리 파일", + "Local library": "로컬 라이브러리", "MANAGE": "관리", "MATERIALS": "재질", "MODEL": "모델", "MOTION": "동작", + "Matched restrictions check the final values.": "조건이 충족된 제한 규칙은 최종 값을 검사합니다.", "Max": "최대", "Min": "최소", "Mitre": "연귀", + "Model Parameters": "모델 매개변수", + "Move down": "아래로 이동", + "Move up": "위로 이동", + "Name": "Name", + "New": "새로 만들기", + "New rule…": "새 규칙…", "New study": "새 연구", + "No matching appearance presets.": "일치하는 외관 프리셋이 없습니다.", "No visible bodies to export.": "표시된 바디가 없어 내보낼 수 없습니다.", "None": "없음", - "Normal to sketch": "스케치에 수직", "Notch": "노치", "OUTPUT": "출력", + "Open local libraries in the desktop app. You can export this design's library here.": "로컬 라이브러리는 데스크톱 앱에서 여세요. 이 디자인의 라이브러리는 여기서 내보낼 수 있습니다.", + "Operator": "연산자", "Other surface (mutual trim)": "다른 곡면(상호 트림)", "Outside": "바깥쪽", "Overall length": "전체 길이", + "Parameter": "매개변수", "Pass through": "통과점", "Picked": "선택한 항목", "Picked point": "선택한 점", @@ -93,7 +121,6 @@ "Position (G0)": "위치 (G0)", "Preferences › Design turns the offer back on": "환경 설정 › 디자인에서 다시 켤 수 있습니다", "Profiles/Curves": "프로파일/곡선", - "Projection": "투영", "Quantity": "양", "REFERENCE": "참조", "RULED SURFACE": "룰드 서피스", @@ -105,9 +132,13 @@ "Restore outer boundary": "외부 경계 복원", "Revert Position": "위치 복원", "Round": "둥글게", + "Rule": "Rule", + "Rule name": "규칙 이름", + "Rules run from top to bottom. Matched drives change effective values.": "규칙은 위에서 아래로 실행됩니다. 조건이 충족된 구동 규칙은 유효 값을 변경합니다.", "SECTION": "단면", "SURFACE QUALITY": "곡면 품질", "Save the study as an animated GIF, seen as the viewport shows it": "뷰포트에 보이는 대로 스터디를 애니메이션 GIF로 저장", + "Search this library": "이 라이브러리 검색", "Select bodies (a face picks its body)": "바디 선택(면을 클릭하면 해당 바디 선택)", "Selection filter": "선택 필터", "Send your report on GitHub": "GitHub에서 보고서 보내기", @@ -124,6 +155,7 @@ "TRIM": "트림", "Tangent (G1)": "접선 (G1)", "Tear": "찢김", + "Then": "Then", "Thickness Outside": "바깥쪽 두께", "Thin": "얇은 벽", "Thin Extrude": "얇은 벽 돌출", @@ -132,12 +164,21 @@ "Turn off section analysis before exporting the view.": "뷰를 내보내기 전에 단면 분석을 끄세요.", "Type": "유형", "UNTRIM": "트림 해제", + "Unit": "단위", + "User Parameters": "사용자 매개변수", "VIEWS": "뷰", + "Value": "Value", "Velocity": "속도", "Vertices": "꼭짓점", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "표시된 바디만 내보냅니다. 곡선은 선분으로 근사합니다 (허용오차 0.01 mm).", "Wall Location": "벽 위치", "Wall Thickness": "벽 두께", + "When": "When", + "X angle": "X 각도", + "X distance": "X 거리", + "Y angle": "Y 각도", + "Y distance": "Y 거리", + "Z angle": "Z 각도", + "Z distance": "Z 거리", "appearance.dielectric_ior": "유전체 굴절률", "appearance.metallic": "금속성", "appearance.pbr_help": "이 표면은 렌더링 작업 공간이나 제품 PNG에서 확인하세요. 굴절과 투과는 지원하지 않습니다.", @@ -145,7 +186,11 @@ "appearance.roughness": "거칠기", "appearance.surface_response": "표면 반응", "browser.body_name": "바디{number}", + "browser.capture_many": "구성요소 {count}개가 이동되었지만 위치가 캡처되지 않았습니다.", + "browser.capture_note": "위치 캡처는 새 위치를 유지하고, 위치 복원은 이전 위치로 되돌립니다.", + "browser.capture_one": "구성요소가 이동되었지만 위치가 캡처되지 않았습니다.", "browser.component_name": "컴포넌트{number}", + "browser.move_title": "이동: {name}", "browser.parameters": "매개변수 ({count})", "browser.precision": "정밀도: {precision}", "browser.sketch_name": "스케치{number}", @@ -471,6 +516,8 @@ "cmd.view_navigate": "보기 탐색", "cmd.view_rename": "이름 있는 보기 이름 변경", "cmd.view_save": "새 이름 있는 보기", + "contact.components_count": "구성요소 {count}개", + "contact.enabled_label": "활성화됨", "coupling.gear_1": "기어 1", "coupling.gear_1_pick": "기어 1 (선택 사항)", "coupling.gear_2": "기어 2", @@ -487,6 +534,7 @@ "coupling.rack": "랙", "coupling.radius_1": "반지름 1", "coupling.radius_2": "반지름 2", + "delete.and_more": "… 외 {count}개", "dialog.bodies_count": "바디 {count}개", "dialog.clearance_invalid": "간격은 0 이상의 길이여야 합니다", "dialog.compute": "계산", @@ -494,6 +542,8 @@ "dialog.computing": "계산 중… {seconds}초", "dialog.could_not_be_checked": "확인할 수 없음", "dialog.custom_axis": "↖ 사용자 정의 축", + "dialog.dimension_insert_hint": "다른 구동 치수를 두 번 클릭하여 이름 삽입", + "dialog.dimension_name": "치수: {name}", "dialog.edit_title": "{title} 편집", "dialog.select": "↖ 선택", "dialog.selected_count": "↖ {count}개 선택됨", @@ -517,6 +567,15 @@ "dxf.progress.write": "DXF 쓰는 중 (5/5)", "dxf.tolerance": "곡선 허용 오차", "dxf.tolerance_help": "표시된 바디만 내보내며 곡선은 직선 구간으로 근사합니다. 허용 오차가 작을수록 곡선이 매끄러워지지만 처리 시간과 메모리 사용량이 늘어납니다.", + "help.apache": "Apache 라이선스 2.0", + "help.attributions": "저작자 표시 (ATTRIBUTION.md)", + "help.build": "버전 {version} · 커밋 {commit} · 빌드 날짜 {date}", + "help.font_details": "인터페이스는 egui의 내장 글꼴을 사용합니다(Ubuntu Font Licence 및 MIT/Bitstream Vera 라이선스의 Ubuntu Light와 Hack, OFL 및 MIT의 Noto Emoji와 이모지 아이콘 글꼴). 스케치 텍스트는 Liberation Sans(SIL OFL 1.1)를 사용합니다. The Crafting Apps의 선택적 글꼴 팩 craft-fonts(BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1)는 SolidCraft 빌드에 포함되지 않습니다.", + "help.fonts": "글꼴", + "help.licences": "라이선스", + "help.mit": "MIT 라이선스", + "help.notice": "NOTICE", + "help.third_party": "타사 라이선스", "home.documentation": "문서", "home.in_this_browser": "이 브라우저에 저장됨", "home.missing": "찾을 수 없음", @@ -545,27 +604,51 @@ "joint.from_bodies_hint": "선택한 각 바디가 제자리에 그대로 있으면서 자체의 새 구성 요소로 옮겨집니다.", "joint.one_body": "두 선택이 모두 {body}에 있습니다. 두 번째는 다른 부품에서 선택하세요.", "joint.one_component": "이 부품들은 한 구성 요소의 바디입니다: {bodies}. 관절은 한 구성 요소를 다른 구성 요소에 대해 움직이므로 부품마다 자체 구성 요소가 필요합니다.", + "keymap.command_id": "명령 ID", + "keymap.empty": "일치하는 명령이 없습니다", + "keymap.use_anyway": "그래도 여기서 {key} 사용", + "manage.appearance_hint": "선택 항목의 외관을 선택하거나 견본을 바디 또는 면으로 끌어다 놓으세요.", + "manage.assign_hint": "바디를 선택하고 플라스틱 설계 규칙을 지정하세요.", + "manage.config_hint": "구성 행, 열 및 규칙을 편집한 다음 행을 활성화하거나 미리 보세요.", + "manage.empty": "일치하는 행이 없습니다.", + "manage.material_hint": "바디를 선택하고 물리 재질을 지정하세요.", + "manage.parameters_hint": "표현식과 단위를 편집하세요. Enter를 누르거나 필드를 벗어나면 변경 사항이 적용됩니다.", + "manage.rules_hint": "편집할 규칙을 선택하거나 새 규칙을 만드세요. 확인하면 규칙이 저장됩니다.", + "measure.at": "위치", + "measure.item": "항목 {number}", "motion.checked_to": "{step} 단계까지만 확인했습니다: 확인 작업량 한도에 도달했습니다", "motion.collision": "{a} · {b}: {steps} 단계", + "motion.delete_key": "키프레임 삭제", "motion.duration_hint": "초 (비우면: 단계당)", + "motion.key_here": "여기에 키프레임 추가", + "motion.key_here_hint": "이 단계에 조인트의 현재 값으로 키프레임 추가", "motion.no_collisions": "연구 전체에서 충돌이 없습니다", + "motion.step_back": "이전 단계", + "motion.step_forward": "다음 단계", + "motion.to_end": "끝으로", + "motion.to_start": "처음으로", + "motion.track_name": "트랙 {number}", "nav.touchpad.help": "Shift를 누른 채 포인터를 움직이면 화면이 이동하고, Alt/Option을 누른 채 움직이면 회전합니다. 마우스 버튼은 필요 없습니다. 세로 스크롤로 확대/축소합니다.", "nav.touchpad.label": "터치패드", "nav.touchpad.orbit": "회전 (Alt/Option을 누르고 이동, 마우스 버튼 불필요)", "nav.touchpad.pan": "화면 이동 (Shift를 누르고 이동, 마우스 버튼 불필요)", "nav.touchpad.scroll": "터치패드는 세로 스크롤로 확대/축소합니다. 두 손가락 이동 설정은 다른 프리셋을 위해 유지됩니다.", "nav.touchpad.zoom": "확대/축소 (세로 스크롤 또는 핀치)", + "new parameter": "new parameter", + "none": "없음", + "parts.automatic_name": "자동", "pattern.alignment_2": "정렬 2", "pattern.alignment_hint": "개수에는 원본이 포함됩니다. 가운데 정렬은 복사본을 양쪽에 배치하며, 개수가 짝수이면 방향 반전으로 복사본이 하나 더 배치되는 쪽을 선택합니다.", "pattern.direction_1": "방향 1", "pattern.direction_2": "방향 2", "pattern.flip_direction": "방향 반전", + "prefs.cpu": "CPU (소프트웨어)", + "prefs.minutes_suffix": " min", + "properties.title": "속성: {name}", "render.background": "장면 배경", "render.fit": "제품에 맞추기", - "render.image_cancel": "렌더링 취소", "render.image_cancelled": "렌더링 취소됨", "render.image_cancelling": "취소 중", - "render.image_check": "렌더 요청 확인", "render.image_completed": "이미지 내보내기 준비됨", "render.image_encoding": "PNG 인코딩 중", "render.image_export": "PNG 내보내기", @@ -622,15 +705,28 @@ "settings.reset_intro": "다음 설정은 이 컴퓨터에서 작동하지 않아 SolidCraft를 시작할 수 없게 하므로 변경되었습니다:", "settings.reset_title": "설정이 변경되었습니다", "settings.supersampling_note": "뷰를 더 높은 해상도로 그린 뒤 축소합니다. 4K 미만 화면에서 모서리, 선, 그리드가 더 매끄러워집니다. GPU 시간이 듭니다: 1.5×에서 약 2.25배, 2×에서 4배의 픽셀.", + "shortcut.json_error": "매개변수는 JSON 형식이어야 합니다: {error}", + "shortcut.no_command": "`{id}` 명령이 없습니다. JSON 앞의 텍스트는 명령 ID여야 합니다", + "shortcut.pin": "S 상자 맨 위에 고정", + "shortcut.unpin": "고정 해제", "sketch.degrees_of_freedom": "자유도 {count}개", + "sketch.dof_left": "남은 자유도 {count}개", + "sketch.dof_left_one": "남은 자유도 {count}개", "sketch.draw_curves": "{linetype}(으)로 그리기", "sketch.make_selection": "선택 항목을 {linetype}(으)로 변경", + "sketch_panel.attached": "부착 대상: {body} / {path}", + "sketch_panel.author": "작성 배치: {path}", + "sketch_panel.dimension_hint": "값이나 수식을 입력한 다음 확인을 누르세요.", + "sketch_panel.move_hint": "거리와 각도를 입력한 다음 확인을 누르세요.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "판금 바디의 평평한 면에 부착되어 있으며 판금이 변경되면 해당 패널을 따릅니다.", + "sketch_panel.text_hint": "텍스트와 배치를 편집한 다음 확인을 누르세요.", + "sketch_panel.use_plane": "{plane} 평면 사용", "sketch_text.align_center": "가운데", "sketch_text.align_left": "왼쪽", "sketch_text.align_right": "오른쪽", "sketch_text.bold": "굵게", "sketch_text.builtin_font": "{family}(내장)", - "sketch_text.font_search": "글꼴 검색", "sketch_text.italic": "기울임꼴", "sketch_text.loading_fonts": "설치된 글꼴을 찾는 중…", "sketch_text.on_path": "텍스트가 경로를 따릅니다: 위치는 곡선이 정합니다", @@ -643,6 +739,7 @@ "timeline.rename_group": "특성 폴더 이름 변경", "timeline.reorder_many": "특성 순서 변경", "timeline.ungroup": "특성 폴더 제거", + "toolbar.customize_note": "{tab} 탭입니다. 명령을 선택하면 도구 모음에 해당 버튼이 추가됩니다. 패널의 드롭다운에서 행을 도구 모음으로 끌거나, 버튼을 도구 모음 밖으로 끌어 제거하거나, 드롭다운 행의 고정 핀을 사용할 수도 있습니다.", "toolbar.image": "이미지", "toolbar.render": "렌더링", "toolbar.render_scene": "장면", @@ -651,7 +748,6 @@ "trace.stopped_by_loop": "샘플 {sample}에서 끝남: {joint}을(를) 지나는 루프를 더 이상 닫을 수 없습니다", "ui.1_degree_of_freedom": "자유도 1개", "ui.3d_sketch": "3D 스케치", - "ui.about": "정보", "ui.activate": "활성화", "ui.active_component": "활성 컴포넌트", "ui.agent_chat": "에이전트 채팅", @@ -689,6 +785,7 @@ "ui.agent_chat_left": "왼쪽", "ui.agent_chat_limit": "에이전트가 메시지당 변경 한도 {limit}에 도달했습니다. 계속하게 하려면 메시지를 하나 더 보내세요.", "ui.agent_chat_minimize": "얇은 막대로 최소화", + "ui.agent_chat_next_picture": "다음 이미지", "ui.agent_chat_no_cli": "앱 옆에서 solidcraft-cli를 찾지 못해 에이전트가 디자인에 접근할 수 없습니다.", "ui.agent_chat_no_control": "에이전트 채팅 엔드포인트가 시작되지 않아 에이전트가 디자인에 접근할 수 없습니다.", "ui.agent_chat_no_images": "이 에이전트는 이미지를 받지 않습니다", @@ -699,6 +796,7 @@ "ui.agent_chat_permission": "에이전트가 실행 허락을 요청합니다: {title}", "ui.agent_chat_pick": "에이전트", "ui.agent_chat_placeholder": "에이전트에게 디자인 변경을 요청하세요…", + "ui.agent_chat_previous_picture": "이전 이미지", "ui.agent_chat_privacy": "사용자의 계정으로 실행하는 에이전트 외에는 아무 데도 전송되지 않습니다.", "ui.agent_chat_privacy_note": "SolidCraft는 아무것도 어디로도 보내지 않습니다. 실행한 에이전트가 사용자의 계정으로 자신의 서비스와 통신합니다. SolidCraft는 키나 비밀번호를 보거나 저장하지 않습니다.", "ui.agent_chat_refresh": "새로 고침", @@ -743,6 +841,7 @@ "ui.ai_bad_url": "http:// 또는 https://로 시작하는 URL을 입력하세요", "ui.ai_base_url": "기본 URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "복사본", "ui.ai_custom_url": "사용자 지정 URL", "ui.ai_default": "기본값", @@ -763,8 +862,10 @@ "ui.ai_mode_edits_note": "모델링 명령은 묻지 않고 실행되며, 저장·열기·내보내기 등 파일 작업이나 파괴적인 작업은 계속 묻습니다.", "ui.ai_mode_global": "위 설정 따름", "ui.ai_model": "모델", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "이 설정은 SolidCraft 도구에 적용됩니다. 에이전트 자체 도구(셸, 파일 편집)는 에이전트에 모드가 있을 때 채팅 패널 머리글에서 고르는 에이전트 자체 모드를 따릅니다.", "ui.ai_name": "이름", + "ui.ai_name_hint": "내 에이전트", "ui.ai_need_command": "에이전트를 시작하는 명령을 입력하세요", "ui.ai_need_model": "모델 이름을 입력하세요", "ui.ai_no_answer": "에이전트가 제시간에 응답하지 않았습니다", @@ -795,6 +896,7 @@ "ui.ai_toolbar_button": "도구 모음에 버튼 표시", "ui.ai_toolbar_button_note": "도구 모음 오른쪽 끝의 AI 버튼. Ctrl+Shift+A와 보기 메뉴로도 패널을 열 수 있습니다.", "ui.ai_undo_tip": "이 메시지에서 에이전트가 바꾼 모든 것을 한 번에 실행 취소", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "모두", "ui.ambient_occlusion": "앰비언트 오클루전", "ui.ambient_occlusion_help": "불투명한 표면이 만나는 곳에 부드러운 음영을 추가합니다. GPU 뷰포트가 필요합니다.", @@ -808,7 +910,6 @@ "ui.anti_aliasing": "안티앨리어싱", "ui.apply": "적용", "ui.arc": "호", - "ui.assemble": "조립", "ui.autosave_every": "자동 저장 간격", "ui.axes": "축", "ui.axis": "축", @@ -819,6 +920,11 @@ "ui.bodies_all_if_none": "바디 (없으면 전체)", "ui.box": "박스", "ui.break": "끊기", + "ui.browser_bodies_one": "{count}개 바디", + "ui.browser_bodies_other": "{count}개 바디", + "ui.browser_collapse": "브라우저 접기", + "ui.browser_expand": "브라우저 펼치기", + "ui.browser_title": "브라우저", "ui.calibrate": "캘리브레이션", "ui.cancel": "취소", "ui.canvases": "캔버스", @@ -828,7 +934,6 @@ "ui.chamfer": "모따기", "ui.chamfer_type": "모따기 유형", "ui.change_workspace": "작업 공간 변경", - "ui.chord_length": "현 길이", "ui.circle": "원", "ui.circular_pattern": "원형 패턴", "ui.clear_recent": "최근 항목 지우기", @@ -858,11 +963,9 @@ "ui.command_done": "{command} 완료", "ui.command_palette_hint": "명령 입력(예: {example}) — 또는 S 키를 눌러 검색", "ui.concentric": "동심", - "ui.configurations": "구성", "ui.conic_curve": "원뿔 곡선", "ui.constant": "상수", "ui.constraints_conflict": "구속 조건 충돌", - "ui.construct": "구성", "ui.construction": "구성선", "ui.continuity_g0": "G0 꺾임", "ui.continuity_g1": "G1 접선", @@ -887,7 +990,6 @@ "ui.cylinder": "원통", "ui.dark": "어둡게", "ui.default_modelling_orientation": "기본 모델링 방향", - "ui.degrees_of_freedom": "자유도", "ui.delete": "삭제", "ui.depth": "깊이", "ui.design": "디자인", @@ -929,7 +1031,6 @@ "ui.equivalent_radius": "등가 반지름", "ui.expand": "펼치기", "ui.exportDxf": "DXF 내보내기…", - "ui.export_ellipsis": "내보내기...", "ui.export_ellipsis_keymap": "내보내기…", "ui.extend": "연장", "ui.extent_type": "범위 유형", @@ -1011,7 +1112,6 @@ "ui.heading_sheet_metal": "판금", "ui.heading_shell": "쉘", "ui.heading_sketch": "스케치", - "ui.heading_sketch_palette": "스케치 팔레트", "ui.heading_sketch_scale": "스케치 배율", "ui.heading_solid": "솔리드", "ui.heading_sphere": "구", @@ -1029,6 +1129,7 @@ "ui.hideOrigin": "원점 숨기기", "ui.hidePlanes": "평면 숨기기", "ui.hideProfile": "윤곽 숨기기", + "ui.history_title": "기록", "ui.hole": "구멍", "ui.hole_tap_type": "탭 유형", "ui.hole_type": "구멍 유형", @@ -1047,7 +1148,6 @@ "ui.length_decimals": "길이 소수점 자릿수", "ui.light": "밝게", "ui.line": "선", - "ui.linetype": "선 종류", "ui.lock": "잠금", "ui.loft": "로프트", "ui.look_at": "바라보기", @@ -1078,10 +1178,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "면 태그가 있는 3MF", "ui.mesh_format_stl": "STL", - "ui.midpoint": "중간점", "ui.mirror": "대칭 복사", "ui.mirror_line": "대칭선", - "ui.modify": "수정", "ui.move": "이동", "ui.multiple": "다중", "ui.named_views": "명명된 뷰", @@ -1101,7 +1199,6 @@ "ui.offset_plane": "오프셋 평면", "ui.one_side": "한 방향", "ui.open": "열기", - "ui.open_ellipsis": "열기...", "ui.open_preferences": "환경 설정 열기", "ui.open_recent": "최근 항목 열기", "ui.operation": "작업", @@ -1110,8 +1207,9 @@ "ui.orbit_around": "궤도 회전 기준", "ui.orientation": "배향", "ui.origin": "원점", - "ui.palette_points": "점", - "ui.palette_profile": "프로파일", + "ui.palette_construction_mode": "보조선 모드 (X)", + "ui.palette_more": "더 보기", + "ui.palette_sketch_options": "스케치 옵션", "ui.pan": "이동", "ui.pan_zoom_orbit": "이동, 확대/축소, 궤도 회전", "ui.parallel": "평행", @@ -1141,7 +1239,6 @@ "ui.quantity_2": "수량 2", "ui.quit": "종료", "ui.radius": "반지름", - "ui.radius_type": "반지름 유형", "ui.reachable": "도달 가능", "ui.rectangle": "직사각형", "ui.rectangular_pattern": "직사각형 패턴", @@ -1161,7 +1258,6 @@ "ui.right": "우측면", "ui.save": "저장", "ui.save_as": "다른 이름으로 저장", - "ui.save_as_ellipsis": "다른 이름으로 저장...", "ui.save_mesh_no_slicer": "슬라이서를 찾을 수 없습니다. '사용자 지정…'에서 슬라이서 프로그램을 선택하거나 '슬라이서로 보내기'를 끄세요", "ui.save_mesh_pick_bodies": "저장할 바디를 선택하세요", "ui.save_mesh_temp_failed": "{folder}을(를) 만들 수 없습니다: {error}", @@ -1189,7 +1285,6 @@ "ui.show_constraints": "구속 조건", "ui.show_construction_geometries": "구성 형상", "ui.show_dimensions": "치수", - "ui.show_points": "점 표시", "ui.show_profile": "프로파일 표시", "ui.show_projected_geometries": "투영된 형상", "ui.simple": "단순", @@ -1199,7 +1294,6 @@ "ui.sketch_dimension": "스케치 치수", "ui.sketch_grid": "스케치 격자", "ui.sketch_scale": "스케치 축척", - "ui.sketch_status": "스케치 상태", "ui.sketches": "스케치", "ui.slice": "슬라이스", "ui.slicer": "슬라이서", @@ -1240,7 +1334,6 @@ "ui.split_body": "바디 분할", "ui.start": "시작", "ui.start_over": "처음부터 다시", - "ui.start_radius": "시작 반지름", "ui.supersampling": "슈퍼샘플링 (SSAA)", "ui.supersampling_adaptive": "뷰가 멈춰 있을 때만", "ui.supersampling_off": "끔 (1×)", @@ -1281,7 +1374,6 @@ "ui.unsuppress": "비활성화 해제", "ui.untitled": "제목 없음", "ui.updateToCurrentView": "현재 보기로 업데이트", - "ui.variable": "가변", "ui.view_centre": "뷰 중심", "ui.visual_style": "시각 스타일", "ui.workspace": "작업 공간", @@ -1291,7 +1383,6 @@ "ui.zoom_to_the_cursor": "커서 위치로 확대/축소", "update.available": "SolidCraft {version} 사용 가능", "update.button": "업데이트", - "update.button_tip": "SolidCraft {version} 사용 가능", "update.button_version": "{version}(으)로 업데이트", "update.check_automatically": "업데이트 자동 확인", "update.check_now": "지금 확인", @@ -1323,5 +1414,6 @@ "update.title": "업데이트 사용 가능", "update.up_to_date": "SolidCraft가 최신 버전입니다.", "update.update_now": "지금 업데이트", - "update.you_have": "현재 버전은 {version}입니다." + "update.you_have": "현재 버전은 {version}입니다.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ 선택 · Enter / Tab 완성 · Esc 닫기" } diff --git a/assets/locales/ms.json b/assets/locales/ms.json index 11765f89a6..7e2f231a43 100644 --- a/assets/locales/ms.json +++ b/assets/locales/ms.json @@ -5,7 +5,6 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Lampirkan tangkapan skrin anda: salin dan tampal, atau simpan dan muat naik PNG.", "3. Click Submit new issue on GitHub to send your report.": "3. Klik Submit new issue di GitHub untuk menghantar laporan anda.", "A new configuration, a copy of the active one": "Konfigurasi baharu, salinan konfigurasi aktif", - "AI": "AI", "About SolidCraft": "Tentang SolidCraft", "Acceleration": "Pecutan", "Active": "Aktif", @@ -59,7 +58,6 @@ "Component1": "Komponen1", "Configuration": "Konfigurasi", "Configuration rules": "Peraturan konfigurasi", - "Construction": "Binaan", "Contact": "Sentuhan", "Contact sets": "Set sentuhan", "Continuity": "Keselanjaran", @@ -84,7 +82,6 @@ "Delete rule": "Padam peraturan", "Delete the row": "Padam baris", "Delete the set": "Padam set", - "Delete this parameter": "Padam parameter ini", "Density": "Ketumpatan", "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.", @@ -116,7 +113,6 @@ "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Banyak fail CAD menggunakan Y ke atas, jadi model terbaring di sini (Z ke atas).", "Filter": "Tapis", "Find in Browser": "Cari dalam Pelayar", - "Flat": "Rata", "Flat size": "Saiz rata", "Follow camera (Look At sketches)": "Ikut kamera (Pandang Tepat pada lakaran)", "Follow the camera (Look At sketches)": "Ikut kamera (Pandang Tepat pada lakaran)", @@ -166,7 +162,6 @@ "No matching appearances": "Tiada penampilan yang sepadan", "No visible bodies to export.": "Tiada jasad kelihatan untuk dieksport.", "None": "Tiada", - "Normal to sketch": "Serenjang", "Not available yet": "Belum tersedia", "Notch": "Takuk", "Opacity": "Kelegapan", @@ -175,7 +170,6 @@ "Other side": "Sebelah lagi", "Outside": "Luar", "Overall length": "Panjang keseluruhan", - "Parameter values": "Nilai parameter", "Pass through": "Tembus", "Path": "Laluan", "Per Edge": "Setiap Tepi", @@ -193,7 +187,6 @@ "Preview Row": "Pratonton Baris", "Preview leaves the current design unchanged.": "Pratonton tidak mengubah reka bentuk semasa.", "Profiles/Curves": "Profil/Lengkung", - "Projection": "Unjuran", "Quantity": "Kuantiti", "RECENT": "TERKINI", "RULED SURFACE": "PERMUKAAN BERGARIS", @@ -294,7 +287,6 @@ "Velocity": "Halaju", "Vertices": "Bucu", "View navigator": "Kawalan pandangan", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Jasad kelihatan sahaja. Lengkung guna segmen garis (toleransi 0.01 mm).", "Volume": "Isi padu", "Wall": "Dinding", "Wall Location": "Lokasi Dinding", @@ -638,7 +630,6 @@ "cmd.view_navigate": "Navigasi Pandangan", "cmd.view_rename": "Tukar Nama Pandangan", "cmd.view_save": "Simpan Pandangan", - "command name": "nama arahan", "coupling.gear_1": "Gear 1", "coupling.gear_1_pick": "Gear 1 (tidak wajib)", "coupling.gear_2": "Gear 2", @@ -680,7 +671,6 @@ "dxf.progress.write": "Menulis DXF (5/5)", "dxf.tolerance": "Toleransi lengkung", "dxf.tolerance_help": "Jasad kelihatan sahaja. Lengkung guna segmen lurus. Toleransi kecil lebih halus tetapi guna lebih masa dan memori.", - "expression, e.g. 25 mm": "ungkapan, cth. 25 mm", "fits the face, e.g. M8": "padan dengan muka, cth. M8", "from the rule": "daripada peraturan", "home.documentation": "Dokumentasi", @@ -723,7 +713,6 @@ "nav.touchpad.zoom": "Zum (tatal menegak atau cubit)", "needs two joints": "memerlukan dua sendi", "new configuration": "konfigurasi baharu", - "new name": "nama baharu", "new parameter": "parameter baharu", "no interference": "tiada pertindihan", "no joint that moves yet": "belum ada sendi yang bergerak", @@ -737,10 +726,8 @@ "pick one or two vertices, edges or faces": "pilih satu atau dua bucu, tepi atau muka", "render.background": "Latar belakang adegan", "render.fit": "Muatkan Produk", - "render.image_cancel": "Batal Render", "render.image_cancelled": "Render dibatalkan", "render.image_cancelling": "Membatalkan", - "render.image_check": "Semak Permintaan Render", "render.image_completed": "Imej sedia untuk dieksport", "render.image_encoding": "Mengekod PNG", "render.image_export": "Eksport PNG", @@ -807,7 +794,6 @@ "sketch_text.align_right": "Kanan", "sketch_text.bold": "Tebal", "sketch_text.builtin_font": "{family} (terbina dalam)", - "sketch_text.font_search": "Cari fon", "sketch_text.italic": "Condong", "sketch_text.loading_fonts": "Mencari fon yang dipasang…", "sketch_text.on_path": "Teks mengikut laluan lengkung, yang menentukan kedudukannya", @@ -834,9 +820,7 @@ "trace.stopped_by_loop": "Berakhir pada sampel {sample}: gelung melalui {joint} tidak lagi dapat ditutup", "ui.1_degree_of_freedom": "1 darjah kebebasan", "ui.3d_mouse_speed": "Kelajuan tetikus 3D", - "ui.3d_mouse_windows": "Tetikus 3D", "ui.3d_sketch": "Lakaran 3D", - "ui.about": "Tentang", "ui.activate": "Aktifkan", "ui.active_component": "Komponen aktif", "ui.agent_chat": "Sembang ejen", @@ -878,7 +862,6 @@ "ui.agent_chat_model_tip": "Model yang digunakan oleh ejen. Pilihan anda disimpan untuk ejen ini.", "ui.agent_chat_new": "Sembang baharu", "ui.agent_chat_new_confirm": "Mulakan sembang baharu? Sembang semasa akan dikosongkan.", - "ui.agent_chat_new_yes": "Sembang baharu", "ui.agent_chat_no_cli": "solidcraft-cli tidak ditemui dalam folder aplikasi, jadi ejen tidak dapat mengakses reka bentuk.", "ui.agent_chat_no_control": "Titik akhir sembang ejen gagal dimulakan, jadi ejen tidak dapat mengakses reka bentuk.", "ui.agent_chat_no_images": "Ejen ini tidak menerima imej", @@ -1006,7 +989,6 @@ "ui.anti_aliasing": "Antialias", "ui.apply": "Gunakan", "ui.arc": "Lengkok", - "ui.assemble": "Pasang", "ui.autosave_every": "Autosimpan setiap", "ui.axes": "Paksi", "ui.axis": "Paksi", @@ -1015,6 +997,11 @@ "ui.bodies": "Jasad", "ui.box": "Kotak", "ui.break": "Putuskan", + "ui.browser_bodies_one": "{count} jasad", + "ui.browser_bodies_other": "{count} jasad", + "ui.browser_collapse": "Runtuhkan pelayar", + "ui.browser_expand": "Kembangkan pelayar", + "ui.browser_title": "Pelayar", "ui.calibrate": "Tentukur", "ui.cancel": "Batal", "ui.canvases": "Kanvas", @@ -1024,7 +1011,6 @@ "ui.chamfer": "Serong", "ui.chamfer_type": "Jenis Serong", "ui.change_workspace": "Tukar ruang kerja", - "ui.chord_length": "Panjang Perentas", "ui.circle": "Bulatan", "ui.circular_pattern": "Corak Membulat", "ui.clear": "Kosongkan", @@ -1057,11 +1043,9 @@ "ui.command_done": "{command} selesai", "ui.command_palette_hint": "Taip arahan, cth. {example} — atau tekan S untuk mencari", "ui.concentric": "Sepusat", - "ui.configurations": "Konfigurasi", "ui.conic_curve": "Lengkung Kon", "ui.constant": "Malar", "ui.constraints_conflict": "Kekangan bercanggah", - "ui.construct": "Bina", "ui.construction": "Binaan", "ui.context_menu": "Menu konteks", "ui.continuity_g0": "Lipatan G0", @@ -1087,7 +1071,6 @@ "ui.cylinder": "Silinder", "ui.dark": "Gelap", "ui.default_modelling_orientation": "Orientasi pemodelan lalai", - "ui.degrees_of_freedom": "darjah kebebasan", "ui.delete": "Padam", "ui.depth": "Kedalaman", "ui.design": "Reka", @@ -1129,7 +1112,6 @@ "ui.equivalent_radius": "Jejari Setara", "ui.expand": "Kembangkan", "ui.exportDxf": "Eksport DXF…", - "ui.export_ellipsis": "Eksport...", "ui.export_ellipsis_keymap": "Eksport…", "ui.export_keymap_tip": "Simpan kekunci, kegemaran kotak S dan bar alat ke fail", "ui.extend": "Panjangkan", @@ -1213,7 +1195,6 @@ "ui.heading_sheet_metal": "LOGAM KEPINGAN", "ui.heading_shell": "RONGGAKAN", "ui.heading_sketch": "LAKARAN", - "ui.heading_sketch_palette": "PALET LAKARAN", "ui.heading_sketch_scale": "SKALA LAKARAN", "ui.heading_solid": "PEPEJAL", "ui.heading_sphere": "SFERA", @@ -1232,6 +1213,7 @@ "ui.hideOrigin": "Sembunyi Titik Asal", "ui.hidePlanes": "Sembunyi Satah", "ui.hideProfile": "Sembunyi Profil", + "ui.history_title": "Sejarah", "ui.hole": "Lubang", "ui.hole_tap_type": "Jenis Bebenang Lubang", "ui.hole_type": "Jenis Lubang", @@ -1251,7 +1233,6 @@ "ui.length_decimals": "Perpuluhan panjang", "ui.light": "Cerah", "ui.line": "Garis", - "ui.linetype": "Jenis garis", "ui.lock": "Kunci", "ui.loft": "Loft", "ui.look_at": "Pandang Tepat", @@ -1283,10 +1264,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF dengan tag muka", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Titik tengah", "ui.mirror": "Cermin", "ui.mirror_line": "Garis cermin", - "ui.modify": "Ubah Suai", "ui.move": "Alih", "ui.multiple": "Berbilang", "ui.named_views": "Pandangan Bernama", @@ -1306,7 +1285,6 @@ "ui.offset_plane": "Satah Ofset", "ui.one_side": "Satu Sisi", "ui.open": "Buka", - "ui.open_ellipsis": "Buka...", "ui.open_ends_tip": "Tidak bersentuh", "ui.open_preferences": "Buka Tetapan", "ui.open_recent": "Buka Terkini", @@ -1316,8 +1294,6 @@ "ui.orbit_around": "Orbit mengelilingi", "ui.orientation": "Orientasi", "ui.origin": "Titik Asal", - "ui.palette_points": "Titik", - "ui.palette_profile": "Profil", "ui.pan": "Sorot", "ui.pan_zoom_orbit": "Sorot, zum, orbit", "ui.parallel": "Selari", @@ -1351,7 +1327,6 @@ "ui.quit": "Keluar", "ui.radial_context_menu": "Menu konteks membulat", "ui.radius": "Jejari", - "ui.radius_type": "Jenis Jejari", "ui.rail": "Rel", "ui.rail_fit": "Padanan Rel", "ui.reachable": "Boleh dicapai", @@ -1374,7 +1349,6 @@ "ui.right": "Kanan", "ui.save": "Simpan", "ui.save_as": "Simpan Sebagai", - "ui.save_as_ellipsis": "Simpan Sebagai...", "ui.save_mesh_no_slicer": "Tiada penghiris: pilih program melalui Tersuai… atau matikan Hantar ke penghiris", "ui.save_mesh_pick_bodies": "Pilih jasad untuk disimpan", "ui.save_mesh_temp_failed": "Tidak dapat mencipta {folder}: {error}", @@ -1385,7 +1359,6 @@ "ui.select": "Pilih", "ui.selectOther": "Pilih Lain…", "ui.selectTangentChain": "Pilih Rantaian Tangen", - "ui.select_other_hint": "Pilih Lain: tahan butang kiri pada model tanpa bergerak (atau klik kanan › Pilih Lain…) untuk senarai objek di bawah kursor. Klik tempat sama untuk objek seterusnya.", "ui.selection": "Pilihan", "ui.send": "Hantar", "ui.send_to_slicer": "Hantar ke penghiris", @@ -1401,7 +1374,6 @@ "ui.show_constraints": "Kekangan", "ui.show_construction_geometries": "Geometri Binaan", "ui.show_dimensions": "Dimensi", - "ui.show_points": "Papar Titik", "ui.show_profile": "Papar Profil", "ui.show_projected_geometries": "Geometri Unjuran", "ui.simple": "Ringkas", @@ -1411,7 +1383,6 @@ "ui.sketch_dimension": "Dimensi Lakaran", "ui.sketch_grid": "Grid Lakaran", "ui.sketch_scale": "Skala Lakaran", - "ui.sketch_status": "Status Lakaran", "ui.sketches": "Lakaran", "ui.slice": "Hiris", "ui.slicer": "Penghiris", @@ -1430,7 +1401,6 @@ "ui.split_body": "Belah Jasad", "ui.stand_up_tip": "Selepas buka STEP atau IGES dengan Z ke atas, cadangkan suku putaran pada X", "ui.start": "Mula", - "ui.start_radius": "Jejari Mula", "ui.stretch": "Regang", "ui.supersampling": "Pensampelan super (SSAA)", "ui.supersampling_adaptive": "Hanya apabila pandangan pegun", @@ -1478,7 +1448,6 @@ "ui.untitled": "Tanpa Tajuk", "ui.up_axis_note": "Paksi yang menghala ke atas dalam pandangan paksi, Utama dan Pandang Tepat. Reka bentuk tidak berubah.", "ui.updateToCurrentView": "Kemas Kini Pandangan Semasa", - "ui.variable": "Berubah", "ui.view_centre": "Pusat pandangan", "ui.visual_shaded_hidden_edges": "Berlorek dengan tepi tersembunyi", "ui.visual_shaded_with_edges": "Berlorek dengan tepi", @@ -1494,7 +1463,6 @@ "ui.zoom_to_the_cursor": "Zum ke kursor", "update.available": "SolidCraft {version} tersedia", "update.button": "Kemas kini", - "update.button_tip": "SolidCraft {version} tersedia", "update.button_version": "Kemas kini ke {version}", "update.check_automatically": "Semak kemas kini secara automatik", "update.check_now": "Semak sekarang", diff --git a/assets/locales/pl.json b/assets/locales/pl.json index 8507e9f071..e605331700 100644 --- a/assets/locales/pl.json +++ b/assets/locales/pl.json @@ -5,9 +5,9 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Dołącz zrzut ekranu: skopiuj go i wklej albo zapisz i prześlij plik PNG.", "3. Click Submit new issue on GitHub to send your report.": "3. Kliknij Submit new issue na GitHubie, aby wysłać raport.", "A new configuration, a copy of the active one": "Nowa konfiguracja, kopia aktywnej", - "AI": "AI", "About SolidCraft": "O programie SolidCraft", "Acceleration": "Przyspieszenie", + "Actions": "Działania", "Active": "Aktywne", "Active rule": "Aktywna reguła", "Add": "Dodaj", @@ -15,6 +15,7 @@ "Add Mid Radius": "Dodaj promień środkowy", "Add Row": "Dodaj wiersz", "Add condition": "Dodaj warunek", + "Add or edit rule": "Dodaj lub edytuj regułę", "Against": "Względem", "Alignment": "Wyrównanie", "All Sketch Points": "Wszystkie punkty szkicu", @@ -28,6 +29,7 @@ "Applied Radius": "Zastosowany promień", "Apply To": "Zastosuj do", "Apply to selection": "Zastosuj do zaznaczenia", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Najpierw zastosuj wygląd. Eksportuje przypisane wyglądy i materiały utworzone w tym projekcie.", "Arc length": "Długość łuku", "Area": "Pole powierzchni", "Attachment": "Zaczepienie", @@ -52,14 +54,15 @@ "Check collisions": "Sprawdzaj kolizje", "Checked": "Sprawdzono", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Wybierz nową płaszczyznę. Geometria i wymiary szkicu pozostają niezmienione w jego dwuwymiarowych współrzędnych.", + "Choose…": "Wybierz…", "Circumference": "Obwód", "Close draft": "Zamknij szkic zgłoszenia", "Collisions": "Kolizje", "Colour": "Kolor", + "Comment": "Komentarz", "Component1": "Komponent1", "Configuration": "Konfiguracja", "Configuration rules": "Reguły konfiguracji", - "Construction": "Konstrukcyjne", "Contact": "Kontakt", "Contact sets": "Zestawy kontaktów", "Continuity": "Ciągłość", @@ -80,11 +83,11 @@ "Current perspective view": "Bieżący widok perspektywiczny", "Curvature (G2)": "Krzywizna (G2)", "Customize Toolbar": "Dostosuj pasek narzędzi", + "Delete": "Usuń", "Delete all": "Usuń wszystko", "Delete rule": "Usuń regułę", "Delete the row": "Usuń wiersz", "Delete the set": "Usuń zestaw", - "Delete this parameter": "Usuń ten parametr", "Density": "Gęstość", "Designs you open or save appear here.": "Tutaj pojawiają się otwierane lub zapisywane projekty.", "Determined by To Object. Choose Angle to edit the angular extent.": "Wynika z opcji Do obiektu. Wybierz Kąt, aby edytować zakres kątowy.", @@ -97,26 +100,31 @@ "Drive Joint": "Steruj złączem", "Duration": "Czas trwania", "Edges": "Krawędzie", + "Edit": "Edytuj", "Edit Profile Sketch": "Edytuj szkic profilu", "Enable rule": "Włącz regułę", "Enabled": "Włączone", "Explode": "Rozstrzel", "Explode Text": "Rozbij tekst", "Export Animation…": "Eksportuj animację…", + "Export CSV…": "Eksportuj CSV…", "Export Current View as DXF": "Eksportuj bieżący widok jako DXF", "Export Current View as DXF…": "Eksportuj bieżący widok jako DXF…", "Export DXF…": "Eksportuj DXF…", + "Export JSON…": "Eksportuj JSON…", "Export hidden edges as dashed lines": "Eksportuj krawędzie niewidoczne jako linie kreskowe", + "Expression": "Wyrażenie", "Extents": "Zakres", "Face": "Ściana", "Face attachment lost": "Utracono zaczepienie do ściany", "Faces": "Ściany", "Faces / edges / vertices": "Ściany / krawędzie / wierzchołki", + "Favorites": "Ulubione", "Favourite": "Ulubione", + "Feature": "Funkcja", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Pliki z wielu programów CAD mają oś Y skierowaną w górę, więc otwierają się tutaj leżąc na plecach (oś Z w górę).", "Filter": "Filtr", "Find in Browser": "Znajdź w przeglądarce", - "Flat": "Płaskie", "Flat size": "Wymiary rozwinięcia", "Follow camera (Look At sketches)": "Podążaj za kamerą (Patrz na szkice)", "Follow the camera (Look At sketches)": "Podążaj za kamerą (Patrz na szkice)", @@ -128,6 +136,7 @@ "Holes": "Otwory", "Holes (edges on them)": "Otwory (krawędzie na nich)", "Home view": "Widok główny", + "Import…": "Importuj…", "In This Design": "W tym projekcie", "In place (surfaces)": "W miejscu (powierzchnie)", "In plane": "W płaszczyźnie", @@ -143,39 +152,49 @@ "Length units": "Jednostki długości", "Letter Spacing": "Odstęp między literami", "Library": "Biblioteka", + "Library file": "Plik biblioteki", "Line Spacing": "Odstęp między wierszami", + "Local library": "Biblioteka lokalna", "Loop": "Pętla", "Margin": "Margines", "Mass": "Masa", + "Matched restrictions check the final values.": "Ograniczenia, których warunki są spełnione, sprawdzają wartości końcowe.", "Material": "Materiał", "Max": "Maks.", "Mid Radius": "Promień środkowy", "Middle double-click": "Dwukrotne kliknięcie środkowym", "Min": "Min.", "Mitre": "Ukos", + "Model Parameters": "Parametry modelu", "Modeled": "Wymodelowane", "More toolbar panels": "Więcej paneli paska narzędzi", "Motion": "Ruch", "Move Type": "Typ przesunięcia", + "Move down": "Przenieś w dół", "Move earlier": "Przesuń wcześniej", "Move later": "Przesuń później", + "Move up": "Przenieś w górę", "Name": "Nazwa", "Nearest": "Najbliższy", + "New": "Nowy", + "New rule…": "Nowa reguła…", "New study": "Nowe badanie", "No crash reports.": "Brak raportów o awariach.", + "No matching appearance presets.": "Brak pasujących ustawień wyglądu.", "No matching appearances": "Brak pasujących wyglądów", "No visible bodies to export.": "Brak widocznych brył do wyeksportowania.", "None": "Brak", - "Normal to sketch": "Prostopadle do szkicu", "Not available yet": "Jeszcze niedostępne", "Notch": "Wycięcie", "Opacity": "Nieprzezroczystość", "Open GitHub draft": "Otwórz szkic zgłoszenia na GitHubie", + "Open local libraries in the desktop app. You can export this design's library here.": "Otwórz biblioteki lokalne w aplikacji komputerowej. Tutaj możesz wyeksportować bibliotekę tego projektu.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Otwartoźródłowy parametryczny CAD 3D w języku Rust. Licencja MIT OR Apache-2.0.", + "Operator": "Operator", "Other side": "Druga strona", "Outside": "Na zewnątrz", "Overall length": "Długość całkowita", - "Parameter values": "Wartości parametrów", + "Parameter": "Parametr", "Pass through": "Przechodzi przez", "Path": "Ścieżka", "Per Edge": "Dla każdej krawędzi", @@ -193,7 +212,6 @@ "Preview Row": "Podgląd wiersza", "Preview leaves the current design unchanged.": "Podgląd nie zmienia bieżącego projektu.", "Profiles/Curves": "Profile/krzywe", - "Projection": "Rzutowanie", "Quantity": "Liczba", "RECENT": "OSTATNIE", "RULED SURFACE": "POWIERZCHNIA PROSTOKREŚLNA", @@ -228,12 +246,15 @@ "Roll History Marker Here": "Przesuń tutaj znacznik historii", "Round": "Okrągłe", "Rule": "Reguła", + "Rule name": "Nazwa reguły", + "Rules run from top to bottom. Matched drives change effective values.": "Reguły są wykonywane od góry do dołu. Reguły sterujące, których warunki są spełnione, zmieniają wartości efektywne.", "Save Rule": "Zapisz regułę", "Save the study as an animated GIF, seen as the viewport shows it": "Zapisz badanie jako animowany GIF, tak jak widać je w widoku", "Saved preview. Geometry is being checked before editing.": "Zapisany podgląd. Geometria jest sprawdzana przed edycją.", "Scale factor": "Współczynnik skali", "Search": "Szukaj", "Search commands": "Szukaj poleceń", + "Search this library": "Przeszukaj tę bibliotekę", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Wybierz inny: przytrzymaj nieruchomo lewy przycisk na modelu (lub prawy przycisk › Wybierz inny…), aby wyświetlić wszystko pod kursorem; kliknij ponownie w tym samym miejscu, aby zaznaczyć kolejny obiekt. Dwukrotne kliknięcie środkowym przyciskiem przechodzi do widoku głównego (Preferencje › Nawigacja).", "Select bodies (a face picks its body)": "Zaznacz bryły (ściana wybiera swoją bryłę)", "Select bodies or faces instead": "Zamiast tego zaznacz bryły lub ściany", @@ -284,17 +305,18 @@ "Turn off section analysis before exporting the view.": "Przed eksportem widoku wyłącz analizę przekroju.", "Type": "Typ", "UNTRIM": "COFNIJ PRZYCIĘCIE", + "Unit": "Jednostka", "Updating the preview": "Aktualizowanie podglądu", "Use active author placement": "Użyj aktywnego położenia autora", "Use fixed plane": "Użyj stałej płaszczyzny", "Use saved fixed plane": "Użyj zapisanej stałej płaszczyzny", "Use selected face as fixed plane": "Użyj wybranej ściany jako stałej płaszczyzny", + "User Parameters": "Parametry użytkownika", "VIEW": "WIDOK", "Value": "Wartość", "Velocity": "Prędkość", "Vertices": "Wierzchołki", "View navigator": "Nawigator widoku", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Tylko widoczne bryły. Krzywe są przybliżane odcinkami (tolerancja 0,01 mm).", "Volume": "Objętość", "Wall": "Ścianka", "Wall Location": "Położenie ścianki", @@ -302,6 +324,12 @@ "When": "Gdy", "Width": "Szerokość", "Will fail": "Nie powiedzie się", + "X angle": "Kąt X", + "X distance": "Odległość X", + "Y angle": "Kąt Y", + "Y distance": "Odległość Y", + "Z angle": "Kąt Z", + "Z distance": "Odległość Z", "angle, height": "kąt, wysokość", "appearance.dielectric_ior": "Współczynnik załamania dielektryka (IOR)", "appearance.metallic": "Metaliczność", @@ -310,7 +338,11 @@ "appearance.roughness": "Chropowatość", "appearance.surface_response": "Reakcja powierzchni", "browser.body_name": "Bryła{number}", + "browser.capture_many": "Przesunięto komponenty ({count}), ale ich położenia nie zostały zapamiętane.", + "browser.capture_note": "Zapamiętaj położenie zachowuje nowe położenie; Przywróć położenie przywraca poprzednie.", + "browser.capture_one": "Przesunięto komponent, ale jego położenie nie zostało zapamiętane.", "browser.component_name": "Komponent{number}", + "browser.move_title": "Przesuń: {name}", "browser.parameters": "Parametry ({count})", "browser.precision": "Dokładność: {precision}", "browser.sketch_name": "Szkic{number}", @@ -638,7 +670,8 @@ "cmd.view_navigate": "Nawiguj widokiem", "cmd.view_rename": "Zmień nazwę widoku nazwanego", "cmd.view_save": "Nowy widok nazwany", - "command name": "nazwa polecenia", + "contact.components_count": "Liczba komponentów: {count}", + "contact.enabled_label": "włączone", "coupling.gear_1": "Koło zębate 1", "coupling.gear_1_pick": "Koło zębate 1 (opcjonalnie)", "coupling.gear_2": "Koło zębate 2", @@ -655,8 +688,11 @@ "coupling.rack": "Zębatka", "coupling.radius_1": "Promień 1", "coupling.radius_2": "Promień 2", + "delete.and_more": "… i jeszcze {count}", "dialog.computing": "Obliczanie… {seconds} s", "dialog.custom_axis": "↖ Oś niestandardowa", + "dialog.dimension_insert_hint": "Kliknij dwukrotnie inny wymiar sterujący, aby wstawić jego nazwę", + "dialog.dimension_name": "Wymiar: {name}", "dialog.edit_title": "EDYCJA: {title}", "dialog.select": "↖ Zaznacz", "dialog.selected_count": "↖ Zaznaczono: {count}", @@ -680,9 +716,17 @@ "dxf.progress.write": "Zapisywanie DXF (5/5)", "dxf.tolerance": "Tolerancja krzywych", "dxf.tolerance_help": "Tylko widoczne bryły. Krzywe są przybliżane odcinkami. Mniejsza tolerancja daje dokładniejsze krzywe, ale zajmuje więcej czasu i pamięci.", - "expression, e.g. 25 mm": "wyrażenie, np. 25 mm", "fits the face, e.g. M8": "pasuje do ściany, np. M8", "from the rule": "z reguły", + "help.apache": "Licencja Apache 2.0", + "help.attributions": "Uznanie autorstwa (ATTRIBUTION.md)", + "help.build": "Wersja {version} · commit {commit} · zbudowano {date}", + "help.font_details": "Interfejs używa wbudowanych czcionek egui (Ubuntu Light i Hack na licencjach Ubuntu Font Licence oraz MIT/Bitstream Vera; Noto Emoji i czcionka ikon emoji na licencjach OFL i MIT). Tekst szkicu używa Liberation Sans (SIL OFL 1.1). Opcjonalny pakiet czcionek The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), nie jest dołączany do kompilacji SolidCraft.", + "help.fonts": "Czcionki", + "help.licences": "Licencje", + "help.mit": "Licencja MIT", + "help.notice": "NOTICE", + "help.third_party": "Licencje stron trzecich", "home.documentation": "Dokumentacja", "home.in_this_browser": "w tej przeglądarce", "home.missing": "brak", @@ -711,10 +755,30 @@ "joint.from_bodies_hint": "Każda wybrana bryła trafia do nowego, własnego komponentu i pozostaje na swoim miejscu.", "joint.one_body": "Oba wybory leżą na {body}. Drugi wybierz na innej części.", "joint.one_component": "Te części są bryłami jednego komponentu: {bodies}. Złącze porusza jeden komponent względem drugiego, więc każda część potrzebuje własnego komponentu.", + "keymap.command_id": "ID polecenia", + "keymap.empty": "Brak pasujących poleceń", + "keymap.use_anyway": "Mimo to użyj tutaj {key}", + "manage.appearance_hint": "Wybierz wygląd dla zaznaczenia lub przeciągnij próbkę na bryłę albo ścianę.", + "manage.assign_hint": "Wybierz bryły i ich regułę projektowania części z tworzyw sztucznych.", + "manage.config_hint": "Edytuj wiersze, kolumny i reguły konfiguracji, a następnie aktywuj wiersz lub wyświetl jego podgląd.", + "manage.empty": "Brak pasujących wierszy.", + "manage.material_hint": "Wybierz bryły i ich materiał fizyczny.", + "manage.parameters_hint": "Edytuj wyrażenia i jednostki. Zmiany są stosowane po naciśnięciu Enter lub opuszczeniu pola.", + "manage.rules_hint": "Wybierz regułę do edycji lub utwórz nową. Potwierdź, aby zapisać regułę.", + "measure.at": "W punkcie", + "measure.item": "Element {number}", "motion.checked_to": "Sprawdzono tylko do kroku {step}: sprawdzanie wyczerpało swój budżet obliczeń", "motion.collision": "{a} · {b}: kroki {steps}", + "motion.delete_key": "Usuń klatkę kluczową", "motion.duration_hint": "sekundy (puste: na krok)", + "motion.key_here": "Wstaw klatkę kluczową tutaj", + "motion.key_here_hint": "Dodaj klatkę kluczową w tym kroku z bieżącą wartością złącza", "motion.no_collisions": "Brak kolizji w trakcie badania", + "motion.step_back": "Krok wstecz", + "motion.step_forward": "Krok naprzód", + "motion.to_end": "Na koniec", + "motion.to_start": "Na początek", + "motion.track_name": "Ścieżka {number}", "nav.touchpad.help": "Przytrzymaj Shift i poruszaj wskaźnikiem, aby przesuwać; przytrzymaj Alt/Option i poruszaj nim, aby obracać widok. Przycisk myszy nie jest potrzebny. Przewijanie w pionie powiększa.", "nav.touchpad.label": "Touchpad", "nav.touchpad.orbit": "Obracanie widoku (przytrzymaj Alt/Option i poruszaj; bez przycisku myszy)", @@ -723,11 +787,12 @@ "nav.touchpad.zoom": "Powiększanie (przewijanie w pionie lub szczypanie)", "needs two joints": "wymaga dwóch złączy", "new configuration": "nowa konfiguracja", - "new name": "nowa nazwa", "new parameter": "nowy parametr", "no interference": "brak kolizji", "no joint that moves yet": "brak jeszcze ruchomego złącza", "no picture": "brak obrazu", + "none": "brak", + "parts.automatic_name": "automatycznie", "pattern.alignment_2": "Wyrównanie 2", "pattern.alignment_hint": "Liczba obejmuje oryginał. Wyśrodkuj rozkłada kopie po obu stronach; Odwróć wybiera stronę z dodatkową kopią przy parzystej liczbie.", "pattern.direction_1": "Kierunek 1", @@ -735,12 +800,13 @@ "pattern.flip_direction": "Odwróć kierunek", "pick a hole to seat it, or OK places it at the origin": "wybierz otwór, aby go osadzić, lub OK umieszcza go w początku układu", "pick one or two vertices, edges or faces": "wybierz jeden lub dwa wierzchołki, krawędzie lub ściany", + "prefs.cpu": "CPU (programowo)", + "prefs.minutes_suffix": " min", + "properties.title": "Właściwości: {name}", "render.background": "Tło sceny", "render.fit": "Dopasuj produkt", - "render.image_cancel": "Anuluj renderowanie", "render.image_cancelled": "Anulowano renderowanie", "render.image_cancelling": "Anulowanie", - "render.image_check": "Sprawdź zlecenie renderowania", "render.image_completed": "Obraz gotowy do eksportu", "render.image_encoding": "Kodowanie PNG", "render.image_export": "Eksportuj PNG", @@ -797,17 +863,30 @@ "settings.reset_intro": "Te ustawienia nie działają na tym komputerze i uniemożliwiłyby uruchomienie SolidCraft, więc zostały zmienione:", "settings.reset_title": "Zmieniono ustawienia", "settings.supersampling_note": "Rysuje widok w wyższej rozdzielczości i go zmniejsza: gładsze krawędzie, linie i siatka na ekranach poniżej 4K. Kosztuje czas GPU: ok. 2,25× więcej pikseli przy 1,5× i 4× przy 2×.", + "shortcut.json_error": "parametry muszą być w formacie JSON: {error}", + "shortcut.no_command": "nie ma polecenia `{id}`; tekst przed JSON musi być identyfikatorem polecenia", + "shortcut.pin": "Przypnij na górze pola S", + "shortcut.unpin": "Odepnij", "sketch.degrees_of_freedom": "Stopnie swobody: {count}", + "sketch.dof_left": "Pozostałe stopnie swobody: {count}", + "sketch.dof_left_one": "Pozostał {count} stopień swobody", "sketch.draw_curves": "Rysuj jako {linetype}", "sketch.make_selection": "Zamień zaznaczenie na {linetype}", "sketch.open_ends": "Otwarte: {count}", "sketch.untied_ends": "Niezwiązane: {count}", + "sketch_panel.attached": "Dołączony do {body} / {path}", + "sketch_panel.author": "Pierwotne położenie: {path}", + "sketch_panel.dimension_hint": "Wprowadź wartość lub wyrażenie, a następnie naciśnij OK.", + "sketch_panel.move_hint": "Wprowadź odległości i kąt, a następnie naciśnij OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Dołączony do płaskiej ściany bryły blaszanej; podąża za tym panelem przy zmianach blachy.", + "sketch_panel.text_hint": "Edytuj tekst i jego położenie, a następnie naciśnij OK.", + "sketch_panel.use_plane": "Użyj płaszczyzny {plane}", "sketch_text.align_center": "Do środka", "sketch_text.align_left": "Do lewej", "sketch_text.align_right": "Do prawej", "sketch_text.bold": "Pogrubienie", "sketch_text.builtin_font": "{family} (wbudowana)", - "sketch_text.font_search": "Szukaj czcionek", "sketch_text.italic": "Kursywa", "sketch_text.loading_fonts": "Wyszukiwanie zainstalowanych czcionek…", "sketch_text.on_path": "Tekst biegnie wzdłuż ścieżki: krzywa go rozmieszcza", @@ -826,6 +905,7 @@ "timeline.reorder_many": "Zmień kolejność funkcji", "timeline.ungroup": "Usuń foldery funkcji", "to the next face": "do następnej ściany", + "toolbar.customize_note": "Karta {tab}. Zaznacz polecenie, aby dodać jego przycisk do paska narzędzi. Możesz też przeciągnąć wiersz z listy rozwijanej panelu na pasek narzędzi, przeciągnąć przycisk poza pasek, aby go usunąć, lub użyć pinezki w wierszu listy rozwijanej.", "toolbar.image": "OBRAZ", "toolbar.render": "RENDEROWANIE", "toolbar.render_scene": "SCENA", @@ -834,9 +914,7 @@ "trace.stopped_by_loop": "Kończy się na próbce {sample}: pętla przez {joint} nie może się bardziej zamknąć", "ui.1_degree_of_freedom": "1 stopień swobody", "ui.3d_mouse_speed": "Czułość myszy 3D", - "ui.3d_mouse_windows": "Mysz 3D", "ui.3d_sketch": "Szkic 3D", - "ui.about": "O programie", "ui.activate": "Aktywuj", "ui.active_component": "Aktywny komponent", "ui.agent_chat": "Czat z agentem", @@ -879,7 +957,7 @@ "ui.agent_chat_model_tip": "Model używany przez agenta. Twój wybór jest zapamiętywany dla tego agenta.", "ui.agent_chat_new": "Nowy czat", "ui.agent_chat_new_confirm": "Rozpocząć nowy czat? Bieżący zostanie wyczyszczony.", - "ui.agent_chat_new_yes": "Nowy czat", + "ui.agent_chat_next_picture": "Następny obraz", "ui.agent_chat_no_cli": "Nie znaleziono solidcraft-cli obok aplikacji, więc agent nie ma dostępu do projektu.", "ui.agent_chat_no_control": "Punkt końcowy czatu z agentem się nie uruchomił, więc agent nie ma dostępu do projektu.", "ui.agent_chat_no_images": "Ten agent nie przyjmuje obrazów", @@ -893,6 +971,7 @@ "ui.agent_chat_placeholder": "Poproś agenta o zmianę projektu…", "ui.agent_chat_placeholder_busy": "Zakolejkuj wiadomość na czas, gdy agent skończy (Ctrl+Enter: wyślij teraz)", "ui.agent_chat_pop_out": "Otwórz w osobnym oknie, które możesz przenieść na inny ekran", + "ui.agent_chat_previous_picture": "Poprzedni obraz", "ui.agent_chat_privacy": "Nic nie trafia nigdzie poza uruchomionym przez Ciebie agentem, na Twoim własnym koncie.", "ui.agent_chat_privacy_note": "SolidCraft nigdzie niczego nie wysyła: uruchomiony przez Ciebie agent łączy się z własną usługą na Twoim koncie. SolidCraft nigdy nie widzi ani nie przechowuje klucza ani hasła.", "ui.agent_chat_queue": "Kolejka", @@ -946,6 +1025,7 @@ "ui.ai_bad_url": "Wpisz adres URL zaczynający się od http:// lub https://", "ui.ai_base_url": "Bazowy URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "kopia", "ui.ai_custom_url": "Niestandardowy URL", "ui.ai_default": "Domyślny", @@ -966,8 +1046,10 @@ "ui.ai_mode_edits_note": "Polecenia modelowania działają bez pytania; zapisywanie, otwieranie, eksport oraz inne operacje na plikach lub nieodwracalne nadal wymagają pytania.", "ui.ai_mode_global": "Jak ustawiono powyżej", "ui.ai_model": "Model", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Te ustawienia dotyczą narzędzi SolidCraft. Własne narzędzia agenta (jego powłoka, edycja plików) podlegają własnemu trybowi agenta, który wybierasz w nagłówku panelu czatu, gdy agent ma tryby.", "ui.ai_name": "Nazwa", + "ui.ai_name_hint": "Mój agent", "ui.ai_need_command": "Wpisz polecenie uruchamiające agenta", "ui.ai_need_model": "Wpisz nazwę modelu", "ui.ai_no_answer": "Agent nie odpowiedział na czas", @@ -998,6 +1080,7 @@ "ui.ai_toolbar_button": "Pokaż przycisk na pasku narzędzi", "ui.ai_toolbar_button_note": "Przycisk AI na prawym końcu paska narzędzi. Ctrl+Shift+A i menu Widok nadal otwierają panel.", "ui.ai_undo_tip": "Cofnij jednym krokiem wszystko, co agent zmienił w ramach tej wiadomości", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Wszystko", "ui.analysis": "Analiza", "ui.angle": "Kąt", @@ -1009,7 +1092,6 @@ "ui.anti_aliasing": "Wygładzanie krawędzi", "ui.apply": "Zastosuj", "ui.arc": "Łuk", - "ui.assemble": "Złożenie", "ui.autosave_every": "Autozapis co", "ui.axes": "Osie", "ui.axis": "Oś", @@ -1018,6 +1100,11 @@ "ui.bodies": "Bryły", "ui.box": "Prostopadłościan", "ui.break": "Podziel", + "ui.browser_bodies_one": "{count} bryła", + "ui.browser_bodies_other": "{count} brył", + "ui.browser_collapse": "Zwiń przeglądarkę", + "ui.browser_expand": "Rozwiń przeglądarkę", + "ui.browser_title": "Przeglądarka", "ui.calibrate": "Kalibruj", "ui.cancel": "Anuluj", "ui.canvases": "Płótna", @@ -1027,7 +1114,6 @@ "ui.chamfer": "Faza", "ui.chamfer_type": "Typ fazy", "ui.change_workspace": "Zmień środowisko pracy", - "ui.chord_length": "Długość cięciwy", "ui.circle": "Okrąg", "ui.circular_pattern": "Szyk kołowy", "ui.clear": "Wyczyść", @@ -1060,11 +1146,9 @@ "ui.command_done": "Wykonano: {command}", "ui.command_palette_hint": "Wpisz polecenie, np. {example} — lub naciśnij S, aby wyszukać", "ui.concentric": "Współśrodkowość", - "ui.configurations": "Konfiguracje", "ui.conic_curve": "Krzywa stożkowa", "ui.constant": "Stały", "ui.constraints_conflict": "Konflikt wiązań", - "ui.construct": "Konstrukcja", "ui.construction": "Konstrukcyjne", "ui.context_menu": "Menu kontekstowe", "ui.continuity_g0": "G0 załamanie", @@ -1090,7 +1174,6 @@ "ui.cylinder": "Walec", "ui.dark": "Ciemny", "ui.default_modelling_orientation": "Domyślna orientacja modelowania", - "ui.degrees_of_freedom": "stopnie swobody", "ui.delete": "Usuń", "ui.depth": "Głębokość", "ui.design": "Projekt", @@ -1132,7 +1215,6 @@ "ui.equivalent_radius": "Promień zastępczy", "ui.expand": "Rozwiń", "ui.exportDxf": "Eksportuj DXF…", - "ui.export_ellipsis": "Eksportuj...", "ui.export_ellipsis_keymap": "Eksportuj…", "ui.export_keymap_tip": "Klawisze, ulubione z okna S i pasek narzędzi, jako plik", "ui.extend": "Wydłuż", @@ -1216,7 +1298,6 @@ "ui.heading_sheet_metal": "BLACHA", "ui.heading_shell": "SKORUPA", "ui.heading_sketch": "SZKIC", - "ui.heading_sketch_palette": "PALETA SZKICU", "ui.heading_sketch_scale": "SKALA SZKICU", "ui.heading_solid": "BRYŁA", "ui.heading_sphere": "SFERA", @@ -1235,6 +1316,7 @@ "ui.hideOrigin": "Ukryj początek układu", "ui.hidePlanes": "Ukryj płaszczyzny", "ui.hideProfile": "Ukryj profil", + "ui.history_title": "Historia", "ui.hole": "Otwór", "ui.hole_tap_type": "Typ gwintu otworu", "ui.hole_type": "Typ otworu", @@ -1254,7 +1336,6 @@ "ui.length_decimals": "Miejsca dziesiętne długości", "ui.light": "Jasny", "ui.line": "Linia", - "ui.linetype": "Rodzaj linii", "ui.lock": "Zablokuj", "ui.loft": "Wyciągnięcie złożone", "ui.look_at": "Patrz na", @@ -1286,10 +1367,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF ze znacznikami ścian", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Punkt środkowy", "ui.mirror": "Lustro", "ui.mirror_line": "Linia odbicia", - "ui.modify": "Modyfikuj", "ui.move": "Przesuń", "ui.multiple": "Wiele", "ui.named_views": "Widoki nazwane", @@ -1309,7 +1388,6 @@ "ui.offset_plane": "Płaszczyzna odsunięta", "ui.one_side": "Jedna strona", "ui.open": "Otwórz", - "ui.open_ellipsis": "Otwórz...", "ui.open_ends_tip": "Niczego nie dotyka", "ui.open_preferences": "Otwórz preferencje", "ui.open_recent": "Otwórz ostatnie", @@ -1319,8 +1397,9 @@ "ui.orbit_around": "Obracaj wokół", "ui.orientation": "Orientacja", "ui.origin": "Początek układu", - "ui.palette_points": "Punkty", - "ui.palette_profile": "Profil", + "ui.palette_construction_mode": "Tryb konstrukcyjny (X)", + "ui.palette_more": "Więcej", + "ui.palette_sketch_options": "Opcje szkicu", "ui.pan": "Przesuwanie", "ui.pan_zoom_orbit": "Przesuwanie, powiększanie, obracanie", "ui.parallel": "Równoległość", @@ -1354,7 +1433,6 @@ "ui.quit": "Zakończ", "ui.radial_context_menu": "Promieniste menu kontekstowe", "ui.radius": "Promień", - "ui.radius_type": "Typ promienia", "ui.rail": "Prowadnica", "ui.rail_fit": "Dopasowanie do prowadnicy", "ui.reachable": "Osiągalne", @@ -1379,7 +1457,6 @@ "ui.right": "Prawy", "ui.save": "Zapisz", "ui.save_as": "Zapisz jako", - "ui.save_as_ellipsis": "Zapisz jako...", "ui.save_mesh_no_slicer": "Nie znaleziono slicera: wybierz Niestandardowy… i wskaż program slicera albo wyłącz Wyślij do slicera", "ui.save_mesh_pick_bodies": "Zaznacz bryły do zapisania", "ui.save_mesh_temp_failed": "Nie udało się utworzyć {folder}: {error}", @@ -1392,7 +1469,6 @@ "ui.select": "Zaznacz", "ui.selectOther": "Wybierz inny…", "ui.selectTangentChain": "Zaznacz łańcuch styczny", - "ui.select_other_hint": "Wybierz inny: przytrzymaj nieruchomo lewy przycisk na modelu (lub prawy przycisk › Wybierz inny…), aby wyświetlić wszystko pod kursorem; kliknij ponownie w tym samym miejscu, aby zaznaczyć kolejny obiekt.", "ui.selection": "Zaznaczenie", "ui.send": "Wyślij", "ui.send_to_slicer": "Wyślij do slicera", @@ -1409,7 +1485,6 @@ "ui.show_constraints": "Wiązania", "ui.show_construction_geometries": "Geometria konstrukcyjna", "ui.show_dimensions": "Wymiary", - "ui.show_points": "Pokaż punkty", "ui.show_profile": "Pokaż profil", "ui.show_projected_geometries": "Geometria rzutowana", "ui.simple": "Prosty", @@ -1419,7 +1494,6 @@ "ui.sketch_dimension": "Wymiar szkicu", "ui.sketch_grid": "Siatka szkicu", "ui.sketch_scale": "Skala szkicu", - "ui.sketch_status": "Stan szkicu", "ui.sketches": "Szkice", "ui.slice": "Przetnij", "ui.slicer": "Slicer", @@ -1438,7 +1512,6 @@ "ui.split_body": "Podziel bryłę", "ui.stand_up_tip": "Gdy plik STEP lub IGES otworzy się z osią Z w górę, proponuj obrót o ćwierć obrotu wokół osi X", "ui.start": "Start", - "ui.start_radius": "Promień początkowy", "ui.stretch": "Rozciągnij", "ui.supersampling": "Supersampling (SSAA)", "ui.supersampling_adaptive": "Tylko gdy widok jest nieruchomy", @@ -1486,7 +1559,6 @@ "ui.untitled": "Bez tytułu", "ui.up_axis_note": "Która oś wskazuje górę w widokach (widok osi, Widok główny, Patrz na). Projekty pozostają bez zmian.", "ui.updateToCurrentView": "Aktualizuj do bieżącego widoku", - "ui.variable": "Zmienny", "ui.view_centre": "Środek widoku", "ui.visual_shaded_hidden_edges": "Cieniowany z krawędziami niewidocznymi", "ui.visual_shaded_with_edges": "Cieniowany z krawędziami", @@ -1502,7 +1574,6 @@ "ui.zoom_to_the_cursor": "Powiększaj w stronę kursora", "update.available": "Dostępna jest wersja SolidCraft {version}", "update.button": "Aktualizuj", - "update.button_tip": "Dostępna jest wersja SolidCraft {version}", "update.button_version": "Aktualizuj do {version}", "update.check_automatically": "Automatycznie sprawdzaj aktualizacje", "update.check_now": "Sprawdź teraz", @@ -1534,5 +1605,6 @@ "update.title": "Dostępna aktualizacja", "update.up_to_date": "SolidCraft jest aktualny.", "update.update_now": "Aktualizuj teraz", - "update.you_have": "Masz wersję {version}." + "update.you_have": "Masz wersję {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ wybierz · Enter / Tab uzupełnij · Esc zamknij" } diff --git a/assets/locales/pt-BR.json b/assets/locales/pt-BR.json index 4ad1c0a484..f48d0d7122 100644 --- a/assets/locales/pt-BR.json +++ b/assets/locales/pt-BR.json @@ -6,9 +6,9 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Clique em Submit new issue no GitHub para enviar o relatório.", "A new configuration, a copy of the active one": "Uma nova configuração, cópia da ativa", - "AI": "IA", "ARRANGE": "ORGANIZAR", "About SolidCraft": "Sobre o SolidCraft", + "Actions": "Ações", "Active": "Ativo", "Active rule": "Regra ativa", "Add": "Adicionar", @@ -16,6 +16,7 @@ "Add Mid Radius": "Adicionar raio intermediário", "Add Row": "Adicionar linha", "Add condition": "Adicionar condição", + "Add or edit rule": "Adicionar ou editar regra", "Alignment": "Alinhamento", "All Sketch Points": "Todos os pontos do esboço", "All open designs": "Todos os projetos abertos", @@ -27,6 +28,7 @@ "Applied Radius": "Raio aplicado", "Apply To": "Aplicar a", "Apply to selection": "Aplicar à seleção", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Aplique a aparência primeiro. Exporta as aparências atribuídas e os materiais criados neste projeto.", "Arc length": "Comprimento do arco", "Area": "Área", "Attachment": "Fixação", @@ -56,14 +58,15 @@ "Check collisions": "Verificar colisões", "Checked": "Marcado", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Escolha um novo plano. A geometria e as cotas do esboço permanecem inalteradas em suas coordenadas bidimensionais.", + "Choose…": "Escolher…", "Circumference": "Circunferência", "Close draft": "Fechar rascunho", "Collisions": "Colisões", "Colour": "Cor", + "Comment": "Comentário", "Component1": "Componente1", "Configuration": "Configuração", "Configuration rules": "Regras de configuração", - "Construction": "Construção", "Contact": "Contato", "Continuity": "Continuidade", "Copy Error Message": "Copiar mensagem de erro", @@ -83,11 +86,11 @@ "Curvature (G2)": "Curvatura (G2)", "Curvature change (G3)": "Variação de curvatura (G3)", "Customize Toolbar": "Personalizar barra de ferramentas", + "Delete": "Excluir", "Delete all": "Excluir tudo", "Delete rule": "Excluir regra", "Delete the row": "Excluir a linha", "Delete the set": "Excluir o conjunto", - "Delete this parameter": "Excluir este parâmetro", "Density": "Densidade", "Designs you open or save appear here.": "Os projetos que você abrir ou salvar aparecem aqui.", "Determined by To Object. Choose Angle to edit the angular extent.": "Determinado por Até o objeto. Escolha Ângulo para editar a extensão angular.", @@ -98,15 +101,19 @@ "Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Clique duplo com o botão do meio na vista: Início (a vista inicial, enquadrando o que é mostrado), Ajustar (enquadra o que é mostrado, mantendo a orientação) ou nada", "Drive Joint": "Acionar junta", "Edges": "Arestas", + "Edit": "Editar", "Edit Profile Sketch": "Editar esboço do perfil", "Enable rule": "Ativar regra", "Enabled": "Ativado", "Explode": "Explodir", "Export Animation…": "Exportar animação…", + "Export CSV…": "Exportar CSV…", "Export Current View as DXF": "Exportar vista atual como DXF", "Export Current View as DXF…": "Exportar vista atual como DXF…", "Export DXF…": "Exportar DXF…", + "Export JSON…": "Exportar JSON…", "Export hidden edges as dashed lines": "Exportar arestas ocultas como linhas tracejadas", + "Expression": "Expressão", "Extend to meet": "Estender até encontrar", "Extents": "Extensões", "FILES": "ARQUIVOS", @@ -115,11 +122,12 @@ "Face attachment lost": "Fixação à face perdida", "Faces": "Faces", "Faces / edges / vertices": "Faces / arestas / vértices", + "Favorites": "Favoritos", "Favourite": "Favorito", + "Feature": "Recurso", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Arquivos de muitos programas CAD usam Y para cima, então abrem deitados aqui (Z para cima).", "Filter": "Filtro", "Find in Browser": "Localizar no navegador", - "Flat": "Plano", "Flat size": "Tamanho planificado", "Follow camera (Look At sketches)": "Seguir a câmera (Olhar para os esboços)", "Follow the camera (Look At sketches)": "Seguir a câmera (Olhar para os esboços)", @@ -134,6 +142,7 @@ "Holes (edges on them)": "Furos (arestas neles)", "Home view": "Vista inicial", "IMAGES": "IMAGENS", + "Import…": "Importar…", "In This Design": "Neste projeto", "In place (surfaces)": "No lugar (superfícies)", "In plane": "No plano", @@ -154,7 +163,9 @@ "Length units": "Unidades de comprimento", "Letter Spacing": "Espaçamento entre letras", "Library": "Biblioteca", + "Library file": "Arquivo de biblioteca", "Line Spacing": "Espaçamento entre linhas", + "Local library": "Biblioteca local", "Loop": "Laço", "MANAGE": "GERENCIAR", "MATERIALS": "MATERIAIS", @@ -162,35 +173,43 @@ "MOTION": "MOVIMENTO", "Margin": "Margem", "Mass": "Massa", + "Matched restrictions check the final values.": "As restrições cujas condições são atendidas verificam os valores finais.", "Material": "Material", "Max": "Máx.", "Mid Radius": "Raio intermediário", "Middle double-click": "Clique duplo do meio", "Min": "Mín.", "Mitre": "Meia-esquadria", + "Model Parameters": "Parâmetros do modelo", "Modeled": "Modelado", "More toolbar panels": "Mais painéis da barra de ferramentas", "Motion": "Movimento", "Move Type": "Tipo de movimento", + "Move down": "Mover para baixo", "Move earlier": "Mover para antes", "Move later": "Mover para depois", + "Move up": "Mover para cima", "Name": "Nome", + "New": "Novo", + "New rule…": "Nova regra…", "No crash reports.": "Nenhum relatório de falha.", + "No matching appearance presets.": "Nenhuma predefinição de aparência correspondente.", "No matching appearances": "Nenhuma aparência correspondente", "No visible bodies to export.": "Nenhum corpo visível para exportar.", "None": "Nenhum", - "Normal to sketch": "Normal ao esboço", "Not available yet": "Ainda não disponível", "Notch": "Entalhe", "OUTPUT": "SAÍDA", "Opacity": "Opacidade", "Open GitHub draft": "Abrir rascunho no GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Abra bibliotecas locais no aplicativo para desktop. Você pode exportar a biblioteca deste projeto aqui.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "CAD 3D paramétrico de código aberto em Rust. Licenciado sob MIT OU Apache-2.0.", + "Operator": "Operador", "Other side": "Outro lado", "Other surface (mutual trim)": "Outra superfície (aparo mútuo)", "Outside": "Fora", "Overall length": "Comprimento total", - "Parameter values": "Valores dos parâmetros", + "Parameter": "Parâmetro", "Pass through": "Atravessar", "Path": "Caminho", "Per Edge": "Por aresta", @@ -205,7 +224,6 @@ "Preview Row": "Visualizar linha", "Preview leaves the current design unchanged.": "A visualização não altera o projeto atual.", "Profiles/Curves": "Perfis/Curvas", - "Projection": "Projeção", "RECENT": "RECENTES", "REFERENCE": "REFERÊNCIA", "RULED SURFACE": "SUPERFÍCIE REGRADA", @@ -241,6 +259,8 @@ "Roll History Marker Here": "Mover o marcador do histórico para cá", "Round": "Arredondado", "Rule": "Regra", + "Rule name": "Nome da regra", + "Rules run from top to bottom. Matched drives change effective values.": "As regras são executadas de cima para baixo. As regras de controle cujas condições são atendidas alteram os valores efetivos.", "SECTION": "SEÇÃO", "SURFACE QUALITY": "QUALIDADE DA SUPERFÍCIE", "Save Rule": "Salvar regra", @@ -249,6 +269,7 @@ "Scale factor": "Fator de escala", "Search": "Pesquisar", "Search commands": "Pesquisar comandos", + "Search this library": "Pesquisar nesta biblioteca", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Selecionar outro: mantenha o botão esquerdo parado sobre o modelo (ou clique com o botão direito › Selecionar outro…) para listar tudo sob o cursor; clique de novo no mesmo ponto para selecionar o próximo item ali. Clique duas vezes com o botão do meio para ir ao Início (Preferências › Navegação).", "Select bodies (a face picks its body)": "Selecione corpos (uma face seleciona seu corpo)", "Select bodies or faces instead": "Selecionar corpos ou faces em vez disso", @@ -303,17 +324,18 @@ "Turn off section analysis before exporting the view.": "Desative a análise de seção antes de exportar a vista.", "Type": "Tipo", "UNTRIM": "DESAPARAR", + "Unit": "Unidade", "Updating the preview": "Atualizando a visualização", "Use active author placement": "Usar o posicionamento de autoria ativo", "Use fixed plane": "Usar plano fixo", "Use saved fixed plane": "Usar o plano fixo salvo", "Use selected face as fixed plane": "Usar a face selecionada como plano fixo", + "User Parameters": "Parâmetros do usuário", "VIEW": "VISTA", "VIEWS": "VISTAS", "Value": "Valor", "Vertices": "Vértices", "View navigator": "Navegador de vistas", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Somente corpos visíveis. As curvas usam segmentos de reta (tolerância de 0,01 mm).", "Volume": "Volume", "Wall": "Parede", "Wall Location": "Posição da parede", @@ -321,6 +343,12 @@ "When": "Quando", "Width": "Largura", "Will fail": "Vai falhar", + "X angle": "Ângulo X", + "X distance": "Distância X", + "Y angle": "Ângulo Y", + "Y distance": "Distância Y", + "Z angle": "Ângulo Z", + "Z distance": "Distância Z", "angle, height": "ângulo, altura", "appearance.dielectric_ior": "IOR dielétrico", "appearance.metallic": "Metálico", @@ -329,7 +357,11 @@ "appearance.roughness": "Rugosidade", "appearance.surface_response": "Resposta da superfície", "browser.body_name": "Corpo{number}", + "browser.capture_many": "{count} componentes foram movidos, mas suas posições não foram capturadas.", + "browser.capture_note": "Capturar posição mantém a nova posição; Reverter posição restaura a anterior.", + "browser.capture_one": "Um componente foi movido, mas sua posição não foi capturada.", "browser.component_name": "Componente{number}", + "browser.move_title": "Mover: {name}", "browser.parameters": "Parâmetros ({count})", "browser.precision": "Precisão: {precision}", "browser.sketch_name": "Esboço{number}", @@ -643,7 +675,8 @@ "cmd.view_navigate": "Navegar na vista", "cmd.view_rename": "Renomear vista nomeada", "cmd.view_save": "Nova vista nomeada", - "command name": "nome do comando", + "contact.components_count": "{count} componentes", + "contact.enabled_label": "ativado", "coupling.gear_1": "Engrenagem 1", "coupling.gear_1_pick": "Engrenagem 1 (opcional)", "coupling.gear_2": "Engrenagem 2", @@ -663,8 +696,11 @@ "crash.detected_time": "Detectado em {time} UTC", "crash.previous_session": "Uma sessão anterior do SolidCraft terminou inesperadamente ou teve um erro interno. Encerramentos sem confirmação são detectados ao iniciar o aplicativo. Consulte Ajuda > Relatórios de falha… para ver os detalhes.", "crash.unclean_explanation": "Detectado em uma inicialização posterior; o horário exato do encerramento não foi registrado. Um desligamento do sistema ou uma parada forçada também pode causar um encerramento sem confirmação.", + "delete.and_more": "… e mais {count}", "dialog.computing": "Calculando… {seconds} s", "dialog.custom_axis": "↖ Eixo personalizado", + "dialog.dimension_insert_hint": "Clique duas vezes em outra cota de controle para inserir seu nome", + "dialog.dimension_name": "Cota: {name}", "dialog.edit_title": "EDITAR {title}", "dialog.select": "↖ Selecionar", "dialog.selected_count": "↖ {count} selecionados", @@ -676,9 +712,17 @@ "drive.stopped_by_contact": "Parado em {value}: {a} atravessaria {b}", "drive.stopped_by_loop": "Parado em {value}: o laço por {joint} não pode fechar mais", "drive.unconnected": "Não move nada: {part} não está conectado a nada fixo", - "expression, e.g. 25 mm": "expressão, p. ex. 25 mm", "fits the face, e.g. M8": "se ajusta à face, p. ex. M8", "from the rule": "da regra", + "help.apache": "Licença Apache 2.0", + "help.attributions": "Atribuições (ATTRIBUTION.md)", + "help.build": "Versão {version} · commit {commit} · compilada em {date}", + "help.font_details": "A interface usa as fontes integradas do egui (Ubuntu Light e Hack, sob as licenças Ubuntu Font Licence e MIT/Bitstream Vera; Noto Emoji e a fonte de ícones emoji, sob OFL e MIT). O texto dos esboços usa Liberation Sans (SIL OFL 1.1). O pacote opcional de fontes da The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), não está incluído nas compilações do SolidCraft.", + "help.fonts": "Fontes", + "help.licences": "Licenças", + "help.mit": "Licença MIT", + "help.notice": "NOTICE", + "help.third_party": "Licenças de terceiros", "home.documentation": "Documentação", "home.in_this_browser": "neste navegador", "home.missing": "ausente", @@ -707,6 +751,26 @@ "joint.from_bodies_hint": "Cada corpo selecionado vai para um novo componente próprio e permanece onde está.", "joint.one_body": "As duas seleções estão em {body}. Selecione a segunda em outra peça.", "joint.one_component": "Estas peças são corpos de um mesmo componente: {bodies}. Uma junta move um componente em relação a outro, então cada peça precisa de seu próprio componente.", + "keymap.command_id": "ID do comando", + "keymap.empty": "Nenhum comando correspondente", + "keymap.use_anyway": "Usar {key} aqui mesmo assim", + "manage.appearance_hint": "Escolha uma aparência para a seleção ou arraste uma amostra para um corpo ou uma face.", + "manage.assign_hint": "Selecione corpos e escolha sua regra de projeto de peças plásticas.", + "manage.config_hint": "Edite as linhas, colunas e regras de configuração e depois ative uma linha ou visualize sua prévia.", + "manage.empty": "Nenhuma linha correspondente.", + "manage.material_hint": "Selecione corpos e escolha seu material físico.", + "manage.parameters_hint": "Edite expressões e unidades. As alterações são aplicadas ao pressionar Enter ou sair de um campo.", + "manage.rules_hint": "Escolha uma regra para editar ou crie uma nova. Confirme para salvar a regra.", + "measure.at": "Em", + "measure.item": "Item {number}", + "motion.delete_key": "Excluir quadro-chave", + "motion.key_here": "Inserir quadro-chave aqui", + "motion.key_here_hint": "Adicionar um quadro-chave neste passo com o valor atual da junta", + "motion.step_back": "Passo anterior", + "motion.step_forward": "Próximo passo", + "motion.to_end": "Para o fim", + "motion.to_start": "Para o início", + "motion.track_name": "Trilha {number}", "nav.touchpad.help": "Segure Shift e mova o ponteiro para deslocar; segure Alt/Option e mova para orbitar. Não é preciso botão do mouse. A rolagem vertical aplica zoom.", "nav.touchpad.label": "Touchpad", "nav.touchpad.orbit": "Orbitar (segure Alt/Option e mova; sem botão do mouse)", @@ -715,11 +779,12 @@ "nav.touchpad.zoom": "Zoom (rolagem vertical ou pinça)", "needs two joints": "precisa de duas juntas", "new configuration": "nova configuração", - "new name": "novo nome", "new parameter": "novo parâmetro", "no interference": "sem interferência", "no joint that moves yet": "ainda não há junta que se mova", "no picture": "sem imagem", + "none": "nenhum", + "parts.automatic_name": "automático", "pattern.alignment_2": "Alinhamento 2", "pattern.alignment_hint": "As contagens incluem o original. Centralizar equilibra as cópias; Inverter escolhe o lado extra para totais pares.", "pattern.direction_1": "Direção 1", @@ -727,12 +792,13 @@ "pattern.flip_direction": "Inverter direção", "pick a hole to seat it, or OK places it at the origin": "selecione um furo para assentá-la, ou OK a coloca na origem", "pick one or two vertices, edges or faces": "selecione um ou dois vértices, arestas ou faces", + "prefs.cpu": "CPU (software)", + "prefs.minutes_suffix": " min", + "properties.title": "Propriedades: {name}", "render.background": "Fundo da cena", "render.fit": "Enquadrar produto", - "render.image_cancel": "Cancelar renderização", "render.image_cancelled": "Renderização cancelada", "render.image_cancelling": "Cancelando", - "render.image_check": "Verificar pedido de renderização", "render.image_completed": "Imagem pronta para exportar", "render.image_encoding": "Codificando PNG", "render.image_export": "Exportar PNG", @@ -789,15 +855,28 @@ "settings.reset_intro": "Estas configurações não funcionam neste computador e impediriam o SolidCraft de iniciar, por isso foram alteradas:", "settings.reset_title": "As configurações foram alteradas", "settings.supersampling_note": "Desenha a vista em resolução maior e a filtra para baixo: arestas, linhas e grade mais suaves em telas abaixo de 4K. Custa tempo de GPU: cerca de 2,25× os pixels em 1,5× e 4× em 2×.", + "shortcut.json_error": "os parâmetros devem estar no formato JSON: {error}", + "shortcut.no_command": "o comando `{id}` não existe; o texto antes do JSON deve ser um ID de comando", + "shortcut.pin": "Fixar no topo da caixa S", + "shortcut.unpin": "Desafixar", "sketch.degrees_of_freedom": "{count} graus de liberdade", + "sketch.dof_left": "{count} graus de liberdade restantes", + "sketch.dof_left_one": "{count} grau de liberdade restante", "sketch.draw_curves": "Desenhar como {linetype}", "sketch.make_selection": "Converter a seleção em {linetype}", + "sketch_panel.attached": "Anexado a {body} / {path}", + "sketch_panel.author": "Posicionamento de autoria: {path}", + "sketch_panel.dimension_hint": "Insira um valor ou uma expressão e pressione OK.", + "sketch_panel.move_hint": "Insira as distâncias e um ângulo e pressione OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Anexado a uma face plana de um corpo de chapa; acompanha esse painel quando a chapa muda.", + "sketch_panel.text_hint": "Edite o texto e seu posicionamento e pressione OK.", + "sketch_panel.use_plane": "Usar plano {plane}", "sketch_text.align_center": "Centro", "sketch_text.align_left": "Esquerda", "sketch_text.align_right": "Direita", "sketch_text.bold": "Negrito", "sketch_text.builtin_font": "{family} (incorporada)", - "sketch_text.font_search": "Pesquisar fontes", "sketch_text.italic": "Itálico", "sketch_text.loading_fonts": "Procurando fontes instaladas…", "sketch_text.on_path": "O texto segue seu caminho: a curva o posiciona", @@ -812,6 +891,7 @@ "timeline.reorder_many": "Reordenar recursos", "timeline.ungroup": "Remover pastas de recursos", "to the next face": "até a próxima face", + "toolbar.customize_note": "Aba {tab}. Marque um comando para adicionar seu botão à barra de ferramentas. Você também pode arrastar uma linha do menu suspenso de um painel para a barra, arrastar um botão para fora da barra para removê-lo ou usar o alfinete de uma linha do menu suspenso.", "toolbar.image": "IMAGEM", "toolbar.render": "RENDERIZAR", "toolbar.render_scene": "CENA", @@ -820,9 +900,7 @@ "trace.stopped_by_loop": "Termina na amostra {sample}: o laço por {joint} não pode fechar mais", "ui.1_degree_of_freedom": "1 grau de liberdade", "ui.3d_mouse_speed": "Velocidade do mouse 3D", - "ui.3d_mouse_windows": "Mouse 3D", "ui.3d_sketch": "Esboço 3D", - "ui.about": "Sobre", "ui.activate": "Ativar", "ui.active_component": "Componente ativo", "ui.agent_chat": "Chat com agente", @@ -864,7 +942,7 @@ "ui.agent_chat_model_tip": "O modelo que o agente usa. Sua escolha é lembrada para este agente.", "ui.agent_chat_new": "Novo chat", "ui.agent_chat_new_confirm": "Começar um novo chat? O atual será apagado.", - "ui.agent_chat_new_yes": "Novo chat", + "ui.agent_chat_next_picture": "Próxima imagem", "ui.agent_chat_no_cli": "O solidcraft-cli não foi encontrado junto ao aplicativo, então o agente não consegue acessar o projeto.", "ui.agent_chat_no_control": "O endpoint do chat com agente não iniciou, então o agente não consegue acessar o projeto.", "ui.agent_chat_no_images": "Este agente não aceita imagens", @@ -876,6 +954,7 @@ "ui.agent_chat_pick": "Agente", "ui.agent_chat_placeholder": "Peça ao agente para alterar o projeto…", "ui.agent_chat_placeholder_busy": "Enfileirar uma mensagem para quando o agente terminar (Ctrl+Enter: enviar agora)", + "ui.agent_chat_previous_picture": "Imagem anterior", "ui.agent_chat_privacy": "Nada vai para lugar algum além do agente que você executa, na sua própria conta.", "ui.agent_chat_privacy_note": "O SolidCraft não envia nada a lugar nenhum: o agente que você executa fala com o próprio serviço, na sua própria conta. O SolidCraft nunca vê nem guarda chaves ou senhas.", "ui.agent_chat_queue": "Fila", @@ -929,6 +1008,7 @@ "ui.ai_bad_url": "Digite uma URL que comece com http:// ou https://", "ui.ai_base_url": "URL base", "ui.ai_button": "IA", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "cópia", "ui.ai_custom_url": "URL personalizada", "ui.ai_default": "Padrão", @@ -949,8 +1029,10 @@ "ui.ai_mode_edits_note": "Comandos de modelagem rodam sem perguntar; salvar, abrir, exportar e outras ações com arquivos ou destrutivas ainda perguntam.", "ui.ai_mode_global": "Como definido acima", "ui.ai_model": "Modelo", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Estas configurações valem para as ferramentas do SolidCraft. As ferramentas do próprio agente (shell, edição de arquivos) seguem o modo do agente, que você escolhe no cabeçalho do painel de chat quando o agente tem modos.", "ui.ai_name": "Nome", + "ui.ai_name_hint": "Meu agente", "ui.ai_need_command": "Digite o comando que inicia o agente", "ui.ai_need_model": "Digite o nome do modelo", "ui.ai_no_answer": "O agente não respondeu a tempo", @@ -981,6 +1063,7 @@ "ui.ai_toolbar_button": "Mostrar botão na barra de ferramentas", "ui.ai_toolbar_button_note": "O botão de IA no fim direito da barra de ferramentas. Ctrl+Shift+A e o menu Exibir ainda abrem o painel.", "ui.ai_undo_tip": "Desfazer tudo o que o agente alterou nesta mensagem, em um passo", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Tudo", "ui.analysis": "Análise", "ui.angle": "Ângulo", @@ -992,7 +1075,6 @@ "ui.anti_aliasing": "Antisserrilhamento", "ui.apply": "Aplicar", "ui.arc": "Arco", - "ui.assemble": "Montar", "ui.autosave_every": "Salvar automaticamente a cada", "ui.axes": "Eixos", "ui.axis": "Eixo", @@ -1002,6 +1084,11 @@ "ui.bodies": "Corpos", "ui.box": "Caixa", "ui.break": "Quebrar", + "ui.browser_bodies_one": "{count} corpo", + "ui.browser_bodies_other": "{count} corpos", + "ui.browser_collapse": "Recolher o navegador", + "ui.browser_expand": "Expandir o navegador", + "ui.browser_title": "Navegador", "ui.calibrate": "Calibrar", "ui.cancel": "Cancelar", "ui.canvases": "Telas de referência", @@ -1011,7 +1098,6 @@ "ui.chamfer": "Chanfro", "ui.chamfer_type": "Tipo de chanfro", "ui.change_workspace": "Mudar espaço de trabalho", - "ui.chord_length": "Comprimento da corda", "ui.circle": "Círculo", "ui.circular_pattern": "Padrão circular", "ui.clear": "Limpar", @@ -1044,11 +1130,9 @@ "ui.command_done": "{command} concluído", "ui.command_palette_hint": "Digite um comando, p. ex. {example} — ou pressione S para pesquisar", "ui.concentric": "Concêntrico", - "ui.configurations": "Configurações", "ui.conic_curve": "Curva cônica", "ui.constant": "Constante", "ui.constraints_conflict": "Restrições em conflito", - "ui.construct": "Construir", "ui.construction": "Construção", "ui.context_menu": "Menu de contexto", "ui.continuity_gap": "Folga", @@ -1071,7 +1155,6 @@ "ui.cylinder": "Cilindro", "ui.dark": "Escuro", "ui.default_modelling_orientation": "Orientação padrão de modelagem", - "ui.degrees_of_freedom": "graus de liberdade", "ui.delete": "Excluir", "ui.depth": "Profundidade", "ui.design": "Projeto", @@ -1113,7 +1196,6 @@ "ui.equivalent_radius": "Raio equivalente", "ui.expand": "Expandir", "ui.exportDxf": "Exportar DXF…", - "ui.export_ellipsis": "Exportar...", "ui.export_ellipsis_keymap": "Exportar…", "ui.export_keymap_tip": "Teclas, favoritos da caixa S e a barra de ferramentas, como um arquivo", "ui.extend": "Estender", @@ -1195,7 +1277,6 @@ "ui.heading_sheet_metal": "CHAPA METÁLICA", "ui.heading_shell": "CASCA", "ui.heading_sketch": "ESBOÇO", - "ui.heading_sketch_palette": "PALETA DE ESBOÇO", "ui.heading_sketch_scale": "ESCALA DO ESBOÇO", "ui.heading_solid": "SÓLIDO", "ui.heading_sphere": "ESFERA", @@ -1214,6 +1295,7 @@ "ui.hideOrigin": "Ocultar origem", "ui.hidePlanes": "Ocultar planos", "ui.hideProfile": "Ocultar perfil", + "ui.history_title": "Histórico", "ui.hole": "Furo", "ui.hole_tap_type": "Tipo de rosca do furo", "ui.hole_type": "Tipo de furo", @@ -1233,7 +1315,6 @@ "ui.length_decimals": "Casas decimais de comprimento", "ui.light": "Claro", "ui.line": "Linha", - "ui.linetype": "Tipo de linha", "ui.lock": "Bloquear", "ui.loft": "Loft", "ui.look_at": "Olhar para", @@ -1265,10 +1346,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF com marcação de faces", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Ponto médio", "ui.mirror": "Espelhar", "ui.mirror_line": "Linha de espelho", - "ui.modify": "Modificar", "ui.move": "Mover", "ui.multiple": "Múltiplo", "ui.named_views": "Vistas nomeadas", @@ -1288,7 +1367,6 @@ "ui.offset_plane": "Plano deslocado", "ui.one_side": "Um lado", "ui.open": "Abrir", - "ui.open_ellipsis": "Abrir...", "ui.open_preferences": "Abrir preferências", "ui.open_recent": "Abrir recente", "ui.operation": "Operação", @@ -1297,8 +1375,9 @@ "ui.orbit_around": "Orbitar em torno de", "ui.orientation": "Orientação", "ui.origin": "Origem", - "ui.palette_points": "Pontos", - "ui.palette_profile": "Perfil", + "ui.palette_construction_mode": "Modo de construção (X)", + "ui.palette_more": "Mais", + "ui.palette_sketch_options": "Opções de esboço", "ui.pan": "Deslocar", "ui.pan_zoom_orbit": "Deslocar, zoom, orbitar", "ui.parallel": "Paralelo", @@ -1332,7 +1411,6 @@ "ui.quit": "Sair", "ui.radial_context_menu": "Menu de contexto radial", "ui.radius": "Raio", - "ui.radius_type": "Tipo de raio", "ui.reachable": "Alcançável", "ui.rectangle": "Retângulo", "ui.rectangular_pattern": "Padrão retangular", @@ -1355,7 +1433,6 @@ "ui.right": "Direita", "ui.save": "Salvar", "ui.save_as": "Salvar como", - "ui.save_as_ellipsis": "Salvar como...", "ui.save_mesh_no_slicer": "Nenhum fatiador encontrado: escolha Personalizado… e selecione seu programa fatiador, ou desative Enviar ao fatiador", "ui.save_mesh_pick_bodies": "Selecione os corpos a salvar", "ui.save_mesh_temp_failed": "Não foi possível criar {folder}: {error}", @@ -1367,7 +1444,6 @@ "ui.select": "Selecionar", "ui.selectOther": "Selecionar outro…", "ui.selectTangentChain": "Selecionar cadeia tangente", - "ui.select_other_hint": "Selecionar outro: mantenha o botão esquerdo parado sobre o modelo (ou clique com o botão direito › Selecionar outro…) para listar tudo sob o cursor; clique de novo no mesmo ponto para selecionar o próximo item ali.", "ui.selection": "Seleção", "ui.send": "Enviar", "ui.send_to_slicer": "Enviar ao fatiador", @@ -1384,7 +1460,6 @@ "ui.show_constraints": "Restrições", "ui.show_construction_geometries": "Geometrias de construção", "ui.show_dimensions": "Cotas", - "ui.show_points": "Mostrar pontos", "ui.show_profile": "Mostrar perfil", "ui.show_projected_geometries": "Geometrias projetadas", "ui.simple": "Simples", @@ -1394,7 +1469,6 @@ "ui.sketch_dimension": "Cota do esboço", "ui.sketch_grid": "Grade do esboço", "ui.sketch_scale": "Escala do esboço", - "ui.sketch_status": "Estado do esboço", "ui.sketches": "Esboços", "ui.slice": "Fatiar", "ui.slicer": "Fatiador", @@ -1440,7 +1514,6 @@ "ui.stand_up_tip": "Depois que um arquivo STEP ou IGES abrir com Z para cima, oferecer um quarto de volta em torno de X", "ui.start": "Início", "ui.start_over": "Recomeçar", - "ui.start_radius": "Raio inicial", "ui.supersampling": "Supersampling (SSAA)", "ui.supersampling_adaptive": "Só quando a vista está parada", "ui.supersampling_off": "Desativado (1×)", @@ -1484,7 +1557,6 @@ "ui.untitled": "Sem título", "ui.up_axis_note": "Qual eixo aponta para cima nas vistas (vista do eixo, Início, Olhar para). Os projetos não mudam.", "ui.updateToCurrentView": "Atualizar para a vista atual", - "ui.variable": "Variável", "ui.view_centre": "Centro da vista", "ui.visual_shaded_hidden_edges": "Sombreado com arestas ocultas", "ui.visual_shaded_with_edges": "Sombreado com arestas", @@ -1500,7 +1572,6 @@ "ui.zoom_to_the_cursor": "Zoom em direção ao cursor", "update.available": "O SolidCraft {version} está disponível", "update.button": "Atualizar", - "update.button_tip": "O SolidCraft {version} está disponível", "update.button_version": "Atualizar para {version}", "update.check_automatically": "Verificar atualizações automaticamente", "update.check_now": "Verificar agora", @@ -1532,5 +1603,6 @@ "update.title": "Atualização disponível", "update.up_to_date": "O SolidCraft está atualizado.", "update.update_now": "Atualizar agora", - "update.you_have": "Você tem a {version}." + "update.you_have": "Você tem a {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ escolher · Enter / Tab completar · Esc fechar" } diff --git a/assets/locales/ru.json b/assets/locales/ru.json index 73cf196674..e28b45de84 100644 --- a/assets/locales/ru.json +++ b/assets/locales/ru.json @@ -5,6 +5,7 @@ "ARRANGE": "РАСПОЛОЖЕНИЕ", "About SolidCraft": "О SolidCraft", "Acceleration": "Ускорение", + "Actions": "Действия", "Active": "Активный", "Active rule": "Активное правило", "Add": "Добавить", @@ -12,6 +13,7 @@ "Add Mid Radius": "Добавить средний радиус", "Add Row": "Добавить строку", "Add condition": "Добавить условие", + "Add or edit rule": "Добавить или изменить правило", "Against": "Против", "Alignment": "Выравнивание", "All Sketch Points": "Все точки эскиза", @@ -25,6 +27,7 @@ "Applied Radius": "Применённый радиус", "Apply To": "Применить к", "Apply to selection": "Применить к выделению", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Сначала примените внешний вид. Экспортирует назначенные внешние виды и материалы, созданные в этом дизайне.", "Area": "Площадь", "Attachment": "Привязка", "BROWSER": "БРАУЗЕР", @@ -52,13 +55,14 @@ "Check collisions": "Проверить столкновения", "Checked": "Включено", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Выберите новую плоскость. Геометрия и размеры эскиза останутся неизменными в её двумерных координатах.", + "Choose…": "Выбрать…", "Close draft": "Закрыть анализ уклонов", "Collisions": "Столкновения", "Colour": "Цвет", + "Comment": "Комментарий", "Component1": "Компонент1", "Configuration": "Конфигурация", "Configuration rules": "Правила конфигурации", - "Construction": "Вспомогательная", "Contact": "Контакт", "Contact sets": "Наборы контактов", "Continuity": "Непрерывность", @@ -77,11 +81,11 @@ "Curvature (G2)": "Кривизна (G2)", "Curvature change (G3)": "Изменение кривизны (G3)", "Customize Toolbar": "Настроить панель инструментов", + "Delete": "Удалить", "Delete all": "Удалить все", "Delete rule": "Удалить правило", "Delete the row": "Удалить строку", "Delete the set": "Удалить набор", - "Delete this parameter": "Удалить этот параметр", "Density": "Плотность", "Designs you open or save appear here.": "Здесь появляются открытые и сохранённые дизайны.", "Determined by To Object. Choose Angle to edit the angular extent.": "Определяется параметром «До объекта». Выберите «Угол», чтобы задать угловой предел.", @@ -92,11 +96,15 @@ "Drive Joint": "Привод сопряжения", "Duration": "Длительность", "Edges": "Рёбра", + "Edit": "Изменить", "Edit Profile Sketch": "Редактировать эскиз профиля", "Enable rule": "Включить правило", "Enabled": "Включено", "Explode": "Разобрать", "Export Animation…": "Экспортировать анимацию…", + "Export CSV…": "Экспорт CSV…", + "Export JSON…": "Экспорт JSON…", + "Expression": "Выражение", "Extend to meet": "Продлить до пересечения", "Extents": "Пределы", "FILES": "ФАЙЛЫ", @@ -104,11 +112,12 @@ "Face attachment lost": "Привязка к грани потеряна", "Faces": "Грани", "Faces / edges / vertices": "Грани / рёбра / вершины", + "Favorites": "Избранное", "Favourite": "Избранное", + "Feature": "Операция", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Файлы многих CAD-программ ориентированы Y вверх, поэтому здесь они открываются лёжа на спине (Z вверх).", "Filter": "Фильтр", "Find in Browser": "Найти в браузере", - "Flat": "Плоский", "Flat size": "Размер развёртки", "Follow camera (Look At sketches)": "Следовать за камерой (эскизы «Взгляд на»)", "Follow the camera (Look At sketches)": "Следовать за камерой (эскизы «Взгляд на»)", @@ -120,6 +129,7 @@ "Height": "Высота", "Home view": "Главный вид", "IMAGES": "ИЗОБРАЖЕНИЯ", + "Import…": "Импорт…", "In This Design": "В этом дизайне", "In plane": "В плоскости", "Interferes": "Пересекается", @@ -138,7 +148,9 @@ "Length units": "Единицы длины", "Letter Spacing": "Межбуквенный интервал", "Library": "Библиотека", + "Library file": "Файл библиотеки", "Line Spacing": "Межстрочный интервал", + "Local library": "Локальная библиотека", "Loop": "Контур", "MANAGE": "УПРАВЛЕНИЕ", "MATERIALS": "МАТЕРИАЛЫ", @@ -146,29 +158,37 @@ "MOTION": "ДВИЖЕНИЕ", "Margin": "Отступ", "Mass": "Масса", + "Matched restrictions check the final values.": "Ограничения, условия которых выполнены, проверяют итоговые значения.", "Material": "Материал", "Mid Radius": "Средний радиус", "Middle double-click": "Двойной щелчок средней кнопкой", + "Model Parameters": "Параметры модели", "Modeled": "Смоделировано", "More toolbar panels": "Другие панели инструментов", "Motion": "Движение", "Move Type": "Тип перемещения", + "Move down": "Переместить вниз", "Move earlier": "Сдвинуть раньше", "Move later": "Сдвинуть позже", + "Move up": "Переместить вверх", "Name": "Имя", + "New": "Создать", + "New rule…": "Новое правило…", "New study": "Новое исследование", "No crash reports.": "Нет отчётов о сбоях.", + "No matching appearance presets.": "Нет подходящих наборов внешнего вида.", "No matching appearances": "Нет подходящих внешних видов", - "Normal to sketch": "По нормали к эскизу", "Not available yet": "Пока недоступно", "Notch": "Вырез", "OUTPUT": "ВЫВОД", "Opacity": "Непрозрачность", "Open GitHub draft": "Открыть черновик на GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Открывайте локальные библиотеки в настольном приложении. Здесь можно экспортировать библиотеку этого дизайна.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Открытая параметрическая 3D CAD на Rust. Лицензия MIT OR Apache-2.0.", + "Operator": "Оператор", "Other side": "Другая сторона", "Other surface (mutual trim)": "Другая поверхность (взаимная обрезка)", - "Parameter values": "Значения параметров", + "Parameter": "Параметр", "Pass through": "Через точки", "Path": "Траектория", "Per Edge": "По каждому ребру", @@ -216,6 +236,8 @@ "Roll History Marker Here": "Переместить маркер истории сюда", "Round": "Круглая", "Rule": "Правило", + "Rule name": "Имя правила", + "Rules run from top to bottom. Matched drives change effective values.": "Правила выполняются сверху вниз. Управляющие правила, условия которых выполнены, изменяют действующие значения.", "SECTION": "СЕЧЕНИЕ", "SURFACE QUALITY": "КАЧЕСТВО ПОВЕРХНОСТИ", "Save Rule": "Сохранить правило", @@ -224,6 +246,7 @@ "Scale factor": "Коэффициент масштаба", "Search": "Поиск", "Search commands": "Поиск команд", + "Search this library": "Поиск в этой библиотеке", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Выбрать другое: удерживайте левую кнопку на модели (или правый щелчок › Выбрать другое…), чтобы увидеть всё под курсором; повторный щелчок в том же месте выбирает следующий объект. Двойной щелчок средней кнопкой — «Главная» (Настройки › Навигация).", "Select bodies (a face picks its body)": "Выберите тела (выбор грани выбирает её тело)", "Select bodies or faces instead": "Вместо этого выберите тела или грани", @@ -272,11 +295,13 @@ "Tolerance": "Допуск", "Trimming tool (plane, face or curves)": "Инструмент обрезки (плоскость, грань или кривые)", "Type": "Тип", + "Unit": "Единица", "Updating the preview": "Обновление предпросмотра", "Use active author placement": "Использовать активное исходное положение", "Use fixed plane": "Использовать фиксированную плоскость", "Use saved fixed plane": "Использовать сохранённую фиксированную плоскость", "Use selected face as fixed plane": "Использовать выбранную грань как фиксированную плоскость", + "User Parameters": "Пользовательские параметры", "VIEW": "ВИД", "VIEWS": "ВИДЫ", "Value": "Значение", @@ -288,6 +313,12 @@ "When": "Когда", "Width": "Ширина", "Will fail": "Завершится с ошибкой", + "X angle": "Угол X", + "X distance": "Расстояние X", + "Y angle": "Угол Y", + "Y distance": "Расстояние Y", + "Z angle": "Угол Z", + "Z distance": "Расстояние Z", "angle, height": "угол, высота", "appearance.dielectric_ior": "Показатель преломления диэлектрика", "appearance.metallic": "Металличность", @@ -296,7 +327,11 @@ "appearance.roughness": "Шероховатость", "appearance.surface_response": "Поведение поверхности", "browser.body_name": "Тело{number}", + "browser.capture_many": "Компоненты ({count}) перемещены, но их положения не зафиксированы.", + "browser.capture_note": "«Зафиксировать положение» сохраняет новое положение; «Вернуть положение» восстанавливает прежнее.", + "browser.capture_one": "Компонент перемещён, но его положение не зафиксировано.", "browser.component_name": "Компонент{number}", + "browser.move_title": "Переместить: {name}", "browser.parameters": "Параметры ({count})", "browser.precision": "Точность: {precision}", "browser.sketch_name": "Эскиз{number}", @@ -614,7 +649,8 @@ "cmd.view_navigate": "Навигация вида", "cmd.view_rename": "Переименовать именованный вид", "cmd.view_save": "Новый именованный вид", - "command name": "имя команды", + "contact.components_count": "Компонентов: {count}", + "contact.enabled_label": "включено", "coupling.gear_1": "Шестерня 1", "coupling.gear_1_pick": "Шестерня 1 (необязательно)", "coupling.gear_2": "Шестерня 2", @@ -631,7 +667,10 @@ "coupling.rack": "Рейка", "coupling.radius_1": "Радиус 1", "coupling.radius_2": "Радиус 2", + "delete.and_more": "… и ещё {count}", "dialog.custom_axis": "↖ Своя ось", + "dialog.dimension_insert_hint": "Дважды щёлкните другой управляющий размер, чтобы вставить его имя", + "dialog.dimension_name": "Размер: {name}", "dialog.edit_title": "ИЗМЕНИТЬ: {title}", "dialog.select": "↖ Выбрать", "dialog.selected_count": "↖ Выбрано: {count}", @@ -643,9 +682,17 @@ "drive.stopped_by_contact": "Остановлено на {value}: {a} прошёл бы сквозь {b}", "drive.stopped_by_loop": "Остановлено на {value}: контур через {joint} дальше не замыкается", "drive.unconnected": "Ничего не движется: {part} не соединён ни с чем закреплённым", - "expression, e.g. 25 mm": "выражение, напр. 25 мм", "fits the face, e.g. M8": "подходит грани, напр. M8", "from the rule": "из правила", + "help.apache": "Лицензия Apache 2.0", + "help.attributions": "Сведения об авторстве (ATTRIBUTION.md)", + "help.build": "Версия {version} · коммит {commit} · сборка от {date}", + "help.font_details": "Интерфейс использует встроенные шрифты egui (Ubuntu Light и Hack под лицензиями Ubuntu Font Licence и MIT/Bitstream Vera; Noto Emoji и шрифт значков эмодзи под лицензиями OFL и MIT). Для текста эскизов используется Liberation Sans (SIL OFL 1.1). Необязательный пакет шрифтов The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), не входит в сборки SolidCraft.", + "help.fonts": "Шрифты", + "help.licences": "Лицензии", + "help.mit": "Лицензия MIT", + "help.notice": "NOTICE", + "help.third_party": "Лицензии сторонних компонентов", "home.documentation": "Документация", "home.in_this_browser": "в этом браузере", "home.missing": "отсутствует", @@ -674,17 +721,38 @@ "joint.from_bodies_hint": "Каждое выбранное тело переходит в свой новый компонент и остаётся на месте.", "joint.one_body": "Оба выбора на {body}. Выберите второй на другой детали.", "joint.one_component": "Эти детали - тела одного компонента: {bodies}. Сопряжение двигает один компонент относительно другого, поэтому каждой детали нужен свой компонент.", + "keymap.command_id": "ID команды", + "keymap.empty": "Нет подходящих команд", + "keymap.use_anyway": "Всё равно использовать здесь {key}", + "manage.appearance_hint": "Выберите внешний вид для выделенных объектов или перетащите образец на тело или грань.", + "manage.assign_hint": "Выберите тела и правило проектирования пластиковых деталей для них.", + "manage.config_hint": "Измените строки, столбцы и правила конфигурации, затем активируйте строку или просмотрите её.", + "manage.empty": "Нет подходящих строк.", + "manage.material_hint": "Выберите тела и их физический материал.", + "manage.parameters_hint": "Измените выражения и единицы. Изменения применяются по нажатию Enter или при выходе из поля.", + "manage.rules_hint": "Выберите правило для редактирования или создайте новое. Подтвердите, чтобы сохранить правило.", + "measure.at": "В точке", + "measure.item": "Элемент {number}", "motion.checked_to": "Проверено только до шага {step}: проверка исчерпала свой бюджет работы", "motion.collision": "{a} · {b}: шаги {steps}", + "motion.delete_key": "Удалить ключевой кадр", "motion.duration_hint": "секунды (пусто: на шаг)", + "motion.key_here": "Вставить ключевой кадр здесь", + "motion.key_here_hint": "Добавить ключевой кадр на этом шаге с текущим значением сопряжения", "motion.no_collisions": "За всё исследование столкновений нет", + "motion.step_back": "Шаг назад", + "motion.step_forward": "Шаг вперёд", + "motion.to_end": "В конец", + "motion.to_start": "В начало", + "motion.track_name": "Дорожка {number}", "needs two joints": "нужны два сопряжения", "new configuration": "новая конфигурация", - "new name": "новое имя", "new parameter": "новый параметр", "no interference": "нет интерференции", "no joint that moves yet": "пока нет подвижных сопряжений", "no picture": "нет картинки", + "none": "нет", + "parts.automatic_name": "автоматически", "pattern.alignment_2": "Выравнивание 2", "pattern.alignment_hint": "Количество включает оригинал. Центрирование распределяет копии по обе стороны; при чётном количестве смена направления выбирает сторону с дополнительной копией.", "pattern.direction_1": "Направление 1", @@ -692,12 +760,13 @@ "pattern.flip_direction": "Изменить направление", "pick a hole to seat it, or OK places it at the origin": "выберите отверстие для установки, или OK разместит в начале координат", "pick one or two vertices, edges or faces": "выберите одну-две вершины, ребра или грани", + "prefs.cpu": "ЦП (программный)", + "prefs.minutes_suffix": " min", + "properties.title": "Свойства: {name}", "render.background": "Фон сцены", "render.fit": "Вписать модель", - "render.image_cancel": "Отменить рендеринг", "render.image_cancelled": "Рендеринг отменён", "render.image_cancelling": "Отмена", - "render.image_check": "Проверить запрос рендеринга", "render.image_completed": "Изображение готово к экспорту", "render.image_encoding": "Кодирование PNG", "render.image_export": "Экспорт PNG", @@ -751,15 +820,28 @@ "settings.msaa_unsupported": "Не поддерживается этим графическим устройством", "settings.reset_intro": "Эти настройки не работают на этом компьютере и могли бы помешать запуску SolidCraft, поэтому они были изменены:", "settings.reset_title": "Настройки были изменены", + "shortcut.json_error": "параметры должны быть в формате JSON: {error}", + "shortcut.no_command": "команда `{id}` не найдена; текст перед JSON должен быть ID команды", + "shortcut.pin": "Закрепить вверху поля S", + "shortcut.unpin": "Открепить", "sketch.degrees_of_freedom": "Степеней свободы: {count}", + "sketch.dof_left": "Осталось степеней свободы: {count}", + "sketch.dof_left_one": "Осталась {count} степень свободы", "sketch.draw_curves": "Рисовать как {linetype}", "sketch.make_selection": "Преобразовать выделение в {linetype}", + "sketch_panel.attached": "Привязано к {body} / {path}", + "sketch_panel.author": "Исходное размещение: {path}", + "sketch_panel.dimension_hint": "Введите значение или выражение, затем нажмите OK.", + "sketch_panel.move_hint": "Введите расстояния и угол, затем нажмите OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Привязано к плоской грани листового тела; следует за этой панелью при изменении листа.", + "sketch_panel.text_hint": "Измените текст и его размещение, затем нажмите OK.", + "sketch_panel.use_plane": "Использовать плоскость {plane}", "sketch_text.align_center": "По центру", "sketch_text.align_left": "По левому краю", "sketch_text.align_right": "По правому краю", "sketch_text.bold": "Полужирный", "sketch_text.builtin_font": "{family} (встроенный)", - "sketch_text.font_search": "Поиск шрифтов", "sketch_text.italic": "Курсив", "sketch_text.loading_fonts": "Поиск установленных шрифтов…", "sketch_text.on_path": "Текст следует по траектории: кривая задаёт его положение", @@ -774,6 +856,7 @@ "timeline.reorder_many": "Изменить порядок операций", "timeline.ungroup": "Убрать папки операций", "to the next face": "до следующей грани", + "toolbar.customize_note": "Вкладка {tab}. Отметьте команду, чтобы добавить её кнопку на панель инструментов. Можно также перетащить строку из выпадающего меню панели на панель инструментов, перетащить кнопку за пределы панели для удаления или использовать значок закрепления в строке выпадающего меню.", "toolbar.image": "ИЗОБРАЖЕНИЕ", "toolbar.render": "РЕНДЕРИНГ", "toolbar.render_scene": "СЦЕНА", @@ -782,13 +865,17 @@ "trace.stopped_by_loop": "Заканчивается на отсчёте {sample}: контур через {joint} дальше не замыкается", "ui.1_degree_of_freedom": "1 степень свободы", "ui.3d_mouse_speed": "Скорость 3D-мыши", - "ui.3d_mouse_windows": "3D-мышь", "ui.3d_sketch": "3D-эскиз", - "ui.about": "О программе", "ui.activate": "Активировать", "ui.active_component": "Активный компонент", + "ui.agent_chat_next_picture": "Следующее изображение", + "ui.agent_chat_previous_picture": "Предыдущее изображение", "ui.agent_cursor": "Курсор агента", "ui.agent_cursor_speed": "Скорость курсора агента", + "ui.ai_command_hint": "my-agent --acp", + "ui.ai_model_hint": "qwen3:14b", + "ui.ai_name_hint": "Мой агент", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Все", "ui.ambient_occlusion": "Окружающее затенение", "ui.ambient_occlusion_help": "Мягкое затенение в местах соприкосновения непрозрачных поверхностей. Требуется видовой экран GPU.", @@ -802,7 +889,6 @@ "ui.anti_aliasing": "Сглаживание", "ui.apply": "Применить", "ui.arc": "Дуга", - "ui.assemble": "Сборка", "ui.autosave_every": "Автосохранение каждые", "ui.axes": "Оси", "ui.axis": "Ось", @@ -811,6 +897,11 @@ "ui.bodies": "Тела", "ui.box": "Бокс", "ui.break": "Разорвать", + "ui.browser_bodies_one": "{count} тело", + "ui.browser_bodies_other": "{count} тел", + "ui.browser_collapse": "Свернуть браузер", + "ui.browser_expand": "Развернуть браузер", + "ui.browser_title": "Браузер", "ui.calibrate": "Калибровать", "ui.cancel": "Отмена", "ui.canvases": "Подложки", @@ -820,7 +911,6 @@ "ui.chamfer": "Фаска", "ui.chamfer_type": "Тип фаски", "ui.change_workspace": "Сменить рабочую область", - "ui.chord_length": "Длина хорды", "ui.circle": "Окружность", "ui.circular_pattern": "Круговой массив", "ui.clear": "Очистить", @@ -853,11 +943,9 @@ "ui.command_done": "{command} — готово", "ui.command_palette_hint": "Введите команду, напр. {example} — или нажмите S для поиска", "ui.concentric": "Концентричность", - "ui.configurations": "Конфигурации", "ui.conic_curve": "Коническая кривая", "ui.constant": "Постоянный", "ui.constraints_conflict": "Ограничения конфликтуют", - "ui.construct": "Построение", "ui.construction": "Вспомогательная", "ui.context_menu": "Контекстное меню", "ui.continuity_gap": "Зазор", @@ -880,7 +968,6 @@ "ui.cylinder": "Цилиндр", "ui.dark": "Тёмная", "ui.default_modelling_orientation": "Ориентация моделирования по умолчанию", - "ui.degrees_of_freedom": "степеней свободы", "ui.delete": "Удалить", "ui.depth": "Глубина", "ui.design": "Дизайн", @@ -917,7 +1004,6 @@ "ui.equivalent_radius": "Эквивалентный радиус", "ui.expand": "Развернуть", "ui.exportDxf": "Экспорт DXF…", - "ui.export_ellipsis": "Экспорт...", "ui.export_ellipsis_keymap": "Экспорт…", "ui.export_keymap_tip": "Сочетания клавиш, избранное панели S и панель инструментов — в файл", "ui.extend": "Удлинить", @@ -1003,7 +1089,6 @@ "ui.heading_sheet_metal": "ЛИСТОВОЙ МЕТАЛЛ", "ui.heading_shell": "ОБОЛОЧКА", "ui.heading_sketch": "ЭСКИЗ", - "ui.heading_sketch_palette": "ПАЛИТРА ЭСКИЗА", "ui.heading_sketch_scale": "МАСШТАБ ЭСКИЗА", "ui.heading_solid": "ТЕЛО", "ui.heading_sphere": "СФЕРА", @@ -1022,6 +1107,7 @@ "ui.hideOrigin": "Скрыть начало координат", "ui.hidePlanes": "Скрыть плоскости", "ui.hideProfile": "Скрыть профиль", + "ui.history_title": "История", "ui.hole": "Отверстие", "ui.hole_tap_type": "Тип резьбы отверстия", "ui.hole_type": "Тип отверстия", @@ -1041,7 +1127,6 @@ "ui.length_decimals": "Знаков после запятой (длины)", "ui.light": "Светлая", "ui.line": "Линия", - "ui.linetype": "Тип линии", "ui.lock": "Заблокировать", "ui.loft": "Лофт", "ui.look_at": "Взгляд на", @@ -1073,10 +1158,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF с метками граней", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Середина", "ui.mirror": "Зеркало", "ui.mirror_line": "Линия зеркала", - "ui.modify": "Изменение", "ui.move": "Переместить", "ui.multiple": "Несколько", "ui.named_views": "Именованные виды", @@ -1096,7 +1179,6 @@ "ui.offset_plane": "Смещённая плоскость", "ui.one_side": "Одна сторона", "ui.open": "Открыть", - "ui.open_ellipsis": "Открыть...", "ui.open_preferences": "Открыть настройки", "ui.open_recent": "Открыть недавние", "ui.operation": "Операция", @@ -1105,8 +1187,9 @@ "ui.orbit_around": "Вращать вокруг", "ui.orientation": "Ориентация", "ui.origin": "Начало координат", - "ui.palette_points": "Точки", - "ui.palette_profile": "Профиль", + "ui.palette_construction_mode": "Вспомогательный режим (X)", + "ui.palette_more": "Ещё", + "ui.palette_sketch_options": "Параметры эскиза", "ui.pan": "Панорамирование", "ui.pan_zoom_orbit": "Панорама, масштаб, орбита", "ui.parallel": "Параллельность", @@ -1139,7 +1222,6 @@ "ui.quit": "Выход", "ui.radial_context_menu": "Радиальное контекстное меню", "ui.radius": "Радиус", - "ui.radius_type": "Тип радиуса", "ui.rectangle": "Прямоугольник", "ui.rectangular_pattern": "Прямоугольный массив", "ui.redo": "Повторить", @@ -1161,7 +1243,6 @@ "ui.right": "Справа", "ui.save": "Сохранить", "ui.save_as": "Сохранить как", - "ui.save_as_ellipsis": "Сохранить как...", "ui.save_mesh_no_slicer": "Слайсер не найден: выберите «Другая…» и укажите программу или отключите «Отправить в слайсер»", "ui.save_mesh_pick_bodies": "Выберите тела для сохранения", "ui.save_mesh_temp_failed": "Не удалось создать {folder}: {error}", @@ -1173,7 +1254,6 @@ "ui.select": "Выбрать", "ui.selectOther": "Выбрать другое…", "ui.selectTangentChain": "Выбрать касательную цепочку", - "ui.select_other_hint": "Выбрать другое: удерживайте левую кнопку на модели (или правый щелчок › Выбрать другое…), чтобы увидеть всё под курсором; повторный щелчок в том же месте выбирает следующий объект.", "ui.selection": "Выделение", "ui.send": "Отправить", "ui.send_to_slicer": "Отправить в слайсер", @@ -1190,7 +1270,6 @@ "ui.show_constraints": "Ограничения", "ui.show_construction_geometries": "Вспомогательная геометрия", "ui.show_dimensions": "Размеры", - "ui.show_points": "Показать точки", "ui.show_profile": "Показать профиль", "ui.show_projected_geometries": "Спроецированная геометрия", "ui.simple": "Простой", @@ -1200,7 +1279,6 @@ "ui.sketch_dimension": "Размер эскиза", "ui.sketch_grid": "Сетка эскиза", "ui.sketch_scale": "Масштаб эскиза", - "ui.sketch_status": "Статус эскиза", "ui.sketches": "Эскизы", "ui.slice": "Срез", "ui.slicer": "Слайсер", @@ -1246,7 +1324,6 @@ "ui.stand_up_tip": "Если файл STEP или IGES открывается с Z вверх, предложить поворот на четверть оборота вокруг X", "ui.start": "Старт", "ui.start_over": "Начать заново", - "ui.start_radius": "Начальный радиус", "ui.suppress": "Подавить", "ui.sweep": "Сдвиг", "ui.sweep_follow_direction": "Следовать направлению", @@ -1286,7 +1363,6 @@ "ui.untitled": "Без имени", "ui.up_axis_note": "Какая ось направлена вверх в видах (осевые виды, «Главная», «Взгляд на»). Дизайны не меняются.", "ui.updateToCurrentView": "Обновить до текущего вида", - "ui.variable": "Переменный", "ui.view_centre": "Центр вида", "ui.visual_shaded_hidden_edges": "Заполненный с невидимыми рёбрами", "ui.visual_shaded_with_edges": "Заполненный с рёбрами", @@ -1299,5 +1375,6 @@ "ui.z_up": "Z вверх", "ui.zoom": "Масштаб", "ui.zoom_to_cursor_tip": "Колесо масштабирует к точке под курсором (выкл.: к центру вида)", - "ui.zoom_to_the_cursor": "Масштаб к курсору" + "ui.zoom_to_the_cursor": "Масштаб к курсору", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ выбрать · Enter / Tab дополнить · Esc закрыть" } diff --git a/assets/locales/tr.json b/assets/locales/tr.json index 907022a7c9..641a162c5c 100644 --- a/assets/locales/tr.json +++ b/assets/locales/tr.json @@ -6,10 +6,10 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Raporu göndermek için GitHub'da Submit new issue düğmesine tıklayın.", "A new configuration, a copy of the active one": "Etkin konfigürasyonun kopyası olan yeni bir konfigürasyon", - "AI": "AI", "ARRANGE": "DÜZENLE", "About SolidCraft": "SolidCraft hakkında", "Acceleration": "İvme", + "Actions": "Eylemler", "Active": "Etkin", "Active rule": "Etkin kural", "Add": "Ekle", @@ -17,6 +17,7 @@ "Add Mid Radius": "Ara yarıçap ekle", "Add Row": "Satır ekle", "Add condition": "Koşul ekle", + "Add or edit rule": "Kural ekle veya düzenle", "Against": "Karşı", "Alignment": "Hizalama", "All Sketch Points": "Tüm eskiz noktaları", @@ -30,6 +31,7 @@ "Applied Radius": "Uygulanan yarıçap", "Apply To": "Uygulama hedefi", "Apply to selection": "Seçime uygula", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Önce görünümünüzü uygulayın. Atanan görünümleri ve bu tasarımda oluşturulan malzemeleri dışa aktarır.", "Arc length": "Yay uzunluğu", "Area": "Alan", "Attachment": "Bağlama", @@ -58,14 +60,15 @@ "Check collisions": "Çarpışmaları kontrol et", "Checked": "Kontrol edildi", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Yeni bir düzlem seçin. Eskiz geometrisi ve ölçüler, iki boyutlu koordinatlarında değişmeden kalır.", + "Choose…": "Seç…", "Circumference": "Çevre", "Close draft": "Taslağı kapat", "Collisions": "Çarpışmalar", "Colour": "Renk", + "Comment": "Yorum", "Component1": "Bileşen1", "Configuration": "Konfigürasyon", "Configuration rules": "Konfigürasyon kuralları", - "Construction": "Yapı çizgisi", "Contact": "Temas", "Contact sets": "Temas kümeleri", "Copy Error Message": "Hata mesajını kopyala", @@ -81,11 +84,11 @@ "Cube": "Küp", "Curvature change (G3)": "Eğrilik değişimi (G3)", "Customize Toolbar": "Araç çubuğunu özelleştir", + "Delete": "Sil", "Delete all": "Tümünü sil", "Delete rule": "Kuralı sil", "Delete the row": "Satırı sil", "Delete the set": "Kümeyi sil", - "Delete this parameter": "Bu parametreyi sil", "Density": "Yoğunluk", "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.", @@ -96,11 +99,15 @@ "Drive Joint": "Bağlantıyı tahrik et", "Duration": "Süre", "Edges": "Kenarlar", + "Edit": "Düzenle", "Edit Profile Sketch": "Profil eskizini düzenle", "Enable rule": "Kuralı etkinleştir", "Enabled": "Etkin", "Explode": "Patlat", "Export Animation…": "Animasyonu dışa aktar…", + "Export CSV…": "CSV dışa aktar…", + "Export JSON…": "JSON dışa aktar…", + "Expression": "İfade", "Extend to meet": "Buluşana kadar uzat", "Extents": "Kapsamlar", "FILES": "DOSYALAR", @@ -109,11 +116,12 @@ "Face attachment lost": "Yüze bağlama kayboldu", "Faces": "Yüzler", "Faces / edges / vertices": "Yüzler / kenarlar / köşe noktaları", + "Favorites": "Sık kullanılanlar", "Favourite": "Favori", + "Feature": "Özellik", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Birçok CAD programının dosyalarında Y yukarıyı gösterir; bu yüzden burada (Z yukarı) sırtüstü yatar hâlde açılırlar.", "Filter": "Filtre", "Find in Browser": "Tarayıcıda bul", - "Flat": "Düz", "Flat size": "Açınım boyutu", "Follow camera (Look At sketches)": "Kamera takibi (eskizlerde Bak)", "Follow the camera (Look At sketches)": "Kamera takibi (eskizlerde Bak)", @@ -127,6 +135,7 @@ "Holes (edges on them)": "Delikler (kenarları)", "Home view": "Başlangıç görünümü", "IMAGES": "GÖRSELLER", + "Import…": "İçe aktar…", "In This Design": "Bu tasarımda", "In place (surfaces)": "Yerinde (yüzeyler)", "In plane": "Düzlemde", @@ -147,7 +156,9 @@ "Length units": "Uzunluk birimleri", "Letter Spacing": "Harf aralığı", "Library": "Kitaplık", + "Library file": "Kitaplık dosyası", "Line Spacing": "Satır aralığı", + "Local library": "Yerel kitaplık", "Loop": "Döngü", "MANAGE": "YÖNET", "MATERIALS": "MALZEMELER", @@ -155,34 +166,42 @@ "MOTION": "HAREKET", "Margin": "Kenar boşluğu", "Mass": "Kütle", + "Matched restrictions check the final values.": "Koşulları sağlanan kısıtlamalar son değerleri denetler.", "Material": "Malzeme", "Max": "Maks", "Mid Radius": "Ara yarıçap", "Middle double-click": "Orta tuşla çift tıklama", "Min": "Min", "Mitre": "Gönye", + "Model Parameters": "Model parametreleri", "Modeled": "Modellenmiş", "More toolbar panels": "Diğer araç çubuğu panelleri", "Motion": "Hareket", "Move Type": "Taşıma türü", + "Move down": "Aşağı taşı", "Move earlier": "Öne al", "Move later": "Sonraya al", + "Move up": "Yukarı taşı", "Name": "Ad", + "New": "Yeni", + "New rule…": "Yeni kural…", "New study": "Yeni etüt", "No crash reports.": "Çökme raporu yok.", + "No matching appearance presets.": "Eşleşen görünüm ön ayarı yok.", "No matching appearances": "Eşleşen görünüm yok", "None": "Hiçbiri", - "Normal to sketch": "Eskize dik", "Not available yet": "Henüz kullanılamıyor", "OUTPUT": "ÇIKTI", "Opacity": "Opaklık", "Open GitHub draft": "GitHub taslağını aç", + "Open local libraries in the desktop app. You can export this design's library here.": "Yerel kitaplıkları masaüstü uygulamasında açın. Bu tasarımın kitaplığını buradan dışa aktarabilirsiniz.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Rust ile yazılmış açık kaynaklı parametrik 3D CAD. Lisans: MIT OR Apache-2.0.", + "Operator": "İşleç", "Other side": "Diğer taraf", "Other surface (mutual trim)": "Diğer yüzey (karşılıklı kırpma)", "Outside": "Dış", "Overall length": "Toplam uzunluk", - "Parameter values": "Parametre değerleri", + "Parameter": "Parametre", "Path": "Yol", "Per Edge": "Kenar başına", "Pick the tool bodies": "Araç gövdelerini seçin", @@ -232,6 +251,8 @@ "Roll History Marker Here": "Geçmiş işaretçisini buraya taşı", "Round": "Yuvarlak", "Rule": "Kural", + "Rule name": "Kural adı", + "Rules run from top to bottom. Matched drives change effective values.": "Kurallar yukarıdan aşağıya çalışır. Koşulları sağlanan sürücü kurallar etkin değerleri değiştirir.", "SECTION": "KESİT", "SURFACE QUALITY": "YÜZEY KALİTESİ", "Save Rule": "Kuralı kaydet", @@ -240,6 +261,7 @@ "Scale factor": "Ölçek faktörü", "Search": "Ara", "Search commands": "Komutlarda ara", + "Search this library": "Bu kitaplıkta ara", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Diğerini seç: İmlecin altındaki her şeyi listelemek için sol tuşu model üzerinde hareket ettirmeden basılı tutun (veya sağ tık › Diğerini seç…); oradaki bir sonraki öğeyi seçmek için aynı noktaya tekrar tıklayın. Başlangıç görünümüne dönmek için orta tuşla çift tıklayın (Ayarlar › Gezinme).", "Select bodies (a face picks its body)": "Gövdeleri seçin (bir yüz kendi gövdesini seçer)", "Select bodies or faces instead": "Bunun yerine gövde veya yüz seçin", @@ -292,11 +314,13 @@ "Trimming tool (plane, face or curves)": "Kırpma aracı (düzlem, yüz veya eğriler)", "Type": "Tür", "UNTRIM": "KIRPMAYI KALDIR", + "Unit": "Birim", "Updating the preview": "Önizleme güncelleniyor", "Use active author placement": "Etkin kaynak yerleşimini kullan", "Use fixed plane": "Sabit düzlem kullan", "Use saved fixed plane": "Kaydedilmiş sabit düzlemi kullan", "Use selected face as fixed plane": "Seçili yüzü sabit düzlem olarak kullan", + "User Parameters": "Kullanıcı parametreleri", "VIEW": "GÖRÜNÜM", "VIEWS": "GÖRÜNÜMLER", "Value": "Değer", @@ -310,6 +334,12 @@ "When": "Eğer", "Width": "Genişlik", "Will fail": "Başarısız olacak", + "X angle": "X açısı", + "X distance": "X mesafesi", + "Y angle": "Y açısı", + "Y distance": "Y mesafesi", + "Z angle": "Z açısı", + "Z distance": "Z mesafesi", "angle, height": "açı, yükseklik", "appearance.dielectric_ior": "Dielektrik kırılma indisi", "appearance.metallic": "Metaliklik", @@ -318,7 +348,11 @@ "appearance.roughness": "Pürüzlülük", "appearance.surface_response": "Yüzey davranışı", "browser.body_name": "Gövde {number}", + "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.", + "browser.capture_one": "Bir bileşen taşındı ancak konumu yakalanmadı.", "browser.component_name": "Bileşen {number}", + "browser.move_title": "Taşı: {name}", "browser.parameters": "Parametreler ({count})", "browser.precision": "Hassasiyet: {precision}", "browser.sketch_name": "Eskiz {number}", @@ -626,9 +660,13 @@ "cmd.view_navigate": "Görünümde gezin", "cmd.view_rename": "Adlandırılmış görünümü yeniden adlandır", "cmd.view_save": "Yeni adlandırılmış görünüm", - "command name": "komut adı", + "contact.components_count": "{count} bileşen", + "contact.enabled_label": "etkin", + "delete.and_more": "… ve {count} tane daha", "dialog.computing": "Hesaplanıyor… {seconds} sn", "dialog.custom_axis": "↖ Özel eksen", + "dialog.dimension_insert_hint": "Adını eklemek için başka bir sürücü ölçüye çift tıklayın", + "dialog.dimension_name": "Ölçü: {name}", "dialog.edit_title": "{title} düzenleniyor", "dialog.select": "↖ Seç", "dialog.selected_count": "↖ {count} seçildi", @@ -640,9 +678,17 @@ "drive.stopped_by_contact": "{value} değerinde durdu: {a} ile {b} iç içe geçerdi", "drive.stopped_by_loop": "{value} değerinde durdu: {joint} üzerinden geçen döngü daha fazla kapatılamıyor", "drive.unconnected": "Hiçbir şey hareket etmiyor: {part} sabitlenmiş hiçbir parçaya bağlı değil", - "expression, e.g. 25 mm": "ifade, ör. 25 mm", "fits the face, e.g. M8": "yüze uyar, ör. M8", "from the rule": "kuraldan", + "help.apache": "Apache lisansı 2.0", + "help.attributions": "Atıflar (ATTRIBUTION.md)", + "help.build": "Sürüm {version} · commit {commit} · derleme tarihi {date}", + "help.font_details": "Arayüz, egui'nin yerleşik yazı tiplerini kullanır (Ubuntu Font Licence ve MIT/Bitstream Vera lisansları altında Ubuntu Light ve Hack; OFL ve MIT altında Noto Emoji ve emoji simge yazı tipi). Eskiz metni Liberation Sans (SIL OFL 1.1) kullanır. The Crafting Apps'in isteğe bağlı yazı tipi paketi craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), SolidCraft derlemelerine dahil değildir.", + "help.fonts": "Yazı tipleri", + "help.licences": "Lisanslar", + "help.mit": "MIT lisansı", + "help.notice": "NOTICE", + "help.third_party": "Üçüncü taraf lisansları", "home.documentation": "Belgeler", "home.in_this_browser": "bu tarayıcıda", "home.missing": "eksik", @@ -671,10 +717,30 @@ "joint.from_bodies_hint": "Seçilen her gövde kendi yeni bileşenine konur ve bulunduğu yerde kalır.", "joint.one_body": "Her iki seçim de {body} üzerinde. İkincisini başka bir parçada seçin.", "joint.one_component": "Bu parçalar tek bir bileşenin gövdeleridir: {bodies}. Bir bağlantı bir bileşeni diğerine göre hareket ettirir, bu nedenle her parçanın kendi bileşeni olması gerekir.", + "keymap.command_id": "Komut kimliği", + "keymap.empty": "Eşleşen komut yok", + "keymap.use_anyway": "Yine de burada {key} kullan", + "manage.appearance_hint": "Seçim için bir görünüm seçin veya bir örneği gövde ya da yüz üzerine sürükleyin.", + "manage.assign_hint": "Gövdeleri seçin ve plastik tasarım kurallarını belirleyin.", + "manage.config_hint": "Yapılandırma satırlarını, sütunlarını ve kurallarını düzenleyin, ardından bir satırı etkinleştirin veya önizleyin.", + "manage.empty": "Eşleşen satır yok.", + "manage.material_hint": "Gövdeleri seçin ve fiziksel malzemelerini belirleyin.", + "manage.parameters_hint": "İfadeleri ve birimleri düzenleyin. Değişiklikler Enter'a basıldığında veya alandan çıkıldığında uygulanır.", + "manage.rules_hint": "Düzenlemek için bir kural seçin veya yeni bir kural oluşturun. Kuralı kaydetmek için onaylayın.", + "measure.at": "Konum", + "measure.item": "Öğe {number}", "motion.checked_to": "Yalnızca {step}. adıma kadar denetlendi: denetim iş bütçesini tüketti", "motion.collision": "{a} · {b}: adımlar {steps}", + "motion.delete_key": "Anahtar kareyi sil", "motion.duration_hint": "saniye (boş: adım başına)", + "motion.key_here": "Buraya anahtar kare ekle", + "motion.key_here_hint": "Bu adıma bağlantının mevcut değeriyle bir anahtar kare ekle", "motion.no_collisions": "Etüt boyunca çarpışma yok", + "motion.step_back": "Bir adım geri", + "motion.step_forward": "Bir adım ileri", + "motion.to_end": "Sona git", + "motion.to_start": "Başa git", + "motion.track_name": "İz {number}", "nav.touchpad.help": "Kaydırmak için Shift tuşunu basılı tutun ve imleci hareket ettirin; Alt/Option ile döndürün. Fare tuşu gerekmez. Dikey kaydırma yakınlaştırır.", "nav.touchpad.label": "Dokunmatik yüzey", "nav.touchpad.orbit": "Döndür (Alt/Option tuşunu basılı tutup hareket ettirin; fare tuşu olmadan)", @@ -683,11 +749,12 @@ "nav.touchpad.zoom": "Yakınlaştır (dikey kaydırın veya parmaklarınızı açıp kapatın)", "needs two joints": "iki bağlantı gerektirir", "new configuration": "yeni konfigürasyon", - "new name": "yeni ad", "new parameter": "yeni parametre", "no interference": "girişim yok", "no joint that moves yet": "henüz hareket eden bir bağlantı yok", "no picture": "resim yok", + "none": "yok", + "parts.automatic_name": "otomatik", "pattern.alignment_2": "Hizalama 2", "pattern.alignment_hint": "Adet, orijinali içerir. Ortala, kopyaları iki yana dağıtır; çift adetlerde yönü ters çevirme, fazladan kopyanın hangi tarafta olacağını belirler.", "pattern.direction_1": "Yön 1", @@ -695,12 +762,13 @@ "pattern.flip_direction": "Yönü ters çevir", "pick a hole to seat it, or OK places it at the origin": "yerleştirmek için bir delik seçin veya OK ile orijine yerleştirin", "pick one or two vertices, edges or faces": "bir veya iki köşe noktası, kenar ya da yüz seçin", + "prefs.cpu": "CPU (yazılım)", + "prefs.minutes_suffix": " min", + "properties.title": "Özellikler: {name}", "render.background": "Sahne arka planı", "render.fit": "Ürünü sığdır", - "render.image_cancel": "Render'ı iptal et", "render.image_cancelled": "Render iptal edildi", "render.image_cancelling": "İptal ediliyor", - "render.image_check": "Render isteğini kontrol et", "render.image_completed": "Resim dışa aktarmaya hazır", "render.image_encoding": "PNG kodlanıyor", "render.image_export": "PNG dışa aktar", @@ -754,15 +822,28 @@ "settings.reset_intro": "Bu ayarlar bu bilgisayarda çalışmadığı ve SolidCraft'ın başlamasını engelleyeceği için değiştirildi:", "settings.reset_title": "Ayarlar değiştirildi", "settings.supersampling_note": "Görünümü daha yüksek çözünürlükte çizer ve küçültür: 4K altındaki ekranlarda kenarlar, çizgiler ve ızgaralar daha pürüzsüz görünür. GPU süresi gerektirir: 1,5× ayarında yaklaşık 2,25 kat, 2× ayarında 4 kat daha fazla piksel işlenir.", + "shortcut.json_error": "parametreler JSON biçiminde olmalıdır: {error}", + "shortcut.no_command": "`{id}` komutu yok; JSON'dan önceki metin bir komut kimliği olmalıdır", + "shortcut.pin": "S kutusunun üstüne sabitle", + "shortcut.unpin": "Sabitlemeyi kaldır", "sketch.degrees_of_freedom": "{count} serbestlik derecesi", + "sketch.dof_left": "Kalan serbestlik derecesi: {count}", + "sketch.dof_left_one": "Kalan serbestlik derecesi: {count}", "sketch.draw_curves": "{linetype} olarak çiz", "sketch.make_selection": "Seçimi {linetype} olarak işaretle", + "sketch_panel.attached": "Bağlı olduğu: {body} / {path}", + "sketch_panel.author": "Yazar yerleşimi: {path}", + "sketch_panel.dimension_hint": "Bir değer veya ifade girip Tamam’a basın.", + "sketch_panel.move_hint": "Uzaklıkları ve bir açı girip Tamam’a basın.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Sac gövdenin düz bir yüzüne bağlıdır; sac değiştiğinde bu paneli izler.", + "sketch_panel.text_hint": "Metni ve yerleşimini düzenleyip Tamam’a basın.", + "sketch_panel.use_plane": "{plane} düzlemini kullan", "sketch_text.align_center": "Orta", "sketch_text.align_left": "Sol", "sketch_text.align_right": "Sağ", "sketch_text.bold": "Kalın", "sketch_text.builtin_font": "{family} (yerleşik)", - "sketch_text.font_search": "Yazı tiplerini ara", "sketch_text.italic": "İtalik", "sketch_text.loading_fonts": "Yüklü yazı tipleri aranıyor…", "sketch_text.on_path": "Metin eğrisini izler: konumunu eğri belirler", @@ -777,14 +858,13 @@ "timeline.reorder_many": "Özellikleri yeniden sırala", "timeline.ungroup": "Özellik klasörünü kaldır", "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.", "toolbar.image": "RESİM", "toolbar.render": "RENDER", "toolbar.render_scene": "SAHNE", "ui.1_degree_of_freedom": "1 serbestlik derecesi", "ui.3d_mouse_speed": "3D fare hızı", - "ui.3d_mouse_windows": "3D fare (Windows)", "ui.3d_sketch": "3D eskiz", - "ui.about": "Hakkında", "ui.activate": "Etkinleştir", "ui.active_component": "Etkin bileşen", "ui.agent_chat": "Ajan sohbeti", @@ -822,6 +902,7 @@ "ui.agent_chat_left": "Sol", "ui.agent_chat_limit": "Ajan, bir mesaj için {limit} değişiklik sınırına ulaştı. Devam etmesi için başka bir mesaj gönderin.", "ui.agent_chat_minimize": "Bir çubuğa küçült", + "ui.agent_chat_next_picture": "Sonraki resim", "ui.agent_chat_no_cli": "solidcraft-cli uygulamanın yanında bulunamadı, bu yüzden ajan tasarıma erişemiyor.", "ui.agent_chat_no_control": "Ajan sohbetinin uç noktası başlatılmadı, bu yüzden ajan tasarıma erişemiyor.", "ui.agent_chat_no_images": "Bu ajan resim kabul etmiyor", @@ -832,6 +913,7 @@ "ui.agent_chat_permission": "Ajan şunu çalıştırmak istiyor: {title}", "ui.agent_chat_pick": "Ajan", "ui.agent_chat_placeholder": "Ajandan tasarımı değiştirmesini isteyin…", + "ui.agent_chat_previous_picture": "Önceki resim", "ui.agent_chat_privacy": "Kendi hesabınızla çalıştırdığınız ajan dışında hiçbir yere bir şey gitmez.", "ui.agent_chat_privacy_note": "SolidCraft hiçbir yere bir şey göndermez: Başlattığınız ajan, sizin hesabınızla kendi hizmetine bağlanır. SolidCraft hiçbir anahtarı veya parolayı görmez ve kaydetmez.", "ui.agent_chat_refresh": "Yenile", @@ -876,6 +958,7 @@ "ui.ai_bad_url": "http:// veya https:// ile başlayan bir URL girin", "ui.ai_base_url": "Temel URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "Kopya", "ui.ai_custom_url": "Özel URL", "ui.ai_default": "Varsayılan", @@ -896,8 +979,10 @@ "ui.ai_mode_edits_note": "Modelleme komutları onay istemeden çalışır; kaydetme, açma, dışa aktarma ve diğer dosya ya da yıkıcı işlemler için yine onay istenir.", "ui.ai_mode_global": "Yukarıda ayarlandığı gibi", "ui.ai_model": "Model", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Bu ayarlar SolidCraft araçları için geçerlidir. Bir ajanın kendi araçları (kabuğu, dosya değişiklikleri), ajanın modları varsa sohbet panelinin başlığında seçtiğiniz kendi modunu izler.", "ui.ai_name": "Ad", + "ui.ai_name_hint": "Ajanım", "ui.ai_need_command": "Ajanı başlatan komutu girin", "ui.ai_need_model": "Model adını girin", "ui.ai_no_answer": "Ajan zamanında yanıt vermedi", @@ -928,6 +1013,7 @@ "ui.ai_toolbar_button": "Araç çubuğunda düğmeyi göster", "ui.ai_toolbar_button_note": "Araç çubuğunun sağ ucundaki AI düğmesi. Ctrl+Shift+A ve Görünüm menüsü paneli açmaya devam eder.", "ui.ai_undo_tip": "Ajanın bu mesaj için değiştirdiği her şeyi tek adımda geri al", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Tümü", "ui.analysis": "Analiz", "ui.angle": "Açı", @@ -939,7 +1025,6 @@ "ui.anti_aliasing": "Kenar yumuşatma", "ui.apply": "Uygula", "ui.arc": "Yay", - "ui.assemble": "Montaj", "ui.autosave_every": "Otomatik kaydetme aralığı", "ui.axes": "Eksenler", "ui.axis": "Eksen", @@ -949,6 +1034,11 @@ "ui.bodies": "Gövdeler", "ui.box": "Kutu", "ui.break": "Kır", + "ui.browser_bodies_one": "{count} gövde", + "ui.browser_bodies_other": "{count} gövde", + "ui.browser_collapse": "Tarayıcıyı daralt", + "ui.browser_expand": "Tarayıcıyı genişlet", + "ui.browser_title": "Tarayıcı", "ui.calibrate": "Kalibre et", "ui.cancel": "İptal", "ui.canvases": "Tuvaller", @@ -958,7 +1048,6 @@ "ui.chamfer": "Pah", "ui.chamfer_type": "Pah türü", "ui.change_workspace": "Çalışma alanını değiştir", - "ui.chord_length": "Kiriş uzunluğu", "ui.circle": "Daire", "ui.circular_pattern": "Dairesel desen", "ui.clear": "Temizle", @@ -991,11 +1080,9 @@ "ui.command_done": "{command} tamamlandı", "ui.command_palette_hint": "Bir komut yazın (ör. {example}) — veya aramak için S tuşuna basın", "ui.concentric": "Eş merkezli", - "ui.configurations": "Konfigürasyonlar", "ui.conic_curve": "Konik eğri", "ui.constant": "Sabit", "ui.constraints_conflict": "Kısıtlamalar çakışıyor", - "ui.construct": "Yapı", "ui.construction": "Yapı çizgisi", "ui.context_menu": "Bağlam menüsü", "ui.continuity_gap": "Boşluk", @@ -1018,7 +1105,6 @@ "ui.cylinder": "Silindir", "ui.dark": "Koyu", "ui.default_modelling_orientation": "Varsayılan modelleme yönü", - "ui.degrees_of_freedom": "Serbestlik dereceleri", "ui.delete": "Sil", "ui.depth": "Derinlik", "ui.design": "Tasarım", @@ -1060,7 +1146,6 @@ "ui.equivalent_radius": "Eşdeğer yarıçap", "ui.expand": "Genişlet", "ui.exportDxf": "DXF olarak dışa aktar…", - "ui.export_ellipsis": "Dışa aktar...", "ui.export_ellipsis_keymap": "Dışa aktar…", "ui.export_keymap_tip": "Tuşları, S kutusu favorilerini ve araç çubuğunu dosya olarak dışa aktarır", "ui.extend": "Uzat", @@ -1141,7 +1226,6 @@ "ui.heading_sheet_metal": "SAC METAL", "ui.heading_shell": "KABUK", "ui.heading_sketch": "ESKİZ", - "ui.heading_sketch_palette": "ESKİZ PALETİ", "ui.heading_sketch_scale": "ESKİZİ ÖLÇEKLENDİR", "ui.heading_solid": "KATI", "ui.heading_sphere": "KÜRE", @@ -1160,6 +1244,7 @@ "ui.hideOrigin": "Orijini gizle", "ui.hidePlanes": "Düzlemleri gizle", "ui.hideProfile": "Profili gizle", + "ui.history_title": "Geçmiş", "ui.hole": "Delik", "ui.hole_tap_type": "Diş türü", "ui.hole_type": "Delik türü", @@ -1178,7 +1263,6 @@ "ui.length_decimals": "Uzunluk ondalık basamakları", "ui.light": "Açık", "ui.line": "Çizgi", - "ui.linetype": "Çizgi türü", "ui.lock": "Kilitle", "ui.loft": "Loft", "ui.look_at": "Bak", @@ -1210,10 +1294,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "Yüz etiketli 3MF", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Orta nokta", "ui.mirror": "Aynala", "ui.mirror_line": "Ayna çizgisi", - "ui.modify": "Değiştir", "ui.move": "Taşı", "ui.multiple": "Çoklu", "ui.named_views": "Adlandırılmış görünümler", @@ -1233,7 +1315,6 @@ "ui.offset_plane": "Ofset düzlem", "ui.one_side": "Tek taraflı", "ui.open": "Aç", - "ui.open_ellipsis": "Aç...", "ui.open_preferences": "Ayarları aç", "ui.open_recent": "Son kullanılanlar", "ui.operation": "İşlem", @@ -1242,8 +1323,9 @@ "ui.orbit_around": "Etrafında döndür", "ui.orientation": "Yönlendirme", "ui.origin": "Orijin", - "ui.palette_points": "Noktalar", - "ui.palette_profile": "Profil", + "ui.palette_construction_mode": "Yardımcı çizim modu (X)", + "ui.palette_more": "Diğer", + "ui.palette_sketch_options": "Eskiz seçenekleri", "ui.pan": "Kaydır", "ui.pan_zoom_orbit": "Kaydır, yakınlaştır, döndür", "ui.parallel": "Paralel", @@ -1277,7 +1359,6 @@ "ui.quit": "Çıkış", "ui.radial_context_menu": "Dairesel bağlam menüsü", "ui.radius": "Yarıçap", - "ui.radius_type": "Yarıçap türü", "ui.reachable": "Erişilebilir", "ui.rectangle": "Dikdörtgen", "ui.rectangular_pattern": "Dikdörtgen desen", @@ -1300,7 +1381,6 @@ "ui.right": "Sağ", "ui.save": "Kaydet", "ui.save_as": "Farklı kaydet", - "ui.save_as_ellipsis": "Farklı kaydet...", "ui.save_mesh_no_slicer": "Dilimleyici bulunamadı: “Özel…” seçeneğini seçip dilimleyici programını belirtin veya “Dilimleyiciye gönder” seçeneğini kapatın", "ui.save_mesh_pick_bodies": "Kaydedilecek gövdeleri seçin", "ui.save_mesh_temp_failed": "{folder} oluşturulamadı: {error}", @@ -1312,7 +1392,6 @@ "ui.select": "Seç", "ui.selectOther": "Diğerini seç…", "ui.selectTangentChain": "Teğet zinciri seç", - "ui.select_other_hint": "Diğerini seç: İmlecin altındaki her şeyi listelemek için sol tuşu model üzerinde hareket ettirmeden basılı tutun (veya sağ tık › Diğerini seç…); oradaki bir sonraki öğeyi seçmek için aynı noktaya tekrar tıklayın.", "ui.selection": "Seçim", "ui.send": "Gönder", "ui.send_to_slicer": "Dilimleyiciye gönder", @@ -1329,7 +1408,6 @@ "ui.show_constraints": "Kısıtlamalar", "ui.show_construction_geometries": "Yapı geometrileri", "ui.show_dimensions": "Ölçüler", - "ui.show_points": "Noktaları göster", "ui.show_profile": "Profili göster", "ui.show_projected_geometries": "Yansıtılmış geometriler", "ui.simple": "Basit", @@ -1339,7 +1417,6 @@ "ui.sketch_dimension": "Eskiz ölçüsü", "ui.sketch_grid": "Eskiz ızgarası", "ui.sketch_scale": "Eskiz ölçeği", - "ui.sketch_status": "Eskiz durumu", "ui.sketches": "Eskizler", "ui.slice": "Dilim", "ui.slicer": "Dilimleyici", @@ -1385,7 +1462,6 @@ "ui.stand_up_tip": "Bir STEP veya IGES dosyası Z yukarı olarak açıldığında X etrafında çeyrek tur döndürmeyi öner", "ui.start": "Başlangıç", "ui.start_over": "Baştan başla", - "ui.start_radius": "Başlangıç yarıçapı", "ui.supersampling": "Süper örnekleme (SSAA)", "ui.supersampling_adaptive": "Yalnızca görünüm hareketsizken", "ui.supersampling_off": "Kapalı (1×)", @@ -1429,7 +1505,6 @@ "ui.untitled": "Adsız", "ui.up_axis_note": "Görünümlerde (eksen görünümü, Başlangıç, Bak) hangi eksenin yukarıyı gösterdiğini belirler. Tasarımlar değişmez.", "ui.updateToCurrentView": "Geçerli görünüme güncelle", - "ui.variable": "Değişken", "ui.view_centre": "Görünüm merkezi", "ui.visual_shaded_hidden_edges": "Gizli kenarlarla gölgeli", "ui.visual_shaded_with_edges": "Kenarlarla gölgeli", @@ -1445,7 +1520,6 @@ "ui.zoom_to_the_cursor": "İmlece doğru yakınlaştır", "update.available": "SolidCraft {version} kullanılabilir", "update.button": "Güncelle", - "update.button_tip": "SolidCraft {version} kullanılabilir", "update.check_automatically": "Güncellemeleri otomatik denetle", "update.check_now": "Şimdi denetle", "update.checking": "Denetleniyor…", @@ -1476,5 +1550,6 @@ "update.title": "Güncelleme mevcut", "update.up_to_date": "SolidCraft güncel.", "update.update_now": "Şimdi güncelle", - "update.you_have": "Yüklü sürüm: {version}." + "update.you_have": "Yüklü sürüm: {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ seç · Enter / Tab tamamla · Esc kapat" } diff --git a/assets/locales/uk.json b/assets/locales/uk.json index 05a8972d2d..4eea652946 100644 --- a/assets/locales/uk.json +++ b/assets/locales/uk.json @@ -6,10 +6,10 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. Натисніть Submit new issue на GitHub, щоб надіслати звіт.", "A new configuration, a copy of the active one": "Нова конфігурація, копія активної", - "AI": "ШІ", "ARRANGE": "РОЗТАШУВАННЯ", "About SolidCraft": "Про SolidCraft", "Acceleration": "Прискорення", + "Actions": "Дії", "Active": "Активна", "Active rule": "Активне правило", "Add": "Додати", @@ -17,6 +17,7 @@ "Add Mid Radius": "Додати середній радіус", "Add Row": "Додати рядок", "Add condition": "Додати умову", + "Add or edit rule": "Додати або змінити правило", "Against": "Проти", "Alignment": "Вирівнювання", "All Sketch Points": "Усі точки ескізу", @@ -30,6 +31,7 @@ "Applied Radius": "Застосований радіус", "Apply To": "Застосувати до", "Apply to selection": "Застосувати до виділення", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "Спочатку застосуйте вигляд. Експортує призначені вигляди та матеріали, створені в цьому проєкті.", "Arc length": "Довжина дуги", "Area": "Площа", "Attachment": "Прив'язка", @@ -60,14 +62,15 @@ "Check collisions": "Перевіряти зіткнення", "Checked": "Перевірено", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Виберіть нову площину. Геометрія та розміри ескізу залишаються незмінними у своїх двовимірних координатах.", + "Choose…": "Вибрати…", "Circumference": "Довжина кола", "Close draft": "Закрити чернетку", "Collisions": "Зіткнення", "Colour": "Колір", + "Comment": "Коментар", "Component1": "Компонент1", "Configuration": "Конфігурація", "Configuration rules": "Правила конфігурації", - "Construction": "Конструкційна", "Contact": "Контакт", "Contact sets": "Набори контактів", "Continuity": "Неперервність", @@ -89,11 +92,11 @@ "Curvature (G2)": "Кривина (G2)", "Curvature change (G3)": "Зміна кривини (G3)", "Customize Toolbar": "Налаштувати панель інструментів", + "Delete": "Видалити", "Delete all": "Видалити все", "Delete rule": "Видалити правило", "Delete the row": "Видалити рядок", "Delete the set": "Видалити набір", - "Delete this parameter": "Видалити цей параметр", "Density": "Густина", "Designs you open or save appear here.": "Проєкти, які ви відкриваєте чи зберігаєте, з'являються тут.", "Determined by To Object. Choose Angle to edit the angular extent.": "Визначається об'єктом призначення. Виберіть «Кут», щоб редагувати кутову протяжність.", @@ -106,16 +109,20 @@ "Drive Joint": "Керувати з'єднанням", "Duration": "Тривалість", "Edges": "Ребра", + "Edit": "Змінити", "Edit Profile Sketch": "Редагувати ескіз профілю", "Enable rule": "Увімкнути правило", "Enabled": "Увімкнено", "Explode": "Рознести", "Explode Text": "Розібрати текст", "Export Animation…": "Експортувати анімацію…", + "Export CSV…": "Експорт CSV…", "Export Current View as DXF": "Експортувати поточний вид як DXF", "Export Current View as DXF…": "Експортувати поточний вид як DXF…", "Export DXF…": "Експорт DXF…", + "Export JSON…": "Експорт JSON…", "Export hidden edges as dashed lines": "Експортувати приховані ребра як штрихові лінії", + "Expression": "Вираз", "Extend to meet": "Подовжити до перетину", "Extents": "Межі", "FILES": "ФАЙЛИ", @@ -124,11 +131,12 @@ "Face attachment lost": "Втрачено прив'язку до грані", "Faces": "Грані", "Faces / edges / vertices": "Грані / ребра / вершини", + "Favorites": "Обране", "Favourite": "Обране", + "Feature": "Операція", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Файли з багатьох CAD-програм мають напрямок Y вгору, тому тут вони відкриваються лежачи на спині (Z вгору).", "Filter": "Фільтр", "Find in Browser": "Знайти в браузері", - "Flat": "Плоска", "Flat size": "Розмір розгортки", "Follow camera (Look At sketches)": "Стежити за камерою (ескізи наведення)", "Follow the camera (Look At sketches)": "Стежити за камерою (ескізи наведення)", @@ -143,6 +151,7 @@ "Holes (edges on them)": "Отвори (ребра на них)", "Home view": "Головний вид", "IMAGES": "ЗОБРАЖЕННЯ", + "Import…": "Імпорт…", "In This Design": "У цьому проєкті", "In place (surfaces)": "На місці (поверхні)", "In plane": "У площині", @@ -163,7 +172,9 @@ "Length units": "Одиниці довжини", "Letter Spacing": "Міжлітерний інтервал", "Library": "Бібліотека", + "Library file": "Файл бібліотеки", "Line Spacing": "Міжрядковий інтервал", + "Local library": "Локальна бібліотека", "Loop": "Контур", "MANAGE": "КЕРУВАННЯ", "MATERIALS": "МАТЕРІАЛИ", @@ -171,37 +182,45 @@ "MOTION": "РУХ", "Margin": "Відступ", "Mass": "Маса", + "Matched restrictions check the final values.": "Обмеження, умови яких виконано, перевіряють кінцеві значення.", "Material": "Матеріал", "Max": "Макс.", "Mid Radius": "Середній радіус", "Middle double-click": "Подвійне клацання середньою кнопкою", "Min": "Мін.", "Mitre": "Скіс", + "Model Parameters": "Параметри моделі", "Modeled": "Змодельовано", "More toolbar panels": "Більше панелей інструментів", "Motion": "Рух", "Move Type": "Тип переміщення", + "Move down": "Перемістити вниз", "Move earlier": "Перемістити раніше", "Move later": "Перемістити пізніше", + "Move up": "Перемістити вгору", "Name": "Назва", "Nearest": "Найближчі", + "New": "Створити", + "New rule…": "Нове правило…", "New study": "Нове дослідження", "No crash reports.": "Немає звітів про збої.", + "No matching appearance presets.": "Немає відповідних наборів вигляду.", "No matching appearances": "Немає відповідних виглядів", "No visible bodies to export.": "Немає видимих тіл для експорту.", "None": "Немає", - "Normal to sketch": "За нормаллю до ескізу", "Not available yet": "Ще недоступно", "Notch": "Виріз", "OUTPUT": "ВИВЕДЕННЯ", "Opacity": "Непрозорість", "Open GitHub draft": "Відкрити чернетку GitHub", + "Open local libraries in the desktop app. You can export this design's library here.": "Відкривайте локальні бібліотеки в настільному застосунку. Тут можна експортувати бібліотеку цього проєкту.", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Параметричний 3D-CAD з відкритим вихідним кодом на Rust. Ліцензовано за MIT OR Apache-2.0.", + "Operator": "Оператор", "Other side": "Інший бік", "Other surface (mutual trim)": "Інша поверхня (взаємне обрізання)", "Outside": "Назовні", "Overall length": "Загальна довжина", - "Parameter values": "Значення параметрів", + "Parameter": "Параметр", "Pass through": "Через точки", "Path": "Траєкторія", "Per Edge": "На кожне ребро", @@ -219,7 +238,6 @@ "Preview Row": "Рядок перегляду", "Preview leaves the current design unchanged.": "Попередній перегляд не змінює поточний проєкт.", "Profiles/Curves": "Профілі/криві", - "Projection": "Проєкція", "Quantity": "Величина", "RECENT": "НЕДАВНІ", "REFERENCE": "ДОПОМІЖНА ГЕОМЕТРІЯ", @@ -256,6 +274,8 @@ "Roll History Marker Here": "Перемістити маркер історії сюди", "Round": "Круглий", "Rule": "Правило", + "Rule name": "Назва правила", + "Rules run from top to bottom. Matched drives change effective values.": "Правила виконуються згори вниз. Керівні правила, умови яких виконано, змінюють дійсні значення.", "SECTION": "ПЕРЕРІЗ", "SURFACE QUALITY": "ЯКІСТЬ ПОВЕРХНІ", "Save Rule": "Зберегти правило", @@ -264,6 +284,7 @@ "Scale factor": "Коефіцієнт масштабу", "Search": "Пошук", "Search commands": "Пошук команд", + "Search this library": "Пошук у цій бібліотеці", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Вибір іншого: утримуйте ліву кнопку нерухомо на моделі (або клацніть правою › Вибрати інше…), щоб побачити список усього під курсором; клацніть ще раз у тому ж місці, щоб вибрати наступний об'єкт там. Двічі клацніть середньою кнопкою, щоб перейти до головного виду (Налаштування › Навігація).", "Select bodies (a face picks its body)": "Виділяти тіла (грань виділяє своє тіло)", "Select bodies or faces instead": "Натомість виділіть тіла або грані", @@ -320,18 +341,19 @@ "Turn off section analysis before exporting the view.": "Вимкніть аналіз перерізу перед експортом виду.", "Type": "Тип", "UNTRIM": "ЗНЯТИ ОБРІЗАННЯ", + "Unit": "Одиниця", "Updating the preview": "Оновлення попереднього перегляду", "Use active author placement": "Використати активне вихідне розміщення", "Use fixed plane": "Використати фіксовану площину", "Use saved fixed plane": "Використати збережену фіксовану площину", "Use selected face as fixed plane": "Використати вибрану грань як фіксовану площину", + "User Parameters": "Параметри користувача", "VIEW": "ВИД", "VIEWS": "ВИГЛЯДИ", "Value": "Значення", "Velocity": "Швидкість", "Vertices": "Вершини", "View navigator": "Навігатор виду", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Лише видимі тіла. Криві подаються відрізками (допуск 0,01 mm).", "Volume": "Об'єм", "Wall": "Стінка", "Wall Location": "Розташування стінки", @@ -339,6 +361,12 @@ "When": "Коли", "Width": "Ширина", "Will fail": "Зазнає невдачі", + "X angle": "Кут X", + "X distance": "Відстань X", + "Y angle": "Кут Y", + "Y distance": "Відстань Y", + "Z angle": "Кут Z", + "Z distance": "Відстань Z", "angle, height": "кут, висота", "appearance.dielectric_ior": "Діелектричний IOR", "appearance.metallic": "Металевість", @@ -347,7 +375,11 @@ "appearance.roughness": "Шорсткість", "appearance.surface_response": "Відгук поверхні", "browser.body_name": "Тіло{number}", + "browser.capture_many": "Компоненти ({count}) переміщено, але їхні положення не зафіксовано.", + "browser.capture_note": "«Зафіксувати положення» зберігає нове положення; «Відновити положення» повертає попереднє.", + "browser.capture_one": "Компонент переміщено, але його положення не зафіксовано.", "browser.component_name": "Компонент{number}", + "browser.move_title": "Перемістити: {name}", "browser.parameters": "Параметри ({count})", "browser.precision": "Точність: {precision}", "browser.sketch_name": "Ескіз{number}", @@ -678,7 +710,8 @@ "cmd.view_navigate": "Навігація видом", "cmd.view_rename": "Перейменувати іменований вид", "cmd.view_save": "Новий іменований вид", - "command name": "назва команди", + "contact.components_count": "Компонентів: {count}", + "contact.enabled_label": "увімкнено", "coupling.gear_1": "Шестерня 1", "coupling.gear_1_pick": "Шестерня 1 (необов'язково)", "coupling.gear_2": "Шестерня 2", @@ -695,6 +728,7 @@ "coupling.rack": "Рейка", "coupling.radius_1": "Радіус 1", "coupling.radius_2": "Радіус 2", + "delete.and_more": "… і ще {count}", "dialog.bodies_count": "Тіл: {count}", "dialog.clearance_invalid": "зазор має бути довжиною 0 або більше", "dialog.compute": "Обчислити", @@ -702,6 +736,8 @@ "dialog.computing": "Обчислення… {seconds} с", "dialog.could_not_be_checked": "не вдалося перевірити", "dialog.custom_axis": "↖ Власна вісь", + "dialog.dimension_insert_hint": "Двічі клацніть інший керівний розмір, щоб вставити його назву", + "dialog.dimension_name": "Розмір: {name}", "dialog.edit_title": "РЕДАГУВАННЯ {title}", "dialog.select": "↖ Вибрати", "dialog.selected_count": "↖ Вибрано: {count}", @@ -725,9 +761,17 @@ "dxf.progress.write": "Запис DXF (5/5)", "dxf.tolerance": "Допуск кривої", "dxf.tolerance_help": "Лише видимі тіла. Криві подаються відрізками. Менший допуск дає точніші криві, але потребує більше часу й пам'яті.", - "expression, e.g. 25 mm": "вираз, напр. 25 mm", "fits the face, e.g. M8": "відповідає грані, напр. M8", "from the rule": "з правила", + "help.apache": "Ліцензія Apache 2.0", + "help.attributions": "Відомості про авторство (ATTRIBUTION.md)", + "help.build": "Версія {version} · коміт {commit} · зібрано {date}", + "help.font_details": "Інтерфейс використовує вбудовані шрифти egui (Ubuntu Light і Hack під ліцензіями Ubuntu Font Licence та MIT/Bitstream Vera; Noto Emoji та шрифт піктограм емодзі під ліцензіями OFL і MIT). Для тексту ескізів використовується Liberation Sans (SIL OFL 1.1). Необов’язковий пакет шрифтів The Crafting Apps, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1), не входить до збірок SolidCraft.", + "help.fonts": "Шрифти", + "help.licences": "Ліцензії", + "help.mit": "Ліцензія MIT", + "help.notice": "NOTICE", + "help.third_party": "Ліцензії сторонніх компонентів", "home.documentation": "Документація", "home.in_this_browser": "у цьому браузері", "home.missing": "відсутній", @@ -756,10 +800,30 @@ "joint.from_bodies_hint": "Кожне вибране тіло переходить у власний новий компонент і лишається на місці.", "joint.one_body": "Обидва вибори на {body}. Виберіть другий на іншій деталі.", "joint.one_component": "Ці деталі - тіла одного компонента: {bodies}. З'єднання рухає один компонент відносно іншого, тому кожній деталі потрібен власний компонент.", + "keymap.command_id": "ID команди", + "keymap.empty": "Немає відповідних команд", + "keymap.use_anyway": "Усе одно використовувати тут {key}", + "manage.appearance_hint": "Виберіть вигляд для виділених об’єктів або перетягніть зразок на тіло чи грань.", + "manage.assign_hint": "Виберіть тіла та їхнє правило проєктування пластикових деталей.", + "manage.config_hint": "Змініть рядки, стовпці та правила конфігурації, а потім активуйте рядок або перегляньте його.", + "manage.empty": "Немає відповідних рядків.", + "manage.material_hint": "Виберіть тіла та їхній фізичний матеріал.", + "manage.parameters_hint": "Змініть вирази й одиниці. Зміни застосовуються після натискання Enter або виходу з поля.", + "manage.rules_hint": "Виберіть правило для редагування або створіть нове. Підтвердьте, щоб зберегти правило.", + "measure.at": "У точці", + "measure.item": "Елемент {number}", "motion.checked_to": "Перевірено лише до кроку {step}: перевірка вичерпала свій бюджет роботи", "motion.collision": "{a} · {b}: кроки {steps}", + "motion.delete_key": "Видалити ключовий кадр", "motion.duration_hint": "секунди (порожньо: на крок)", + "motion.key_here": "Вставити ключовий кадр тут", + "motion.key_here_hint": "Додати ключовий кадр на цьому кроці з поточним значенням з’єднання", "motion.no_collisions": "За все дослідження зіткнень немає", + "motion.step_back": "Крок назад", + "motion.step_forward": "Крок уперед", + "motion.to_end": "У кінець", + "motion.to_start": "На початок", + "motion.track_name": "Доріжка {number}", "nav.touchpad.help": "Утримуйте Shift і рухайте вказівник, щоб панорамувати; утримуйте Alt/Option і рухайте, щоб обертати. Кнопка миші не потрібна. Вертикальна прокрутка масштабує.", "nav.touchpad.label": "Трекпад", "nav.touchpad.orbit": "Обертання (утримуйте Alt/Option і рухайте; без кнопки миші)", @@ -768,13 +832,13 @@ "nav.touchpad.zoom": "Масштаб (вертикальна прокрутка або стискання)", "needs two joints": "потрібні два з'єднання", "new configuration": "нова конфігурація", - "new name": "нова назва", "new parameter": "новий параметр", "no hole": "без отвору", "no interference": "немає перетинів", "no joint that moves yet": "ще немає рухомого з'єднання", "no picture": "немає зображення", "none": "немає", + "parts.automatic_name": "автоматично", "pattern.alignment_2": "Вирівнювання 2", "pattern.alignment_hint": "Кількість включає оригінал. Центрування розподіляє копії з обох боків; за парної кількості зміна напрямку визначає бік із додатковою копією.", "pattern.direction_1": "Напрямок 1", @@ -782,12 +846,13 @@ "pattern.flip_direction": "Змінити напрямок", "pick a hole to seat it, or OK places it at the origin": "виберіть отвір для посадки, або OK розмістить його в початку координат", "pick one or two vertices, edges or faces": "виберіть одну-дві вершини, ребра або грані", + "prefs.cpu": "ЦП (програмний)", + "prefs.minutes_suffix": " min", + "properties.title": "Властивості: {name}", "render.background": "Фон сцени", "render.fit": "Вписати виріб", - "render.image_cancel": "Скасувати рендеринг", "render.image_cancelled": "Рендеринг скасовано", "render.image_cancelling": "Скасування", - "render.image_check": "Перевірити запит на рендеринг", "render.image_completed": "Зображення готове до експорту", "render.image_encoding": "Кодування PNG", "render.image_export": "Експортувати PNG", @@ -844,17 +909,30 @@ "settings.reset_intro": "Ці налаштування не працюють на цьому комп'ютері й не давали б SolidCraft запуститися, тому їх змінено:", "settings.reset_title": "Налаштування було змінено", "settings.supersampling_note": "Малює вид у вищій роздільності й потім зменшує його: плавніші ребра, лінії та сітка на екранах до 4K. Це коштує часу GPU: приблизно 2,25× пікселів за 1,5× і 4× за 2×.", + "shortcut.json_error": "параметри мають бути у форматі JSON: {error}", + "shortcut.no_command": "команду `{id}` не знайдено; текст перед JSON має бути ID команди", + "shortcut.pin": "Закріпити вгорі поля S", + "shortcut.unpin": "Відкріпити", "sketch.degrees_of_freedom": "{count} ступенів вільності", + "sketch.dof_left": "Залишилося ступенів свободи: {count}", + "sketch.dof_left_one": "Залишився {count} ступінь свободи", "sketch.draw_curves": "Малювати як {linetype}", "sketch.make_selection": "Перетворити виділене на {linetype}", "sketch.open_ends": "Відкритих: {count}", "sketch.untied_ends": "Незв'язаних: {count}", + "sketch_panel.attached": "Прив’язано до {body} / {path}", + "sketch_panel.author": "Початкове розміщення: {path}", + "sketch_panel.dimension_hint": "Введіть значення або вираз, потім натисніть OK.", + "sketch_panel.move_hint": "Введіть відстані та кут, потім натисніть OK.", + "sketch_panel.redefine_title": "{command}: {name}", + "sketch_panel.sheet_attachment": "Прив’язано до плоскої грані листового тіла; слідує за цією панеллю під час зміни листа.", + "sketch_panel.text_hint": "Змініть текст і його розміщення, потім натисніть OK.", + "sketch_panel.use_plane": "Використати площину {plane}", "sketch_text.align_center": "По центру", "sketch_text.align_left": "Ліворуч", "sketch_text.align_right": "Праворуч", "sketch_text.bold": "Жирний", "sketch_text.builtin_font": "{family} (вбудований)", - "sketch_text.font_search": "Пошук шрифтів", "sketch_text.italic": "Курсив", "sketch_text.loading_fonts": "Пошук встановлених шрифтів…", "sketch_text.on_path": "Текст дотримується своєї траєкторії: крива його розміщує", @@ -875,6 +953,7 @@ "timeline.ungroup": "Прибрати теки операцій", "to the face": "до грані", "to the next face": "до наступної грані", + "toolbar.customize_note": "Вкладка {tab}. Позначте команду, щоб додати її кнопку на панель інструментів. Також можна перетягнути рядок зі спадного меню панелі на панель інструментів, перетягнути кнопку за межі панелі для видалення або скористатися значком закріплення в рядку спадного меню.", "toolbar.image": "ЗОБРАЖЕННЯ", "toolbar.render": "РЕНДЕР", "toolbar.render_scene": "СЦЕНА", @@ -883,9 +962,7 @@ "trace.stopped_by_loop": "Закінчується на відліку {sample}: контур через {joint} далі не замикається", "ui.1_degree_of_freedom": "1 ступінь вільності", "ui.3d_mouse_speed": "Швидкість 3D-миші", - "ui.3d_mouse_windows": "3D-миша", "ui.3d_sketch": "3D-ескіз", - "ui.about": "Про програму", "ui.activate": "Активувати", "ui.active_component": "Активний компонент", "ui.agent_chat": "Чат з агентом", @@ -951,7 +1028,7 @@ "ui.agent_chat_model_tip": "Модель, яку використовує агент. Ваш вибір запам'ятовується для цього агента.", "ui.agent_chat_new": "Новий чат", "ui.agent_chat_new_confirm": "Почати новий чат? Поточний буде очищено.", - "ui.agent_chat_new_yes": "Новий чат", + "ui.agent_chat_next_picture": "Наступне зображення", "ui.agent_chat_no_cli": "solidcraft-cli не знайдено поруч із програмою, тож агент не може дістатися до проєкту.", "ui.agent_chat_no_control": "Кінцева точка чату з агентом не запустилася, тож агент не може дістатися до проєкту.", "ui.agent_chat_no_images": "Цей агент не приймає зображень", @@ -967,6 +1044,7 @@ "ui.agent_chat_placeholder": "Попросіть агента змінити проєкт…", "ui.agent_chat_placeholder_busy": "Поставте повідомлення в чергу, доки агент завершить (Ctrl+Enter: надіслати зараз)", "ui.agent_chat_pop_out": "Відкрити у власному вікні, яке можна перемістити на інший екран", + "ui.agent_chat_previous_picture": "Попереднє зображення", "ui.agent_chat_privacy": "Нічого нікуди не надсилається, крім агента, який ви запускаєте, під вашим власним обліковим записом.", "ui.agent_chat_privacy_note": "SolidCraft нічого нікуди не надсилає: агент, який ви запускаєте, спілкується з власним сервісом під вашим обліковим записом. SolidCraft ніколи не бачить і не зберігає ключ чи пароль.", "ui.agent_chat_queue": "У чергу", @@ -1022,6 +1100,7 @@ "ui.ai_bad_url": "Введіть URL-адресу, що починається з http:// або https://", "ui.ai_base_url": "Базова URL-адреса", "ui.ai_button": "ШІ", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "копія", "ui.ai_custom_url": "Власна URL-адреса", "ui.ai_default": "Типовий", @@ -1042,8 +1121,10 @@ "ui.ai_mode_edits_note": "Команди моделювання виконуються без запиту; збереження, відкриття, експорт та інші файлові чи руйнівні дії все ще запитують.", "ui.ai_mode_global": "Як задано вище", "ui.ai_model": "Модель", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "Ці налаштування стосуються інструментів SolidCraft. Власні інструменти агента (його оболонка, його редагування файлів) підкоряються власному режиму агента, який ви вибираєте в заголовку панелі чату, коли агент має режими.", "ui.ai_name": "Назва", + "ui.ai_name_hint": "Мій агент", "ui.ai_need_command": "Введіть команду, що запускає агента", "ui.ai_need_model": "Введіть назву моделі", "ui.ai_no_answer": "Агент не відповів вчасно", @@ -1074,6 +1155,7 @@ "ui.ai_toolbar_button": "Показувати кнопку на панелі інструментів", "ui.ai_toolbar_button_note": "Кнопка ШІ в правому кінці панелі інструментів. Ctrl+Shift+A і меню «Вид» усе одно відкривають панель.", "ui.ai_undo_tip": "Скасувати все, що агент змінив для цього повідомлення, одним кроком", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "Усі", "ui.ambient_occlusion": "Окружне затінення", "ui.ambient_occlusion_help": "М'яке затінення в місцях, де стикаються непрозорі поверхні. Потребує вікна перегляду на GPU.", @@ -1087,7 +1169,6 @@ "ui.anti_aliasing": "Згладжування", "ui.apply": "Застосувати", "ui.arc": "Дуга", - "ui.assemble": "Скласти", "ui.autosave_every": "Автозбереження кожні", "ui.axes": "Осі", "ui.axis": "Вісь", @@ -1098,6 +1179,11 @@ "ui.bodies_all_if_none": "Тіла (усі, якщо не вибрано)", "ui.box": "Куб", "ui.break": "Розірвати", + "ui.browser_bodies_one": "{count} тіло", + "ui.browser_bodies_other": "{count} тіл", + "ui.browser_collapse": "Згорнути браузер", + "ui.browser_expand": "Розгорнути браузер", + "ui.browser_title": "Браузер", "ui.calibrate": "Калібрувати", "ui.cancel": "Скасувати", "ui.canvases": "Полотна", @@ -1107,7 +1193,6 @@ "ui.chamfer": "Фаска", "ui.chamfer_type": "Тип фаски", "ui.change_workspace": "Змінити робочу область", - "ui.chord_length": "Довжина хорди", "ui.circle": "Коло", "ui.circular_pattern": "Круговий масив", "ui.clear": "Очистити", @@ -1140,11 +1225,9 @@ "ui.command_done": "{command} виконано", "ui.command_palette_hint": "Введіть команду, напр. {example} — або натисніть S для пошуку", "ui.concentric": "Концентричність", - "ui.configurations": "Конфігурації", "ui.conic_curve": "Конічна крива", "ui.constant": "Стала", "ui.constraints_conflict": "Конфлікт обмежень", - "ui.construct": "Побудувати", "ui.construction": "Конструкційна", "ui.context_menu": "Контекстне меню", "ui.continuity_g0": "G0 злам", @@ -1170,7 +1253,6 @@ "ui.cylinder": "Циліндр", "ui.dark": "Темна", "ui.default_modelling_orientation": "Типова орієнтація моделювання", - "ui.degrees_of_freedom": "ступенів вільності", "ui.delete": "Видалити", "ui.depth": "Глибина", "ui.design": "Проєкт", @@ -1212,7 +1294,6 @@ "ui.equivalent_radius": "Еквівалентний радіус", "ui.expand": "Розгорнути", "ui.exportDxf": "Експорт DXF…", - "ui.export_ellipsis": "Експорт...", "ui.export_ellipsis_keymap": "Експорт…", "ui.export_keymap_tip": "Клавіші, обрані в полі S і панель інструментів як файл", "ui.extend": "Подовжити", @@ -1302,7 +1383,6 @@ "ui.heading_sheet_metal": "ЛИСТОВИЙ МЕТАЛ", "ui.heading_shell": "ОБОЛОНКА", "ui.heading_sketch": "ЕСКІЗ", - "ui.heading_sketch_palette": "ПАЛІТРА ЕСКІЗУ", "ui.heading_sketch_scale": "МАСШТАБ ЕСКІЗУ", "ui.heading_solid": "ТІЛО", "ui.heading_sphere": "КУЛЯ", @@ -1321,6 +1401,7 @@ "ui.hideOrigin": "Приховати початок координат", "ui.hidePlanes": "Приховати площини", "ui.hideProfile": "Приховати профіль", + "ui.history_title": "Історія", "ui.hole": "Отвір", "ui.hole_tap_type": "Тип різьби отвору", "ui.hole_type": "Тип отвору", @@ -1340,7 +1421,6 @@ "ui.length_decimals": "Знаки після коми для довжин", "ui.light": "Світла", "ui.line": "Пряма", - "ui.linetype": "Тип лінії", "ui.lock": "Заблокувати", "ui.loft": "Лофт", "ui.look_at": "Наведення", @@ -1372,10 +1452,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF з мітками граней", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Середня точка", "ui.mirror": "Дзеркало", "ui.mirror_line": "Вісь дзеркала", - "ui.modify": "Змінити", "ui.move": "Перемістити", "ui.multiple": "Кілька", "ui.named_views": "Іменовані види", @@ -1395,7 +1473,6 @@ "ui.offset_plane": "Зсунута площина", "ui.one_side": "Один бік", "ui.open": "Відкрити", - "ui.open_ellipsis": "Відкрити...", "ui.open_ends_tip": "Нічого не торкається", "ui.open_preferences": "Відкрити налаштування", "ui.open_recent": "Відкрити недавні", @@ -1405,8 +1482,9 @@ "ui.orbit_around": "Обертати навколо", "ui.orientation": "Орієнтація", "ui.origin": "Початок координат", - "ui.palette_points": "Точки", - "ui.palette_profile": "Профіль", + "ui.palette_construction_mode": "Допоміжний режим (X)", + "ui.palette_more": "Більше", + "ui.palette_sketch_options": "Параметри ескізу", "ui.pan": "Панорамувати", "ui.pan_zoom_orbit": "Панорамування, масштаб, обертання", "ui.parallel": "Паралельність", @@ -1440,7 +1518,6 @@ "ui.quit": "Вихід", "ui.radial_context_menu": "Кругове контекстне меню", "ui.radius": "Радіус", - "ui.radius_type": "Тип радіуса", "ui.rail": "Напрямна", "ui.rail_fit": "Підгонка за напрямною", "ui.reachable": "Досяжні", @@ -1467,7 +1544,6 @@ "ui.right": "Справа", "ui.save": "Зберегти", "ui.save_as": "Зберегти як", - "ui.save_as_ellipsis": "Зберегти як...", "ui.save_mesh_no_slicer": "Слайсер не знайдено: виберіть «Власна…» і вкажіть свою програму-слайсер або вимкніть «Надіслати в слайсер»", "ui.save_mesh_pick_bodies": "Виберіть тіла для збереження", "ui.save_mesh_temp_failed": "Не вдалося створити {folder}: {error}", @@ -1480,7 +1556,6 @@ "ui.select": "Вибрати", "ui.selectOther": "Вибрати інше…", "ui.selectTangentChain": "Вибрати дотичний ланцюг", - "ui.select_other_hint": "Вибір іншого: утримуйте ліву кнопку нерухомо на моделі (або клацніть правою › Вибрати інше…), щоб побачити список усього під курсором; клацніть ще раз у тому ж місці, щоб вибрати наступний об'єкт там.", "ui.selection": "Виділення", "ui.send": "Надіслати", "ui.send_to_slicer": "Надіслати в слайсер", @@ -1497,7 +1572,6 @@ "ui.show_constraints": "Обмеження", "ui.show_construction_geometries": "Конструкційна геометрія", "ui.show_dimensions": "Розміри", - "ui.show_points": "Показати точки", "ui.show_profile": "Показати профіль", "ui.show_projected_geometries": "Проєкційна геометрія", "ui.simple": "Простий", @@ -1507,7 +1581,6 @@ "ui.sketch_dimension": "Розмір ескізу", "ui.sketch_grid": "Сітка ескізу", "ui.sketch_scale": "Масштаб ескізу", - "ui.sketch_status": "Стан ескізу", "ui.sketches": "Ескізи", "ui.slice": "Розріз", "ui.slicer": "Слайсер", @@ -1553,7 +1626,6 @@ "ui.stand_up_tip": "Після відкриття файлу STEP або IGES з напрямком Z вгору пропонувати поворот на чверть оберту навколо X", "ui.start": "Початок", "ui.start_over": "Почати спочатку", - "ui.start_radius": "Початковий радіус", "ui.stretch": "Розтягнути", "ui.supersampling": "Наддискретизація (SSAA)", "ui.supersampling_adaptive": "Лише коли вид нерухомий", @@ -1601,7 +1673,6 @@ "ui.untitled": "Без назви", "ui.up_axis_note": "Яка вісь спрямована вгору у видах (вид осі, головний, наведення). Проєкти не змінюються.", "ui.updateToCurrentView": "Оновити до поточного виду", - "ui.variable": "Змінна", "ui.view_centre": "Центр виду", "ui.visual_shaded_hidden_edges": "Затінений із прихованими ребрами", "ui.visual_shaded_with_edges": "Затінений із ребрами", @@ -1617,7 +1688,6 @@ "ui.zoom_to_the_cursor": "Масштабувати до курсора", "update.available": "Доступна версія SolidCraft {version}", "update.button": "Оновити", - "update.button_tip": "Доступна версія SolidCraft {version}", "update.button_version": "Оновити до {version}", "update.check_automatically": "Автоматично перевіряти оновлення", "update.check_now": "Перевірити зараз", @@ -1649,5 +1719,6 @@ "update.title": "Доступне оновлення", "update.up_to_date": "SolidCraft оновлено.", "update.update_now": "Оновити зараз", - "update.you_have": "У вас версія {version}." + "update.you_have": "У вас версія {version}.", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ вибрати · Enter / Tab доповнити · Esc закрити" } diff --git a/assets/locales/vi.json b/assets/locales/vi.json index 623e5e3431..0f96cd0db6 100644 --- a/assets/locales/vi.json +++ b/assets/locales/vi.json @@ -5,7 +5,6 @@ "2. Attach your screenshot: copy and paste it, or save and upload the PNG.": "2. Đính kèm ảnh chụp màn hình: sao chép và dán, hoặc lưu và tải lên tệp PNG.", "3. Click Submit new issue on GitHub to send your report.": "3. Nhấp vào 'Submit new issue' trên GitHub để gửi báo cáo.", "A new configuration, a copy of the active one": "Cấu hình mới, sao chép từ cấu hình hiện tại", - "AI": "AI", "About SolidCraft": "Giới thiệu SolidCraft", "Acceleration": "Gia tốc", "Active": "Đang kích hoạt", @@ -59,7 +58,6 @@ "Component1": "Cụm chi tiết 1", "Configuration": "Cấu hình", "Configuration rules": "Quy tắc cấu hình", - "Construction": "Đường dựng hình (nét đứt)", "Contact": "Tiếp xúc", "Contact sets": "Tập hợp tiếp xúc", "Continuity": "Độ liên tục", @@ -84,7 +82,6 @@ "Delete rule": "Xóa quy tắc", "Delete the row": "Xóa hàng", "Delete the set": "Xóa tập hợp", - "Delete this parameter": "Xóa tham số này", "Density": "Khối lượng riêng", "Designs you open or save appear here.": "Các thiết kế bạn mở hoặc lưu sẽ xuất hiện tại đây.", "Determined by To Object. Choose Angle to edit the angular extent.": "Được xác định theo Đến đối tượng (To Object). Chọn Góc để chỉnh sửa góc mở rộng.", @@ -116,7 +113,6 @@ "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Tệp từ nhiều phần mềm CAD có trục Y hướng lên, nên khi mở ở đây (trục Z hướng lên) mô hình sẽ nằm ngang.", "Filter": "Bộ lọc", "Find in Browser": "Tìm trong cây đối tượng", - "Flat": "Trải phẳng", "Flat size": "Kích thước phôi phẳng", "Follow camera (Look At sketches)": "Đi theo camera (Nhìn vuông góc phác thảo)", "Follow the camera (Look At sketches)": "Đi theo camera (Nhìn vuông góc phác thảo)", @@ -166,7 +162,6 @@ "No matching appearances": "Không có màu sắc/vật liệu phù hợp", "No visible bodies to export.": "Không có thân khối hiển thị nào để xuất.", "None": "Không có", - "Normal to sketch": "Vuông góc với phác thảo", "Not available yet": "Chưa khả dụng", "Notch": "Rãnh khía chữ V", "Opacity": "Độ mờ đục", @@ -175,7 +170,6 @@ "Other side": "Phía bên kia", "Outside": "Bên ngoài", "Overall length": "Chiều dài tổng thể", - "Parameter values": "Giá trị tham số", "Pass through": "Đi xuyên qua", "Path": "Đường dẫn", "Per Edge": "Theo từng cạnh", @@ -193,7 +187,6 @@ "Preview Row": "Xem trước hàng", "Preview leaves the current design unchanged.": "Xem trước giữ nguyên thiết kế hiện tại không đổi.", "Profiles/Curves": "Biên dạng / Đường cong", - "Projection": "Phép chiếu", "Quantity": "Số lượng", "RECENT": "GẦN ĐÂY", "RULED SURFACE": "MẶT KẺ (RULED SURFACE)", @@ -294,7 +287,6 @@ "Velocity": "Vận tốc", "Vertices": "Các đỉnh", "View navigator": "Khối điều hướng góc nhìn", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "Chỉ các thân khối hiển thị. Đường cong sử dụng các đoạn thẳng (dung sai 0,01 mm).", "Volume": "Thể tích", "Wall": "Thành vách", "Wall Location": "Vị trí thành vách", @@ -640,7 +632,6 @@ "cmd.view_navigate": "Điều hướng góc nhìn", "cmd.view_rename": "Đổi tên góc nhìn đã lưu", "cmd.view_save": "Tạo góc nhìn đã lưu mới", - "command name": "tên lệnh", "coupling.gear_1": "Bánh răng 1", "coupling.gear_1_pick": "Bánh răng 1 (tùy chọn)", "coupling.gear_2": "Bánh răng 2", @@ -687,7 +678,6 @@ "dxf.progress.write": "Đang ghi tệp DXF (5/5)", "dxf.tolerance": "Dung sai đường cong", "dxf.tolerance_help": "Chỉ áp dụng cho các thân khối hiển thị. Đường cong sử dụng các đoạn thẳng. Dung sai nhỏ hơn cho đường cong mịn hơn nhưng tốn nhiều thời gian và bộ nhớ hơn.", - "expression, e.g. 25 mm": "biểu thức, ví dụ: 25 mm", "fits the face, e.g. M8": "vừa khít bề mặt, ví dụ M8", "from the rule": "từ quy tắc", "home.documentation": "Tài liệu hướng dẫn", @@ -730,7 +720,6 @@ "nav.touchpad.zoom": "Thu phóng (cuộn dọc hoặc chụm ngón tay)", "needs two joints": "cần hai khớp nối", "new configuration": "cấu hình mới", - "new name": "tên mới", "new parameter": "tham số mới", "no hole": "không có lỗ", "no interference": "không có giao thoa", @@ -746,10 +735,8 @@ "pick one or two vertices, edges or faces": "chọn một hoặc hai đỉnh, cạnh hoặc bề mặt", "render.background": "Hình nền khung cảnh", "render.fit": "Thu phóng vừa sản phẩm", - "render.image_cancel": "Hủy bỏ Kết xuất hình ảnh", "render.image_cancelled": "Đã hủy kết xuất hình ảnh", "render.image_cancelling": "Đang hủy…", - "render.image_check": "Kiểm tra yêu cầu kết xuất", "render.image_completed": "Hình ảnh đã sẵn sàng để xuất", "render.image_encoding": "Đang mã hóa PNG", "render.image_export": "Xuất tệp PNG", @@ -816,7 +803,6 @@ "sketch_text.align_right": "Phải", "sketch_text.bold": "In đậm", "sketch_text.builtin_font": "{family} (tích hợp sẵn)", - "sketch_text.font_search": "Tìm kiếm phông chữ", "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", @@ -845,9 +831,7 @@ "trace.stopped_by_loop": "Kết thúc tại mẫu {sample}: vòng lặp qua {joint} không thể khép kín thêm được nữa", "ui.1_degree_of_freedom": "1 bậc tự do", "ui.3d_mouse_speed": "Tốc độ chuột 3D", - "ui.3d_mouse_windows": "Chuột 3D", "ui.3d_sketch": "3D Phác thảo", - "ui.about": "Thông tin phiên bản", "ui.activate": "Kích hoạt", "ui.active_component": "Cụm chi tiết đang kích hoạt", "ui.agent_chat": "Trò chuyện với AI Agent", @@ -913,7 +897,6 @@ "ui.agent_chat_model_tip": "Mô hình AI đang sử dụng. Lựa chọn của bạn sẽ được ghi nhớ cho Agent này.", "ui.agent_chat_new": "Cuộc trò chuyện mới", "ui.agent_chat_new_confirm": "Bắt đầu cuộc trò chuyện mới? Cuộc trò chuyện hiện tại sẽ bị xóa.", - "ui.agent_chat_new_yes": "Cuộc trò chuyện mới", "ui.agent_chat_no_cli": "Không tìm thấy solidcraft-cli cạnh ứng dụng, vì vậy AI Agent không thể truy cập thiết kế.", "ui.agent_chat_no_control": "Điểm cuối trò chuyện của tác tử không thể khởi động, vì vậy AI Agent không thể truy cập thiết kế.", "ui.agent_chat_no_images": "AI Agent này không hỗ trợ tiếp nhận hình ảnh", @@ -1049,7 +1032,6 @@ "ui.anti_aliasing": "Khử răng cưa", "ui.apply": "Áp dụng", "ui.arc": "Cung tròn", - "ui.assemble": "Lắp ráp", "ui.autosave_every": "Tự động lưu sau mỗi", "ui.axes": "Các trục", "ui.axis": "Trục", @@ -1068,7 +1050,6 @@ "ui.chamfer": "Vát mép", "ui.chamfer_type": "Kiểu vát mép", "ui.change_workspace": "Đổi không gian làm việc", - "ui.chord_length": "Chiều dài dây cung", "ui.circle": "Hình tròn", "ui.circular_pattern": "Mảng tròn quanh trục", "ui.clear": "Xóa", @@ -1101,11 +1082,9 @@ "ui.command_done": "Hoàn tất lệnh {command}", "ui.command_palette_hint": "Nhập một lệnh, ví dụ: {example} — hoặc nhấn S để tìm kiếm", "ui.concentric": "Đồng tâm", - "ui.configurations": "Các cấu hình", "ui.conic_curve": "Đường cong Conic", "ui.constant": "Hằng số", "ui.constraints_conflict": "Xung đột ràng buộc", - "ui.construct": "Dựng hình", "ui.construction": "Đường gióng dựng hình", "ui.context_menu": "Menu ngữ cảnh", "ui.continuity_g0": "Nếp gập G0", @@ -1131,7 +1110,6 @@ "ui.cylinder": "Hình trụ", "ui.dark": "Tối", "ui.default_modelling_orientation": "Hướng mô hình hóa mặc định", - "ui.degrees_of_freedom": "bậc tự do", "ui.delete": "Xóa", "ui.depth": "Độ sâu", "ui.design": "Thiết kế", @@ -1173,7 +1151,6 @@ "ui.equivalent_radius": "Bán kính tương đương", "ui.expand": "Mở rộng", "ui.exportDxf": "Xuất tệp DXF…", - "ui.export_ellipsis": "Xuất tệp...", "ui.export_ellipsis_keymap": "Xuất tệp…", "ui.export_keymap_tip": "Xuất phím tắt, danh sách S box yêu thích và thanh công cụ ra tệp", "ui.extend": "Kéo dài đường", @@ -1263,7 +1240,6 @@ "ui.heading_sheet_metal": "KIM LOẠI TẤM", "ui.heading_shell": "TẠO VỎ MỎNG", "ui.heading_sketch": "PHÁC THẢO", - "ui.heading_sketch_palette": "BẢNG ĐIỀU KHIỂN PHÁC THẢO", "ui.heading_sketch_scale": "TỶ LỆ PHÁC THẢO", "ui.heading_solid": "KHỐI ĐẶC", "ui.heading_sphere": "HÌNH CẦU", @@ -1301,7 +1277,6 @@ "ui.length_decimals": "Số chữ số thập phân cho chiều dài", "ui.light": "Sáng", "ui.line": "Đoạn thẳng", - "ui.linetype": "Kiểu đường nét", "ui.lock": "Khóa", "ui.loft": "Vuốt nối biên dạng (Loft)", "ui.look_at": "Nhìn trực diện", @@ -1333,10 +1308,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "3MF kèm thẻ bề mặt", "ui.mesh_format_stl": "STL", - "ui.midpoint": "Trung điểm", "ui.mirror": "Lấy đối xứng", "ui.mirror_line": "Đường đối xứng", - "ui.modify": "Hiệu chỉnh", "ui.move": "Di chuyển", "ui.multiple": "Nhiều đối tượng", "ui.named_views": "Các góc nhìn đã đặt tên", @@ -1356,7 +1329,6 @@ "ui.offset_plane": "Mặt phẳng song song cách khoảng (Offset Plane)", "ui.one_side": "Một phía", "ui.open": "Mở tệp", - "ui.open_ellipsis": "Mở tệp...", "ui.open_ends_tip": "Không chạm vào đối tượng nào", "ui.open_preferences": "Mở tệp Tùy chọn cài đặt", "ui.open_recent": "Mở tệp gần đây", @@ -1366,8 +1338,6 @@ "ui.orbit_around": "Xoay quanh", "ui.orientation": "Định hướng", "ui.origin": "Gốc tọa độ", - "ui.palette_points": "Điểm", - "ui.palette_profile": "Biên dạng", "ui.pan": "Dời góc nhìn (Pan)", "ui.pan_zoom_orbit": "Dời góc nhìn, thu phóng, xoay quanh tâm", "ui.parallel": "Song song", @@ -1401,7 +1371,6 @@ "ui.quit": "Thoát ứng dụng", "ui.radial_context_menu": "Menu ngữ cảnh dạng vòng (Radial)", "ui.radius": "Bán kính", - "ui.radius_type": "Loại bán kính", "ui.rail": "Đường ray dẫn hướng", "ui.rail_fit": "Độ khớp đường dẫn (Rail Fit)", "ui.reachable": "Có thể tiếp cận", @@ -1426,7 +1395,6 @@ "ui.right": "Phải (Hình chiếu cạnh)", "ui.save": "Lưu", "ui.save_as": "Lưu thành tệp mới", - "ui.save_as_ellipsis": "Lưu thành...", "ui.save_mesh_no_slicer": "Không tìm thấy phần mềm cắt lớp (slicer): hãy chọn Tùy chỉnh… và chỉ định chương trình cắt lớp, hoặc tắt tùy chọn Gửi đến slicer", "ui.save_mesh_pick_bodies": "Chọn các thân khối cần lưu", "ui.save_mesh_temp_failed": "Không thể tạo thư mục {folder}: {error}", @@ -1439,7 +1407,6 @@ "ui.select": "Chọn", "ui.selectOther": "Chọn đối tượng khác…", "ui.selectTangentChain": "Chọn chuỗi tiếp tuyến", - "ui.select_other_hint": "Chọn đối tượng khác: giữ nút chuột trái đứng yên trên mô hình (hoặc nhấp chuột phải › Chọn đối tượng khác…) để liệt kê mọi đối tượng dưới con trỏ; nhấp lại vào cùng vị trí đó để chọn đối tượng kế tiếp.", "ui.selection": "Vùng chọn", "ui.send": "Gửi", "ui.send_to_slicer": "Gửi sang phần mềm cắt lát (Slicer)", @@ -1456,7 +1423,6 @@ "ui.show_constraints": "Các ràng buộc", "ui.show_construction_geometries": "Đối tượng dựng hình (nét đứt)", "ui.show_dimensions": "Các kích thước", - "ui.show_points": "Hiển thị điểm", "ui.show_profile": "Hiển thị biên dạng", "ui.show_projected_geometries": "Hình học đã chiếu", "ui.simple": "Đơn giản", @@ -1466,7 +1432,6 @@ "ui.sketch_dimension": "Phác thảo Kích thước", "ui.sketch_grid": "Lưới phác thảo", "ui.sketch_scale": "Tỷ lệ phác thảo", - "ui.sketch_status": "Trạng thái phác thảo", "ui.sketches": "Các phác thảo", "ui.slice": "Cắt lát", "ui.slicer": "Phần mềm cắt lát (Slicer)", @@ -1485,7 +1450,6 @@ "ui.split_body": "Chia tách khối", "ui.stand_up_tip": "Sau khi mở tệp STEP hoặc IGES với trục Z hướng lên, đề xuất xoay 90° quanh trục X", "ui.start": "Bắt đầu", - "ui.start_radius": "Bán kính bắt đầu", "ui.stretch": "Kéo giãn", "ui.supersampling": "Khử răng cưa SSAA", "ui.supersampling_adaptive": "Chỉ khi góc nhìn đứng yên", @@ -1533,7 +1497,6 @@ "ui.untitled": "Chưa đặt tên", "ui.up_axis_note": "Trục nào hướng lên trên trong các góc nhìn (góc nhìn theo trục, Chuẩn, Nhìn thẳng). Các mô hình thiết kế không bị thay đổi.", "ui.updateToCurrentView": "Cập nhật theo góc nhìn hiện tại", - "ui.variable": "Biến thiên", "ui.view_centre": "Tâm góc nhì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", @@ -1549,7 +1512,6 @@ "ui.zoom_to_the_cursor": "Thu phóng theo con trỏ chuột", "update.available": "Đã có phiên bản SolidCraft {version}", "update.button": "Cập nhật", - "update.button_tip": "Đã có phiên bản SolidCraft {version}", "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_now": "Kiểm tra ngay", diff --git a/assets/locales/zh.json b/assets/locales/zh.json index 219f035915..7a266d3b31 100644 --- a/assets/locales/zh.json +++ b/assets/locales/zh.json @@ -6,11 +6,11 @@ "2D": "2D", "3. Click Submit new issue on GitHub to send your report.": "3. 在 GitHub 上点击 Submit new issue 发送报告。", "A new configuration, a copy of the active one": "新建配置,为当前配置的副本", - "AI": "AI", "ARRANGE": "排列", "About SolidCraft": "关于 SolidCraft", "Acceleration": "加速度", "Access": "可达性", + "Actions": "操作", "Active": "活动", "Active rule": "活动规则", "Add": "添加", @@ -18,6 +18,7 @@ "Add Mid Radius": "添加中间半径", "Add Row": "添加行", "Add condition": "添加条件", + "Add or edit rule": "添加或编辑规则", "Against": "对照", "Alignment": "对齐", "All Sketch Points": "所有草图点", @@ -31,6 +32,7 @@ "Applied Radius": "应用半径", "Apply To": "应用于", "Apply to selection": "应用到选择", + "Apply your appearance first. Exports assigned appearances and materials created in this design.": "请先应用外观。导出已指定的外观和在此设计中创建的材质。", "Arc length": "弧长", "Area": "面积", "As-Built": "现有位置", @@ -67,16 +69,17 @@ "Check collisions": "检查碰撞", "Checked": "已检查", "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "选择一个新平面。草图几何和尺寸在其二维坐标中保持不变。", + "Choose…": "选择…", "Circular": "圆周", "Circumference": "周长", "Close draft": "关闭草稿", "Collisions": "碰撞", "Colour": "颜色", + "Comment": "注释", "Component1": "组件1", "Config Table": "配置表", "Configuration": "配置", "Configuration rules": "配置规则", - "Construction": "构造", "Contact": "接触", "Contact Sets": "接触集", "Contact sets": "接触集", @@ -100,11 +103,11 @@ "Curvature (G2)": "曲率 (G2)", "Curvature change (G3)": "曲率变化 (G3)", "Customize Toolbar": "自定义工具栏", + "Delete": "删除", "Delete all": "全部删除", "Delete rule": "删除规则", "Delete the row": "删除此行", "Delete the set": "删除此集合", - "Delete this parameter": "删除此参数", "Density": "密度", "Dependents": "依赖项", "Designs you open or save appear here.": "你打开或保存的设计会显示在此处。", @@ -118,6 +121,7 @@ "Drive Joint": "驱动接头", "Duration": "时长", "Edges": "边", + "Edit": "编辑", "Edit Profile Sketch": "编辑轮廓草图", "Enable rule": "启用规则", "Enabled": "已启用", @@ -126,11 +130,14 @@ "Explode": "爆炸", "Explode Text": "分解文字", "Export Animation…": "导出动画…", + "Export CSV…": "导出 CSV…", "Export Current View as DXF": "将当前视图导出为DXF", "Export Current View as DXF…": "将当前视图导出为DXF…", "Export DXF": "导出DXF", "Export DXF…": "导出DXF…", + "Export JSON…": "导出 JSON…", "Export hidden edges as dashed lines": "以虚线导出隐藏线", + "Expression": "表达式", "Extend to meet": "延伸至相交", "Extents": "范围", "FILES": "文件", @@ -140,12 +147,13 @@ "Faces": "面", "Faces / edges / vertices": "面 / 边 / 顶点", "Fastener": "紧固件", + "Favorites": "收藏", "Favourite": "收藏", + "Feature": "特征", "Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "许多 CAD 程序的文件以 Y 为上轴,因此在此处打开时会平躺(Z 朝上)。", "Filter": "筛选", "Find in Browser": "在浏览器中查找", "Finish": "完成", - "Flat": "平面", "Flat Pattern": "展开图", "Flat size": "平板尺寸", "Follow camera (Look At sketches)": "跟随相机(注视草图)", @@ -164,6 +172,7 @@ "Home view": "主视图", "Horiz./Vert.": "水平 / 垂直", "IMAGES": "图像", + "Import…": "导入…", "In This Design": "在此设计中", "In place (surfaces)": "原位(曲面)", "In plane": "在平面内", @@ -185,7 +194,9 @@ "Length units": "长度单位", "Letter Spacing": "字间距", "Library": "零件库", + "Library file": "库文件", "Line Spacing": "行间距", + "Local library": "本地库", "Loop": "循环", "MANAGE": "管理", "MATERIALS": "材料", @@ -194,41 +205,49 @@ "Margin": "余量", "Mass": "质量", "Mass Center": "质心", + "Matched restrictions check the final values.": "满足条件的限制规则检查最终值。", "Material": "材质", "Max": "最大", "Mid Radius": "中间半径", "Middle double-click": "中键双击", "Min": "最小", "Mitre": "斜接", + "Model Parameters": "模型参数", "Modeled": "已建模", "More toolbar panels": "更多工具栏面板", "Motion": "运动", "Move / Copy": "移动 / 复制", "Move Type": "移动类型", + "Move down": "下移", "Move earlier": "向前移动", "Move later": "向后移动", + "Move up": "上移", "Name": "名称", "Nearest": "最近", + "New": "新建", "New Comp.": "新建组件", "New Sketch": "新建草图", + "New rule…": "新建规则…", "New study": "新建研究", "No crash reports.": "没有崩溃报告。", + "No matching appearance presets.": "没有匹配的外观预设。", "No matching appearances": "没有匹配的外观", "No visible bodies to export.": "没有可导出的可见实体。", "None": "无", - "Normal to sketch": "垂直于草图", "Not available yet": "暂不可用", "Notch": "缺口", "OUTPUT": "输出", "Opacity": "不透明度", "Open GitHub draft": "打开 GitHub 草稿", + "Open local libraries in the desktop app. You can export this design's library here.": "请在桌面应用中打开本地库。您可以在此导出当前设计的库。", "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "使用 Rust 编写的开源参数化 3D CAD。基于 MIT OR Apache-2.0 许可。", + "Operator": "运算符", "Other side": "另一侧", "Other surface (mutual trim)": "另一曲面(相互修剪)", "Outside": "外侧", "Overall length": "总长", "Param. Graph": "参数图", - "Parameter values": "参数值", + "Parameter": "参数", "Parts": "零件", "Pass through": "通过点", "Path": "路径", @@ -249,7 +268,6 @@ "Preview Row": "预览行", "Preview leaves the current design unchanged.": "预览不会更改当前设计。", "Profiles/Curves": "轮廓/曲线", - "Projection": "投影", "Quantity": "物理量", "RECENT": "最近使用", "REFERENCE": "参考", @@ -289,6 +307,8 @@ "Roll History Marker Here": "将历史标记滚动到此处", "Round": "圆角", "Rule": "规则", + "Rule name": "规则名称", + "Rules run from top to bottom. Matched drives change effective values.": "规则从上到下依次运行。满足条件的驱动规则更改有效值。", "SECTION": "剖面", "SURFACE QUALITY": "曲面质量", "Save Rule": "保存规则", @@ -299,6 +319,7 @@ "Scene": "场景", "Search": "搜索", "Search commands": "搜索命令", + "Search this library": "搜索此库", "Section": "剖面", "Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "“选择其他”:在模型上按住左键(或右键单击 › 选择其他…)列出光标下的所有对象;在同一位置再次单击可选择该处的下一个对象。双击中键转到主视图(首选项 › 导航)。", "Select bodies (a face picks its body)": "选择实体(单击面即选择其所属实体)", @@ -360,18 +381,19 @@ "Turn off section analysis before exporting the view.": "请关闭截面分析后再导出视图。", "Type": "类型", "UNTRIM": "取消修剪", + "Unit": "单位", "Updating the preview": "正在更新预览", "Use active author placement": "使用活动作者放置", "Use fixed plane": "使用固定平面", "Use saved fixed plane": "使用已保存的固定平面", "Use selected face as fixed plane": "使用所选面作为固定平面", + "User Parameters": "用户参数", "VIEW": "视图", "VIEWS": "视图", "Value": "值", "Velocity": "速度", "Vertices": "顶点", "View navigator": "视图导航器", - "Visible bodies only. Curves use line segments (0.01 mm tolerance).": "仅导出可见实体。曲线以线段近似(公差0.01毫米)。", "Volume": "体积", "Wall": "壁厚", "Wall Location": "壁位置", @@ -379,6 +401,12 @@ "When": "条件", "Width": "宽度", "Will fail": "将失败", + "X angle": "X 角度", + "X distance": "X 距离", + "Y angle": "Y 角度", + "Y distance": "Y 距离", + "Z angle": "Z 角度", + "Z distance": "Z 距离", "Zebra": "斑马纹", "angle, height": "角度、高度", "appearance.dielectric_ior": "电介质折射率", @@ -388,7 +416,13 @@ "appearance.roughness": "粗糙度", "appearance.surface_response": "表面响应", "browser.body_name": "实体{number}", + "browser.calibrate_title": "校准:{name}", + "browser.capture_many": "{count} 个组件已移动,但尚未捕获其位置。", + "browser.capture_note": "“捕获位置”保留新位置;“恢复位置”还原原先的位置。", + "browser.capture_one": "一个组件已移动,但尚未捕获其位置。", "browser.component_name": "组件{number}", + "browser.edit_canvas_title": "编辑画布:{name}", + "browser.move_title": "移动:{name}", "browser.parameters": "参数 ({count})", "browser.precision": "精度: {precision}", "browser.sketch_name": "草图{number}", @@ -726,7 +760,15 @@ "cmd.view_navigate": "导航视图", "cmd.view_rename": "重命名命名视图", "cmd.view_save": "新建命名视图", - "command name": "命令名称", + "color.copy": "单击复制颜色值", + "color.edit": "单击编辑颜色", + "color.gamma": "以 0–255 伽马空间显示颜色值", + "color.hue": "色相", + "color.linear": "以 0–1 线性空间显示颜色值", + "color.saturation_value": "饱和度和明度", + "color.selected": "所选颜色", + "contact.components_count": "{count} 个组件", + "contact.enabled_label": "已启用", "coupling.gear_1": "齿轮 1", "coupling.gear_1_pick": "齿轮 1(可选)", "coupling.gear_2": "齿轮 2", @@ -743,9 +785,21 @@ "coupling.rack": "齿条", "coupling.radius_1": "半径 1", "coupling.radius_2": "半径 2", + "crash.announce": "SolidCraft 上次意外关闭或发生了内部错误。崩溃报告已保存(帮助 > 崩溃报告…)。", + "crash.copy_folder": "复制文件夹路径", + "crash.copy_path": "复制路径", + "crash.deleted": "已删除 {count} 份报告。", "crash.detected_time": "检测时间:{time} UTC", + "crash.intro": "崩溃报告保存在此计算机上,不会发送到任何地方。“报告…”会打开草稿,供您在分享前检查。", + "crash.kind_command": "命令内部错误", + "crash.kind_internal": "内部错误", + "crash.kind_kernel": "内核错误(已恢复)", + "crash.kind_unclean": "意外关闭", "crash.previous_session": "较早的 SolidCraft 会话意外结束或发生了内部错误。未正常结束的会话会在启动时被检测到。详情请参阅 帮助 > 崩溃报告…。", + "crash.replayable": "可重放", + "crash.report": "报告…", "crash.unclean_explanation": "在后续启动时检测到;未记录确切的退出时间。系统关机或强制停止也可能导致会话未正常结束。", + "delete.and_more": "…以及另外 {count} 项", "dialog.bodies_count": "{count} 个实体", "dialog.clearance_invalid": "间隙必须是 0 或更大的长度", "dialog.compute": "计算", @@ -753,6 +807,8 @@ "dialog.computing": "正在计算… {seconds} 秒", "dialog.could_not_be_checked": "无法检查", "dialog.custom_axis": "↖ 自定义轴", + "dialog.dimension_insert_hint": "单击其他驱动尺寸以插入其名称", + "dialog.dimension_name": "尺寸:{name}", "dialog.edit_title": "编辑 {title}", "dialog.select": "↖ 选择", "dialog.selected_count": "↖ 已选择 {count} 个", @@ -776,9 +832,30 @@ "dxf.progress.write": "正在写入 DXF (5/5)", "dxf.tolerance": "曲线公差", "dxf.tolerance_help": "仅导出可见实体。曲线使用直线段近似。公差越小,曲线越精细,但需要更多时间和内存。", - "expression, e.g. 25 mm": "表达式,例如 25 mm", + "file_load.built": "已构建:{built}/{total} 个特征。", + "file_load.converting": "正在转换实体 {done}/{total}", + "file_load.inserting": "正在插入几何体", + "file_load.opening": "正在打开设计", + "file_load.preparing_thumbnail": "正在准备缩略图...", + "file_load.preparing_view": "正在准备视图...", + "file_load.preview_note": "这是已保存的预览。正在检查几何体,完成后即可编辑。", + "file_load.reading": "正在读取文件...", + "file_load.reading_percent": "正在读取文件... {percent}%", + "file_load.rebuilding": "正在重建特征 {done}/{total}", + "file_load.recovering": "正在恢复设计", + "file_load.stopping": "正在等待上一次操作停止...", + "file_load.waiting": "正在等待文件操作", "fits the face, e.g. M8": "适配该面,例如 M8", "from the rule": "来自规则", + "help.apache": "Apache 许可证 2.0", + "help.attributions": "署名信息 (ATTRIBUTION.md)", + "help.build": "版本 {version} · 提交 {commit} · 构建日期 {date}", + "help.font_details": "界面使用 egui 的内置字体(Ubuntu Light 和 Hack,采用 Ubuntu Font Licence 和 MIT/Bitstream Vera 许可证;Noto Emoji 和表情图标字体,采用 OFL 和 MIT 许可证)。草图文本使用 Liberation Sans(SIL OFL 1.1)。The Crafting Apps 的可选字体包 craft-fonts(BIZ UDPGothic、Shippori Mincho、BIZ UDMincho、Noto Sans CJK SC、Noto Sans Arabic;SIL OFL 1.1)未随 SolidCraft 构建版本捆绑提供。", + "help.fonts": "字体", + "help.licences": "许可证", + "help.mit": "MIT 许可证", + "help.notice": "NOTICE", + "help.third_party": "第三方许可证", "home.documentation": "文档", "home.in_this_browser": "在此浏览器中", "home.missing": "文件不存在", @@ -804,9 +881,30 @@ "install.menu": "安装应用…", "install.steps_intro": "Safari 通过自身的菜单安装网页应用:", "install.title": "安装 SolidCraft", + "issue.body_label": "报告正文", + "issue.note_image_copied": "请将图片粘贴到 GitHub 草稿中。如果无法复制图片,请保存 PNG。", + "issue.note_opened": "请检查 GitHub 草稿并附上截图。点击 GitHub 的 Submit new issue 按钮即可发布。", + "issue.note_opened_copied": "报告已复制。请粘贴到 GitHub 草稿中,附上截图,然后在 GitHub 上点击 Submit new issue。", + "issue.note_removed": "已从此草稿中移除截图。", + "issue.note_report_copied": "报告已复制。如有需要,请将其粘贴到 GitHub 草稿中。", + "issue.note_save_failed": "无法保存截图:{error}", + "issue.note_saved": "截图已保存。请在 GitHub 草稿中附上 PNG。", + "issue.save_screenshot": "保存截图…", "joint.from_bodies_hint": "每个所选实体移入各自的新组件,位置保持不变。", "joint.one_body": "两次选择都在 {body} 上。请在另一个零件上选择第二个。", "joint.one_component": "这些零件是同一组件的实体:{bodies}。接头使一个组件相对另一个组件运动,因此每个零件都需要自己的组件。", + "keymap.command_id": "命令 ID", + "keymap.empty": "没有匹配的命令", + "keymap.use_anyway": "仍在此处使用 {key}", + "manage.appearance_hint": "为所选对象选择外观,或将色样拖到实体或面上。", + "manage.assign_hint": "选择实体并指定其塑料设计规则。", + "manage.config_hint": "编辑配置行、列和规则,然后激活或预览某一行。", + "manage.empty": "没有匹配的行。", + "manage.material_hint": "选择实体并指定其物理材质。", + "manage.parameters_hint": "编辑表达式和单位。按 Enter 或离开字段时应用更改。", + "manage.rules_hint": "选择要编辑的规则,或创建新规则。确认以保存规则。", + "measure.at": "位置", + "measure.item": "项目 {number}", "messages.ago_h": "{n} 小时前", "messages.ago_min": "{n} 分钟前", "messages.ago_s": "{n} 秒前", @@ -818,8 +916,16 @@ "messages.title": "消息", "motion.checked_to": "仅检查到第 {step} 步:检查已用完其工作量上限", "motion.collision": "{a} · {b}:第 {steps} 步", + "motion.delete_key": "删除关键帧", "motion.duration_hint": "秒(留空:按步)", + "motion.key_here": "在此添加关键帧", + "motion.key_here_hint": "在此步骤添加关键帧,使用关节的当前值", "motion.no_collisions": "整个研究中没有碰撞", + "motion.step_back": "上一步", + "motion.step_forward": "下一步", + "motion.to_end": "到结尾", + "motion.to_start": "到开头", + "motion.track_name": "轨道 {number}", "nav.touchpad.help": "按住 Shift 并移动指针可平移;按住 Alt/Option 并移动可旋转。无需按鼠标按钮。垂直滚动可缩放。", "nav.touchpad.label": "触控板", "nav.touchpad.orbit": "旋转(按住 Alt/Option 并移动,无需鼠标按钮)", @@ -828,13 +934,13 @@ "nav.touchpad.zoom": "缩放(垂直滚动或双指捏合)", "needs two joints": "需要两个接头", "new configuration": "新配置", - "new name": "新名称", "new parameter": "新参数", "no hole": "无孔", "no interference": "无干涉", "no joint that moves yet": "尚无运动接头", "no picture": "无图片", "none": "无", + "parts.automatic_name": "自动", "pattern.alignment_2": "对齐 2", "pattern.alignment_hint": "数量包含原始图形。居中时副本分布在两侧;数量为偶数时,反转可切换多一个副本的一侧。", "pattern.direction_1": "方向 1", @@ -842,12 +948,19 @@ "pattern.flip_direction": "反转方向", "pick a hole to seat it, or OK places it at the origin": "选择一个孔放置,或单击确定放置在原点", "pick one or two vertices, edges or faces": "选择一个或两个顶点、边或面", + "prefs.cpu": "CPU(软件)", + "prefs.minutes_suffix": " min", + "properties.title": "属性:{name}", + "recovery.days_ago": "{count} 天前", + "recovery.detail": "{file} · {features} 个特征 · 自动保存于 {when}", + "recovery.hours_ago": "{count} 小时前", + "recovery.just_now": "刚刚", + "recovery.min_ago": "{count} 分钟前", + "recovery.never_saved": "从未保存", "render.background": "场景背景", "render.fit": "适配产品视图", - "render.image_cancel": "取消渲染", "render.image_cancelled": "渲染已取消", "render.image_cancelling": "正在取消", - "render.image_check": "检查渲染请求", "render.image_completed": "图像可导出", "render.image_encoding": "正在编码 PNG", "render.image_export": "导出 PNG", @@ -894,6 +1007,19 @@ "sample.plate.name": "示例板", "sample.vents.about": "按矩形阵列排列的槽:行数、列数和间距均为参数", "sample.vents.name": "通风板", + "search.command_keys": "Enter 运行 Shift+Enter 运行并保持打开 Esc 关闭", + "search.commands": "命令", + "search.design": "此设计中", + "search.feature": "特征", + "search.invalid": "无效", + "search.keys": "↑ ↓ 选择 Enter 运行 Tab 补全名称 Esc 关闭", + "search.no_match": "没有与“{query}”匹配的命令", + "search.parameter": "参数", + "search.recent": "最近使用", + "search.run": "运行命令", + "search.suggest": "您是否想找 {label}?", + "search.try": "输入命令名称、此设计中的特征或参数。要带值运行命令,请输入其ID和JSON对象:{example}", + "search.valid": "有效", "settings.display_quality_note": "级别越精细,曲面和边显示越平滑,但网格生成耗时更长。只影响视口,不影响设计或导出。", "settings.frame_rate_note": "在视图右下角显示每秒帧数。开启时视图会持续重绘。", "settings.measure_axis_distances": "显示 X/Y/Z 距离", @@ -904,17 +1030,30 @@ "settings.reset_intro": "这些设置在此计算机上无法正常工作,会导致 SolidCraft 无法启动,因此已更改:", "settings.reset_title": "设置已更改", "settings.supersampling_note": "以更高分辨率绘制视图再缩小:在低于 4K 的屏幕上边、线和网格更平滑。会消耗 GPU 时间:1.5× 时约 2.25 倍像素,2× 时 4 倍。", + "shortcut.json_error": "参数必须采用 JSON 格式:{error}", + "shortcut.no_command": "没有命令 `{id}`;JSON 前的文本必须是命令 ID", + "shortcut.pin": "固定到 S 框顶部", + "shortcut.unpin": "取消固定", "sketch.degrees_of_freedom": "{count} 个自由度", + "sketch.dof_left": "剩余 {count} 个自由度", + "sketch.dof_left_one": "剩余 {count} 个自由度", "sketch.draw_curves": "绘制为 {linetype}", "sketch.make_selection": "将选择转换为 {linetype}", "sketch.open_ends": "开放 {count}", "sketch.untied_ends": "未约束 {count}", + "sketch_panel.attached": "附着到 {body} / {path}", + "sketch_panel.author": "创建时的放置:{path}", + "sketch_panel.dimension_hint": "输入数值或表达式,然后按“确定”。", + "sketch_panel.move_hint": "输入距离和角度,然后按“确定”。", + "sketch_panel.redefine_title": "{command}:{name}", + "sketch_panel.sheet_attachment": "附着到钣金实体的平面上;钣金变化时会跟随该面板。", + "sketch_panel.text_hint": "编辑文字及其位置,然后按“确定”。", + "sketch_panel.use_plane": "使用 {plane} 平面", "sketch_text.align_center": "居中", "sketch_text.align_left": "左对齐", "sketch_text.align_right": "右对齐", "sketch_text.bold": "粗体", "sketch_text.builtin_font": "{family}(内置)", - "sketch_text.font_search": "搜索字体", "sketch_text.italic": "斜体", "sketch_text.loading_fonts": "正在查找已安装字体…", "sketch_text.on_path": "文字沿路径排列:由曲线放置", @@ -935,6 +1074,7 @@ "timeline.ungroup": "移除特征文件夹", "to the face": "到面", "to the next face": "到下一个面", + "toolbar.customize_note": "{tab} 选项卡。勾选命令可将其按钮添加到工具栏。您也可以将面板下拉菜单中的某一行拖到工具栏,将按钮拖出工具栏以移除,或使用下拉菜单行中的固定图标。", "toolbar.image": "图像", "toolbar.render": "渲染", "toolbar.render_scene": "场景", @@ -943,11 +1083,11 @@ "trace.stopped_by_loop": "在采样 {sample} 处结束:经过 {joint} 的环路无法再闭合", "ui.1_degree_of_freedom": "1 个自由度", "ui.3d_mouse_speed": "3D 鼠标速度", - "ui.3d_mouse_windows": "3D 鼠标", "ui.3d_sketch": "3D 草图", - "ui.about": "关于", "ui.activate": "激活", "ui.active_component": "活动组件", + "ui.add_configuration": "添加配置", + "ui.add_parameter": "添加参数", "ui.agent_chat": "智能体聊天", "ui.agent_chat_add": "附加文件", "ui.agent_chat_add_file": "选择文件…", @@ -989,6 +1129,7 @@ "ui.agent_chat_file_error": "{name}:{error}", "ui.agent_chat_file_heic": "{name}:这里无法读取 HEIC 图片;请先导出为 JPEG 或 PNG", "ui.agent_chat_file_importing": "正在导入设计…", + "ui.agent_chat_file_kinds": "{name}:可以附加图片(PNG、JPEG、GIF、WebP、BMP、TIFF)、文本文件(包括 SVG)和 CAD 文件(STEP、IGES、FreeCAD、Rhino、STL、3MF、OBJ、glTF)", "ui.agent_chat_file_loading": "正在读取文件…", "ui.agent_chat_file_not_a_file": "{name}:不是文件", "ui.agent_chat_file_not_a_picture": "{name}:不是图片", @@ -1011,7 +1152,7 @@ "ui.agent_chat_model_tip": "智能体使用的模型。你的选择会为此智能体记住。", "ui.agent_chat_new": "新对话", "ui.agent_chat_new_confirm": "开始新对话?当前对话将被清除。", - "ui.agent_chat_new_yes": "新对话", + "ui.agent_chat_next_picture": "下一张图片", "ui.agent_chat_no_cli": "在应用旁边未找到 solidcraft-cli,因此智能体无法访问设计。", "ui.agent_chat_no_control": "智能体聊天的端点未能启动,因此智能体无法访问设计。", "ui.agent_chat_no_images": "该智能体不接收图片", @@ -1027,6 +1168,7 @@ "ui.agent_chat_placeholder": "让智能体修改设计…", "ui.agent_chat_placeholder_busy": "输入消息,待智能体完成后发送(Ctrl+Enter:立即发送)", "ui.agent_chat_pop_out": "在独立窗口中打开(可移到另一个屏幕)", + "ui.agent_chat_previous_picture": "上一张图片", "ui.agent_chat_privacy": "除了你以自己的账户运行的智能体之外,不会向任何地方发送任何内容。", "ui.agent_chat_privacy_note": "SolidCraft 不向任何地方发送任何内容:你运行的智能体以你自己的账户与它自己的服务通信。SolidCraft 从不查看或保存任何密钥或密码。", "ui.agent_chat_queue": "加入队列", @@ -1082,6 +1224,7 @@ "ui.ai_bad_url": "请输入以 http:// 或 https:// 开头的 URL", "ui.ai_base_url": "基础 URL", "ui.ai_button": "AI", + "ui.ai_command_hint": "my-agent --acp", "ui.ai_copy_suffix": "副本", "ui.ai_custom_url": "自定义 URL", "ui.ai_default": "默认", @@ -1102,8 +1245,10 @@ "ui.ai_mode_edits_note": "建模命令无需询问即可运行;保存、打开、导出等文件操作或破坏性操作仍会询问。", "ui.ai_mode_global": "按上方设置", "ui.ai_model": "模型", + "ui.ai_model_hint": "qwen3:14b", "ui.ai_modes_scope": "这些设置适用于 SolidCraft 的工具。智能体自己的工具(它的 shell、文件编辑)遵循智能体自己的模式;如果智能体提供模式,可在聊天面板标题栏中选择。", "ui.ai_name": "名称", + "ui.ai_name_hint": "我的智能体", "ui.ai_need_command": "请输入启动智能体的命令", "ui.ai_need_model": "请输入模型名称", "ui.ai_no_answer": "智能体未及时响应", @@ -1134,6 +1279,7 @@ "ui.ai_toolbar_button": "在工具栏显示按钮", "ui.ai_toolbar_button_note": "工具栏右端的 AI 按钮。Ctrl+Shift+A 和视图菜单仍可打开面板。", "ui.ai_undo_tip": "将智能体为此消息所做的全部更改一步撤销", + "ui.ai_url_hint": "https://example.com/v1", "ui.all": "全部", "ui.ambient_occlusion": "环境光遮蔽", "ui.ambient_occlusion_help": "在不透明表面相接处添加柔和阴影。需要 GPU 视口。", @@ -1147,7 +1293,6 @@ "ui.anti_aliasing": "抗锯齿", "ui.apply": "应用", "ui.arc": "圆弧", - "ui.assemble": "装配", "ui.assistant": "助手", "ui.assistant_waiting": "{count} 个批准请求等待中", "ui.autosave_every": "自动保存间隔", @@ -1160,6 +1305,11 @@ "ui.bodies_all_if_none": "实体(未选择则全部)", "ui.box": "长方体", "ui.break": "打断", + "ui.browser_bodies_one": "{count} 个实体", + "ui.browser_bodies_other": "{count} 个实体", + "ui.browser_collapse": "折叠浏览器", + "ui.browser_expand": "展开浏览器", + "ui.browser_title": "浏览器", "ui.calibrate": "校准", "ui.cancel": "取消", "ui.canvases": "画布", @@ -1169,7 +1319,6 @@ "ui.chamfer": "倒角", "ui.chamfer_type": "倒角类型", "ui.change_workspace": "更改工作区", - "ui.chord_length": "弦长", "ui.circle": "圆", "ui.circular_pattern": "圆形阵列", "ui.clear": "清除", @@ -1202,11 +1351,9 @@ "ui.command_done": "{command} 完成", "ui.command_palette_hint": "输入命令,例如 {example} — 或按 S 搜索", "ui.concentric": "同心", - "ui.configurations": "配置", "ui.conic_curve": "圆锥曲线", "ui.constant": "恒定", "ui.constraints_conflict": "约束冲突", - "ui.construct": "构造", "ui.construction": "构造", "ui.context_menu": "上下文菜单", "ui.continuity_g0": "G0 折痕", @@ -1232,7 +1379,6 @@ "ui.cylinder": "圆柱体", "ui.dark": "深色", "ui.default_modelling_orientation": "默认建模方向", - "ui.degrees_of_freedom": "自由度", "ui.delete": "删除", "ui.depth": "深度", "ui.design": "设计", @@ -1274,7 +1420,6 @@ "ui.equivalent_radius": "等效半径", "ui.expand": "展开", "ui.exportDxf": "导出 DXF…", - "ui.export_ellipsis": "导出...", "ui.export_ellipsis_keymap": "导出…", "ui.export_keymap_tip": "将按键、S 框收藏和工具栏导出为文件", "ui.extend": "延伸", @@ -1298,6 +1443,7 @@ "ui.frame_rate": "帧率", "ui.front": "前视", "ui.fully_constrained": "完全约束", + "ui.function": "函数", "ui.general": "常规", "ui.graphics": "图形", "ui.graphics_custom": "自定义", @@ -1364,7 +1510,6 @@ "ui.heading_sheet_metal": "钣金", "ui.heading_shell": "抽壳", "ui.heading_sketch": "草图", - "ui.heading_sketch_palette": "草图面板", "ui.heading_sketch_scale": "草图缩放", "ui.heading_solid": "实体", "ui.heading_sphere": "球体", @@ -1383,6 +1528,7 @@ "ui.hideOrigin": "隐藏原点", "ui.hidePlanes": "隐藏平面", "ui.hideProfile": "隐藏轮廓", + "ui.history_title": "历史", "ui.hole": "孔", "ui.hole_tap_type": "孔攻丝类型", "ui.hole_type": "孔类型", @@ -1402,7 +1548,6 @@ "ui.length_decimals": "长度小数位", "ui.light": "浅色", "ui.line": "直线", - "ui.linetype": "线型", "ui.lock": "锁定", "ui.loft": "放样", "ui.look_at": "朝向", @@ -1436,10 +1581,8 @@ "ui.mesh_format_3mf": "3MF", "ui.mesh_format_3mf_tagged": "带面标签的 3MF", "ui.mesh_format_stl": "STL", - "ui.midpoint": "中点", "ui.mirror": "镜像", "ui.mirror_line": "镜像线", - "ui.modify": "修改", "ui.move": "移动", "ui.multiple": "多个", "ui.named_views": "命名视图", @@ -1460,7 +1603,6 @@ "ui.offset_plane": "偏移平面", "ui.one_side": "单侧", "ui.open": "打开", - "ui.open_ellipsis": "打开...", "ui.open_ends_tip": "未接触任何曲线", "ui.open_preferences": "打开首选项", "ui.open_recent": "打开最近", @@ -1470,8 +1612,9 @@ "ui.orbit_around": "环绕", "ui.orientation": "定向", "ui.origin": "原点", - "ui.palette_points": "点", - "ui.palette_profile": "轮廓", + "ui.palette_construction_mode": "构造模式 (X)", + "ui.palette_more": "更多", + "ui.palette_sketch_options": "草图选项", "ui.pan": "平移", "ui.pan_zoom_orbit": "平移、缩放、环绕", "ui.parallel": "平行", @@ -1512,7 +1655,6 @@ "ui.quit": "退出", "ui.radial_context_menu": "环形上下文菜单", "ui.radius": "半径", - "ui.radius_type": "半径类型", "ui.rail": "导轨", "ui.rail_fit": "导轨适配方式", "ui.reachable": "可到达", @@ -1545,7 +1687,6 @@ "ui.right": "右视", "ui.save": "保存", "ui.save_as": "另存为", - "ui.save_as_ellipsis": "另存为...", "ui.save_mesh_no_slicer": "未找到切片软件:请选择“自定义…”并指定切片软件程序,或关闭“发送到切片软件”", "ui.save_mesh_pick_bodies": "请选择要保存的实体", "ui.save_mesh_temp_failed": "无法创建 {folder}:{error}", @@ -1558,7 +1699,6 @@ "ui.select": "选择", "ui.selectOther": "选择其他…", "ui.selectTangentChain": "选择相切链", - "ui.select_other_hint": "“选择其他”:在模型上按住左键(或右键单击 › 选择其他…)列出光标下的所有对象;在同一位置再次单击可选择该处的下一个对象。", "ui.selection": "选择", "ui.selection_modifiers": "选择修饰键", "ui.selection_shift_add_only": "Shift+点击仅添加、不取消选择", @@ -1578,7 +1718,6 @@ "ui.show_constraints": "约束", "ui.show_construction_geometries": "构造几何图元", "ui.show_dimensions": "尺寸", - "ui.show_points": "显示点", "ui.show_profile": "显示轮廓", "ui.show_projected_geometries": "投影几何", "ui.simple": "简单", @@ -1589,7 +1728,6 @@ "ui.sketch_grid": "草图网格", "ui.sketch_modifiers": "草图修饰键", "ui.sketch_scale": "草图比例", - "ui.sketch_status": "草图状态", "ui.sketches": "草图", "ui.slice": "切片", "ui.slicer": "切片软件", @@ -1636,7 +1774,6 @@ "ui.start": "开始", "ui.start_over": "重新开始", "ui.start_page": "起始页", - "ui.start_radius": "起始半径", "ui.stretch": "伸展", "ui.supersampling": "超级采样 (SSAA)", "ui.supersampling_adaptive": "仅在视图静止时", @@ -1686,7 +1823,6 @@ "ui.untitled": "无标题", "ui.up_axis_note": "视图中朝上的轴(轴视图、主视图、注视)。设计本身不变。", "ui.updateToCurrentView": "更新为当前视图", - "ui.variable": "变量", "ui.view_centre": "视图中心", "ui.visual_shaded_hidden_edges": "带隐藏边的着色", "ui.visual_shaded_with_edges": "带边着色", @@ -1714,7 +1850,6 @@ "ui.zoom_to_the_cursor": "缩放到光标", "update.available": "SolidCraft {version} 已发布", "update.button": "更新", - "update.button_tip": "SolidCraft {version} 已发布", "update.button_version": "更新到 {version}", "update.check_automatically": "自动检查更新", "update.check_now": "立即检查", @@ -1746,5 +1881,6 @@ "update.title": "有可用更新", "update.up_to_date": "SolidCraft 已是最新版本。", "update.update_now": "立即更新", - "update.you_have": "当前版本为 {version}。" + "update.you_have": "当前版本为 {version}。", + "↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ 选择 · Enter / Tab 补全 · Esc 关闭" } diff --git a/assets/styles/ui/palettes.rs b/assets/styles/ui/palettes.rs index c2052dec1d..6cd46af07a 100644 --- a/assets/styles/ui/palettes.rs +++ b/assets/styles/ui/palettes.rs @@ -31,6 +31,42 @@ impl Tokens { font_label: 12.5, font_button: 13.0, font_heading: 14.0, + font_triad: 8.0, + font_compass: 13.5, + font_subtitle: 15.0, + font_intro: 16.0, + font_article_heading: 17.0, + font_title: 22.0, + font_display: 26.0, + control_height: 28.0, + form_ink: Color32::from_rgb(27, 36, 48), + form_dim: Color32::from_rgb(74, 88, 104), + form_primary: Color32::from_rgb(35, 110, 173), + form_error: Color32::from_rgb(161, 52, 44), + form_error_fill: Color32::from_rgb(252, 239, 237), + form_success: Color32::from_rgb(39, 107, 44), + form_success_fill: Color32::from_rgb(229, 242, 230), + control_radius: 6, + form_label_width: 70.0, + form_gap_y: 5.0, + form_footer_height: 48.0, + form_header_height: 40.0, + form_card_width: 264.0, + form_icon_size: 19.0, + form_close_size: 26.0, + form_inset: 9.0, + form_icon_inset: 4.5, + form_section_icon_size: 11.0, + form_checkbox_height: 22.0, + form_button_height: 28.0, + sketch_palette_width: 232.0, + sketch_mode_size: 36.0, + sketch_mode_radius: 7, + sketch_mode_border: 1.5, + sketch_palette_alpha: 0.96, + sketch_mode_tint: 0.12, + sketch_footer_icon_size: 15.0, + font_section_tracking: 0.04, app_bar: Color32::from_rgb(52, 56, 64), app_bar_separator: Color32::from_rgb(136, 145, 160), toolbar: Color32::from_rgb(246, 247, 249), @@ -47,6 +83,24 @@ impl Tokens { mesh_accent: Color32::from_rgb(130, 103, 0), form_accent: Color32::from_rgb(115, 73, 205), render_accent: Color32::from_rgb(174, 45, 113), + sketch_ink: Color32::from_rgb(5, 127, 69), + sketch_soft: Color32::from_rgb(237, 247, 241), + surface_fill: Color32::from_rgb(164, 80, 0), + surface_ink: Color32::from_rgb(164, 80, 0), + surface_soft: Color32::from_rgb(251, 235, 216), + mesh_fill: Color32::from_rgb(224, 178, 0), + mesh_ink: Color32::from_rgb(138, 109, 0), + mesh_soft: Color32::from_rgb(255, 248, 220), + form_fill: Color32::from_rgb(115, 73, 205), + form_tab_ink: Color32::from_rgb(115, 73, 205), + form_soft: Color32::from_rgb(238, 232, 250), + sheet_fill: Color32::from_rgb(30, 111, 150), + sheet_ink: Color32::from_rgb(30, 111, 150), + sheet_soft: Color32::from_rgb(225, 240, 247), + render_fill: Color32::from_rgb(168, 50, 110), + render_ink: Color32::from_rgb(168, 50, 110), + render_soft: Color32::from_rgb(248, 230, 239), + tab_on_fill_dark: Color32::BLACK, on_accent: Color32::WHITE, agent_ok: Color32::from_rgb(76, 194, 122), agent_busy: Color32::from_rgb(108, 180, 240), @@ -101,7 +155,7 @@ impl Tokens { sketch_point_free: Color32::from_rgb(30, 90, 200), section_cap: Color32::from_rgb(214, 128, 72), dialog_bg: Color32::from_rgb(250, 251, 252), - dialog_border: Color32::from_rgb(208, 212, 220), + dialog_border: Color32::from_rgb(213, 219, 227), chip: Color32::from_rgb(38, 120, 218), heading_bg: Color32::from_rgb(218, 223, 231), subheading_bg: Color32::from_rgb(234, 237, 242), @@ -117,6 +171,14 @@ impl Tokens { app_bar_hover_button: Color32::from_rgba_unmultiplied_const(255, 255, 255, 30), app_bar_hover_strong: Color32::from_rgba_unmultiplied_const(255, 255, 255, 40), app_bar_tab_active: Color32::from_rgb(53, 59, 71), + dialog_body: Color32::from_rgb(255, 255, 255), + dialog_footer: Color32::from_rgb(244, 246, 249), + dialog_rule: Color32::from_rgb(230, 234, 240), + control_fill: Color32::from_rgb(255, 255, 255), + control_border: Color32::from_rgb(195, 203, 214), + dialog_label: Color32::from_rgb(58, 70, 86), + selected_ink: Color32::from_rgb(26, 80, 127), + selected_bg: Color32::from_rgb(232, 241, 250), timeline_item: Color32::WHITE, timeline_item_error: Color32::from_rgb(250, 220, 220), timeline_marker: Color32::from_rgb(70, 76, 88), @@ -170,6 +232,36 @@ impl Tokens { } pub const fn dark() -> Tokens { let mut t = Tokens::light(); + // Shared typography and dense form dimensions stay identical in both palettes. + t.font_triad = 8.0; + t.font_compass = 13.5; + t.font_subtitle = 15.0; + t.font_intro = 16.0; + t.font_article_heading = 17.0; + t.font_title = 22.0; + t.font_display = 26.0; + t.control_height = 28.0; + t.control_radius = 6; + t.form_label_width = 70.0; + t.form_gap_y = 5.0; + t.form_footer_height = 48.0; + t.form_header_height = 40.0; + t.form_card_width = 264.0; + t.form_icon_size = 19.0; + t.form_close_size = 26.0; + t.form_inset = 9.0; + t.form_icon_inset = 4.5; + t.form_section_icon_size = 11.0; + t.form_checkbox_height = 22.0; + t.form_button_height = 28.0; + t.sketch_palette_width = 232.0; + t.sketch_mode_size = 36.0; + t.sketch_mode_radius = 7; + t.sketch_mode_border = 1.5; + t.sketch_palette_alpha = 0.96; + t.sketch_mode_tint = 0.12; + t.sketch_footer_icon_size = 15.0; + t.font_section_tracking = 0.04; t.app_bar = Color32::from_rgb(28, 31, 38); t.toolbar = Color32::from_rgb(39, 43, 52); t.panel = Color32::from_rgb(39, 43, 52); @@ -184,6 +276,32 @@ impl Tokens { t.mesh_accent = Color32::from_rgb(133, 113, 0); t.form_accent = Color32::from_rgb(123, 99, 166); t.render_accent = Color32::from_rgb(224, 116, 174); + t.sketch_ink = Color32::from_rgb(70, 190, 120); + t.sketch_soft = Color32::from_rgb(31, 59, 43); + t.surface_fill = Color32::from_rgb(178, 106, 20); + t.surface_ink = Color32::from_rgb(224, 145, 74); + t.surface_soft = Color32::from_rgb(69, 51, 30); + t.mesh_fill = Color32::from_rgb(198, 168, 0); + t.mesh_ink = Color32::from_rgb(212, 183, 38); + t.mesh_soft = Color32::from_rgb(65, 57, 26); + t.form_fill = Color32::from_rgb(138, 111, 204); + t.form_tab_ink = Color32::from_rgb(179, 155, 238); + t.form_soft = Color32::from_rgb(55, 47, 79); + t.sheet_fill = Color32::from_rgb(42, 127, 168); + t.sheet_ink = Color32::from_rgb(79, 179, 217); + t.sheet_soft = Color32::from_rgb(31, 58, 71); + t.render_fill = Color32::from_rgb(200, 88, 143); + t.render_ink = Color32::from_rgb(224, 116, 174); + t.render_soft = Color32::from_rgb(64, 35, 54); + t.tab_on_fill_dark = Color32::BLACK; + t.dialog_body = Color32::from_rgb(59, 68, 83); + t.dialog_footer = Color32::from_rgb(47, 52, 62); + t.dialog_rule = Color32::from_rgb(53, 59, 71); + t.control_fill = Color32::from_rgb(39, 43, 52); + t.control_border = Color32::from_rgb(74, 82, 98); + t.dialog_label = Color32::from_rgb(180, 187, 200); + t.selected_ink = Color32::from_rgb(156, 199, 245); + t.selected_bg = Color32::from_rgb(42, 70, 110); t.construction_geom = icon_primary(crate::icon_svg::CONSTRUCTION_PRIMARY[1]); t.error = Color32::from_rgb(240, 100, 96); t.warning = Color32::from_rgb(236, 170, 70); @@ -225,6 +343,13 @@ impl Tokens { t.box_crossing = Color32::from_rgb(80, 200, 120); t.bend_up = Color32::from_rgb(110, 200, 120); t.bend_down = Color32::from_rgb(240, 150, 80); + t.form_ink = t.text; + t.form_dim = t.text_dim; + t.form_primary = t.accent; + t.form_error = t.error; + t.form_error_fill = t.timeline_item_error; + t.form_success = Color32::from_rgb(143, 219, 154); + t.form_success_fill = Color32::from_rgb(44, 74, 51); t } @@ -242,6 +367,12 @@ impl Tokens { t.analysis.draft = [positive, [0.95, 0.90, 0.60], negative]; t.analysis.access = [positive, negative]; t.analysis.hatch = true; + // Keep validation badges separate from red errors under both red-green + // simulations, while retaining their explicit Valid / Not valid labels. + t.form_success_fill = if dark { Color32::from_rgb(44, 57, 67) } else { Color32::from_rgb(227, 227, 246) }; + if !dark { + t.form_success = Color32::from_rgb(39, 88, 78); + } // The icons that show the same meanings follow; on the dark toolbar the blue is a little // stronger, to stay clear of the icon fill. let good = if dark { Color32::from_rgb(35, 115, 245) } else { Color32::from_rgb(51, 115, 230) }; @@ -249,6 +380,7 @@ impl Tokens { t.icons.axes[1] = t.triad[1]; t.icons.access = [good, Color32::from_rgb(217, 102, 13)]; t.construction_geom = icon_primary(crate::icon_svg::CONSTRUCTION_RED_GREEN[dark as usize]); + t.glyph_text = icon_primary(crate::icon_svg::CONSTRAINT_RED_GREEN[dark as usize]); t.icons.draft = [good, Color32::from_rgb(242, 230, 153)]; t.surface_accent = if dark { Color32::from_rgb(200, 135, 55) } else { Color32::from_rgb(164, 80, 0) }; t.mesh_accent = if dark { Color32::from_rgb(190, 175, 70) } else { Color32::from_rgb(130, 103, 0) }; diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index d1bf0fc4b5..0f25e308ec 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -36,6 +36,10 @@ tiny-skia = { workspace = true } # updated by its installer (MIT; already in the tree as a build dependency). winreg = "0.56" +[dev-dependencies] +# Parse the source guard structurally, including code after inline test modules. +syn = { workspace = true, features = ["visit"] } + # Pictures pasted into the agent chat; already in the tree, with the same features, through # egui-winit's clipboard. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/ui-egui/build.rs b/crates/ui-egui/build.rs index 797da0514e..79c9c0b0f9 100644 --- a/crates/ui-egui/build.rs +++ b/crates/ui-egui/build.rs @@ -132,6 +132,7 @@ fn icon_data() -> Result<(), String> { ("CONSTRUCTION_PRIMARY", palette.get("accents").and_then(|v| v.get("construction"))), ("SKETCH_RED_GREEN", palette.get("variants").and_then(|v| v.get("red-green")).and_then(|v| v.get("sketch"))), ("CONSTRUCTION_RED_GREEN", palette.get("variants").and_then(|v| v.get("red-green")).and_then(|v| v.get("construction"))), + ("CONSTRAINT_RED_GREEN", palette.get("variants").and_then(|v| v.get("red-green")).and_then(|v| v.get("constraint"))), ("CONSTRUCTION_BLUE_YELLOW", palette.get("variants").and_then(|v| v.get("blue-yellow")).and_then(|v| v.get("construction"))), ("CONSTRAINT_BLUE_YELLOW", palette.get("variants").and_then(|v| v.get("blue-yellow")).and_then(|v| v.get("constraint"))), ] { diff --git a/crates/ui-egui/src/agent_chat/markdown_view.rs b/crates/ui-egui/src/agent_chat/markdown_view.rs index 2d8d6ebfb8..ba8647329b 100644 --- a/crates/ui-egui/src/agent_chat/markdown_view.rs +++ b/crates/ui-egui/src/agent_chat/markdown_view.rs @@ -5,6 +5,7 @@ //! so the parsed blocks are kept per conversation item and parsed again only when its text //! changes. +use crate::fonts::RichTextExt as _; use std::hash::{Hash, Hasher}; use std::sync::Arc; @@ -41,9 +42,11 @@ fn job(ui: &egui::Ui, spans: &[Span], size: f32, color: Color32) -> LayoutJob { let plain = TextFormat { font_id: base.clone(), color, ..Default::default() }; match s { Span::Text(x) => j.append(x, 0.0, plain), - Span::Bold(x) => j.append(x, 0.0, TextFormat { color: t.text, ..plain }), + Span::Bold(x) => j.append(x, 0.0, TextFormat { font_id: crate::fonts::semibold(size), color: t.text, ..plain }), Span::Italic(x) => j.append(x, 0.0, TextFormat { italics: true, ..plain }), - Span::Code(x) => j.append(x, 0.0, TextFormat { font_id: FontId::monospace(size - 1.0), background: t.field, ..plain }), + Span::Code(x) => { + j.append(x, 0.0, TextFormat { font_id: FontId::monospace(size - (t.font_button - t.font_body)), background: t.field, ..plain }) + } Span::Link { label, .. } => j.append(label, 0.0, TextFormat { color: t.accent, underline: Stroke::new(1.0, t.accent), ..plain }), } } @@ -58,6 +61,7 @@ fn host(url: &str) -> &str { /// A paragraph's text, then its links as buttons (a laid-out text cannot be clicked in parts). fn spans(ui: &mut egui::Ui, spans: &[Span], size: f32, color: Color32) { + let t = Tokens::get(); ui.add(egui::Label::new(job(ui, spans, size, color)).selectable(true).wrap()); let links: Vec<(&str, &str)> = spans.iter().filter_map(|s| if let Span::Link { label, url } = s { Some((label.as_str(), url.as_str())) } else { None }).collect(); @@ -66,7 +70,7 @@ fn spans(ui: &mut egui::Ui, spans: &[Span], size: f32, color: Color32) { for (label, url) in links { // The text above already shows the label; the button says where it goes. let to = format!("↗ {}", host(url)); - ui.hyperlink_to(RichText::new(to).size(size - 1.0), url).on_hover_text(format!("{label}: {url}")); + ui.hyperlink_to(RichText::new(to).size(size - (t.font_button - t.font_body)), url).on_hover_text(format!("{label}: {url}")); } }); } @@ -75,24 +79,28 @@ fn spans(ui: &mut egui::Ui, spans: &[Span], size: f32, color: Color32) { /// Draw `src` as Markdown. `slot`: the conversation item it belongs to. pub fn show(ui: &mut egui::Ui, slot: usize, src: &str) { let t = Tokens::get(); - let size = 13.0; + let size = t.font_button; let parsed = blocks(ui.ctx(), slot, src); for block in parsed.iter() { match block { Block::Paragraph(s) => spans(ui, s, size, t.text), Block::Heading { level, spans: s } => { let size = match level { - 1 => 17.0, - 2 => 15.0, - _ => 14.0, + 1 => t.font_article_heading, + 2 => t.font_subtitle, + _ => t.font_heading, }; ui.add_space(4.0); - ui.add(egui::Label::new(RichText::new(markdown::plain(s)).size(size).strong().color(t.text)).selectable(true).wrap()); + ui.add(egui::Label::new(RichText::new(markdown::plain(s)).size(size).semibold().color(t.text)).selectable(true).wrap()); } Block::Code { text, .. } => { egui::Frame::NONE.fill(t.field).corner_radius(4.0).inner_margin(Margin::same(6)).show(ui, |ui| { ui.set_width(ui.available_width()); - ui.add(egui::Label::new(RichText::new(text).monospace().size(size - 1.0).color(t.text)).selectable(true).wrap()); + ui.add( + egui::Label::new(RichText::new(text).monospace().size(size - (t.font_button - t.font_body)).color(t.text)) + .selectable(true) + .wrap(), + ); if ui.small_button(tr("ui.agent_chat_copy")).clicked() { ui.ctx().copy_text(text.clone()); } @@ -116,7 +124,7 @@ pub fn show(ui: &mut egui::Ui, slot: usize, src: &str) { } Block::Pre(text) => { egui::ScrollArea::horizontal().id_salt(("agent_chat_pre", slot, text.len())).show(ui, |ui| { - ui.add(egui::Label::new(RichText::new(text).monospace().size(size - 2.0).color(t.text)).selectable(true)); + ui.add(egui::Label::new(RichText::new(text).monospace().size(t.font_small).color(t.text)).selectable(true)); }); } Block::Rule => { diff --git a/crates/ui-egui/src/agent_chat/panel.rs b/crates/ui-egui/src/agent_chat/panel.rs index 8f6c792c2d..0b108f1779 100644 --- a/crates/ui-egui/src/agent_chat/panel.rs +++ b/crates/ui-egui/src/agent_chat/panel.rs @@ -2,10 +2,12 @@ //! or in Preferences), resizable, folded to a thin strip with Minimise, closed with ×. Its side, //! width and fold are preferences, so they come back next time. +use crate::fonts::RichTextExt as _; use egui::{Align, Align2, Color32, Id, Layout, Rect, RichText, Sense, Stroke, pos2, vec2}; use super::*; use crate::theme::Tokens; +use crate::ui_kit; pub const MIN_WIDTH: f32 = 280.0; pub const MAX_WIDTH: f32 = 760.0; @@ -109,7 +111,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) { .size_range(MIN_WIDTH..=MAX_WIDTH) .resizable(true) .frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(8, 6))) - .show(ui, |ui| body(app, ui, left)); + .show(ui, |ui| ui_kit::scope(ui, |ui| body(app, ui, left))); crate::scenario::publish_rect("agent_chat:panel", r.response.rect); app.agent_chat.panel_rect = Some(r.response.rect); #[cfg(not(target_arch = "wasm32"))] @@ -134,13 +136,13 @@ fn picture_strip(ui: &mut egui::Ui, n: usize, pictures: &[Picture]) -> Option texture, // Being decoded: a spinner the size of a thumbnail's corner, until it is in. Shown::Loading => { - ui.add(egui::Spinner::new().size(THUMB_MAX.1 / 2.0)); + ui.add(egui::Spinner::new().size(t.space_24)); continue; } // Not a readable picture: its name, or the plain caption. Shown::Failed => { let caption = if picture.note.is_empty() { tr("ui.agent_chat_view_attached").to_string() } else { picture.note.clone() }; - ui.label(RichText::new(caption).color(t.text_dim).size(11.0)); + ui.label(RichText::new(caption).color(t.text_dim).size(t.font_small)); continue; } }; @@ -184,9 +186,9 @@ fn gallery_window(app: &mut SolveApp, ctx: &egui::Context) { let modal = egui::Modal::new(Id::new("sc_agent_chat_gallery")).show(ctx, |ui| { ui.set_max_width(max.x); ui.horizontal(|ui| { - let prev = ui.add_enabled(count > 1, egui::Button::new("‹")); + let prev = ui_kit::button(ui, tr("ui.agent_chat_previous_picture"), ui_kit::Button::Enabled(count > 1)); crate::scenario::publish_handle("agent_chat:gallery_prev", prev.rect.center()); - let next = ui.add_enabled(count > 1, egui::Button::new("›")); + let next = ui_kit::button(ui, tr("ui.agent_chat_next_picture"), ui_kit::Button::Enabled(count > 1)); crate::scenario::publish_handle("agent_chat:gallery_next", next.rect.center()); if prev.clicked() { step = -1; @@ -203,7 +205,7 @@ fn gallery_window(app: &mut SolveApp, ctx: &egui::Context) { crate::scenario::publish_rect("agent_chat:gallery_picture", resp.rect); } Shown::Loading => { - ui.add(egui::Spinner::new().size(32.0)); + ui.add(egui::Spinner::new().size(t.space_24)); } Shown::Failed => { ui.label(RichText::new(tr("ui.agent_chat_view_attached")).color(t.text_dim)); @@ -242,7 +244,7 @@ fn drop_hint(ctx: &egui::Context, area: Rect) { let painter = ctx.layer_painter(egui::LayerId::new(egui::Order::Foreground, Id::new("sc_agent_chat_drop_hint"))); let zone = area.shrink(6.0); painter.rect(zone, 10.0, t.accent.gamma_multiply(0.16), Stroke::new(2.0, t.accent), egui::StrokeKind::Inside); - let font = egui::FontId::proportional(14.0); + let font = egui::FontId::proportional(Tokens::get().font_heading); let text = painter.layout(tr("ui.agent_chat_drop_hint").to_string(), font, t.text, (zone.width() - 24.0).max(40.0)); let at = zone.center() - text.size() / 2.0; painter.galley(at, text, t.text); @@ -301,7 +303,7 @@ fn window(app: &mut SolveApp, ctx: &egui::Context) { ctx.show_viewport_immediate(egui::ViewportId::from_hash_of("sc_agent_chat_window"), builder, |ui, class| { let t = Tokens::get(); let frame = egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(8, 6)); - let r = egui::CentralPanel::default().frame(frame).show(ui, |ui| body(app, ui, false)); + let r = egui::CentralPanel::default().frame(frame).show(ui, |ui| ui_kit::scope(ui, |ui| body(app, ui, false))); // The picture viewer opens over this window, where the click was, not over the main one. gallery_window(app, ui.ctx()); if class == egui::ViewportClass::EmbeddedWindow { @@ -359,7 +361,7 @@ fn glyph_button(ui: &mut egui::Ui, kind: &str, tip: &str) -> egui::Response { let p = ui.painter(); match kind { "minimize" => { - p.line_segment([c + vec2(-5.0, 4.0), c + vec2(5.0, 4.0)], s); + ui_kit::disclosure(p, c, true, t.text); } "close" => { p.line_segment([c + vec2(-4.5, -4.5), c + vec2(4.5, 4.5)], s); @@ -415,14 +417,20 @@ fn strip(app: &mut SolveApp, ui: &mut egui::Ui) { if resp.hovered() { ui.painter().rect_filled(button, 4.0, t.hover); } - crate::icons::paint(ui.painter(), button.shrink(4.0), "agent_chat", t.icon, t.icon_fill, t.accent); + ui_kit::disclosure(ui.painter(), button.center(), false, t.text); let state = app.agent_chat.state(); ui.painter().circle_filled(pos2(r.center().x, button.bottom() + 10.0), 3.5, state_color(state)); let waiting = app.agent_chat.waiting(); if waiting > 0 { let at = button.right_top() + vec2(-3.0, 3.0); ui.painter().circle_filled(at, 6.5, t.warning); - ui.painter().text(at, Align2::CENTER_CENTER, waiting.min(9).to_string(), egui::FontId::proportional(9.5), t.panel); + ui.painter().text( + at, + Align2::CENTER_CENTER, + waiting.min(9).to_string(), + egui::FontId::proportional(crate::theme::Tokens::get().font_badge), + t.panel, + ); } ui.painter().line_segment( if app.preferences.agent_chat_side == "left" { [r.right_top(), r.right_bottom()] } else { [r.left_top(), r.left_bottom()] }, @@ -451,7 +459,9 @@ fn header(app: &mut SolveApp, ui: &mut egui::Ui, left: bool) { } } } - let l = ui.label(RichText::new(tr("ui.agent_chat")).strong().size(14.0)); + let l = ui.label( + RichText::new(tr("ui.agent_chat")).semibold().color(crate::theme::Tokens::get().text).size(crate::theme::Tokens::get().font_heading), + ); grip.union(l.rect) }; let mut s = state.label().to_string(); @@ -495,9 +505,9 @@ fn header(app: &mut SolveApp, ui: &mut egui::Ui, left: bool) { #[cfg(not(target_arch = "wasm32"))] { let b = if state.running() { - ui.button(tr("ui.agent_chat_stop")).on_hover_text(tr("ui.agent_chat_stop_tip")) + ui_kit::button(ui, tr("ui.agent_chat_stop"), ui_kit::Button::Normal).on_hover_text(tr("ui.agent_chat_stop_tip")) } else { - ui.button(tr("ui.agent_chat_connect")) + ui_kit::button(ui, tr("ui.agent_chat_connect"), ui_kit::Button::Normal) }; crate::scenario::publish_handle("agent_chat:connect", b.rect.center()); if b.clicked() { @@ -539,7 +549,7 @@ fn dock_drag(app: &mut SolveApp, ui: &egui::Ui, drag: &egui::Response, left: boo zone.center(), Align2::CENTER_CENTER, tr(if to_left { "ui.agent_chat_dock_left" } else { "ui.agent_chat_dock_right" }), - egui::FontId::proportional(14.0), + egui::FontId::proportional(crate::theme::Tokens::get().font_heading), t.text, ); } @@ -570,17 +580,16 @@ fn desktop_body(app: &mut SolveApp, ui: &mut egui::Ui) { } let state = app.agent_chat.state(); if app.agent_chat.confirm_new { - let t = Tokens::get(); - egui::Frame::NONE.fill(t.accent_soft).corner_radius(6.0).inner_margin(egui::Margin::same(6)).show(ui, |ui| { + ui_kit::scope(ui, |ui| { ui.set_width(ui.available_width()); - ui.label(tr("ui.agent_chat_new_confirm")); + ui_kit::text(ui, tr("ui.agent_chat_new_confirm"), ui_kit::Text::Report("new_chat", false)); ui.horizontal(|ui| { - let yes = ui.button(tr("ui.agent_chat_new_yes")); + let yes = ui_kit::button(ui, tr("ui.agent_chat_new"), ui_kit::Button::Normal); crate::scenario::publish_handle("agent_chat:new_confirm", yes.rect.center()); if yes.clicked() { new_chat(app); } - if ui.button(tr("ui.agent_chat_cancel")).clicked() { + if ui_kit::button(ui, tr("ui.agent_chat_cancel"), ui_kit::Button::Normal).clicked() { app.agent_chat.confirm_new = false; } }); @@ -615,16 +624,18 @@ fn desktop_body(app: &mut SolveApp, ui: &mut egui::Ui) { if focused { super::paste::handle(app, ui); } - let edit = egui::TextEdit::multiline(&mut app.agent_chat.input) - .hint_text(tr(if busy { "ui.agent_chat_placeholder_busy" } else { "ui.agent_chat_placeholder" })) - .desired_rows(2) - .desired_width(f32::INFINITY) - .id_salt("sc_agent_chat_input"); let r = egui::ScrollArea::vertical() .id_salt("sc_agent_chat_input_scroll") .max_height(edit_max) .auto_shrink([false, true]) - .show(ui, |ui| ui.add(edit)) + .show(ui, |ui| { + ui_kit::message_field( + ui, + Id::new("sc_agent_chat_input"), + &mut app.agent_chat.input, + tr(if busy { "ui.agent_chat_placeholder_busy" } else { "ui.agent_chat_placeholder" }), + ) + }) .inner; crate::scenario::publish_rect("agent_chat:input", r.rect); // macOS shows a window's cursors only while it is the active window: over the text box of a @@ -640,8 +651,7 @@ fn desktop_body(app: &mut SolveApp, ui: &mut egui::Ui) { add_menu(app, ui); // A message waits for files still being read. let reading = !app.agent_chat.loading.is_empty(); - let send_b = ui - .add_enabled(!reading, egui::Button::new(tr(if busy { "ui.agent_chat_queue" } else { "ui.agent_chat_send" }))) + let send_b = ui_kit::button(ui, tr(if busy { "ui.agent_chat_queue" } else { "ui.agent_chat_send" }), ui_kit::Button::Enabled(!reading)) .on_disabled_hover_text(tr("ui.agent_chat_files_loading")); crate::scenario::publish_handle("agent_chat:send", send_b.rect.center()); send_clicked = send_b.clicked(); @@ -649,7 +659,8 @@ fn desktop_body(app: &mut SolveApp, ui: &mut egui::Ui) { // A picture of the view goes with the message to agents that take pictures. let images = app.agent_chat.images || !state.running(); let attach = ui - .add_enabled(images, egui::Checkbox::new(&mut app.agent_chat.attach_view, tr("ui.agent_chat_attach_view"))) + .add_enabled_ui(images, |ui| ui_kit::checkbox(ui, tr("ui.agent_chat_attach_view"), &mut app.agent_chat.attach_view)) + .inner .on_hover_text(tr("ui.agent_chat_attach_view_tip")) .on_disabled_hover_text(tr("ui.agent_chat_no_images")); crate::scenario::publish_handle("agent_chat:attach", attach.rect.center()); @@ -693,32 +704,10 @@ pub(super) fn login_chip(ui: &mut egui::Ui, login: &str) { _ => return, }; egui::Frame::NONE.stroke(Stroke::new(1.0, color)).corner_radius(8.0).inner_margin(egui::Margin::symmetric(6, 1)).show(ui, |ui| { - ui.label(RichText::new(text).color(color).size(11.0)); + ui.label(RichText::new(text).color(color).size(crate::theme::Tokens::get().font_small)); }); } -/// A small mark for an agent's kind: a terminal prompt for a program, a cloud for an endpoint. -pub(super) fn kind_icon(ui: &mut egui::Ui, kind: agents::Kind) { - let t = Tokens::get(); - let (r, _) = ui.allocate_exact_size(vec2(16.0, 14.0), Sense::hover()); - let p = ui.painter(); - let s = Stroke::new(1.3, t.text_dim); - match kind { - agents::Kind::Acp => { - p.rect_stroke(r.shrink(1.0), 2.0, s, egui::StrokeKind::Inside); - p.line_segment([r.left_top() + vec2(4.0, 4.0), r.left_top() + vec2(7.0, 7.0)], s); - p.line_segment([r.left_top() + vec2(7.0, 7.0), r.left_top() + vec2(4.0, 10.0)], s); - p.line_segment([r.left_top() + vec2(8.5, 10.0), r.left_top() + vec2(12.0, 10.0)], s); - } - agents::Kind::Api => { - let c = r.center(); - p.circle_stroke(c + vec2(-3.0, 1.5), 3.2, s); - p.circle_stroke(c + vec2(1.0, -1.0), 4.2, s); - p.circle_stroke(c + vec2(4.0, 2.0), 2.8, s); - } - } -} - /// The header's second row: the agent this conversation uses (a compact switcher), the agent's /// own mode when it has modes, and the state of its login. #[cfg(not(target_arch = "wasm32"))] @@ -736,31 +725,29 @@ fn switcher(app: &mut SolveApp, ui: &mut egui::Ui) { (ui.available_width() - if app.agent_chat.modes.is_empty() { 4.0 } else { 116.0 } - if models { 146.0 } else { 0.0 }).clamp(90.0, 260.0); let mut picked: Option = None; let mut add = false; - let combo = egui::ComboBox::from_id_salt("sc_agent_switch").width(w).selected_text(RichText::new(label).strong()).show_ui(ui, |ui| { - for e in &all { - let sel = current.as_ref().is_some_and(|c| c.id == e.id); - let r = ui.horizontal(|ui| { - kind_icon(ui, e.kind); - let r = ui.selectable_label(sel, &e.name); - ui.label(RichText::new(e.detail()).color(t.text_dim).size(11.0)); - login_chip(ui, e.login); - r - }); - crate::scenario::publish_handle(&format!("agent_chat:agent:{}", e.id), r.inner.rect.center()); - if r.inner.clicked() { - picked = Some(e.id.clone()); - } - } - ui.separator(); - if ui.selectable_label(false, tr("ui.ai_add_agent")).clicked() { - add = true; - } - if ui.selectable_label(false, tr("ui.ai_settings")).clicked() { - crate::prefs::open_at(app, crate::prefs::PAGE_AI); - } - }); - crate::scenario::publish_handle("agent_chat:switcher", combo.response.rect.center()); - combo.response.on_hover_text(tr("ui.ai_switch_tip")); + let combo = ui + .allocate_ui(vec2(w, t.control_height), |ui| { + ui_kit::dropdown_menu(ui, "sc_agent_switch", &label, |ui| { + for e in &all { + let sel = current.as_ref().is_some_and(|c| c.id == e.id); + let r = ui_kit::list_picker(ui, &e.id, &e.name, sel).on_hover_text(format!("{} {}", e.detail(), e.login)); + crate::scenario::publish_handle(&format!("agent_chat:agent:{}", e.id), r.rect.center()); + if r.clicked() { + picked = Some(e.id.clone()); + } + } + ui.separator(); + if ui_kit::list_picker(ui, "add", tr("ui.ai_add_agent"), false).clicked() { + add = true; + } + if ui_kit::list_picker(ui, "settings", tr("ui.ai_settings"), false).clicked() { + crate::prefs::open_at(app, crate::prefs::PAGE_AI); + } + }) + }) + .inner; + crate::scenario::publish_handle("agent_chat:switcher", combo.rect.center()); + combo.on_hover_text(tr("ui.ai_switch_tip")); if add { super::settings::open_add(app); } @@ -775,15 +762,19 @@ fn switcher(app: &mut SolveApp, ui: &mut egui::Ui) { let modes = app.agent_chat.modes.clone(); let cur = modes.iter().find(|(id, _)| *id == app.agent_chat.mode).map_or_else(|| app.agent_chat.mode.clone(), |(_, n)| n.clone()); let mut pick: Option = None; - let r = egui::ComboBox::from_id_salt("sc_agent_mode").width(100.0).selected_text(cur).show_ui(ui, |ui| { - for (id, name) in &modes { - if ui.selectable_label(*id == app.agent_chat.mode, name).clicked() { - pick = Some(id.clone()); - } - } - }); - crate::scenario::publish_handle("agent_chat:mode", r.response.rect.center()); - r.response.on_hover_text(tr("ui.ai_agent_mode_tip")); + let r = ui + .allocate_ui(vec2(t.form_label_width, t.control_height), |ui| { + ui_kit::dropdown_menu(ui, "sc_agent_mode", &cur, |ui| { + for (id, name) in &modes { + if ui_kit::list_picker(ui, id, name, *id == app.agent_chat.mode).clicked() { + pick = Some(id.clone()); + } + } + }) + }) + .inner; + crate::scenario::publish_handle("agent_chat:mode", r.rect.center()); + r.on_hover_text(tr("ui.ai_agent_mode_tip")); if let Some(m) = pick { set_mode(app, &m); } @@ -793,32 +784,35 @@ fn switcher(app: &mut SolveApp, ui: &mut egui::Ui) { let list = app.agent_chat.models.clone(); let cur = list.iter().find(|m| m.id == app.agent_chat.model).map_or_else(|| app.agent_chat.model.clone(), |m| m.name.clone()); let mut pick: Option = None; - let r = egui::ComboBox::from_id_salt("sc_agent_model").width(140.0).truncate().selected_text(cur).show_ui(ui, |ui| { - for m in &list { - let row = ui.selectable_label(m.id == app.agent_chat.model, &m.name); - let row = if m.description.is_empty() { row } else { row.on_hover_text(&m.description) }; - crate::scenario::publish_handle(&format!("agent_chat:model:{}", m.id), row.rect.center()); - if row.clicked() { - pick = Some(m.id.clone()); - } - } - }); - crate::scenario::publish_handle("agent_chat:model", r.response.rect.center()); - r.response.on_hover_text(tr("ui.agent_chat_model_tip")); + let r = ui + .allocate_ui(vec2(t.form_label_width, t.control_height), |ui| { + ui_kit::dropdown_menu(ui, "sc_agent_model", &cur, |ui| { + for m in &list { + let row = ui_kit::list_picker(ui, &m.id, &m.name, m.id == app.agent_chat.model); + let row = if m.description.is_empty() { row } else { row.on_hover_text(&m.description) }; + crate::scenario::publish_handle(&format!("agent_chat:model:{}", m.id), row.rect.center()); + if row.clicked() { + pick = Some(m.id.clone()); + } + } + }) + }) + .inner; + crate::scenario::publish_handle("agent_chat:model", r.rect.center()); + r.on_hover_text(tr("ui.agent_chat_model_tip")); if let Some(m) = pick { set_model(app, &m); } } }); if let Some(a) = &app.agent_chat.auth_status { - ui.label(RichText::new(a).color(t.text_dim).size(11.0)); + ui.label(RichText::new(a).color(t.text_dim).size(crate::theme::Tokens::get().font_small)); } } /// Before the first connection: what's installed, how to install the rest, and Add agent. #[cfg(not(target_arch = "wasm32"))] fn picker(app: &mut SolveApp, ui: &mut egui::Ui) { - let t = Tokens::get(); let open = app.agent_chat.picking.unwrap_or(!app.agent_chat.state().running() && app.agent_chat.items.is_empty()); if !open { return; @@ -826,33 +820,23 @@ fn picker(app: &mut SolveApp, ui: &mut egui::Ui) { let all = agents::entries(app); let current = agents::current(app); ui.horizontal(|ui| { - ui.label(RichText::new(tr("ui.agent_chat_pick")).strong()); + ui_kit::text(ui, tr("ui.agent_chat_pick"), ui_kit::Text::Inline); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { let refreshing = app.agent_chat.detecting.is_some(); - if ui.add_enabled(!refreshing, egui::Button::new(tr("ui.agent_chat_refresh")).small()).clicked() { + if ui_kit::button(ui, tr("ui.agent_chat_refresh"), ui_kit::Button::Enabled(!refreshing)).clicked() { refresh(app, ui.ctx()); } - if ui.small_button(tr("ui.ai_add_agent")).clicked() { + if ui_kit::button(ui, tr("ui.ai_add_agent"), ui_kit::Button::Small).clicked() { super::settings::open_add(app); } }); }); if app.agent_chat.detecting.is_some() { - ui.label(RichText::new(tr("ui.agent_chat_detecting")).color(t.text_dim)); + ui_kit::text(ui, tr("ui.agent_chat_detecting"), ui_kit::Text::Note("detecting")); } for e in &all { let selected = current.as_ref().is_some_and(|c| c.id == e.id); - let fill = if selected { t.accent_soft } else { Color32::TRANSPARENT }; - let r = egui::Frame::NONE.fill(fill).corner_radius(5.0).inner_margin(egui::Margin::symmetric(6, 3)).show(ui, |ui| { - ui.set_width(ui.available_width()); - ui.horizontal(|ui| { - kind_icon(ui, e.kind); - ui.label(RichText::new(&e.name).strong().color(t.text)); - ui.label(RichText::new(e.detail()).color(t.text_dim).size(11.0)); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| login_chip(ui, e.login)); - }); - }); - let resp = ui.interact(r.response.rect, Id::new(("sc_agent_pick", &e.id)), Sense::click()); + let resp = ui_kit::list_picker(ui, &e.id, &e.name, selected).on_hover_text(format!("{} {}", e.detail(), e.login)); crate::scenario::publish_handle(&format!("agent_chat:pick:{}", e.id), resp.rect.center()); if resp.clicked() { app.agent_chat.agent_id = Some(e.id.clone()); @@ -861,66 +845,63 @@ fn picker(app: &mut SolveApp, ui: &mut egui::Ui) { let missing: Vec = app.agent_chat.detection.as_ref().map(|d| d.missing.clone()).unwrap_or_default(); if !missing.is_empty() { let heading = if all.is_empty() { tr("ui.agent_chat_none_found") } else { tr("ui.agent_chat_not_installed") }; - egui::CollapsingHeader::new(RichText::new(heading).color(t.text_dim)).id_salt("sc_agent_missing").default_open(all.is_empty()).show( - ui, - |ui| { - for m in missing { - ui.horizontal(|ui| { - ui.label(RichText::new(m.name).strong()); - if ui.small_button(tr("ui.agent_chat_copy")).on_hover_text(m.install).clicked() { - ui.ctx().copy_text(m.install.to_string()); - } - }); - ui.add(egui::Label::new(RichText::new(m.install).monospace().size(11.0).color(t.text_dim)).selectable(true).wrap()); - } - }, - ); + ui_kit::section(ui, "sc_agent_missing", heading, all.is_empty(), |ui| { + for m in missing { + ui.horizontal(|ui| { + ui_kit::text(ui, m.name, ui_kit::Text::Inline); + if ui_kit::button(ui, tr("ui.agent_chat_copy"), ui_kit::Button::Small).on_hover_text(m.install).clicked() { + ui.ctx().copy_text(m.install.to_string()); + } + }); + ui_kit::text(ui, m.install, ui_kit::Text::Report(m.name, false)); + } + }); } } #[cfg(not(target_arch = "wasm32"))] fn auth_box(app: &mut SolveApp, ui: &mut egui::Ui) { - let t = Tokens::get(); - egui::Frame::group(ui.style()).stroke(Stroke::new(1.0, t.warning)).show(ui, |ui| { + ui_kit::section(ui, "authentication", tr("ui.agent_chat_auth_title"), true, |ui| { ui.set_width(ui.available_width()); - ui.label(RichText::new(tr("ui.agent_chat_auth_title")).strong()); - ui.label(RichText::new(tr("ui.agent_chat_auth_needed")).color(t.text_dim)); + ui_kit::text(ui, tr("ui.agent_chat_auth_needed"), ui_kit::Text::Note("auth_needed")); let methods = app.agent_chat.auth.clone(); let base = app.agent_chat.argv.iter().map(|a| quote(a)).collect::>().join(" "); if methods.is_empty() { - ui.label(tr("ui.agent_chat_auth_none")); + ui_kit::text(ui, tr("ui.agent_chat_auth_none"), ui_kit::Text::Note("auth_none")); } for m in methods { - ui.add_space(4.0); + ui.add_space(crate::theme::Tokens::get().space_4); let desc = m.description.trim(); - ui.label(RichText::new(if desc.is_empty() || desc == m.name { m.name.clone() } else { format!("{} — {desc}", m.name) }).strong()); + ui_kit::text( + ui, + &if desc.is_empty() || desc == m.name { m.name.clone() } else { format!("{} — {desc}", m.name) }, + ui_kit::Text::Report(&m.id, false), + ); match &m.kind { acp::AuthKind::Terminal { args, .. } => { - ui.label(tr("ui.agent_chat_auth_terminal")); + ui_kit::text(ui, tr("ui.agent_chat_auth_terminal"), ui_kit::Text::Note("terminal")); let cmd = format!("{base} {}", args.iter().map(|a| quote(a)).collect::>().join(" ")); ui.horizontal_wrapped(|ui| { - egui::Frame::NONE.fill(t.toolbar).corner_radius(4.0).inner_margin(egui::Margin::symmetric(6, 3)).show(ui, |ui| { - ui.add(egui::Label::new(RichText::new(&cmd).monospace().size(11.0)).selectable(true).wrap()); - }); - if ui.small_button(tr("ui.agent_chat_copy")).clicked() { + ui_kit::text(ui, &cmd, ui_kit::Text::Report("auth_command", false)); + if ui_kit::button(ui, tr("ui.agent_chat_copy"), ui_kit::Button::Small).clicked() { ui.ctx().copy_text(cmd.clone()); } }); } acp::AuthKind::Agent => { - let b = ui.button(trf("ui.agent_chat_auth_agent", &[("name", &m.name)])); + let b = ui_kit::button(ui, trf("ui.agent_chat_auth_agent", &[("name", &m.name)]), ui_kit::Button::Normal); crate::scenario::publish_handle(&format!("agent_chat:auth:{}", m.id), b.rect.center()); if b.clicked() { app.agent_chat.authenticate(&m.id); } } acp::AuthKind::EnvVar { vars } => { - ui.label(trf("ui.agent_chat_auth_env", &[("vars", &vars.join(", "))])); + ui_kit::text(ui, &trf("ui.agent_chat_auth_env", &[("vars", &vars.join(", "))]), ui_kit::Text::Report("auth_environment", false)); } } } - ui.add_space(4.0); - if ui.button(tr("ui.agent_chat_auth_retry")).clicked() { + ui.add_space(crate::theme::Tokens::get().space_4); + if ui_kit::button(ui, tr("ui.agent_chat_auth_retry"), ui_kit::Button::Normal).clicked() { connect(app, ui.ctx()); } }); @@ -950,7 +931,7 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { } } else if *image { // Taken when the agent starts on the message: nothing to show yet. - ui.label(RichText::new(tr("ui.agent_chat_view_attached")).color(t.text_dim).size(11.0)); + ui.label(RichText::new(tr("ui.agent_chat_view_attached")).color(t.text_dim).size(t.font_small)); } files_line(ui, files.iter().map(String::as_str)); }); @@ -981,7 +962,7 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { Item::Permission { title, options, answer: given, .. } => { egui::Frame::group(ui.style()).stroke(Stroke::new(1.0, t.accent)).show(ui, |ui| { ui.set_width(ui.available_width()); - ui.label(RichText::new(trf("ui.agent_chat_permission", &[("title", title)])).strong()); + ui.label(RichText::new(trf("ui.agent_chat_permission", &[("title", title)])).semibold().color(crate::theme::Tokens::get().text)); match given { Some(a) => { ui.label(RichText::new(trf("ui.agent_chat_answered", &[("option", a)])).color(t.text_dim)); @@ -989,9 +970,11 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { None => { ui.horizontal_wrapped(|ui| { for o in options { - let b = egui::Button::new(&o.name); - let b = if o.kind.starts_with("allow") { b.fill(t.accent_soft) } else { b }; - let r = ui.add(b); + let r = if o.kind.starts_with("allow") { + ui_kit::primary_button(ui, &o.name) + } else { + ui_kit::button(ui, &o.name, ui_kit::Button::Normal) + }; crate::scenario::publish_handle(&format!("agent_chat:permission:{}", o.id), r.rect.center()); if r.clicked() { answer = Some((n, Some(o.id.clone()))); @@ -1005,8 +988,8 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { Item::Approval { id, title, tool, answer: given } => { egui::Frame::group(ui.style()).stroke(Stroke::new(1.5, t.warning)).show(ui, |ui| { ui.set_width(ui.available_width()); - ui.label(RichText::new(tr("ui.agent_chat_approval")).strong()); - ui.add(egui::Label::new(RichText::new(title).monospace().size(11.5)).selectable(true).wrap()); + ui.label(RichText::new(tr("ui.agent_chat_approval")).semibold().color(crate::theme::Tokens::get().text)); + ui.add(egui::Label::new(RichText::new(title).monospace().size(crate::theme::Tokens::get().font_caption)).selectable(true).wrap()); match given { Some(true) => { ui.label(RichText::new(tr("ui.agent_chat_allowed")).color(t.text_dim)); @@ -1016,9 +999,10 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { } None => { ui.horizontal(|ui| { - let allow = ui.add(egui::Button::new(tr("ui.agent_chat_allow")).fill(t.accent_soft)); - let always = ui.button(tr("ui.ai_always_allow")).on_hover_text(trf("ui.ai_always_allow_tip", &[("tool", tool)])); - let deny = ui.button(tr("ui.agent_chat_deny")); + let allow = ui_kit::primary_button(ui, tr("ui.agent_chat_allow")); + let always = ui_kit::button(ui, tr("ui.ai_always_allow"), ui_kit::Button::Normal) + .on_hover_text(trf("ui.ai_always_allow_tip", &[("tool", tool)])); + let deny = ui_kit::button(ui, tr("ui.agent_chat_deny"), ui_kit::Button::Normal); crate::scenario::publish_handle("agent_chat:allow", allow.rect.center()); crate::scenario::publish_handle("agent_chat:always", always.rect.center()); crate::scenario::publish_handle("agent_chat:deny", deny.rect.center()); @@ -1039,11 +1023,11 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { Item::Changes { steps, doc, undone } => { ui.horizontal(|ui| { let text = trf("ui.agent_chat_changes", &[("n", &steps.to_string())]); - ui.label(RichText::new(text).color(t.text_dim).size(11.5)); + ui.label(RichText::new(text).color(t.text_dim).size(crate::theme::Tokens::get().font_caption)); if *undone { - ui.label(RichText::new(tr("ui.agent_chat_undone")).color(t.text_dim).size(11.5)); + ui.label(RichText::new(tr("ui.agent_chat_undone")).color(t.text_dim).size(crate::theme::Tokens::get().font_caption)); } else if Arc::ptr_eq(doc, ¤t_doc) { - let b = ui.button(tr("ui.agent_chat_undo_changes")).on_hover_text(tr("ui.ai_undo_tip")); + let b = ui_kit::button(ui, tr("ui.agent_chat_undo_changes"), ui_kit::Button::Normal).on_hover_text(tr("ui.ai_undo_tip")); crate::scenario::publish_handle("agent_chat:undo", b.rect.center()); if b.clicked() { undo = Some(n); @@ -1071,15 +1055,15 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { ui.set_width(ui.available_width()); ui.horizontal(|ui| { let tag = if stopped && !busy { "ui.agent_chat_queued_stopped" } else { "ui.agent_chat_queued" }; - ui.label(RichText::new(tr(tag)).color(t.accent).strong().size(11.0)); + ui.label(RichText::new(tr(tag)).color(t.accent).semibold().size(crate::theme::Tokens::get().font_small)); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - let x = ui.add(egui::Button::new(RichText::new("×").size(14.0)).frame(false)).on_hover_text(tr("ui.agent_chat_remove")); + let x = ui_kit::button(ui, "×", ui_kit::Button::Small).on_hover_text(tr("ui.agent_chat_remove")); crate::scenario::publish_handle(&format!("agent_chat:pending_remove:{n}"), x.rect.center()); if x.clicked() { remove = Some(n); } if editing.as_ref().is_none_or(|(i, _)| *i != n) { - let e = ui.add(egui::Button::new(RichText::new(tr("ui.agent_chat_edit")).size(11.5)).frame(false)); + let e = ui_kit::button(ui, tr("ui.agent_chat_edit"), ui_kit::Button::Small); crate::scenario::publish_handle(&format!("agent_chat:pending_edit:{n}"), e.rect.center()); if e.clicked() { start_edit = Some((n, p.text.clone())); @@ -1089,13 +1073,13 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { }); match (&editing, edit_buf.as_mut()) { (Some((i, _)), Some(buf)) if *i == n => { - let te = ui.add(egui::TextEdit::multiline(buf).desired_rows(2).desired_width(f32::INFINITY)); + let te = ui_kit::message_field(ui, Id::new(("sc_agent_pending_input", n)), buf, ""); crate::scenario::publish_rect("agent_chat:pending_input", te.rect); ui.horizontal(|ui| { - if ui.button(tr("ui.agent_chat_save")).clicked() { + if ui_kit::button(ui, tr("ui.agent_chat_save"), ui_kit::Button::Normal).clicked() { edit_done = Some((n, Some(buf.clone()))); } - if ui.button(tr("ui.agent_chat_cancel")).clicked() { + if ui_kit::button(ui, tr("ui.agent_chat_cancel"), ui_kit::Button::Normal).clicked() { edit_done = Some((n, None)); } }); @@ -1105,7 +1089,7 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { ui.add(egui::Label::new(RichText::new(&p.text).color(t.text)).selectable(true).wrap()); } if p.image { - ui.label(RichText::new(tr("ui.agent_chat_view_attached")).color(t.text_dim).size(11.0)); + ui.label(RichText::new(tr("ui.agent_chat_view_attached")).color(t.text_dim).size(crate::theme::Tokens::get().font_small)); } files_line(ui, p.files.iter().map(|f| f.name.as_str())); } @@ -1115,11 +1099,11 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { if !app.agent_chat.pending.is_empty() && editing.is_none() { ui.horizontal(|ui| { if busy { - let b = ui.button(tr("ui.agent_chat_send_now")).on_hover_text(tr("ui.agent_chat_send_now_tip")); + let b = ui_kit::button(ui, tr("ui.agent_chat_send_now"), ui_kit::Button::Normal).on_hover_text(tr("ui.agent_chat_send_now_tip")); crate::scenario::publish_handle("agent_chat:send_now", b.rect.center()); send_now_clicked = b.clicked(); } else { - let b = ui.add(egui::Button::new(tr("ui.agent_chat_send")).fill(t.accent_soft)); + let b = ui_kit::primary_button(ui, tr("ui.agent_chat_send")); crate::scenario::publish_handle("agent_chat:send_queue", b.rect.center()); send_queue_clicked = b.clicked(); } @@ -1134,7 +1118,7 @@ fn conversation(app: &mut SolveApp, ui: &mut egui::Ui) { ui.add_space(12.0); ui.label(RichText::new(tr("ui.agent_chat_intro")).color(t.text_dim)); ui.add_space(6.0); - ui.label(RichText::new(tr("ui.agent_chat_privacy")).color(t.text_dim).size(11.0)); + ui.label(RichText::new(tr("ui.agent_chat_privacy")).color(t.text_dim).size(crate::theme::Tokens::get().font_small)); } if let Some((n, text)) = edit_done { if let (Some(text), Some(p)) = (text.filter(|t| !t.trim().is_empty()), app.agent_chat.pending.get_mut(n)) { @@ -1173,7 +1157,7 @@ fn files_line<'a>(ui: &mut egui::Ui, names: impl Iterator) { let list = names.collect::>().join(", "); if !list.is_empty() { let t = Tokens::get(); - ui.add(egui::Label::new(RichText::new(trf("ui.agent_chat_files", &[("files", &list)])).color(t.text_dim).size(11.0)).wrap()); + ui.add(egui::Label::new(RichText::new(trf("ui.agent_chat_files", &[("files", &list)])).color(t.text_dim).size(t.font_small)).wrap()); } } @@ -1248,11 +1232,11 @@ fn loading_chip(ui: &mut egui::Ui, name: &str, importing: bool, i: usize, w: f32 ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 4.0; let doing = tr(if importing { "ui.agent_chat_file_importing" } else { "ui.agent_chat_file_loading" }); - let mark = ui.add(egui::Spinner::new().size(16.0)).on_hover_text(doing).rect.width(); + let mark = ui.add(egui::Spinner::new().size(t.form_icon_size)).on_hover_text(doing).rect.width(); let name_w = (inner - mark - 18.0 - 8.0).max(8.0); ui.allocate_ui(vec2(name_w, 20.0), |ui| { ui.set_width(name_w); - ui.add(egui::Label::new(RichText::new(name).size(11.5).color(t.text_dim)).truncate()) + ui.add(egui::Label::new(RichText::new(name).size(t.font_small).color(t.text_dim)).truncate()) }) .inner .on_hover_text(name); @@ -1295,14 +1279,20 @@ fn chip(ui: &mut egui::Ui, thumbs: &mut std::collections::HashMap) -> Test { Test::Running(rx) } -fn test_line(ui: &mut egui::Ui, t: &Option<&Test>) { - let tk = Tokens::get(); - match t { +fn test_line(ui: &mut egui::Ui, test: &Option<&Test>) { + match test { Some(Test::Running(_)) => { - ui.label(RichText::new(tr("ui.ai_testing")).color(tk.text_dim).size(11.0)); + ui_kit::text(ui, tr("ui.ai_testing"), ui_kit::Text::Note("testing")); } - Some(Test::Done(Ok(m))) => { - ui.label(RichText::new(m.clone()).color(tk.accent).size(11.0)); + Some(Test::Done(Ok(message))) => { + ui_kit::text(ui, message, ui_kit::Text::Report("test_ok", false)); } - Some(Test::Done(Err(e))) => { - ui.add(egui::Label::new(RichText::new(e).color(tk.error).size(11.0)).wrap()); + Some(Test::Done(Err(error))) => { + ui_kit::text(ui, error, ui_kit::Text::Report("test_error", true)); } None => {} } } -fn section(ui: &mut egui::Ui, title: &str) { - ui.add_space(10.0); - ui.label(RichText::new(title).strong().size(13.0)); - ui.separator(); -} - -fn note(ui: &mut egui::Ui, text: &str) { - let t = Tokens::get(); - ui.add(egui::Label::new(RichText::new(text).size(11.0).color(t.text_dim)).wrap()); -} - /// The AI page of Settings. pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) { - let t = Tokens::get(); - let r = ui.checkbox(&mut app.preferences.ai_features, RichText::new(tr("ui.ai_show_features")).strong()); + let r = ui_kit::checkbox(ui, tr("ui.ai_show_features"), &mut app.preferences.ai_features); crate::scenario::publish_handle("prefs:ai_features", r.rect.center()); - note(ui, tr("ui.ai_show_features_note")); + ui_kit::text(ui, tr("ui.ai_show_features_note"), ui_kit::Text::Note("features")); if cfg!(target_arch = "wasm32") { - note(ui, tr("ui.agent_chat_desktop_only")); + ui_kit::text(ui, tr("ui.agent_chat_desktop_only"), ui_kit::Text::Note("desktop")); return; } if !app.preferences.ai_features { return; } - ui.add_enabled_ui(true, |ui| { - let r = ui.checkbox(&mut app.preferences.ai_toolbar_button, tr("ui.ai_toolbar_button")); - crate::scenario::publish_handle("prefs:ai_toolbar_button", r.rect.center()); - }); - note(ui, tr("ui.ai_toolbar_button_note")); + let r = ui_kit::checkbox(ui, tr("ui.ai_toolbar_button"), &mut app.preferences.ai_toolbar_button); + crate::scenario::publish_handle("prefs:ai_toolbar_button", r.rect.center()); + ui_kit::text(ui, tr("ui.ai_toolbar_button_note"), ui_kit::Text::Note("toolbar")); #[cfg(not(target_arch = "wasm32"))] { if app.agent_chat.detection.is_none() && app.agent_chat.detecting.is_none() { @@ -140,153 +123,139 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) { } agents_section(app, ui); } - section(ui, tr("ui.ai_permissions")); - for m in Mode::ALL { - let r = ui.radio_value(&mut app.preferences.ai_permissions, m, m.label()); - crate::scenario::publish_handle(&format!("prefs:ai_mode:{}", m.id()), r.rect.center()); - note( - ui, - tr(match m { - Mode::Ask => "ui.ai_mode_ask_note", - Mode::Edits => "ui.ai_mode_edits_note", - Mode::All => "ui.ai_mode_all_note", - }), - ); - } - if app.preferences.ai_permissions == Mode::All { - egui::Frame::NONE.stroke(Stroke::new(1.0, t.warning)).corner_radius(4.0).inner_margin(egui::Margin::same(6)).show(ui, |ui| { - ui.add(egui::Label::new(RichText::new(tr("ui.ai_mode_all_warning")).color(t.warning)).wrap()); - }); - } - note(ui, tr("ui.ai_modes_scope")); - // "Always allow" answers. - let remembered: Vec<(String, Vec)> = - app.preferences.agent_always_allow.iter().filter(|(_, v)| !v.is_empty()).map(|(k, v)| (k.clone(), v.clone())).collect(); - ui.add_space(4.0); - ui.label(RichText::new(tr("ui.ai_always_allowed")).strong()); - if remembered.is_empty() { - note(ui, tr("ui.ai_always_allowed_none")); - } - let names: std::collections::HashMap = agents::entries(app).into_iter().map(|e| (e.id, e.name)).collect(); - for (agent, tools) in remembered { - ui.horizontal_wrapped(|ui| { - ui.label(names.get(&agent).cloned().unwrap_or_else(|| agent.clone())); - for tool in &tools { - ui.label(RichText::new(tool).monospace().size(11.0).color(t.text_dim)); + ui_kit::section(ui, "permissions", tr("ui.ai_permissions"), true, |ui| { + for mode in Mode::ALL { + let r = ui_kit::list_picker(ui, mode.id(), mode.label(), app.preferences.ai_permissions == mode); + crate::scenario::publish_handle(&format!("prefs:ai_mode:{}", mode.id()), r.rect.center()); + if r.clicked() { + app.preferences.ai_permissions = mode; } - if ui.small_button(tr("ui.ai_reset")).clicked() { - app.preferences.agent_always_allow.remove(&agent); + ui_kit::text( + ui, + tr(match mode { + Mode::Ask => "ui.ai_mode_ask_note", + Mode::Edits => "ui.ai_mode_edits_note", + Mode::All => "ui.ai_mode_all_note", + }), + ui_kit::Text::Note(mode.id()), + ); + } + if app.preferences.ai_permissions == Mode::All { + ui_kit::text(ui, tr("ui.ai_mode_all_warning"), ui_kit::Text::Report("permissions_warning", true)); + } + ui_kit::text(ui, tr("ui.ai_modes_scope"), ui_kit::Text::Note("scope")); + ui_kit::section(ui, "always_allowed", tr("ui.ai_always_allowed"), true, |ui| { + let remembered: Vec<_> = + app.preferences.agent_always_allow.iter().filter(|(_, v)| !v.is_empty()).map(|(k, v)| (k.clone(), v.clone())).collect(); + if remembered.is_empty() { + ui_kit::text(ui, tr("ui.ai_always_allowed_none"), ui_kit::Text::Note("none")); + } + let names: std::collections::HashMap<_, _> = agents::entries(app).into_iter().map(|e| (e.id, e.name)).collect(); + for (agent, tools) in remembered { + ui_kit::label_actions(ui, names.get(&agent).unwrap_or(&agent), |ui| { + if ui_kit::button(ui, tr("ui.ai_reset"), ui_kit::Button::Small).clicked() { + app.preferences.agent_always_allow.remove(&agent); + } + }); + ui_kit::text(ui, &tools.join(", "), ui_kit::Text::Report(&agent, false)); } }); - } - section(ui, tr("ui.ai_panel")); - egui::Grid::new("sc_ai_panel_grid").num_columns(2).spacing([16.0, 6.0]).show(ui, |ui| { - ui.label(tr("ui.agent_chat_side")); - ui.horizontal(|ui| { - let p = &mut app.preferences; - let (left, popped) = (p.agent_chat_side == "left", p.agent_chat_popped); - if ui.radio(left && !popped, tr("ui.agent_chat_left")).clicked() { - p.agent_chat_side = "left".into(); - p.agent_chat_popped = false; - } - if ui.radio(!left && !popped, tr("ui.agent_chat_right")).clicked() { - p.agent_chat_side = "right".into(); - p.agent_chat_popped = false; - } - #[cfg(not(target_arch = "wasm32"))] - if ui.radio(popped, tr("ui.agent_chat_own_window")).on_hover_text(tr("ui.agent_chat_pop_out")).clicked() { - p.agent_chat_popped = true; - } - }); - ui.end_row(); - ui.label(tr("ui.agent_chat_turn_limit")); - ui.add(egui::DragValue::new(&mut app.preferences.agent_chat_turn_limit).range(1..=200)); - ui.end_row(); }); - ui.add_space(6.0); - note(ui, tr("ui.agent_chat_privacy_note")); + ui_kit::section(ui, "panel", tr("ui.ai_panel"), true, |ui| { + ui_kit::row(ui, tr("ui.agent_chat_side"), |ui| { + let p = &mut app.preferences; + let mut selected = if p.agent_chat_popped { 2 } else { usize::from(p.agent_chat_side != "left") }; + let options = [tr("ui.agent_chat_left"), tr("ui.agent_chat_right"), tr("ui.agent_chat_own_window")]; + if ui_kit::segmented(ui, &mut selected, &options, Default::default()) { + p.agent_chat_popped = selected == 2; + if selected < 2 { + p.agent_chat_side = if selected == 0 { "left" } else { "right" }.into(); + } + } + }); + ui_kit::row(ui, tr("ui.agent_chat_turn_limit"), |ui| { + ui_kit::number_field(ui, &mut app.preferences.agent_chat_turn_limit, 1..=200, 1.0, ""); + }); + ui_kit::text(ui, tr("ui.agent_chat_privacy_note"), ui_kit::Text::Note("privacy")); + }); } /// The agents: found and added, with Test, Default, a permission override and Remove. #[cfg(not(target_arch = "wasm32"))] fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) { - let t = Tokens::get(); - section(ui, tr("ui.ai_agents")); for test in app.agent_chat.settings.tests.values_mut() { test.poll(); } let all = agents::entries(app); let d = &app.preferences.agent_default; let default = if all.iter().any(|e| &e.id == d) { Some(d.clone()) } else { all.first().map(|e| e.id.clone()) }; - if app.agent_chat.detecting.is_some() { - ui.label(RichText::new(tr("ui.agent_chat_detecting")).color(t.text_dim)); - } else if all.is_empty() { - note(ui, tr("ui.agent_chat_none_found")); - } let mut remove: Option = None; let mut test: Option = None; - for e in &all { - egui::Frame::NONE.stroke(Stroke::new(1.0, t.border)).corner_radius(5.0).inner_margin(egui::Margin::symmetric(8, 5)).show(ui, |ui| { - ui.set_width(ui.available_width()); - ui.horizontal(|ui| { - super::panel::kind_icon(ui, e.kind); - ui.label(RichText::new(&e.name).strong()); - ui.label(RichText::new(e.detail()).color(t.text_dim).size(11.0)); + ui_kit::section(ui, "agents", tr("ui.ai_agents"), true, |ui| { + if app.agent_chat.detecting.is_some() { + ui_kit::text(ui, tr("ui.agent_chat_detecting"), ui_kit::Text::Note("detecting")); + } else if all.is_empty() { + ui_kit::text(ui, tr("ui.agent_chat_none_found"), ui_kit::Text::Note("none")); + } + for e in &all { + ui_kit::section(ui, &e.id, &e.name, true, |ui| { + ui_kit::text(ui, &e.detail(), ui_kit::Text::Report("detail", false)); super::panel::login_chip(ui, e.login); if !e.found { - ui.label(RichText::new(tr("ui.ai_added")).color(t.text_dim).size(11.0)); + ui_kit::text(ui, tr("ui.ai_added"), ui_kit::Text::Note("added")); } - }); - ui.horizontal(|ui| { - let is_default = default.as_deref() == Some(e.id.as_str()); - if ui.radio(is_default, tr("ui.ai_default")).clicked() { - app.preferences.agent_default = e.id.clone(); - } - let b = ui.small_button(tr("ui.ai_test")); - crate::scenario::publish_handle(&format!("prefs:ai_test:{}", e.id), b.rect.center()); - if b.on_hover_text(tr(if e.kind == Kind::Acp { "ui.ai_test_acp_tip" } else { "ui.ai_test_api_tip" })).clicked() { - test = Some(e.clone()); - } - // Its own permission mode, or the global one. - let cur = app.preferences.agent_permissions.get(&e.id).copied(); - let shown = cur.map_or_else(|| tr("ui.ai_mode_global").to_string(), |m| m.label().to_string()); - let mut pick: Option> = None; - egui::ComboBox::from_id_salt(("sc_ai_mode", &e.id)).width(150.0).selected_text(shown).show_ui(ui, |ui| { - if ui.selectable_label(cur.is_none(), tr("ui.ai_mode_global")).clicked() { - pick = Some(None); - } - for m in Mode::ALL { - if ui.selectable_label(cur == Some(m), m.label()).clicked() { - pick = Some(Some(m)); - } + ui.horizontal_wrapped(|ui| { + if ui_kit::list_picker(ui, &e.id, tr("ui.ai_default"), default.as_deref() == Some(e.id.as_str())).clicked() { + app.preferences.agent_default = e.id.clone(); } }); - match pick { - Some(Some(m)) => { - app.preferences.agent_permissions.insert(e.id.clone(), m); + ui_kit::row(ui, tr("ui.ai_permissions"), |ui| { + let cur = app.preferences.agent_permissions.get(&e.id).copied(); + let shown = cur.map_or_else(|| tr("ui.ai_mode_global").to_string(), |m| m.label().to_string()); + let mut pick: Option> = None; + ui_kit::dropdown_menu(ui, ("sc_ai_mode", &e.id), &shown, |ui| { + if ui_kit::list_picker(ui, "global", tr("ui.ai_mode_global"), cur.is_none()).clicked() { + pick = Some(None); + } + for m in Mode::ALL { + if ui_kit::list_picker(ui, m.id(), m.label(), cur == Some(m)).clicked() { + pick = Some(Some(m)); + } + } + }); + match pick { + Some(Some(m)) => { + app.preferences.agent_permissions.insert(e.id.clone(), m); + } + Some(None) => { + app.preferences.agent_permissions.remove(&e.id); + } + None => {} } - Some(None) => { - app.preferences.agent_permissions.remove(&e.id); + }); + ui.horizontal_wrapped(|ui| { + let b = ui_kit::button(ui, tr("ui.ai_test"), ui_kit::Button::Normal); + crate::scenario::publish_handle(&format!("prefs:ai_test:{}", e.id), b.rect.center()); + if b.on_hover_text(tr(if e.kind == Kind::Acp { "ui.ai_test_acp_tip" } else { "ui.ai_test_api_tip" })).clicked() { + test = Some(e.clone()); } - None => {} - } - if !e.found && ui.small_button(tr("ui.ai_remove")).clicked() { - remove = Some(e.id.clone()); - } + if !e.found && ui_kit::button(ui, tr("ui.ai_remove"), ui_kit::Button::Normal).clicked() { + remove = Some(e.id.clone()); + } + }); + test_line(ui, &app.agent_chat.settings.tests.get(&e.id)); }); - test_line(ui, &app.agent_chat.settings.tests.get(&e.id)); + } + ui.horizontal_wrapped(|ui| { + let b = ui_kit::button(ui, tr("ui.ai_add_agent"), ui_kit::Button::Normal); + crate::scenario::publish_handle("prefs:ai_add", b.rect.center()); + if b.clicked() { + open_add(app); + } + if ui_kit::button(ui, tr("ui.agent_chat_refresh"), ui_kit::Button::Enabled(app.agent_chat.detecting.is_none())).clicked() { + refresh(app, &ui.ctx().clone()); + } }); - ui.add_space(3.0); - } - ui.horizontal(|ui| { - let b = ui.button(tr("ui.ai_add_agent")); - crate::scenario::publish_handle("prefs:ai_add", b.rect.center()); - if b.clicked() { - open_add(app); - } - if ui.add_enabled(app.agent_chat.detecting.is_none(), egui::Button::new(tr("ui.agent_chat_refresh"))).clicked() { - refresh(app, &ui.ctx().clone()); - } }); if let Some(e) = test { let key = if e.key { app.services.secrets.as_ref().and_then(|s| s.get(&e.id).ok().flatten()) } else { None }; @@ -322,145 +291,121 @@ pub fn dialog(app: &mut SolveApp, ctx: &egui::Context) { let mut open = true; let mut add = false; let mut test = false; - let t = Tokens::get(); - egui::Window::new(tr("ui.ai_add_agent_title")) - .id(egui::Id::new("sc_ai_add")) - .collapsible(false) - .resizable(false) - .default_width(460.0) - .pivot(egui::Align2::CENTER_CENTER) - .default_pos(ctx.content_rect().center()) - .open(&mut open) - .order(egui::Order::Foreground) - .show(ctx, |ui| { + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_ai_add"), "settings", tr("ui.ai_add_agent_title")) + .cards(2.0) + .fields() + .actions(tr("ui.ai_add"), "Cancel", None) + .show(ctx, true, |ui| { let Some(s) = app.agent_chat.settings.add.as_mut() else { return }; if let Some(test) = s.test.as_mut() { test.poll(); } - ui.horizontal(|ui| { - for (k, label) in - [(AddKind::Installed, "ui.ai_add_installed"), (AddKind::Custom, "ui.ai_add_custom"), (AddKind::Api, "ui.ai_add_api")] + let kinds = [(AddKind::Installed, "ui.ai_add_installed"), (AddKind::Custom, "ui.ai_add_custom"), (AddKind::Api, "ui.ai_add_api")]; + ui_kit::row(ui, tr("ui.ai_preset"), |ui| { + let mut selected = kinds.iter().position(|(k, _)| *k == s.kind).unwrap_or(0); + let labels = kinds.map(|(_, key)| tr(key)); + if ui_kit::segmented(ui, &mut selected, &labels, ui_kit::Segments { compact: true, ..Default::default() }) + && let Some((kind, _)) = kinds.get(selected) { - let r = ui.selectable_value(&mut s.kind, k, tr(label)); - crate::scenario::publish_handle(&format!("ai_add:{label}"), r.rect.center()); - if r.clicked() { - s.error = None; - s.test = None; - s.command.clear(); - s.name.clear(); - if k == AddKind::Api - && let Some((name, url, _)) = agents::API_PRESETS.get(s.preset) - { - s.name = name.to_string(); - s.base_url = url.to_string(); - } + s.kind = *kind; + s.error = None; + s.test = None; + } + for (_, key) in kinds { + if let Some(at) = crate::scenario::handle_at(&format!("dialog:{}:{}", tr("ui.ai_preset"), tr(key))) { + crate::scenario::publish_handle(&format!("ai_add:{key}"), at); } } }); - ui.separator(); - egui::Grid::new("sc_ai_add_grid").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| match s.kind { + match s.kind { AddKind::Installed => { - ui.label(tr("ui.agent_chat_pick")); - if found.is_empty() { - ui.label(RichText::new(tr("ui.agent_chat_none_found_short")).color(t.text_dim)); - } else { - let sel = found.get(s.installed).map(|e| e.name.clone()).unwrap_or_default(); - egui::ComboBox::from_id_salt("sc_ai_add_installed").width(260.0).selected_text(sel).show_ui(ui, |ui| { - for (i, e) in found.iter().enumerate() { - if ui.selectable_value(&mut s.installed, i, format!("{} {}", e.name, e.detail())).clicked() { - s.command = e.argv.iter().map(|a| super::panel::quote(a)).collect::>().join(" "); - s.name = format!("{} ({})", e.name, tr("ui.ai_copy_suffix")); + ui_kit::row(ui, tr("ui.agent_chat_pick"), |ui| { + if found.is_empty() { + ui_kit::text(ui, tr("ui.agent_chat_none_found_short"), ui_kit::Text::Inline); + } else { + let sel = found.get(s.installed).map(|e| e.name.clone()).unwrap_or_default(); + ui_kit::dropdown_menu(ui, "sc_ai_add_installed", &sel, |ui| { + for (i, e) in found.iter().enumerate() { + if ui_kit::list_picker(ui, &e.id, &format!("{} {}", e.name, e.detail()), s.installed == i).clicked() { + s.installed = i; + s.command = e.argv.iter().map(|a| super::panel::quote(a)).collect::>().join(" "); + s.name = format!("{} ({})", e.name, tr("ui.ai_copy_suffix")); + } } - } - }); - } - ui.end_row(); + }); + } + }); if s.command.is_empty() && let Some(e) = found.get(s.installed) { s.command = e.argv.iter().map(|a| super::panel::quote(a)).collect::>().join(" "); s.name = format!("{} ({})", e.name, tr("ui.ai_copy_suffix")); } - ui.label(tr("ui.ai_name")); - ui.add(egui::TextEdit::singleline(&mut s.name).desired_width(300.0)); - ui.end_row(); - ui.label(tr("ui.agent_chat_command")); - ui.add(egui::TextEdit::singleline(&mut s.command).desired_width(300.0)); - ui.end_row(); - ui.label(""); - ui.add(egui::Label::new(RichText::new(tr("ui.ai_add_installed_note")).size(11.0).color(t.text_dim)).wrap()); - ui.end_row(); + ui_kit::row(ui, tr("ui.ai_name"), |ui| { + ui_kit::text_field(ui, &mut s.name, ""); + }); + ui_kit::row(ui, tr("ui.agent_chat_command"), |ui| { + ui_kit::text_field(ui, &mut s.command, ""); + }); + ui_kit::text(ui, tr("ui.ai_add_installed_note"), ui_kit::Text::Note("installed")); } AddKind::Custom => { - ui.label(tr("ui.ai_name")); - ui.add(egui::TextEdit::singleline(&mut s.name).hint_text("My agent").desired_width(300.0)); - ui.end_row(); - ui.label(tr("ui.agent_chat_command")); - ui.add(egui::TextEdit::singleline(&mut s.command).hint_text("my-agent --acp").desired_width(300.0)); - ui.end_row(); - ui.label(""); - ui.add(egui::Label::new(RichText::new(tr("ui.agent_chat_custom_hint")).size(11.0).color(t.text_dim)).wrap()); - ui.end_row(); + ui_kit::row(ui, tr("ui.ai_name"), |ui| { + ui_kit::text_field(ui, &mut s.name, tr("ui.ai_name_hint")); + }); + ui_kit::row(ui, tr("ui.agent_chat_command"), |ui| { + ui_kit::text_field(ui, &mut s.command, tr("ui.ai_command_hint")); + }); + ui_kit::text(ui, tr("ui.agent_chat_custom_hint"), ui_kit::Text::Note("custom")); } AddKind::Api => { - ui.label(tr("ui.ai_preset")); - let presets: Vec = - agents::API_PRESETS.iter().map(|p| p.0.to_string()).chain(std::iter::once(tr("ui.ai_custom_url").to_string())).collect(); - let sel = presets.get(s.preset).cloned().unwrap_or_default(); - egui::ComboBox::from_id_salt("sc_ai_add_preset").width(200.0).selected_text(sel).show_ui(ui, |ui| { - for (i, p) in presets.iter().enumerate() { - if ui.selectable_value(&mut s.preset, i, p).clicked() { - if let Some((name, url, _)) = agents::API_PRESETS.get(i) { - s.base_url = url.to_string(); - s.name = name.to_string(); - } else { - s.base_url.clear(); + let presets: Vec<_> = agents::API_PRESETS.iter().map(|p| p.0).chain(std::iter::once(tr("ui.ai_custom_url"))).collect(); + ui_kit::row(ui, tr("ui.ai_preset"), |ui| { + ui_kit::dropdown_menu(ui, "sc_ai_add_preset", presets.get(s.preset).copied().unwrap_or_default(), |ui| { + for (index, preset) in presets.iter().enumerate() { + if ui_kit::list_picker(ui, preset, preset, s.preset == index).clicked() { + s.preset = index; + if let Some((name, url, _)) = agents::API_PRESETS.get(index) { + s.base_url = url.to_string(); + s.name = name.to_string(); + } else { + s.base_url.clear(); + } } } - } + }); }); - ui.end_row(); - ui.label(tr("ui.ai_name")); - ui.add(egui::TextEdit::singleline(&mut s.name).desired_width(300.0)); - ui.end_row(); - ui.label(tr("ui.ai_base_url")); - ui.add(egui::TextEdit::singleline(&mut s.base_url).hint_text("https://example.com/v1").desired_width(300.0)); - ui.end_row(); - ui.label(tr("ui.ai_model")); - ui.add(egui::TextEdit::singleline(&mut s.model).hint_text("qwen3:14b").desired_width(300.0)); - ui.end_row(); - ui.label(tr("ui.ai_key")); - ui.add_enabled( - has_keychain, - egui::TextEdit::singleline(&mut s.key).password(true).hint_text(tr("ui.ai_key_hint")).desired_width(300.0), - ); - ui.end_row(); - ui.label(""); - let note = if has_keychain { tr("ui.ai_key_note") } else { tr("ui.ai_no_keychain") }; - ui.add(egui::Label::new(RichText::new(note).size(11.0).color(t.text_dim)).wrap()); - ui.end_row(); - ui.label(""); - ui.horizontal(|ui| { - if ui.button(tr("ui.ai_test")).clicked() { - test = true; - } - test_line(ui, &s.test.as_ref()); + ui_kit::row(ui, tr("ui.ai_name"), |ui| { + ui_kit::text_field(ui, &mut s.name, ""); }); - ui.end_row(); + ui_kit::row(ui, tr("ui.ai_base_url"), |ui| { + ui_kit::text_field(ui, &mut s.base_url, tr("ui.ai_url_hint")); + }); + ui_kit::row(ui, tr("ui.ai_model"), |ui| { + ui_kit::text_field(ui, &mut s.model, tr("ui.ai_model_hint")); + }); + ui_kit::row(ui, tr("ui.ai_key"), |ui| { + ui.add_enabled_ui(has_keychain, |ui| { + let r = ui_kit::password_field(ui, egui::Id::new("sc_ai_add_key"), &mut s.key, tr("ui.ai_key_hint")); + crate::scenario::publish_target("ai_add:key", r.rect); + }); + }); + ui_kit::text(ui, tr(if has_keychain { "ui.ai_key_note" } else { "ui.ai_no_keychain" }), ui_kit::Text::Note("keychain")); + if ui_kit::button(ui, tr("ui.ai_test"), ui_kit::Button::Normal).clicked() { + test = true; + } + test_line(ui, &s.test.as_ref()); } - }); - if let Some(e) = &s.error { - ui.label(RichText::new(e).color(t.error)); } - ui.separator(); - ui.horizontal(|ui| { - let b = ui.add(egui::Button::new(tr("ui.ai_add")).fill(t.accent_soft)); - crate::scenario::publish_handle("ai_add:add", b.rect.center()); - if b.clicked() { - add = true; - } - }); + if let Some(e) = &s.error { + ui_kit::text(ui, e, ui_kit::Text::Report("add_error", true)); + } }); + if let Some(rect) = crate::scenario::rect_of("dialog:ok") { + crate::scenario::publish_handle("ai_add:add", rect.center()); + } + add |= action == ui_kit::Action::Ok; + open &= !closed && action != ui_kit::Action::Cancel; if test { #[cfg(not(target_arch = "wasm32"))] if let Some(s) = app.agent_chat.settings.add.as_ref() { @@ -546,3 +491,38 @@ fn add_agent(app: &mut SolveApp) -> Result<(), String> { app.preferences.agents.push(c); Ok(()) } + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod card_tests { + use super::*; + use crate::scenario::{self, Harness}; + use serde_json::json; + + #[test] + fn add_agent_card_retains_kind_handles_draft_and_validation() { + let mut h = Harness::new(); + h.app.home.open = false; + h.app.preferences.ai_features = true; + h.app.agent_chat.detection = Some(Default::default()); + open_add(&mut h.app); + h.frames(4); + h.step(&json!({"click":{"handle":"ai_add:ui.ai_add_custom"}})).unwrap(); + assert_eq!(h.app.agent_chat.settings.add.as_ref().unwrap().kind, AddKind::Custom); + h.step(&json!({"click":{"handle":"ai_add:add"}})).unwrap(); + assert!(h.app.agent_chat.settings.add.as_ref().unwrap().error.is_some()); + let add = h.app.agent_chat.settings.add.as_mut().unwrap(); + add.name = "Local fixture".into(); + add.command = "solidcraft-fixture-agent --acp".into(); + h.frames(3); + h.step(&json!({"click":{"handle":"ai_add:add"}})).unwrap(); + assert!(h.app.agent_chat.settings.add.is_none()); + assert!(h.app.preferences.agents.iter().any(|e| e.name == "Local fixture")); + open_add(&mut h.app); + h.frames(3); + h.step(&json!({"click":{"handle":"ai_add:ui.ai_add_api"}})).unwrap(); + assert!(scenario::handle_at("ai_add:key").is_some()); + assert!(h.app.services.secrets.is_none(), "this fixture has no keychain"); + h.step(&json!({"key":"Escape"})).unwrap(); + assert!(h.app.agent_chat.settings.add.is_none()); + } +} diff --git a/crates/ui-egui/src/agent_chat/tests.rs b/crates/ui-egui/src/agent_chat/tests.rs index 26246fd727..193abff08d 100644 --- a/crates/ui-egui/src/agent_chat/tests.rs +++ b/crates/ui-egui/src/agent_chat/tests.rs @@ -79,7 +79,7 @@ fn ask(tx: &Sender, method: &str, params: Value) -> Receiver (SolveApp, Script, egui::Context) { let script = Script::default(); app.agent_chat.backend = Some(Box::new(script.clone())); app.agent_chat.state = Some(State::Ready); - (app, script, egui::Context::default()) + (app, script, crate::fonts::test_context()) } fn ended(script: &Script, reason: &str) { @@ -631,7 +631,7 @@ done let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default()); app.agent_chat.backend = Some(Box::new(acp_client::AcpBackend::start(&argv, start).unwrap())); app.agent_chat.state = Some(State::Starting); - let ctx = egui::Context::default(); + let ctx = crate::fonts::test_context(); let until = |app: &mut SolveApp, what: &str, done: &dyn Fn(&SolveApp) -> bool| { let t = std::time::Instant::now(); while !done(app) { @@ -724,7 +724,7 @@ done let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default()); app.agent_chat.backend = Some(Box::new(acp_client::AcpBackend::start(&argv, start).unwrap())); app.agent_chat.state = Some(State::Starting); - let ctx = egui::Context::default(); + let ctx = crate::fonts::test_context(); let until = |app: &mut SolveApp, done: &dyn Fn(&SolveApp) -> bool| { let t = std::time::Instant::now(); while !done(app) { @@ -842,7 +842,7 @@ done app.agent_chat.state = Some(State::Starting); app.agent_chat.running_id = "fake".into(); app.preferences.agent_models.insert("fake".into(), "smart".into()); - let ctx = egui::Context::default(); + let ctx = crate::fonts::test_context(); let t = std::time::Instant::now(); loop { poll(&mut app, &ctx); diff --git a/crates/ui-egui/src/agent_cursor.rs b/crates/ui-egui/src/agent_cursor.rs index 530f285d6e..3e929fa2ca 100644 --- a/crates/ui-egui/src/agent_cursor.rs +++ b/crates/ui-egui/src/agent_cursor.rs @@ -101,8 +101,15 @@ pub fn speed_slider(ui: &mut egui::Ui, speed: &mut Speed) -> egui::Response { Speed::Times(x) => x.clamp(1.0, 5.0), Speed::Instant => 5.5, }; - let r = ui.add( - egui::Slider::new(&mut v, 1.0..=5.5).step_by(0.5).custom_formatter(|v, _| if v > 5.25 { "Instant".into() } else { Speed::Times(v).label() }), + let r = crate::ui_kit::slider( + ui, + &mut v, + 1.0..=5.5, + crate::ui_kit::Slider { + step: Some(0.5), + format: |v| if v > 5.25 { crate::i18n::tr_label("Instant").into() } else { Speed::Times(v).label() }, + ..Default::default() + }, ); if r.changed() { *speed = if v > 5.25 { Speed::Instant } else { Speed::Times(v) }; diff --git a/crates/ui-egui/src/bom_panel.rs b/crates/ui-egui/src/bom_panel.rs index 5bab059de9..d57c05522b 100644 --- a/crates/ui-egui/src/bom_panel.rs +++ b/crates/ui-egui/src/bom_panel.rs @@ -89,17 +89,26 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { let bom = d.rows(app); let rows: Vec = bom["rows"].as_array().cloned().unwrap_or_default(); let (mut export, mut apply_row) = (None, None); - let title = RichText::new(crate::i18n::tr_label("Bill of Materials")).strong().size(13.0); + let title = RichText::new(crate::i18n::tr_label("Bill of Materials")).strong().size(t.font_button); crate::frame::window(ctx, title, crate::frame::Width::Wide).id(egui::Id::new("sc_bom")).open(&mut open).resizable(true).show(ctx, |ui| { - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { let was = (d.top, d.by_definition, d.group_children); - ui.selectable_value(&mut d.top, false, "Parts only"); - ui.selectable_value(&mut d.top, true, "Top level"); + let mut levels = usize::from(d.top); + let levels_width = crate::ui_kit::ContentWidth::button(ui, "Parts only").max(crate::ui_kit::ContentWidth::button(ui, "Top level")) * 2.0; + ui.allocate_ui(egui::vec2(levels_width, crate::ui_kit::row_height()), |ui| { + crate::ui_kit::segmented(ui, &mut levels, &["Parts only", "Top level"], Default::default()); + }); + d.top = levels == 1; ui.separator(); - ui.selectable_value(&mut d.by_definition, false, "By part number"); - ui.selectable_value(&mut d.by_definition, true, "By definition"); + let mut grouping = usize::from(d.by_definition); + let grouping_width = + crate::ui_kit::ContentWidth::button(ui, "By part number").max(crate::ui_kit::ContentWidth::button(ui, "By definition")) * 2.0; + ui.allocate_ui(egui::vec2(grouping_width, crate::ui_kit::row_height()), |ui| { + crate::ui_kit::segmented(ui, &mut grouping, &["By part number", "By definition"], Default::default()); + }); + d.by_definition = grouping == 1; ui.separator(); - ui.checkbox(&mut d.group_children, "Sub-parts in their part's row"); + crate::ui_kit::checkbox(ui, "Sub-parts in their part's row", &mut d.group_children); if was != (d.top, d.by_definition, d.group_children) { d.picked = None; } @@ -113,7 +122,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { ui.end_row(); for (i, row) in rows.iter().enumerate() { let picked = d.picked == Some(i); - if ui.selectable_label(picked, text(row, "item")).clicked() { + if crate::ui_kit::list_picker(ui, &i.to_string(), &text(row, "item"), picked).clicked() { d.picked = if picked { None } else { Some(i) }; d.fields = [text(row, "part_number"), text(row, "description"), text(row, "revision")]; } @@ -141,11 +150,13 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { egui::Grid::new("sc_bom_edit").num_columns(2).spacing([12.0, 6.0]).show(ui, |ui| { for (label, field) in ["Part number", "Description", "Revision"].into_iter().zip(d.fields.iter_mut()) { ui.label(label); - ui.add(egui::TextEdit::singleline(field).desired_width(280.0)); + ui.allocate_ui(egui::vec2(280.0, crate::ui_kit::row_height()), |ui| { + crate::ui_kit::cell_editor(ui, ui.make_persistent_id(label), field, label, false); + }); ui.end_row(); } }); - if ui.button("Apply").clicked() { + if crate::ui_kit::button(ui, "Apply", crate::ui_kit::Button::Normal).clicked() { apply_row = Some(row.clone()); } } @@ -155,7 +166,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { ui.separator(); ui.horizontal(|ui| { for (label, ext) in [("Export CSV…", "csv"), ("Export JSON…", "json")] { - if ui.button(label).clicked() { + if crate::ui_kit::button(ui, label, crate::ui_kit::Button::Normal).clicked() { export = Some(ext); } } diff --git a/crates/ui-egui/src/browser.rs b/crates/ui-egui/src/browser.rs index 7a429b3851..3c65076fd7 100644 --- a/crates/ui-egui/src/browser.rs +++ b/crates/ui-egui/src/browser.rs @@ -30,7 +30,7 @@ mod model_tests; use crate::icons::glyphs::badge; use std::collections::BTreeSet; -use egui::{Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, pos2, vec2}; +use egui::{Color32, FontId, Pos2, Rect, Sense, Stroke, pos2, vec2}; use serde_json::{Value, json}; use solidcraft_engine::Sel; use solidcraft_engine::doc::FeatureKind; @@ -59,6 +59,8 @@ pub fn drawn_occurrences() -> Vec<(Vec, String, Rect, bool)> { /// Browser state kept between frames. #[derive(Default)] pub struct TreeState { + /// The floating browser card is folded to its header. + pub panel_collapsed: bool, /// Geometry and assembly queries, independent of the camera, selection and row visibility. model: Option, /// Folders closed by the user (keys like `c0/bodies`). @@ -174,14 +176,14 @@ pub fn group_name(app: &SolveApp, id: u64) -> Option { pub fn group_items(app: &SolveApp, id: u64, scope: &str) -> Vec { let Some(g) = find_group(app, id) else { return Vec::new() }; let mut v = vec![ - Item::action("ui.rename", "Rename", "").with(json!({ "group": id })), + Item::action("ui.rename", "Rename", "rename").with(json!({ "group": id })), Item::action("ui.ungroup", "Ungroup", "folder").with(json!({ "group": id })), Item::sep(), // The group itself: its items keep their own state. Item::action("ui.folderVisible", "Show/Hide", "eye").key("V").with(json!({ "key": format!("{scope}/g{id}") })), ]; if g.folder == "bodies" { - v.push(Item::action("ui.isolate", "Isolate", "").with(json!({ "bodies": group_bodies(app, id) }))); + v.push(Item::action("ui.isolate", "Isolate", "body").with(json!({ "bodies": group_bodies(app, id) }))); } v } @@ -274,7 +276,11 @@ pub fn group_action(app: &mut SolveApp, action: &str, p: &Value) { // Drawing // --------------------------------------------------------------------------------------------- -const ROW_H: f32 = 22.0; +const ROW_H: f32 = 26.0; +/// The floating card's inner margin: the header highlight sits this far from the card's top, left +/// and right edges, so its corners are concentric with the card's. +const CARD_INSET: f32 = 4.0; +const HEADER_H: f32 = 26.0; const INDENT: f32 = 14.0; /// Opacity of a dimmed row's icon (a hidden body, sketch, origin item, …). const DIM_ICON_OPACITY: f32 = 0.4; @@ -387,9 +393,7 @@ pub(crate) fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp { let hover_on = resp.hover_pos().map_or(0, at); // Fold arrow. if let Some(open) = row.fold { - let ar = Rect::from_center_size(pos2(x + 6.0, cy), vec2(12.0, 12.0)); - let ink = if hover_on == 1 { t.text } else { t.text_dim }; - crate::icons::glyphs::disclosure(ui.painter(), ar, open, ink); + crate::ui_kit::disclosure(ui.painter(), pos2(x + 6.0, cy), open, if hover_on == 1 { t.text } else { t.text_dim }); out.fold = resp.clicked() && click_on == 1; crate::scenario::publish_handle(&format!("fold:{}", row.label), fold_r.center()); } @@ -397,11 +401,8 @@ pub(crate) fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp { // Eye. if let Some(vis) = row.eye { let er = Rect::from_center_size(pos2(x + 8.0, cy), vec2(15.0, 15.0)); - // A closed eye, or the eye of a faded row, draws faint like the row's icon. - icons::paint_icon(ui.painter(), er, "eye", if vis && !row.dim { 1.0 } else { DIM_ICON_OPACITY }); - if !vis { - ui.painter().line_segment([er.left_bottom() + vec2(2.0, -2.0), er.right_top() + vec2(-2.0, 2.0)], Stroke::new(1.4, t.text_dim)); - } + // Hidden or faded rows keep main's dimming, with the rounded suppression asset. + icons::paint_icon(ui.painter(), er, if vis { "eye" } else { "suppress" }, if vis && !row.dim { 0.85 } else { DIM_ICON_OPACITY }); out.eye = resp.clicked() && click_on == 2; crate::scenario::publish_handle(&format!("eye:{}", row.label), er.center()); } @@ -423,9 +424,11 @@ pub(crate) fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp { icons::paint_icon(ui.painter(), ir, row.icon, if row.dim { DIM_ICON_OPACITY } else { 1.0 }); x += 21.0; } - let col = row.color.unwrap_or(if row.dim { t.text_dim } else { t.text }); + let col = + if row.selected { crate::theme::TabAccent::current(ui.ctx()).ink } else { row.color.unwrap_or(if row.dim { t.text_dim } else { t.text }) }; let label_display = if row.name { crate::i18n::tr_name(row.label) } else { crate::i18n::tr_dyn(row.label) }; - let galley = ui.painter().layout_no_wrap(label_display, FontId::proportional(t.font_label), col); + let font = if row.selected { crate::fonts::semibold(t.font_label) } else { FontId::proportional(t.font_label) }; + let galley = ui.painter().layout_no_wrap(label_display, font, col); let lw = galley.size().x; ui.painter().galley(pos2(x, cy - galley.size().y / 2.0), galley, col); let mut bx = (x + lw + 6.0).min(r.right() - 16.0); @@ -669,8 +672,7 @@ fn browser_panel(app: &mut SolveApp, ui: &mut egui::Ui, timeline_above: bool) { DRAWN.with(|d| d.borrow_mut().clear()); OCCURRENCES.with(|d| d.borrow_mut().clear()); let mut acts = Actions::default(); - let remembered = app.preferences.browser_width; - let width = if remembered.is_finite() { remembered.clamp(MIN_BROWSER_WIDTH, MAX_BROWSER_WIDTH) } else { crate::prefs::DEFAULT_BROWSER_WIDTH }; + let width = browser_width(app.preferences.browser_width); let panel = egui::Panel::left("sc_browser") .exact_size(width) .resizable(false) @@ -700,9 +702,22 @@ fn browser_panel(app: &mut SolveApp, ui: &mut egui::Ui, timeline_above: bool) { ui.painter().hline(rect.x_range(), rect.center().y, egui::Stroke::new(1.0, t.border)); crate::scenario::publish_handle("left-history-divider", rect.center()); } - let arrow = if browser_collapsed { "▶" } else { "▼" }; - let response = - ui.add(egui::Button::new(RichText::new(format!("{arrow} BROWSER")).size(t.font_small).strong().color(t.text_dim)).frame(false)); + let (rect, response) = ui.allocate_exact_size(vec2(ui.available_width(), crate::ui_kit::row_height()), Sense::click()); + crate::ui_kit::disclosure( + ui.painter(), + pos2(rect.left() + t.form_section_icon_size / 2.0, rect.center().y), + !browser_collapsed, + t.text_dim, + ); + ui.painter().text( + pos2(rect.left() + t.form_section_icon_size + t.space_8, rect.center().y), + egui::Align2::LEFT_CENTER, + "BROWSER", + crate::fonts::semibold(t.font_small), + t.text_dim, + ); + response + .widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::CollapsingHeader, ui.is_enabled(), !browser_collapsed, "BROWSER")); crate::scenario::publish_handle("left-browser-toggle", response.rect.center()); if response.clicked() { app.preferences.timeline_left_browser_collapsed = !browser_collapsed; @@ -712,63 +727,123 @@ fn browser_panel(app: &mut SolveApp, ui: &mut egui::Ui, timeline_above: bool) { browser_rows(app, ui, &mut acts); } } else { - browser_contents(app, ui, &mut acts); + browser_contents(app, ui, &mut acts, false); } }); - // The sidebar's right edge is a drag handle; the width it is dragged to is remembered. - let rect = panel.response.rect; - let rail = egui::Rect::from_x_y_ranges(rect.right() - BROWSER_RAIL..=rect.right(), rect.y_range()); - let response = ui.interact(rail, egui::Id::new("sc_browser_rail"), egui::Sense::drag()).on_hover_cursor(egui::CursorIcon::ResizeHorizontal); + resize_browser(app, ui, panel.response.rect, width); + finish_browser(app, ui, acts); +} + +fn browser_width(remembered: f32) -> f32 { + if remembered.is_finite() { remembered.clamp(MIN_BROWSER_WIDTH, MAX_BROWSER_WIDTH) } else { crate::prefs::DEFAULT_BROWSER_WIDTH } +} + +fn resize_browser(app: &mut SolveApp, ui: &egui::Ui, rect: Rect, width: f32) { + let rail = Rect::from_x_y_ranges(rect.right() - BROWSER_RAIL..=rect.right(), rect.y_range()); + let response = ui.interact(rail, egui::Id::new("sc_browser_rail"), Sense::drag()).on_hover_cursor(egui::CursorIcon::ResizeHorizontal); if response.dragged() { let delta = ui.input(|input| input.pointer.delta().x); if delta.is_finite() { app.preferences.browser_width = (width + delta).clamp(MIN_BROWSER_WIDTH, MAX_BROWSER_WIDTH); } } - crate::scenario::publish_handle("left-browser-rail", egui::pos2(rect.right() - BROWSER_RAIL / 2.0, rect.center().y)); - finish_browser(app, ui, acts); + crate::scenario::publish_handle("left-browser-rail", pos2(rect.right() - BROWSER_RAIL / 2.0, rect.center().y)); } -/// The tree on a translucent card over the workspace, instead of a column that takes width from -/// it (Preferences: "Float it over the workspace"). Drawn before the central view, in a layer -/// above it, so the viewport keeps the whole window and the card sits on top of it. -pub fn floating(app: &mut SolveApp, ui: &mut egui::Ui, viewport: egui::Rect) { +/// One browser card for the floating shell and the docked shell's optional overlay. +/// The preference keeps its translucent overlay semantics; the floating shell always has a card. +pub fn floating(app: &mut SolveApp, ui: &mut egui::Ui, available: Rect) { let t = Tokens::get(); app.tree.order.clear(); DRAWN.with(|d| d.borrow_mut().clear()); OCCURRENCES.with(|d| d.borrow_mut().clear()); let mut acts = Actions::default(); - let remembered = app.preferences.browser_width; - let width = if remembered.is_finite() { remembered.clamp(MIN_BROWSER_WIDTH, MAX_BROWSER_WIDTH) } else { crate::prefs::DEFAULT_BROWSER_WIDTH }; - // Tall enough for the tree, short enough to leave the view room; the rows scroll inside. - let height = (viewport.height() * 0.7).clamp(160.0, 560.0); - let card = crate::frame::area(egui::Id::new("sc_browser_float")).fixed_pos(viewport.min + vec2(8.0, 8.0)).order(egui::Order::Middle).show( - ui.ctx(), - |ui| { - egui::Frame::new() - .fill(t.panel.gamma_multiply(BROWSER_FLOAT_ALPHA)) - .stroke(egui::Stroke::new(1.0, t.border)) - .corner_radius(6) - .inner_margin(egui::Margin::symmetric(8, 6)) - .show(ui, |ui| { - ui.set_width(width - 16.0); - ui.set_max_height(height); - ui.label(RichText::new(crate::i18n::tr_label("BROWSER")).size(11.0).strong().color(t.text_dim)); - ui.add_space(4.0); - // The card hugs the tree, so it hides as little of the view as it can. - browser_rows_hugging(app, ui, &mut acts); - }); - }, - ); - crate::scenario::publish_rect("browser:float", card.response.rect); + let gap = crate::shell::SHELL_GAP; + let top_gap = if app.ui.layout == crate::shell::LayoutMode::Docked { gap } else { 0.0 }; + let width = browser_width(app.preferences.browser_width); + let bounds = floating_bounds(available, app.shell.history, width, top_gap); + app.shell.browser = None; + let min_height = HEADER_H + 2.0 * (CARD_INSET + 1.0); + if bounds.is_positive() && bounds.height() >= min_height { + let fill = if app.preferences.browser_float { t.panel.gamma_multiply(BROWSER_FLOAT_ALPHA) } else { t.panel }; + let frame = crate::frame::shell_card(fill).inner_margin(CARD_INSET as i8); + let inner_width = (bounds.width() - frame.total_margin().sum().x).max(0.0); + let inner_height = (bounds.height() - frame.total_margin().sum().y).max(0.0); + let card = crate::frame::area(egui::Id::new("sc_browser_card")).fixed_pos(bounds.min).order(egui::Order::Middle).show(ui.ctx(), |ui| { + let response = frame.show(ui, |ui| { + ui.set_width(inner_width); + ui.set_max_height(inner_height); + browser_contents(app, ui, &mut acts, true); + }); + resize_browser(app, ui, response.response.rect, width); + }); + let rect = card.response.rect; + app.shell.browser = Some(rect); + crate::scenario::publish_rect("browser:card", rect); + // Preserve the overlay handle used by main's automation. + if app.preferences.browser_float { + crate::scenario::publish_rect("browser:float", rect); + } + } finish_browser(app, ui, acts); } -fn browser_contents(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Actions) { +fn floating_bounds(available: Rect, history: Option, width: f32, top_gap: f32) -> Rect { + let gap = crate::shell::SHELL_GAP; + let min = available.min + vec2(gap, top_gap); + let right = (available.right() - gap).max(min.x); + let bottom = history.map_or(available.bottom() - gap, |r| r.top() - gap).max(min.y); + Rect::from_min_max(min, pos2((min.x + width).min(right), bottom)) +} + +fn browser_contents(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Actions, collapsible: bool) { let t = Tokens::get(); - ui.label(RichText::new(crate::i18n::tr_label("BROWSER")).size(t.font_small).strong().color(t.text_dim)); - ui.add_space(t.space_4); - browser_rows(app, ui, acts); + // Only the floating card folds; the docked column keeps its full height. + browser_header(app, ui, collapsible); + if collapsible && app.tree.panel_collapsed { + return; + } + ui.painter().hline(ui.max_rect().x_range(), ui.cursor().top() + 1.0, egui::Stroke::new(1.0, t.toolbar.lerp_to_gamma(t.border, 0.5))); + ui.add_space(t.space_3); + if collapsible { + browser_rows_hugging(app, ui, acts); + } else { + browser_rows(app, ui, acts); + } +} + +/// The header: the title, the body count and (on the floating card) a chevron that folds the +/// card to just this row. The whole row is the button. +fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) { + let t = Tokens::get(); + let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), HEADER_H), Sense::hover()); + let response = ui.interact(rect, ui.id().with("browser_header"), if collapsible { Sense::click() } else { Sense::hover() }); + crate::scenario::publish_target("browser:header", rect); + let collapsed = collapsible && app.tree.panel_collapsed; + if collapsible && (response.hovered() || response.has_focus()) { + ui.painter().rect_filled(rect, t.radius_lg.saturating_sub(CARD_INSET as u8), t.hover); + } + let title = crate::i18n::tr("ui.browser_title").to_string(); + let kind = if collapsible { egui::WidgetType::Button } else { egui::WidgetType::Label }; + response.widget_info(|| egui::WidgetInfo::labeled(kind, true, &title)); + let galley = ui.painter().layout_no_wrap(title, crate::fonts::semibold(t.font_heading), t.text); + ui.painter().galley(pos2(rect.left() + t.space_6, rect.center().y - galley.size().y / 2.0), galley, t.text); + let mut right = rect.right() - t.space_6; + if collapsible { + crate::ui_kit::disclosure(ui.painter(), pos2(right - t.form_section_icon_size / 2.0, rect.center().y), !collapsed, t.text_dim); + right -= 18.0; + } + let bodies = app.session.model.state().bodies.len(); + let key = if bodies == 1 { "ui.browser_bodies_one" } else { "ui.browser_bodies_other" }; + let count = crate::i18n::format(key, &[("count", &bodies.to_string())]); + let galley = ui.painter().layout_no_wrap(count, FontId::proportional(t.font_body), t.text_dim); + ui.painter().galley(pos2(right - galley.size().x, rect.center().y - galley.size().y / 2.0), galley, t.text_dim); + if collapsible { + let tip = crate::i18n::tr(if collapsed { "ui.browser_expand" } else { "ui.browser_collapse" }); + if response.on_hover_text(tip).clicked() { + app.tree.panel_collapsed = !app.tree.panel_collapsed; + } + } } fn prepare_model(app: &mut SolveApp) { @@ -853,11 +928,10 @@ fn finish_browser(app: &mut SolveApp, ui: &mut egui::Ui, acts: Actions) { if let Some((at, target)) = acts.menu { crate::context_menu::open_for(app, at, target); } - occurrence_panel(app, ui); - capture_prompt(app, ui.ctx()); - canvas_panel(app, ui.ctx()); - let repair_anchor = app.viewport.rect.map(|rect| rect.right_top() + vec2(-16.0, 16.0)).unwrap_or_else(|| ui.ctx().content_rect().right_top()); - crate::sketch_attachment_ui::panel(app, ui.ctx(), repair_anchor); + crate::browser_prompts::occurrence_panel(app, ui); + crate::browser_prompts::capture_prompt(app, ui.ctx()); + crate::browser_canvas::panel(app, ui.ctx()); + crate::sketch_attachment_ui::panel(app, ui.ctx()); } fn component_name(app: &SolveApp, id: u64) -> String { @@ -1262,7 +1336,7 @@ pub fn view_items(app: &SolveApp, name: &str) -> Vec { Item::action("ui.restoreView", "Restore", "home").with(json!({ "view": name })), Item::action("ui.updateView", "Update to Current View", "perspective").with(json!({ "view": name })).on_if(saved), Item::sep(), - Item::action("ui.rename", "Rename", "").with(json!({ "view": name })).on_if(saved), + Item::action("ui.rename", "Rename", "rename").with(json!({ "view": name })).on_if(saved), Item::action("view.delete", "Delete", "delete").with(json!({ "view": name })).on_if(saved), ] } @@ -1443,7 +1517,7 @@ pub fn canvas_items(app: &SolveApp, id: u64) -> Vec { Item::action("ui.calibrateCanvas", "Calibrate", "measure").with(json!({ "canvas": id })), Item::sep(), Item::action("ui.delete", "Delete", "delete").with(json!({ "canvases": [id] })), - Item::action("ui.rename", "Rename", "").with(json!({ "canvas": id })), + Item::action("ui.rename", "Rename", "rename").with(json!({ "canvas": id })), Item::sep(), Item::action("ui.canvasVisible", "Show/Hide", "eye").key("V").with(json!({ "canvas": id, "visible": !visible })), ] @@ -1456,7 +1530,7 @@ pub struct CanvasPanel { pub canvas: u64, pub calibrate: bool, /// Undo depth when the panel opened. - depth: usize, + pub(crate) depth: usize, pub a: [f64; 2], pub b: [f64; 2], pub distance: f64, @@ -1477,86 +1551,6 @@ impl CanvasPanel { } } -fn canvas_panel(app: &mut SolveApp, ctx: &egui::Context) { - let Some(mut m) = app.tree.canvas_panel.clone() else { return }; - let Some(c) = app.session.doc.canvases.iter().find(|c| c.id == m.canvas).cloned() else { - app.tree.canvas_panel = None; - return; - }; - let mut open = true; - let mut edit: Option = None; - let mut done = false; - let title = if m.calibrate { format!("Calibrate: {}", c.name) } else { format!("Edit Canvas: {}", c.name) }; - crate::frame::window(ctx, title, crate::frame::Width::Normal) - .id(egui::Id::new("sc_canvas_panel")) - .pivot(egui::Align2::LEFT_TOP) - .default_pos(panel_pos(app)) - .open(&mut open) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - egui::Grid::new("sc_canvas_grid").num_columns(2).spacing([12.0, 6.0]).show(ui, |ui| { - if m.calibrate { - for (label, p) in [("Point A", &mut m.a), ("Point B", &mut m.b)] { - ui.label(label); - ui.horizontal(|ui| { - ui.add(crate::prefs::length_drag(&mut p[0], &app.session.doc.units, app.preferences.length_decimals)); - ui.add(crate::prefs::length_drag(&mut p[1], &app.session.doc.units, app.preferences.length_decimals)); - }); - ui.end_row(); - } - ui.label(crate::i18n::tr_label("Distance")); - ui.add(crate::prefs::length_drag(&mut m.distance, &app.session.doc.units, app.preferences.length_decimals).range(0.001..=1e6)); - ui.end_row(); - } else { - let (mut x, mut y, mut w, mut ang, mut op, mut flip) = (c.center.x, c.center.y, c.width, c.angle.to_degrees(), c.opacity, c.flip); - ui.label(crate::i18n::tr_label("Center X")); - let r1 = ui.add(crate::prefs::length_drag(&mut x, &app.session.doc.units, app.preferences.length_decimals)); - ui.end_row(); - ui.label(crate::i18n::tr_label("Center Y")); - let r2 = ui.add(crate::prefs::length_drag(&mut y, &app.session.doc.units, app.preferences.length_decimals)); - ui.end_row(); - ui.label(crate::i18n::tr_label("Width")); - let r3 = ui.add(crate::prefs::length_drag(&mut w, &app.session.doc.units, app.preferences.length_decimals).range(0.01..=1e6)); - ui.end_row(); - ui.label(crate::i18n::tr_label("Angle")); - let r4 = ui.add(egui::DragValue::new(&mut ang).speed(1.0).suffix(" deg").custom_parser(crate::prefs::parse_number)); - ui.end_row(); - ui.label(crate::i18n::tr_label("Opacity")); - let r5 = ui.add(egui::Slider::new(&mut op, 0.0..=1.0)); - ui.end_row(); - ui.label(crate::i18n::tr_label("Flip")); - let r6 = ui.checkbox(&mut flip, ""); - ui.end_row(); - if [r1, r2, r3, r4, r5, r6].iter().any(|r| r.changed()) { - edit = Some(json!({ "canvas": c.id, "center": [x, y], "width": w, "angle": ang, "opacity": op, "flip": flip })); - } - } - }); - ui.add_space(6.0); - if ui.button(if m.calibrate { "Calibrate" } else { "OK" }).clicked() { - done = true; - } - }); - if let Some(p) = edit - && app.run("canvas.edit", p).is_ok() - { - // One undo step for the whole edit. - if app.session.undo.len() > m.depth + 1 { - let keep = app.session.undo.get(m.depth).cloned(); - app.session.undo.truncate(m.depth); - app.session.undo.extend(keep); - } - } - if done && m.calibrate { - if app.run("canvas.calibrate", json!({ "canvas": c.id, "a": m.a, "b": m.b, "distance": m.distance })).is_ok() { - app.tree.canvas_panel = None; - } - return; - } - app.tree.canvas_panel = if done || !open { None } else { Some(m) }; -} - /// A browser click on a plane or axis: it goes to the open dialog's input if that takes it, /// otherwise it becomes the selection. fn pick_from_browser(app: &mut SolveApp, x: Sel) { @@ -2058,96 +2052,6 @@ fn apply_drop(app: &mut SolveApp, d: DropAction) { // Panels opened from the menus // --------------------------------------------------------------------------------------------- -/// Move/Copy of an occurrence: offsets and a rotation about Z, pending until Capture Position. -fn occurrence_panel(app: &mut SolveApp, ui: &mut egui::Ui) { - let Some(mut m) = app.tree.occurrence_move.clone() else { return }; - if !m.path.is_empty() - && let Err(error) = app.session.occurrence_placement(&m.path) - { - app.tree.occurrence_move = None; - app.set_status(error.to_string(), true); - return; - } - if m.baseline.is_none() { - m.had_pending = app.session.pending_moves.contains_key(&m.occurrence); - m.baseline = app - .session - .pending_moves - .get(&m.occurrence) - .copied() - .or_else(|| app.session.doc.occurrences.iter().find(|o| o.id == m.occurrence).map(|o| o.transform)); - } - let ctx = ui.ctx().clone(); - if m.pivot.is_none() { - m.pivot = if m.path.is_empty() { - occurrence_origin(app, m.occurrence) - } else { - app.session.occurrence_placement(&m.path).ok().map(|p| solidcraft_engine::doc::apply_point(&p.world, solidcraft_engine::geom::Vec3::ZERO)) - }; - } - let name = app.session.doc.occurrences.iter().find(|o| o.id == m.occurrence).map(|o| o.name.clone()).unwrap_or_default(); - let mut open = true; - let mut action: Option<&str> = None; - let before = (m.translate, m.angles); - crate::frame::window(&ctx, format!("Move: {name}"), crate::frame::Width::Normal) - .id(egui::Id::new("sc_occ_move")) - .pivot(egui::Align2::LEFT_TOP) - .default_pos(panel_pos(app)) - .open(&mut open) - .resizable(false) - .collapsible(false) - .show(&ctx, |ui| { - if !m.path.is_empty() && app.session.occurrence_scope_count(&m.path).is_ok_and(|n| n > 1) { - ui.label(crate::i18n::tr_label("This child is shared. Position changes apply to every copy of its parent.")); - } - egui::Grid::new("sc_occ_grid").num_columns(2).spacing([12.0, 6.0]).show(ui, |ui| { - for (k, axis) in ["X distance", "Y distance", "Z distance"].iter().enumerate() { - ui.label(*axis); - if let Some(v) = m.translate.get_mut(k) { - let response = ui.add(crate::prefs::length_drag(v, &app.session.doc.units, app.preferences.length_decimals)); - crate::scenario::publish_handle(&format!("occurrence-move:distance:{k}"), response.rect.center()); - } - ui.end_row(); - } - for (k, axis) in ["X angle", "Y angle", "Z angle"].iter().enumerate() { - ui.label(*axis); - if let Some(v) = m.angles.get_mut(k) { - let response = ui.add(egui::DragValue::new(v).speed(1.0).suffix(" deg").custom_parser(crate::prefs::parse_number)); - crate::scenario::publish_handle(&format!("occurrence-move:angle:{k}"), response.rect.center()); - } - ui.end_row(); - } - }); - ui.add_space(6.0); - ui.horizontal(|ui| { - if ui.button(crate::i18n::tr_label("Capture All Positions")).clicked() { - action = Some("capture"); - } - if ui.button(crate::i18n::tr_label("Revert All Positions")).clicked() { - action = Some("revert"); - } - }); - }); - if (m.translate, m.angles) != before { - apply_occurrence_move(app, &m); - } - match action { - Some("capture") => { - let _ = app.run("component.capture_position", json!({})); - app.tree.occurrence_move = None; - } - Some(_) => { - let _ = app.run("component.revert_position", json!({})); - app.tree.occurrence_move = None; - } - None if !open => { - revert_occurrence_dialog(app, &m); - app.tree.occurrence_move = None; - } - None => app.tree.occurrence_move = Some(m), - } -} - pub(crate) fn revert_occurrence_dialog(app: &mut SolveApp, m: &OccMove) { if m.had_pending { let _ = app.run("occurrence.move", json!({"occurrence":m.occurrence,"base_transform":m.baseline,"capture":false})); @@ -2275,9 +2179,12 @@ pub fn dims_overlay(app: &SolveApp, ctx: &egui::Context) { } } -/// Where the browser's panels open: the viewport's top left corner. -fn panel_pos(app: &SolveApp) -> Pos2 { - app.viewport.rect.map(|r| r.left_top() + vec2(16.0, 16.0)).unwrap_or(pos2(270.0, 140.0)) +/// Keep browser panels beside the browser and below the ribbon, including the floating shell. +pub(crate) fn panel_pos(app: &SolveApp) -> Pos2 { + app.viewport + .rect + .map(|r| crate::shell::content_rect(app, r).left_top() + vec2(Tokens::get().space_16, Tokens::get().space_16)) + .unwrap_or(pos2(270.0, 140.0)) } /// The Capture Position question stands for saving. @@ -2315,47 +2222,8 @@ pub fn answer_capture(app: &mut SolveApp, action: &str) -> Result<(), String> { Ok(()) } -fn capture_prompt(app: &mut SolveApp, ctx: &egui::Context) { - if app.tree.capture_prompt.is_none() { - return; - } - let n = app.session.pending_moves.len(); - let mut answer: Option<&str> = None; - crate::frame::window(ctx, crate::i18n::tr_label("Capture Position"), crate::frame::Width::Normal) - .id(egui::Id::new("sc_capture_prompt")) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, vec2(0.0, -80.0)) - .show(ctx, |ui| { - ui.label(if n == 1 { - "A component was moved but its position isn't captured.".to_string() - } else { - format!("{n} components were moved but their positions aren't captured.") - }); - ui.label(RichText::new("Capture keeps the new position; Revert puts it back.").color(Tokens::get().text_dim)); - ui.add_space(6.0); - ui.horizontal(|ui| { - if ui.button(crate::i18n::tr_label("Capture Position")).clicked() { - answer = Some("capture"); - } - if ui.button(crate::i18n::tr_label("Revert")).clicked() { - answer = Some("revert"); - } - if ui.button("Cancel").clicked() { - answer = Some("cancel"); - } - }); - if ui.input(|i| i.key_pressed(egui::Key::Escape)) { - answer = Some("cancel"); - } - }); - if let Some(a) = answer { - let _ = answer_capture(app, a); - } -} - /// Where an occurrence's origin is now (with its pending move). -fn occurrence_origin(app: &SolveApp, occurrence: u64) -> Option { +pub(crate) fn occurrence_origin(app: &SolveApp, occurrence: u64) -> Option { let o = app.session.doc.occurrences.iter().find(|o| o.id == occurrence)?; let parent = app.session.doc.component_transform(o.parent); let own = app.session.pending_moves.get(&occurrence).copied().unwrap_or(o.transform); @@ -2371,3 +2239,7 @@ pub(crate) fn body_rows(app: &SolveApp) -> Vec { #[cfg(test)] #[path = "section_browser_tests.rs"] mod section_browser_tests; + +#[cfg(test)] +#[path = "browser_card_tests.rs"] +mod card_tests; diff --git a/crates/ui-egui/src/browser_canvas.rs b/crates/ui-egui/src/browser_canvas.rs new file mode 100644 index 0000000000..96e4337f8f --- /dev/null +++ b/crates/ui-egui/src/browser_canvas.rs @@ -0,0 +1,129 @@ +//! The Edit Canvas / Calibrate card the Browser opens for a reference image: position, size, +//! angle, opacity and flip (live, one undo step per opening), or two points on the image and +//! their true distance. + +use serde_json::{Value, json}; + +use crate::SolveApp; +use crate::browser::panel_pos; +use crate::ui_kit; + +pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) { + let Some(mut m) = app.tree.canvas_panel.clone() else { return }; + let Some(c) = app.session.doc.canvases.iter().find(|c| c.id == m.canvas).cloned() else { + app.tree.canvas_panel = None; + return; + }; + let mut edit: Option = None; + let (units, decimals) = (app.session.doc.units.clone(), app.preferences.length_decimals); + let title = crate::i18n::format(if m.calibrate { "browser.calibrate_title" } else { "browser.edit_canvas_title" }, &[("name", &c.name)]); + let at = panel_pos(app); + let (calibrate, name) = (m.calibrate, c.id); + let (ok, cancel) = if calibrate { (crate::i18n::tr_label("Calibrate"), crate::i18n::tr("ui.cancel")) } else { ("", crate::i18n::tr("ui.close")) }; + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_canvas_panel"), "canvas", &title) + .placed(egui::Align2::LEFT_TOP, at) + .fields() + .actions(ok, cancel, None) + .show(ctx, true, |ui| { + if calibrate { + for (label, p) in [("Point A", &mut m.a), ("Point B", &mut m.b)] { + ui_kit::row(ui, crate::i18n::tr_label(label), |ui| { + let [x, y] = p; + ui.horizontal(|ui| { + ui_kit::length_field(ui, x, &units, decimals); + ui_kit::length_field(ui, y, &units, decimals); + }); + }); + } + ui_kit::row(ui, crate::i18n::tr_label("Distance"), |ui| ui_kit::length_field(ui, &mut m.distance, &units, decimals)); + m.distance = m.distance.clamp(0.001, 1e6); + } else { + let (mut x, mut y, mut w, mut ang, mut op, mut flip) = (c.center.x, c.center.y, c.width, c.angle.to_degrees(), c.opacity, c.flip); + let mut changed = false; + changed |= ui_kit::row(ui, crate::i18n::tr_label("Center X"), |ui| ui_kit::length_field(ui, &mut x, &units, decimals)).changed(); + changed |= ui_kit::row(ui, crate::i18n::tr_label("Center Y"), |ui| ui_kit::length_field(ui, &mut y, &units, decimals)).changed(); + changed |= ui_kit::row(ui, crate::i18n::tr_label("Width"), |ui| ui_kit::length_field(ui, &mut w, &units, decimals)).changed(); + changed |= ui_kit::row(ui, crate::i18n::tr_label("Angle"), |ui| ui_kit::angle_field(ui, &mut ang)).changed(); + changed |= + ui_kit::row(ui, crate::i18n::tr_label("Opacity"), |ui| ui_kit::slider(ui, &mut op, 0.0..=1.0, Default::default())).changed(); + if ui_kit::checkbox_row(ui, "flip", crate::i18n::tr_label("Flip"), flip) { + flip = !flip; + changed = true; + } + if changed { + edit = Some(json!({ "canvas": name, "center": [x, y], "width": w.clamp(0.01, 1e6), "angle": ang, "opacity": op, "flip": flip })); + } + } + }); + if let Some(p) = edit + && app.run("canvas.edit", p).is_ok() + { + // One undo step for the whole edit. + if app.session.undo.len() > m.depth + 1 { + let keep = app.session.undo.get(m.depth).cloned(); + app.session.undo.truncate(m.depth); + app.session.undo.extend(keep); + } + } + if action == ui_kit::Action::Ok { + if app.run("canvas.calibrate", json!({ "canvas": c.id, "a": m.a, "b": m.b, "distance": m.distance })).is_ok() { + app.tree.canvas_panel = None; + } + return; + } + app.tree.canvas_panel = if closed || action == ui_kit::Action::Cancel { None } else { Some(m) }; +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + fn canvas(h: &mut crate::scenario::Harness) -> u64 { + let mut png = b"\x89PNG\r\n\x1a\n\0\0\0\rIHDR".to_vec(); + png.extend(400u32.to_be_bytes()); + png.extend(200u32.to_be_bytes()); + png.extend([8, 6, 0, 0, 0, 0, 0, 0, 0]); + let data = solidcraft_engine::doc::canvas::base64_encode(&png); + h.app.run("canvas.insert", json!({"data": data, "plane": "XY", "width": 80})).unwrap()["canvas"].as_u64().unwrap() + } + + #[test] + fn edit_canvas_is_a_card_whose_close_keeps_the_live_edits() { + let mut h = crate::scenario::Harness::new(); + h.app.home.open = false; + let id = canvas(&mut h); + h.app.tree.canvas_panel = Some(crate::browser::CanvasPanel::new(&h.app, id, false)); + h.frames(4); + assert!(crate::scenario::handle_at("dialog:close").is_some() && h.widget("Close").is_some()); + assert!(h.widget("Calibrate").is_none(), "editing has no Calibrate action"); + for row in ["dialog:length:Center X", "dialog:angle:Angle", "dialog:slider:Opacity"] { + assert!(crate::scenario::handle_at(row).is_some(), "{row}"); + } + h.step(&json!({"widget": "Close"})).unwrap(); + h.frames(2); + assert!(h.app.tree.canvas_panel.is_none()); + assert_eq!(h.app.session.doc.canvases.len(), 1); + } + + #[test] + fn calibrate_runs_the_command_and_cancel_leaves_the_canvas_alone() { + let mut h = crate::scenario::Harness::new(); + h.app.home.open = false; + let id = canvas(&mut h); + let width = |h: &crate::scenario::Harness| h.app.session.doc.canvases[0].width; + h.app.tree.canvas_panel = Some(crate::browser::CanvasPanel::new(&h.app, id, true)); + h.frames(4); + h.step(&json!({"widget": "Cancel"})).unwrap(); + h.frames(2); + assert!(h.app.tree.canvas_panel.is_none()); + assert!((width(&h) - 80.0).abs() < 1e-9); + let mut panel = crate::browser::CanvasPanel::new(&h.app, id, true); + panel.distance = 40.0; + h.app.tree.canvas_panel = Some(panel); + h.frames(4); + h.step(&json!({"widget": "Calibrate"})).unwrap(); + h.frames(2); + assert!(h.app.tree.canvas_panel.is_none()); + assert!((width(&h) - 40.0).abs() < 1e-6, "calibrating to half the distance halves the image: {}", width(&h)); + } +} diff --git a/crates/ui-egui/src/browser_card_tests.rs b/crates/ui-egui/src/browser_card_tests.rs new file mode 100644 index 0000000000..dc579ea63a --- /dev/null +++ b/crates/ui-egui/src/browser_card_tests.rs @@ -0,0 +1,241 @@ +use super::*; +use crate::scenario::{Harness, handle_at, rect_of}; +use crate::shell::{LayoutMode, SHELL_GAP}; + +#[test] +fn browser_card_shrinks_and_scrolls_without_covering_history() { + let mut h = Harness::new(); + h.call("ui.resize", json!({"width":1400,"height":900})); + h.settle(); + let short = rect_of("browser:card").unwrap(); + let history = rect_of("history:card").unwrap(); + assert_eq!(short.left(), SHELL_GAP); + assert_eq!(short.width(), 250.0); + assert_eq!(short.top(), rect_of("ribbon:card").unwrap().bottom() + SHELL_GAP); + assert!(short.bottom() < history.top() - SHELL_GAP); + assert!(drawn_rows().iter().all(|(_, rect, _, _)| rect.height() == 26.0)); + + for i in 0..35 { + h.app.run("sketch.create", json!({"plane":"XY", "name":format!("Sketch{i}")})).unwrap(); + h.app.run("sketch.finish", json!({})).unwrap(); + } + h.settle(); + let tall = rect_of("browser:card").unwrap(); + assert!(tall.height() > short.height()); + assert!((tall.bottom() - (history.top() - SHELL_GAP)).abs() < 1.0, "{tall:?} {history:?}"); + assert!(h.app.viewport.rect.unwrap().contains_rect(tall)); + assert!(handle_at("row:Sketch34").is_none(), "last row starts below the scroll clip"); + let camera = h.app.cam; + h.call("ui.scroll", json!({"x":tall.center().x,"y":tall.center().y,"delta":-1800})); + h.settle(); + assert!(handle_at("row:Sketch34").is_some(), "scrolling must reveal the last row"); + assert_eq!(h.app.cam, camera); + assert_eq!(rect_of("browser:card").unwrap(), tall); + + h.call("ui.set", json!({"layout":"docked"})); + h.settle(); + assert!(rect_of("browser:card").is_none()); + assert_eq!(h.app.viewport.rect.unwrap().left(), 250.0); + assert!(drawn_rows().iter().all(|(_, rect, _, _)| rect.height() == 26.0)); + h.call("ui.set", json!({"layout":"floating"})); + h.app.tree.collapsed.insert("c0".into()); + h.settle(); + assert!(rect_of("browser:card").unwrap().height() < short.height()); +} + +#[test] +fn selected_row_paints_tab_soft_fill_and_accent_text_in_both_layouts() { + let previous = crate::theme::is_dark(); + for dark in [false, true] { + crate::theme::set_dark(dark); + for mode in [LayoutMode::Floating, LayoutMode::Docked] { + for tab in ["MODEL", "SKETCH", "SURFACE", "MESH", "FORM", "SHEET METAL", "RENDER"] { + let ctx = crate::fonts::test_context(); + let mut app = SolveApp::new(Default::default(), Default::default()); + app.ui.layout = mode; + app.ui.tab = tab.into(); + crate::theme::TabAccent::publish(&app, &ctx); + let accent = crate::theme::TabAccent::for_tab(&Tokens::get(), tab); + let mut row_rect = Rect::NOTHING; + let out = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| { + let row = Row { selected: true, label: "Selected body", dim: true, color: Some(Tokens::get().error), ..Default::default() }; + row_rect = draw_row(ui, egui::Id::new("selected-row"), &row).rect.unwrap(); + }); + assert_eq!(row_rect.height(), 26.0); + assert!(out.shapes.iter().any(|s| matches!(&s.shape, egui::Shape::Rect(r) if r.rect == row_rect && r.fill == accent.soft))); + assert!( + out.shapes + .iter() + .any(|s| matches!(&s.shape, egui::Shape::Text(t) if t.galley.text() == "Selected body" && t.fallback_color == accent.ink)) + ); + } + } + } + crate::theme::set_dark(previous); +} + +#[test] +fn card_and_column_keep_body_eyes_menus_and_group_drops() { + for mode in [LayoutMode::Floating, LayoutMode::Docked] { + let mut h = Harness::new(); + h.app.ui.layout = mode; + for (name, x) in [("One", 0), ("Two", 20)] { + h.app.run("solid.box", json!({"length":10,"width":10,"height":10,"corner":[x,0,0],"operation":"new_body","body_name":name})).unwrap(); + } + let group = + h.app.run("browser.group", json!({"component":0,"folder":"bodies","items":["One"],"name":"Group"})).unwrap()["group"].as_u64().unwrap(); + h.settle(); + h.step(&json!({"click":{"handle":"eye:Two"}})).unwrap(); + assert!(h.app.session.visibility.hidden_bodies.iter().any(|name| name == "Two")); + h.step(&json!({"click":{"handle":"eye:Two"}})).unwrap(); + assert!(!h.app.session.visibility.hidden_bodies.iter().any(|name| name == "Two")); + h.step(&json!({"click":{"handle":"row:Two"},"button":"right"})).unwrap(); + assert!(matches!(&h.app.menu.target, crate::context_menu::Target::Body { name } if name == "Two")); + h.step(&json!({"key":"Escape"})).unwrap(); + h.step(&json!({"drag":[{"handle":"row:Two"},{"handle":"row:Group (1)"}]})).unwrap(); + h.settle(); + let group = h.app.session.doc.browser_groups.iter().find(|g| g.id == group).unwrap(); + assert!(group.items.iter().any(|name| name == "Two"), "body drop into the folder failed in {mode:?}"); + } +} + +#[test] +fn browser_header_folds_the_floating_card_to_its_header_and_back() { + let mut h = Harness::new(); + h.call("ui.resize", json!({"width":1400,"height":900})); + h.settle(); + let open = rect_of("browser:card").unwrap(); + assert!(!h.app.tree.panel_collapsed); + let at = handle_at("browser:header").unwrap(); + h.call("ui.click", json!({"x":at.x,"y":at.y})); + h.settle(); + let folded = rect_of("browser:card").unwrap(); + assert!(h.app.tree.panel_collapsed); + assert!(folded.height() < 60.0 && folded.height() < open.height(), "{folded:?} {open:?}"); + assert_eq!(folded.top(), open.top()); + assert!(drawn_rows().is_empty() || handle_at("row:Plate").is_none()); + h.call("ui.click", json!({"x":at.x,"y":at.y})); + h.settle(); + assert!(!h.app.tree.panel_collapsed); + assert!((rect_of("browser:card").unwrap().height() - open.height()).abs() < 1.0); +} + +/// Tiny or odd window sizes never panic: the cards leave out what cannot fit. +#[test] +fn tiny_floating_windows_never_panic() { + for (w, h) in [(100, 60), (240, 90), (320, 140), (700, 150), (700, 300), (1400, 120), (180, 800)] { + for scale in [1.0, 2.0] { + let mut harness = Harness::new(); + harness.call("ui.resize", json!({"width": w, "height": h})); + harness.call("ui.prefs", json!({"ui_scale": scale})); + harness.app.run("sketch.create", json!({"plane": "XY"})).unwrap(); + harness.settle(); + for mode in [LayoutMode::Floating, LayoutMode::Docked] { + harness.app.ui.layout = mode; + harness.settle(); + } + } + } +} + +/// Both shell layouts share one tree and one saved width. Docked can switch its card +/// back into a column; Floating keeps its card when the overlay preference is cleared. +#[test] +fn one_browser_keeps_its_resized_width_across_layouts_and_overlay_preferences() { + let mut h = Harness::new(); + h.call("ui.resize", json!({"width":1400,"height":900})); + for mode in [LayoutMode::Floating, LayoutMode::Docked] { + for overlay in [false, true] { + h.app.ui.layout = mode; + h.app.preferences.browser_float = overlay; + h.app.preferences.browser_width = 310.0; + h.settle(); + let card = mode == LayoutMode::Floating || overlay; + assert_eq!(rect_of("browser:card").is_some(), card); + assert_eq!(crate::scenario::handles("browser:header").len(), 1, "only one browser header"); + assert_eq!(drawn_rows().iter().filter(|(name, ..)| name == "Untitled").count(), 1, "only one tree"); + if card { + assert!((rect_of("browser:card").unwrap().width() - 310.0).abs() < 1.0); + } else { + assert_eq!(h.app.viewport.rect.unwrap().left(), 310.0); + } + let rail = handle_at("left-browser-rail").unwrap(); + h.call("ui.drag", json!({"x0":rail.x,"y0":rail.y,"x1":rail.x+60.0,"y1":rail.y,"steps":6})); + h.settle(); + let width = h.app.preferences.browser_width; + assert!(width > 340.0 && width <= MAX_BROWSER_WIDTH, "{mode:?}, overlay={overlay}: {width}"); + h.app.ui.layout = LayoutMode::Docked; + h.app.preferences.browser_float = false; + h.settle(); + assert!((h.app.viewport.rect.unwrap().left() - width).abs() < 1.0, "the column uses the card's saved width"); + } + } +} +#[test] +fn browser_title_uses_a_single_semibold_run() { + let ctx = crate::fonts::test_context(); + let mut app = SolveApp::new(Default::default(), Default::default()); + let output = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| { + ui.set_width(250.0); + browser_header(&mut app, ui, true); + }); + let title = crate::i18n::tr("ui.browser_title"); + let titles: Vec<_> = output + .shapes + .iter() + .filter_map(|s| match &s.shape { + egui::Shape::Text(text) if text.galley.text() == title => Some(text), + _ => None, + }) + .collect(); + assert_eq!(titles.len(), 1); + assert_eq!(titles[0].galley.job.sections[0].format.font_id, crate::fonts::semibold(Tokens::get().font_heading)); +} + +#[test] +fn resizing_browser_card_and_column_persists_width_including_floating_preference() { + for (mode, float, left_history) in [ + (LayoutMode::Floating, false, false), + (LayoutMode::Docked, false, false), + (LayoutMode::Docked, true, false), + (LayoutMode::Floating, true, true), + (LayoutMode::Docked, true, true), + ] { + let mut h = Harness::new(); + h.app.ui.layout = mode; + h.app.preferences.browser_float = float; + h.app.preferences.timeline_left = left_history; + h.call("ui.resize", json!({"width":1400,"height":900})); + h.settle(); + let before = h.app.viewport.rect.unwrap(); + let at = handle_at("left-browser-rail").unwrap(); + h.call("ui.drag", json!({"x0":at.x,"y0":at.y,"x1":at.x+80.0,"y1":at.y,"steps":8})); + h.settle(); + let width = h.app.preferences.browser_width; + assert!(width > crate::prefs::DEFAULT_BROWSER_WIDTH + 40.0, "{mode:?}, float={float}, width={width}"); + if mode == LayoutMode::Floating || float { + assert!((rect_of("browser:card").unwrap().width() - width).abs() < 1.0); + assert_eq!(h.app.viewport.rect.unwrap(), before, "the card leaves the projection intact"); + } else { + assert!((h.app.viewport.rect.unwrap().left() - width).abs() < 1.0); + } + let mut restored = SolveApp::new(Default::default(), Default::default()); + restored.load_prefs(&h.app.prefs()); + assert_eq!(restored.preferences.browser_width, width); + assert_eq!(restored.preferences.browser_float, float); + } +} + +#[test] +fn browser_width_rejects_hostile_preferences_and_tiny_windows_still_fit() { + for width in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] { + assert_eq!(browser_width(width), crate::prefs::DEFAULT_BROWSER_WIDTH); + } + assert_eq!(browser_width(-100.0), MIN_BROWSER_WIDTH); + assert_eq!(browser_width(f32::MAX), MAX_BROWSER_WIDTH); + let available = Rect::from_min_size(Pos2::ZERO, vec2(100.0, 300.0)); + for top_gap in [0.0, crate::shell::SHELL_GAP] { + let bounds = floating_bounds(available, None, MAX_BROWSER_WIDTH, top_gap); + assert!(available.contains_rect(bounds)); + } +} diff --git a/crates/ui-egui/src/browser_model_tests.rs b/crates/ui-egui/src/browser_model_tests.rs index 8f4ceadc62..c2c3f41a86 100644 --- a/crates/ui-egui/src/browser_model_tests.rs +++ b/crates/ui-egui/src/browser_model_tests.rs @@ -3,17 +3,7 @@ use std::sync::Arc; use serde_json::json; use solidcraft_engine::Sel; -use crate::scenario::Harness; - -fn fixture() -> (Harness, u64, u64) { - let mut h = Harness::new(); - let part = h.app.run("component.create", json!({"name":"Block"})).unwrap(); - let component = part["component"].as_u64().unwrap(); - let occurrence = part["occurrence"].as_u64().unwrap(); - h.app.run("solid.box", json!({"length":10,"width":10,"height":10})).unwrap(); - h.app.run("component.activate", json!({"component":0})).unwrap(); - (h, component, occurrence) -} +use crate::test_support::component_box as fixture; #[test] fn navigating_and_selecting_reuse_placements_but_rows_read_live_view_state() { diff --git a/crates/ui-egui/src/browser_prompts.rs b/crates/ui-egui/src/browser_prompts.rs new file mode 100644 index 0000000000..bab9dd2922 --- /dev/null +++ b/crates/ui-egui/src/browser_prompts.rs @@ -0,0 +1,122 @@ +//! The two prompts the Browser opens over the viewport: Move/Copy of an occurrence (offsets and +//! turns, pending until Capture Position) and the Capture Position question asked before a +//! command or a save while components are moved but not captured. + +use serde_json::json; + +use crate::SolveApp; +use crate::browser::{answer_capture, apply_occurrence_move, occurrence_origin, panel_pos, revert_occurrence_dialog}; +use crate::ui_kit; + +/// Move/Copy of an occurrence: offsets and a rotation about Z, pending until Capture Position. +pub(crate) fn occurrence_panel(app: &mut SolveApp, ui: &mut egui::Ui) { + let Some(mut m) = app.tree.occurrence_move.clone() else { return }; + if !m.path.is_empty() + && let Err(error) = app.session.occurrence_placement(&m.path) + { + app.tree.occurrence_move = None; + app.set_status(error.to_string(), true); + return; + } + if m.baseline.is_none() { + m.had_pending = app.session.pending_moves.contains_key(&m.occurrence); + m.baseline = app + .session + .pending_moves + .get(&m.occurrence) + .copied() + .or_else(|| app.session.doc.occurrences.iter().find(|o| o.id == m.occurrence).map(|o| o.transform)); + } + let ctx = ui.ctx().clone(); + if m.pivot.is_none() { + m.pivot = if m.path.is_empty() { + occurrence_origin(app, m.occurrence) + } else { + app.session.occurrence_placement(&m.path).ok().map(|p| solidcraft_engine::doc::apply_point(&p.world, solidcraft_engine::geom::Vec3::ZERO)) + }; + } + let name = app.session.doc.occurrences.iter().find(|o| o.id == m.occurrence).map(|o| o.name.clone()).unwrap_or_default(); + let before = (m.translate, m.angles); + let shared = !m.path.is_empty() && app.session.occurrence_scope_count(&m.path).is_ok_and(|n| n > 1); + let title = crate::i18n::format("browser.move_title", &[("name", &name)]); + let at = panel_pos(app); + let (units, decimals) = (app.session.doc.units.clone(), app.preferences.length_decimals); + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_occ_move"), "move", &title) + .placed(egui::Align2::LEFT_TOP, at) + .fields() + .cards(2.0) + .actions("Capture All Positions", "Cancel", Some("Revert All Positions")) + .show(&ctx, true, |ui| { + if shared { + ui_kit::text( + ui, + crate::i18n::tr_label("This child is shared. Position changes apply to every copy of its parent."), + ui_kit::Text::Note("shared"), + ); + } + for (k, axis) in ["X distance", "Y distance", "Z distance"].into_iter().enumerate() { + if let Some(v) = m.translate.get_mut(k) { + let response = ui_kit::row(ui, crate::i18n::tr_label(axis), |ui| ui_kit::length_field(ui, v, &units, decimals)); + crate::scenario::publish_handle(&format!("occurrence-move:distance:{k}"), response.rect.center()); + } + } + for (k, axis) in ["X angle", "Y angle", "Z angle"].into_iter().enumerate() { + if let Some(v) = m.angles.get_mut(k) { + let response = ui_kit::row(ui, crate::i18n::tr_label(axis), |ui| ui_kit::angle_field(ui, v)); + crate::scenario::publish_handle(&format!("occurrence-move:angle:{k}"), response.rect.center()); + } + } + }); + if (m.translate, m.angles) != before { + apply_occurrence_move(app, &m); + } + match action { + ui_kit::Action::Ok => { + let _ = app.run("component.capture_position", json!({})); + app.tree.occurrence_move = None; + } + ui_kit::Action::Apply => { + let _ = app.run("component.revert_position", json!({})); + app.tree.occurrence_move = None; + } + ui_kit::Action::Cancel => { + revert_occurrence_dialog(app, &m); + app.tree.occurrence_move = None; + } + ui_kit::Action::None if closed => { + revert_occurrence_dialog(app, &m); + app.tree.occurrence_move = None; + } + ui_kit::Action::None => app.tree.occurrence_move = Some(m), + } +} + +/// The Capture Position question: capture, revert or cancel the waiting command (or save). +pub(crate) fn capture_prompt(app: &mut SolveApp, ctx: &egui::Context) { + if app.tree.capture_prompt.is_none() { + return; + } + let n = app.session.pending_moves.len(); + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_capture_prompt"), "capture", crate::i18n::tr_label("Capture Position")) + .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, -80.0)) + .actions("Capture Position", "Cancel", Some("Revert")) + .show(ctx, true, |ui| { + let question = if n == 1 { + crate::i18n::tr("browser.capture_one").to_string() + } else { + crate::i18n::format("browser.capture_many", &[("count", &n.to_string())]) + }; + ui_kit::text(ui, &question, ui_kit::Text::Report("question", false)); + ui_kit::text(ui, crate::i18n::tr("browser.capture_note"), ui_kit::Text::Note("capture")); + }); + let answer = match action { + ui_kit::Action::Ok => Some("capture"), + ui_kit::Action::Apply => Some("revert"), + ui_kit::Action::Cancel => Some("cancel"), + ui_kit::Action::None if closed => Some("cancel"), + ui_kit::Action::None => None, + }; + if let Some(answer) = answer { + let _ = answer_capture(app, answer); + } +} diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index a6944e88be..2203d74b48 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -571,7 +571,7 @@ fn value_box(ui: &mut egui::Ui, label: &str, value: &mut String, at: Pos2, selec crate::frame::area(egui::Id::new("sc_canvas_area")).fixed_pos(at).order(egui::Order::Foreground).show(ui.ctx(), |ui| { egui::Frame::popup(ui.style()).inner_margin(egui::Margin::symmetric(6, 3)).show(ui, |ui| { ui.horizontal(|ui| { - ui.label(egui::RichText::new(crate::i18n::tr_label(label)).color(t.text_dim).size(11.5)); + ui.label(egui::RichText::new(crate::i18n::tr_label(label)).color(t.text_dim).size(crate::theme::Tokens::get().font_caption)); let r = ui.add(egui::TextEdit::singleline(value).id(id).desired_width(72.0)); crate::params_dialog::complete(ui, &r, value); if select_all { @@ -889,10 +889,9 @@ fn measure_line(app: &SolveApp, painter: &egui::Painter, proj: &Proj, r: &serde_ painter.circle(s, 3.5, t.manipulator, Stroke::new(1.0, Color32::WHITE)); } let mid = sa + (sb - sa) * 0.5 + vec2(10.0, -14.0); - let galley = painter.layout_no_wrap(crate::prefs::show_mm(app, d, 1), egui::FontId::proportional(12.5), t.text); + let galley = painter.layout_no_wrap(crate::prefs::show_mm(app, d, 1), egui::FontId::proportional(crate::theme::Tokens::get().font_label), t.text); let rect = egui::Rect::from_min_size(mid, galley.size() + vec2(10.0, 6.0)); - crate::scenario::publish_handle("measure:label", rect.center()); - crate::scenario::publish_rect("measure:label", rect); + crate::scenario::publish_target("measure:label", rect); if app.preferences.measure_axis_distances { measure_components(app, painter, proj, a, b, rect); } @@ -931,7 +930,7 @@ fn measure_components(app: &SolveApp, painter: &egui::Painter, proj: &Proj, a: V (total.center(), vec2(20.0, 20.0)) }; let text = format!("{}: {}", axis.to_uppercase(), crate::prefs::show_mm(app, distance, 1)); - let galley = painter.layout_no_wrap(text, egui::FontId::proportional(12.5), t.text); + let galley = painter.layout_no_wrap(text, egui::FontId::proportional(crate::theme::Tokens::get().font_label), t.text); let size = galley.size() + vec2(10.0, 6.0); let bounds = proj.rect.shrink(4.0); let clamp = |rect: egui::Rect| { @@ -959,8 +958,7 @@ fn measure_components(app: &SolveApp, painter: &egui::Painter, proj: &Proj, a: V painter.rect(rect, 3.0, t.overlay, Stroke::new(1.0, color), egui::StrokeKind::Inside); painter.galley(rect.min + vec2(5.0, 3.0), galley, t.text); let handle = format!("measure:label:{axis}"); - crate::scenario::publish_handle(&handle, rect.center()); - crate::scenario::publish_rect(&handle, rect); + crate::scenario::publish_target(&handle, rect); occupied.push(rect); } } @@ -1178,7 +1176,10 @@ fn draw_revolve( } } if to_mode { - crate::frame::area(egui::Id::new("sc_canvas_area")).fixed_pos(box_at).order(egui::Order::Foreground).show(ui.ctx(), |ui| { + // This badge is read-only. Its placement follows the model and can overlap the + // command dialog, whose buttons must keep receiving clicks. + let area = crate::frame::area(egui::Id::new("sc_canvas_area")).fixed_pos(box_at).order(egui::Order::Foreground).interactable(false); + area.show(ui.ctx(), |ui| { egui::Frame::popup(ui.style()).inner_margin(egui::Margin::symmetric(6, 3)).show(ui, |ui| { let text = if extent.curved_to { "To surface".into() diff --git a/crates/ui-egui/src/colour_tokens_tests.rs b/crates/ui-egui/src/colour_tokens_tests.rs index 82a1b7d741..8239e23308 100644 --- a/crates/ui-egui/src/colour_tokens_tests.rs +++ b/crates/ui-egui/src/colour_tokens_tests.rs @@ -121,23 +121,30 @@ fn colours_outside_the_theme_are_only_the_listed_neutral_ones() { let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let dirs = [root.join("src"), root.join("../../assets/icons/ui/source")]; let mut found = Vec::new(); - for dir in &dirs { - let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("{}: {e}", dir.display())); - for entry in entries.flatten() { - let path = entry.path(); - let name = path.file_name().and_then(|n| n.to_str()).unwrap_or_default().to_string(); - if !name.ends_with(".rs") || name.ends_with("_tests.rs") || name == "theme.rs" { + fn scan(dir: &std::path::Path, prefix: &str, found: &mut Vec) { + for entry in std::fs::read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + let name = path.file_name().unwrap().to_str().unwrap(); + let relative = format!("{prefix}{name}"); + if path.is_dir() { + scan(&path, &format!("{relative}/"), found); continue; } - let src = std::fs::read_to_string(&path).unwrap_or_default(); + if !name.ends_with(".rs") || name.ends_with("_tests.rs") || matches!(name, "theme.rs" | "gallery.rs" | "test_support.rs") { + continue; + } + let src = std::fs::read_to_string(&path).unwrap(); let code = src.split("#[cfg(test)]").next().unwrap_or(&src); let n: usize = COLOUR_LITERALS.iter().map(|p| code.matches(p).count()).sum(); - let allowed = ALLOWED_LITERALS.iter().find(|(f, _, _)| *f == name).map_or(0, |(_, n, _)| *n); + let allowed = ALLOWED_LITERALS.iter().find(|(f, _, _)| *f == relative).map_or(0, |(_, n, _)| *n); if n > allowed { - found.push(format!("{name}: {n} colour literal(s), at most {allowed} allowed")); + found.push(format!("{relative}: {n} colour literal(s), at most {allowed} allowed")); } } } + for dir in &dirs { + scan(dir, "", &mut found); + } assert!(found.is_empty(), "colour literals outside theme::Tokens: {found:?}"); } diff --git a/crates/ui-egui/src/colour_vision_tests.rs b/crates/ui-egui/src/colour_vision_tests.rs index 4e028c73bb..dc63b066cf 100644 --- a/crates/ui-egui/src/colour_vision_tests.rs +++ b/crates/ui-egui/src/colour_vision_tests.rs @@ -278,6 +278,18 @@ fn groups(t: &Tokens) -> Vec { &[("timeline_item", t.timeline_item), ("timeline_item_error", t.timeline_item_error), ("hover", t.hover), ("accent_soft", t.accent_soft)], &[], ), + group("card text", &[("form_ink", t.form_ink), ("form_error", t.form_error)], &[("dialog_body", t.dialog_body)]), + group("card hints", &[("form_dim", t.form_dim)], &[("dialog_body", t.dialog_body)]), + group("card primary", &[("form_primary", t.form_primary)], &[("dialog_body", t.dialog_body)]), + // Validation is identified by its words as well as colour. Measure both the + // ink and badge surfaces against the corresponding failure states. + group( + "card validation", + &[("form_success", t.form_success), ("form_error", t.form_error)], + &[("form_success_fill", t.form_success_fill), ("form_error_fill", t.form_error_fill)], + ), + group("validation status", &[("form_success", t.form_success), ("error", t.error)], &[("panel", t.panel)]), + group("validation surfaces", &[("form_success_fill", t.form_success_fill), ("form_error_fill", t.form_error_fill)], &[]), group("UI text", &[("error", t.error), ("warning", t.warning), ("text", t.text)], &[("panel", t.panel)]), group("origin axes", &[("axis_x", t.axis_x), ("axis_y", t.axis_y), ("axis_z", t.axis_z)], &[("viewport_top", t.viewport_top)]), group("view triad", &[("triad[0]", t.triad[0]), ("triad[1]", t.triad[1]), ("triad[2]", t.triad[2])], &[("viewport_top", t.viewport_top)]), @@ -363,6 +375,29 @@ fn groups(t: &Tokens) -> Vec { group("agent status", &[("agent_ok", t.agent_ok), ("agent_busy", t.agent_busy)], &[("app_bar", t.app_bar)]), group("logo", &[("logo[0]", t.logo[0]), ("logo[1]", t.logo[1]), ("logo[2]", t.logo[2])], &[("app_bar", t.app_bar)]), ]; + // Tab fills and small-text inks have distinct jobs: compare each with its actual + // grounds, rather than with the same-hue fill/ink. WCAG text ratios are also guarded + // in theme::tests::tab_accents_hold_wcag_small_text_contrast_in_both_themes. + for (name, ink_name, ink, soft_name, soft) in [ + ("sketch tab text", "sketch_ink", t.sketch_ink, "sketch_soft", t.sketch_soft), + ("surface tab text", "surface_ink", t.surface_ink, "surface_soft", t.surface_soft), + ("mesh tab text", "mesh_ink", t.mesh_ink, "mesh_soft", t.mesh_soft), + ("form tab text", "form_tab_ink", t.form_tab_ink, "form_soft", t.form_soft), + ("sheet tab text", "sheet_ink", t.sheet_ink, "sheet_soft", t.sheet_soft), + ("render tab text", "render_ink", t.render_ink, "render_soft", t.render_soft), + ] { + out.push(group(name, &[(ink_name, ink)], &[(soft_name, soft), ("panel", t.panel)])); + } + for (name, key, fill) in [ + ("surface tab fill", "surface_fill", t.surface_fill), + ("mesh tab fill", "mesh_fill", t.mesh_fill), + ("form tab fill", "form_fill", t.form_fill), + ("sheet tab fill", "sheet_fill", t.sheet_fill), + ("render tab fill", "render_fill", t.render_fill), + ] { + out.push(group(name, &[(key, fill)], &[("panel", t.panel)])); + } + out.push(group("dark pill text", &[("tab_on_fill_dark", t.tab_on_fill_dark)], &[("mesh_fill", t.mesh_fill)])); // The removed part of a cut preview is translucent over the body. if let Some(sel) = out.iter_mut().find(|g| g.name == "selection") { sel.members.push(("preview_cut", over(t.preview_cut, t.body))); @@ -399,11 +434,47 @@ const NO_MEANING: &[(&str, &str)] = &[ ("font_label", "layout dimension"), ("font_button", "layout dimension"), ("font_heading", "layout dimension"), + ("font_triad", "layout dimension"), + ("font_compass", "layout dimension"), + ("font_subtitle", "layout dimension"), + ("font_intro", "layout dimension"), + ("font_article_heading", "layout dimension"), + ("font_title", "layout dimension"), + ("font_display", "layout dimension"), + ("control_height", "layout dimension"), + ("control_radius", "layout dimension"), + ("form_label_width", "layout dimension"), + ("form_gap_y", "layout dimension"), + ("form_footer_height", "layout dimension"), + ("form_header_height", "layout dimension"), + ("form_card_width", "layout dimension"), + ("form_icon_size", "layout dimension"), + ("form_close_size", "layout dimension"), + ("form_inset", "layout dimension"), + ("form_icon_inset", "layout dimension"), + ("form_section_icon_size", "layout dimension"), + ("form_checkbox_height", "layout dimension"), + ("form_button_height", "layout dimension"), + ("sketch_palette_width", "layout dimension"), + ("sketch_mode_size", "layout dimension"), + ("sketch_mode_radius", "layout dimension"), + ("sketch_mode_border", "layout dimension"), + ("sketch_palette_alpha", "neutral panel opacity"), + ("sketch_mode_tint", "selected background tint of the measured sketch accent"), + ("sketch_footer_icon_size", "layout dimension"), + ("font_section_tracking", "layout dimension"), ("on_accent", "ink on an accent fill"), ("app_bar_hover_subtle", "translucent hover chrome"), ("app_bar_hover_button", "translucent hover chrome"), ("app_bar_hover_strong", "translucent hover chrome"), ("app_bar_tab_active", "the active document tab's surface"), + ("dialog_footer", "dense dialog footer band"), + ("dialog_rule", "dense dialog divider"), + ("control_fill", "input control fill"), + ("control_border", "input control border"), + ("dialog_label", "dense dialog field label"), + ("selected_ink", "foreground of selected elements"), + ("selected_bg", "background of selected elements"), ("render_accent", "the Render workspace's identity colour"), // The window's chrome: grounds, text and borders (measured as grounds where meaning sits on them). ("panel_header", "chrome"), @@ -675,10 +746,11 @@ fn each_mode_changes_only_its_table() { "form_accent", "sketch_accent", "preview_sketch", + "form_success_fill", ]; let mut red_green_light = red_green_both.into_iter().filter(|name| !["surface_accent", "mesh_accent", "form_accent"].contains(name)).collect::>(); - red_green_light.extend(["sketch_projected", "sketch_locked", "timeline_item_error", "construction_geom"]); + red_green_light.extend(["sketch_projected", "sketch_locked", "timeline_item_error", "construction_geom", "glyph_text", "form_success"]); for (dark, mode, want) in [ (false, ColorVision::Protan, set(&red_green_light)), (true, ColorVision::Protan, set(&red_green_both)), @@ -695,6 +767,7 @@ fn each_mode_changes_only_its_table() { "sketch_locked", "box_crossing", "construction_geom", + "glyph_text", "preview_sketch", "analysis.hatch", ]), @@ -757,6 +830,33 @@ fn every_mode_keeps_colours_apart_for_its_eye() { } } +/// Badge meaning must survive each simulation even for subtle default tints (which +/// the general groups check may otherwise skip when they are close for normal vision). +#[test] +fn validation_badges_are_distinct_and_readable_in_every_mode() { + let mut failures = Vec::new(); + for dark in [false, true] { + for mode in std::iter::once(ColorVision::Off).chain(MODES) { + let t = Tokens::themed(dark, mode); + for (name, other) in [("form_error", t.form_error), ("error", t.error)] { + let distance = worst(mode, rgb(t.form_success), rgb(other)); + if distance < FLOOR { + failures.push(format!("{mode:?} dark={dark} form_success vs {name}: {distance:.2}")); + } + } + let distance = worst(mode, rgb(t.form_success_fill), rgb(t.form_error_fill)); + if distance < FLOOR { + failures.push(format!("{mode:?} dark={dark} validation fills: {distance:.2}")); + } + let contrast = crate::theme::contrast_ratio(t.form_success, t.form_success_fill); + if contrast < 4.5 { + failures.push(format!("{mode:?} dark={dark} success contrast: {contrast:.2}")); + } + } + } + assert!(failures.is_empty(), "{}", failures.join("\n")); +} + #[test] fn the_shader_gets_the_modes_colours_and_its_stripes() { for dark in [false, true] { diff --git a/crates/ui-egui/src/configuration_rules_ui.rs b/crates/ui-egui/src/configuration_rules_ui.rs index e88ec83790..4f5089108c 100644 --- a/crates/ui-egui/src/configuration_rules_ui.rs +++ b/crates/ui-egui/src/configuration_rules_ui.rs @@ -1,6 +1,7 @@ //! Structured configuration rule editing and non-mutating row preview. use crate::SolveApp; -use egui::{RichText, Ui}; +use crate::ui_kit; +use egui::Ui; use serde_json::{Value, json}; use solidcraft_engine::doc::config::{Column, ConfigRule, RuleCondition, RuleEffect, RuleOperator}; @@ -11,6 +12,9 @@ pub struct Editor { preview: Option, preview_revision: u64, + pub(super) new_values: std::collections::BTreeMap, + pub(super) adding_row: bool, + pub(super) focus_new_row: bool, pub(super) drafts: std::collections::BTreeMap<(String, String), String>, pub(super) table: Option<(u64, Value)>, } @@ -29,21 +33,39 @@ impl Default for Editor { preview: None, preview_revision: 0, + new_values: Default::default(), + adding_row: false, + focus_new_row: false, drafts: Default::default(), table: None, } } } fn choose(ui: &mut Ui, id: &str, value: &mut String, options: &[String]) { - egui::ComboBox::from_id_salt(id).selected_text(if value.is_empty() { "Choose…" } else { value.as_str() }).width(130.0).show_ui(ui, |ui| { - for option in options { - ui.selectable_value(value, option.clone(), option); - } - }); + let mut labels = vec![crate::i18n::tr_label("Choose…")]; + labels.extend(options.iter().map(String::as_str)); + let mut selected = options.iter().position(|o| o == value).map_or(0, |i| i + 1); + let before = selected; + let label = match id { + "rule_length_units" => "Length units", + "rule_effect_kind" => "Then", + "condition_kind" => "When", + "effect_parameter" | "range_parameter" | "allowed_parameter" | "condition_parameter" => "Parameter", + "effect_feature" | "condition_feature" => "Feature", + "preview_configuration" => "Configuration", + _ => id, + }; + ui_kit::row(ui, label, |ui| ui_kit::dropdown(ui, id, &mut selected, &labels)); + if before != selected + && let Some(choice) = selected.checked_sub(1).and_then(|i| options.get(i)) + { + *value = choice.clone(); + } } fn expression(ui: &mut Ui, label: &str, value: &mut String) { - ui.label(label); - ui.add(egui::TextEdit::singleline(value).desired_width(160.0)); + ui_kit::row(ui, label, |ui| { + ui_kit::text_field(ui, value, label); + }); } pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, editor: &mut Editor) { @@ -57,38 +79,17 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, let features: Vec<(u64, String)> = app.session.doc.features.iter().map(|f| (f.id, f.name.clone())).collect(); let suppression_features: Vec<(u64, String)> = features.iter().filter(|(id, _)| app.session.doc.configs.columns.contains(&Column::Suppress(*id))).cloned().collect(); - ui.separator(); - ui.label(RichText::new(crate::i18n::tr_label("Configuration rules")).strong()); - ui.add(egui::Label::new("Rules run from top to bottom. Matched drives change effective values.").wrap()); - ui.add(egui::Label::new("Matched restrictions check the final values.").wrap()); + ui_kit::divider(ui, "configuration_rules"); + ui_kit::text(ui, crate::i18n::tr_label("Configuration rules"), ui_kit::Text::Heading("rules_heading")); + ui_kit::text( + ui, + crate::i18n::tr_label("Rules run from top to bottom. Matched drives change effective values."), + ui_kit::Text::Note("rules_drives"), + ); + ui_kit::text(ui, crate::i18n::tr_label("Matched restrictions check the final values."), ui_kit::Text::Note("rules_restrictions")); for (index, rule) in rules.iter().enumerate() { ui.push_id(&rule.id, |ui| { - ui.horizontal(|ui| { - let mut enabled = rule.enabled; - if ui.checkbox(&mut enabled, "").on_hover_text(crate::i18n::tr_label("Enable rule")).changed() { - let mut r = rule.clone(); - r.enabled = enabled; - *error = app.run("config.rule", json!({"rule":r})).err(); - } - ui.add(egui::Label::new(&rule.name).truncate()); - if ui.small_button("Edit").clicked() { - editor.rule = rule.clone(); - } - if ui.add_enabled(index > 0, egui::Button::new("↑").small()).on_hover_text(crate::i18n::tr_label("Move earlier")).clicked() { - *error = app.run("config.rule", json!({"id":rule.id,"index":index-1})).err(); - } - if ui - .add_enabled(index + 1 < rules.len(), egui::Button::new("↓").small()) - .on_hover_text(crate::i18n::tr_label("Move later")) - .clicked() - { - *error = app.run("config.rule", json!({"id":rule.id,"index":index+1})).err(); - } - if ui.small_button("Delete").clicked() { - *error = app.run("config.rule", json!({"id":rule.id,"delete":true})).err(); - } - }); - ui.label(match &rule.effect { + let summary = match &rule.effect { RuleEffect::DriveParameter { parameter, expression } => format!("Drives {parameter} = {expression}"), RuleEffect::DriveSuppression { feature, value } => format!( "Drives suppression {} = {value}", @@ -96,21 +97,46 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, ), RuleEffect::ParameterRange { parameter, minimum, maximum } => format!("Restricts {parameter}: {minimum} through {maximum}"), RuleEffect::AllowParameterValues { parameter, expressions } => format!("Restricts {parameter}: {}", expressions.join(", ")), - }); + }; + let actions_width = ["Edit", "↑", "↓", "Delete"].iter().map(|label| ui_kit::ContentWidth::button(ui, label)).sum::() + + crate::theme::Tokens::get().space_4 * 3.0; + let mut enabled = rule.enabled; + if ui_kit::summary_row(ui, &rule.name, &summary, &mut enabled, actions_width, |ui| { + if ui_kit::button(ui, crate::i18n::tr_label("Edit"), ui_kit::Button::Normal).clicked() { + editor.rule = rule.clone(); + } + let up = ui_kit::button(ui, "↑", ui_kit::Button::Enabled(index > 0)).on_hover_text(crate::i18n::tr_label("Move earlier")); + crate::scenario::publish_handle(&format!("config:rule:{}:up", rule.id), up.rect.center()); + if up.clicked() { + *error = app.run("config.rule", json!({"id":rule.id,"index":index-1})).err(); + } + if ui_kit::button(ui, "↓", ui_kit::Button::Enabled(index + 1 < rules.len())) + .on_hover_text(crate::i18n::tr_label("Move later")) + .clicked() + { + *error = app.run("config.rule", json!({"id":rule.id,"index":index+1})).err(); + } + if ui_kit::button(ui, crate::i18n::tr_label("Delete"), ui_kit::Button::Normal).clicked() { + *error = app.run("config.rule", json!({"id":rule.id,"delete":true})).err(); + } + }) { + let mut updated = rule.clone(); + updated.enabled = enabled; + *error = app.run("config.rule", json!({"rule":updated})).err(); + } }); } - ui.collapsing("Add or edit rule", |ui| { - ui.horizontal(|ui| { + ui_kit::section(ui, "rule_editor", "Add or edit rule", false, |ui| { + ui.vertical(|ui| { expression(ui, "Rule name", &mut editor.rule.name); - ui.checkbox(&mut editor.rule.enabled, crate::i18n::tr_label("Enabled")); - if ui.small_button("New").clicked() { + ui_kit::checkbox(ui, crate::i18n::tr_label("Enabled"), &mut editor.rule.enabled); + if ui_kit::button(ui, crate::i18n::tr_label("New"), ui_kit::Button::Small).clicked() { editor.rule = Editor::default().rule; } }); let mut length_unit = editor.rule.length_unit.clone().unwrap_or_else(|| app.session.doc.units.clone()); let old_unit = length_unit.clone(); - ui.horizontal(|ui| { - ui.label(crate::i18n::tr_label("Length units")); + ui.vertical(|ui| { choose(ui, "rule_length_units", &mut length_unit, &["mm".into(), "cm".into(), "m".into(), "in".into(), "ft".into()]); }); if length_unit != old_unit { @@ -125,8 +151,7 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, } .to_string(); let prior = effect_kind.clone(); - ui.horizontal(|ui| { - ui.label(crate::i18n::tr_label("Then")); + ui.vertical(|ui| { choose( ui, "rule_effect_kind", @@ -145,36 +170,37 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, } match &mut editor.rule.effect { RuleEffect::DriveParameter { parameter, expression: value } => { - ui.horizontal(|ui| { + ui.vertical(|ui| { choose(ui, "effect_parameter", parameter, ¶meters); expression(ui, "Value", value); }); } RuleEffect::DriveSuppression { feature, value } => { - ui.horizontal(|ui| { + ui.vertical(|ui| { feature_choice(ui, "effect_feature", feature, &suppression_features); - ui.checkbox(value, crate::i18n::tr_label("Suppressed")); + ui_kit::checkbox(ui, crate::i18n::tr_label("Suppressed"), value); }); } RuleEffect::ParameterRange { parameter, minimum, maximum } => { choose(ui, "range_parameter", parameter, ¶meters); - ui.horizontal(|ui| { + ui.vertical(|ui| { expression(ui, "Minimum", minimum); }); - ui.horizontal(|ui| { + ui.vertical(|ui| { expression(ui, "Maximum", maximum); }); } RuleEffect::AllowParameterValues { parameter, expressions } => { choose(ui, "allowed_parameter", parameter, ¶meters); let mut text = expressions.join("; "); - ui.label(crate::i18n::tr_label("Allowed expressions, separated by semicolons")); - if ui.add(egui::TextEdit::singleline(&mut text).desired_width(300.0)).changed() { + if ui_kit::row(ui, "Allowed expressions, separated by semicolons", |ui| ui_kit::text_field(ui, &mut text, "").1) { *expressions = text.split(';').map(|s| s.trim().to_string()).collect(); } } } - if ui.add_enabled(!app.session.doc.configs.rows.is_empty(), egui::Button::new(crate::i18n::tr_label("Save Rule"))).clicked() { + let save = ui_kit::button(ui, "Save Rule", ui_kit::Button::Enabled(!app.session.doc.configs.rows.is_empty())); + crate::scenario::publish_handle("config:save_rule", save.rect.center()); + if save.clicked() { let result = (|| -> Result<(), String> { let mut rule = editor.rule.clone(); if rule.id.is_empty() { @@ -187,92 +213,110 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, *error = result.err(); } }); - let rows: Vec = app.session.doc.configs.rows.iter().map(|r| r.name.clone()).collect(); - if !rows.contains(&editor.row) { - editor.row = app.session.doc.configs.active.clone().or_else(|| rows.first().cloned()).unwrap_or_default(); - } - let previous_row = editor.row.clone(); - ui.horizontal(|ui| { - choose(ui, "preview_configuration", &mut editor.row, &rows); - if ui.add_enabled(!editor.row.is_empty(), egui::Button::new(crate::i18n::tr_label("Preview Row"))).clicked() { - match app.session.execute("config.preview", &json!({"row":editor.row})) { - Ok(result) => { - editor.preview = Some(result); - editor.preview_revision = app.session.revision; - *error = None; - } - Err(e) => { - editor.preview = None; - *error = Some(e.to_string()); + ui_kit::section(ui, "preview", "Preview", true, |ui| { + let rows: Vec = app.session.doc.configs.rows.iter().map(|r| r.name.clone()).collect(); + if !rows.contains(&editor.row) { + editor.row = app.session.doc.configs.active.clone().or_else(|| rows.first().cloned()).unwrap_or_default(); + } + let previous_row = editor.row.clone(); + ui.vertical(|ui| { + choose(ui, "preview_configuration", &mut editor.row, &rows); + let preview = ui_kit::button(ui, "Preview Row", ui_kit::Button::Enabled(!editor.row.is_empty())); + crate::scenario::publish_handle("config:preview_row", preview.rect.center()); + if preview.clicked() { + match app.session.execute("config.preview", &json!({"row":editor.row})) { + Ok(result) => { + editor.preview = Some(result); + editor.preview_revision = app.session.revision; + *error = None; + } + Err(e) => { + editor.preview = None; + *error = Some(e.to_string()); + } } } + }); + if previous_row != editor.row { + editor.preview = None; + } + if let Some(preview) = &editor.preview { + ui_kit::section(ui, "rule_preview", &format!("Preview: {}", preview["active"].as_str().unwrap_or("row")), false, |ui| { + report(ui, crate::i18n::tr_label("Preview leaves the current design unchanged.")); + if let Some(styles) = preview["styles"].as_array() { + for style in styles { + report( + ui, + format!( + "{}: {} (shared definition)", + style["target"].as_str().unwrap_or("Appearance"), + if style["dormant"].as_bool() == Some(true) { "dormant" } else { "resolved" } + ), + ); + } + } + if let Some(trace) = preview["trace"].as_array() { + for entry in trace { + report( + ui, + format!( + "{} — {}", + entry["name"].as_str().unwrap_or("rule"), + if entry["matched"].as_bool() == Some(true) { "matched" } else { "skipped" } + ), + ); + if entry["matched"].as_bool() == Some(true) + && let Ok(effect) = serde_json::from_value::(entry["effect"].clone()) + { + report( + ui, + match effect { + RuleEffect::DriveParameter { parameter, expression } => format!("Driven: {parameter} = {expression}"), + RuleEffect::DriveSuppression { feature, value } => format!( + "Driven: {} is {}", + features.iter().find(|f| f.0 == feature).map(|f| f.1.as_str()).unwrap_or("feature"), + if value { "suppressed" } else { "unsuppressed" } + ), + RuleEffect::ParameterRange { parameter, minimum, maximum } => { + format!("Restricted: {parameter}, {minimum} through {maximum}") + } + RuleEffect::AllowParameterValues { parameter, expressions } => { + format!("Restricted: {parameter}, {}", expressions.join("; ")) + } + }, + ); + } + } + } + if let Some(parameters) = preview["parameters"].as_array() { + for parameter in parameters { + if let Some(name) = parameter["name"].as_str() + && all_parameters.iter().any(|p| p == name) + { + let response = ui_kit::text( + ui, + &format!("{name} = {}", parameter["expression"].as_str().unwrap_or("")), + ui_kit::Text::Report("preview_parameter", false), + ); + crate::scenario::publish_handle(&format!("config:preview:param:{name}"), response.rect.center()); + } + } + } + }); } }); - if previous_row != editor.row { - editor.preview = None; - } - if let Some(preview) = &editor.preview { - ui.collapsing(format!("Preview: {}", preview["active"].as_str().unwrap_or("row")), |ui| { - ui.label(crate::i18n::tr_label("Preview leaves the current design unchanged.")); - if let Some(styles) = preview["styles"].as_array() { - for style in styles { - ui.add( - egui::Label::new(format!( - "{}: {} (shared definition)", - style["target"].as_str().unwrap_or("Appearance"), - if style["dormant"].as_bool() == Some(true) { "dormant" } else { "resolved" } - )) - .wrap(), - ); - } - } - if let Some(trace) = preview["trace"].as_array() { - for entry in trace { - ui.label(format!( - "{} — {}", - entry["name"].as_str().unwrap_or("rule"), - if entry["matched"].as_bool() == Some(true) { "matched" } else { "skipped" } - )); - if entry["matched"].as_bool() == Some(true) - && let Ok(effect) = serde_json::from_value::(entry["effect"].clone()) - { - ui.label(match effect { - RuleEffect::DriveParameter { parameter, expression } => format!("Driven: {parameter} = {expression}"), - RuleEffect::DriveSuppression { feature, value } => format!( - "Driven: {} is {}", - features.iter().find(|f| f.0 == feature).map(|f| f.1.as_str()).unwrap_or("feature"), - if value { "suppressed" } else { "unsuppressed" } - ), - RuleEffect::ParameterRange { parameter, minimum, maximum } => { - format!("Restricted: {parameter}, {minimum} through {maximum}") - } - RuleEffect::AllowParameterValues { parameter, expressions } => { - format!("Restricted: {parameter}, {}", expressions.join("; ")) - } - }); - } - } - } - if let Some(parameters) = preview["parameters"].as_array() { - for parameter in parameters { - if let Some(name) = parameter["name"].as_str() - && all_parameters.iter().any(|p| p == name) - { - ui.label(format!("{name} = {}", parameter["expression"].as_str().unwrap_or(""))); - } - } - } - }); - } } +fn report(ui: &mut Ui, text: impl AsRef) { + ui_kit::text(ui, crate::i18n::tr_label(text.as_ref()), ui_kit::Text::Report("rule_report", false)); +} + fn feature_choice(ui: &mut Ui, id: &str, feature: &mut u64, features: &[(u64, String)]) { - egui::ComboBox::from_id_salt(id) - .selected_text(features.iter().find(|f| f.0 == *feature).map(|f| f.1.as_str()).unwrap_or("Choose feature…")) - .show_ui(ui, |ui| { - for (id, name) in features { - ui.selectable_value(feature, *id, name); - } - }); + let options: Vec<_> = features.iter().map(|(_, name)| name.clone()).collect(); + let mut selected = features.iter().find(|f| f.0 == *feature).map(|f| f.1.clone()).unwrap_or_default(); + choose(ui, id, &mut selected, &options); + if let Some((id, _)) = features.iter().find(|(_, name)| *name == selected) { + *feature = *id; + } } fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[String], features: &[(u64, String)], depth: usize) { @@ -290,8 +334,7 @@ fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[St if depth < 8 { choices.extend(["All".into(), "Any".into(), "Not".into()]); } - ui.horizontal(|ui| { - ui.label(crate::i18n::tr_label("When")); + ui.vertical(|ui| { choose(ui, "condition_kind", &mut kind, &choices); }); if previous != kind { @@ -310,37 +353,29 @@ fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[St } match condition { RuleCondition::Parameter { parameter, operator, expression } => { - ui.horizontal(|ui| { + ui.vertical(|ui| { choose(ui, "condition_parameter", parameter, parameters); - egui::ComboBox::from_id_salt("condition_operator") - .width(45.0) - .selected_text(match operator { - RuleOperator::Eq => "=", - RuleOperator::Ne => "≠", - RuleOperator::Lt => "<", - RuleOperator::Le => "≤", - RuleOperator::Gt => ">", - RuleOperator::Ge => "≥", - }) - .show_ui(ui, |ui| { - for (label, op) in [ - ("=", RuleOperator::Eq), - ("≠", RuleOperator::Ne), - ("<", RuleOperator::Lt), - ("≤", RuleOperator::Le), - (">", RuleOperator::Gt), - ("≥", RuleOperator::Ge), - ] { - ui.selectable_value(operator, op, label); - } - }); - ui.add(egui::TextEdit::singleline(expression).desired_width(110.0)); + let operators = [ + ("=", RuleOperator::Eq), + ("≠", RuleOperator::Ne), + ("<", RuleOperator::Lt), + ("≤", RuleOperator::Le), + (">", RuleOperator::Gt), + ("≥", RuleOperator::Ge), + ]; + let mut index = operators.iter().position(|(_, op)| op == operator).unwrap_or(0); + let labels: Vec<_> = operators.iter().map(|(label, _)| *label).collect(); + ui_kit::row(ui, "Operator", |ui| ui_kit::dropdown(ui, "condition_operator", &mut index, &labels)); + if let Some((_, op)) = operators.get(index) { + *operator = *op; + } + ui_kit::row(ui, "Expression", |ui| ui_kit::field(ui, expression)); }); } RuleCondition::Suppression { feature, value } => { - ui.horizontal(|ui| { + ui.vertical(|ui| { feature_choice(ui, "condition_feature", feature, features); - ui.checkbox(value, crate::i18n::tr_label("Suppressed")); + ui_kit::checkbox(ui, crate::i18n::tr_label("Suppressed"), value); }); } RuleCondition::All { conditions } | RuleCondition::Any { conditions } => { @@ -349,7 +384,7 @@ fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[St ui.push_id(index, |ui| { ui.indent("condition_group", |ui| { condition_editor(ui, child, parameters, features, depth + 1); - if ui.small_button("Remove condition").clicked() { + if ui_kit::button(ui, crate::i18n::tr_label("Remove condition"), ui_kit::Button::Small).clicked() { remove = Some(index); } }); @@ -358,7 +393,7 @@ fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[St if let Some(index) = remove { conditions.remove(index); } - if ui.add_enabled(conditions.len() < 32, egui::Button::new(crate::i18n::tr_label("Add condition")).small()).clicked() { + if ui_kit::button(ui, "Add condition", ui_kit::Button::Enabled(conditions.len() < 32)).clicked() { conditions.push(RuleCondition::Always); } } diff --git a/crates/ui-egui/src/configurations_ui.rs b/crates/ui-egui/src/configurations_ui.rs new file mode 100644 index 0000000000..28e315f097 --- /dev/null +++ b/crates/ui-egui/src/configurations_ui.rs @@ -0,0 +1,449 @@ +//! Manage configuration table. Commands and cell decoding remain in dialogs_motion. +use super::{cell_value, column_parts, configuration_rules_ui, table, unit_of}; +use crate::{SolveApp, ui_kit}; +use serde_json::{Value, json}; +/// The Configurations table, its rules and errors. +pub(super) fn configurations_rows( + app: &mut SolveApp, + ui: &mut egui::Ui, + new_row: &mut String, + error: &mut Option, + rule_editor: &mut configuration_rules_ui::Editor, +) { + // Physical materials for the configuration table's Library: the library's, then the design's own. + let material_names = app.session.doc.material_names(); + let revision = app.session.revision; + let v = rule_editor.table.as_ref().filter(|(r, _)| *r == revision).map(|(_, v)| v.clone()).unwrap_or_else(|| { + let value = app.session.execute("config.table", &json!({})).unwrap_or(Value::Null); + rule_editor.table = Some((revision, value.clone())); + value + }); + let mut tb = table(&v); + // Bare numbers show with their parameter's unit ("40" is 40 mm), as Fusion shows them. + let kinds = app.session.doc.all_param_exprs(); + for (_, vals) in &mut tb.rows { + for (c, val) in tb.columns.iter().zip(vals.iter_mut()) { + let (kind, n) = column_parts(c); + if kind == "param" + && val.trim().parse::().is_ok() + && let Some(unit) = app.session.doc.param(n).map(|parameter| parameter.unit.clone()).or_else(|| { + kinds.iter().find(|x| x.0 == n).and_then(|x| { + unit_of(x.2).map(|unit| { + if x.2 == solidcraft_engine::doc::expr::Kind::Length { app.session.doc.units.clone() } else { unit.to_string() } + }) + }) + }) + { + *val = format!("{} {unit}", val.trim()); + } + } + } + let run = |app: &mut SolveApp, error: &mut Option, id: &str, p: Value| { + *error = app.run(id, p).err(); + }; + let labels: Vec<_> = std::iter::once(crate::i18n::tr_label("Configuration").to_owned()) + .chain(tb.columns.iter().map(|c| { + let (kind, name) = column_parts(c); + v["targets"] + .as_array() + .and_then(|targets| targets.iter().find(|target| target["key"].as_str() == Some(c))) + .and_then(|target| target["label"].as_str()) + .filter(|_| matches!(kind, "component_appearance" | "face_appearance")) + .map(str::to_string) + .unwrap_or_else(|| match kind { + "material" => format!("{}: {name}", crate::i18n::tr_label("Material")), + "appearance" => format!("{}: {name}", crate::i18n::tr_label("Appearance")), + "suppress" => format!("{}: {name}", crate::i18n::tr_label("Suppress")), + _ => name.to_owned(), + }) + })) + .chain(std::iter::once(crate::i18n::tr_label("Actions").to_owned())) + .collect(); + let gap = ui.spacing().item_spacing.x; + let name_width = tb.rows.iter().map(|(name, _)| ui_kit::ContentWidth::name(ui, name)).fold(0.0, f32::max); + let status_width = (ui_kit::ContentWidth::label(ui, "Active") + gap + ui_kit::ContentWidth::button(ui, "Apply")) + .max(ui_kit::ContentWidth::button(ui, "Activate")); + let columns: Vec<_> = labels + .iter() + .enumerate() + .map(|(index, label)| { + let min_width = if index == 0 { + name_width + gap + status_width + crate::theme::Tokens::get().space_24 + } else { + ui_kit::row_height() * 2.0 + crate::theme::Tokens::get().space_24 + }; + if index == labels.len() - 1 { + ui_kit::Column::new(label, 0.0) + .icon_only() + .min_content_width(crate::theme::Tokens::get().control_height + crate::theme::Tokens::get().space_12) + } else { + let weight = if index == 0 { + 1.6 + } else { + match tb.columns.get(index - 1).map(|column| column_parts(column).0) { + Some("param") => 0.9, + Some("suppress") => 1.1, + Some("material") => 1.2, + _ => 1.3, + } + }; + ui_kit::Column::new(label, weight).flex().min_content_width(min_width) + } + }) + .collect(); + ui_kit::table_custom(ui, "configuration_table", &columns, crate::i18n::tr("manage.empty"), |ui, table| { + table.headers(ui, |ui, index| { + if index == labels.len() - 1 { + column_menu(app, ui, error, &tb.columns); + true + } else if index > 0 { + if let (Some(column), Some(label)) = (tb.columns.get(index - 1), labels.get(index)) + && ui_kit::removable_header(ui, label) + { + let (kind, name) = column_parts(column); + let key = if kind == "suppress" { "feature" } else { kind }; + run(app, error, "config.column", json!({key:if kind == "face_appearance" {column.as_str()} else {name},"delete":true})); + } + true + } else { + false + } + }); + for (name, values) in &tb.rows { + let active = tb.active.as_deref() == Some(name.as_str()); + table.row(ui, name, active, |row| { + row.cell(|ui| { + // A measured name slot keeps status/actions inside the Configuration column. + ui.allocate_ui_with_layout( + egui::vec2(name_width, ui_kit::row_height()), + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui_kit::text(ui, name, ui_kit::Text::NameCell(true)); + }, + ); + if active { + ui_kit::active_pill(ui); + if v.get("matches").and_then(Value::as_bool) == Some(false) { + let apply = ui_kit::button(ui, crate::i18n::tr_label("Apply"), ui_kit::Button::Small); + crate::scenario::publish_handle(&format!("config:activate:{name}"), apply.rect.center()); + if apply.clicked() { + run(app, error, "config.activate", json!({"row":name})); + } + } + } else { + let activate = ui_kit::button(ui, crate::i18n::tr_label("Activate"), ui_kit::Button::Small); + crate::scenario::publish_handle(&format!("config:activate:{name}"), activate.rect.center()); + if activate.clicked() { + run(app, error, "config.activate", json!({"row":name})); + } + } + }); + for (column, value) in tb.columns.iter().zip(values) { + row.cell(|ui| { + let (kind, _) = column_parts(column); + let shown = display_cell(kind, value); + let key = (name.clone(), column.clone()); + let mut text = rule_editor.drafts.get(&key).cloned().unwrap_or_else(|| shown.clone()); + let id = ui.make_persistent_id(&key); + let (response, choice) = value_control(ui, id, &mut text, kind, &material_names); + if let Some(choice) = choice { + run(app, error, "config.row", json!({"name":name,"values":{column:choice}})); + } + crate::scenario::publish_handle(&format!("config:cell:{name}:{column}"), response.rect.center()); + let focused = response.has_focus(); + let completed = crate::params_dialog::complete(ui, &response, &mut text); + if response.changed() || completed { + rule_editor.drafts.insert(key.clone(), text.clone()); + } + let enter = !completed && focused && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Enter)); + if response.lost_focus() || enter { + rule_editor.drafts.remove(&key); + if text != shown { + run(app, error, "config.row", json!({"name":name,"values":{column:cell_value(column,&text)}})); + } + } + }); + } + row.cell(|ui| { + if ui_kit::bin_button(ui, "Delete the row").clicked() { + run(app, error, "config.row", json!({"name":name,"delete":true})); + } + }); + }); + } + if rule_editor.adding_row { + let menu_was_open = egui::Popup::is_any_open(ui.ctx()); + let mut commit = false; + let mut discard = false; + let mut left = false; + let reveal = rule_editor.focus_new_row; + let response = ui + .push_id("configuration_draft", |ui| { + table.row(ui, "new", false, |row| { + row.cell(|ui| { + let id = ui.make_persistent_id("new_configuration"); + let focused = ui.memory(|m| m.has_focus(id) || m.had_focus_last_frame(id)); + let response = ui_kit::cell_editor(ui, id, new_row, "new configuration", false); + crate::scenario::publish_handle("config:new:name", response.rect.center()); + if rule_editor.focus_new_row { + response.request_focus(); + rule_editor.focus_new_row = false; + } + commit = focused && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Enter)); + left = response.lost_focus(); + }); + let source = tb.rows.iter().find(|(name, _)| tb.active.as_ref() == Some(name)); + for (index, column) in tb.columns.iter().enumerate() { + row.cell(|ui| { + let mut value = rule_editor.new_values.get(column).cloned().unwrap_or_else(|| { + source + .and_then(|(_, values)| values.get(index)) + .map(|value| display_cell(column_parts(column).0, value)) + .unwrap_or_default() + }); + let (kind, _) = column_parts(column); + let (response, choice) = value_control(ui, ui.id().with(column), &mut value, kind, &material_names); + let picked = choice.is_some(); + if let Some(choice) = choice { + value = choice; + } + crate::scenario::publish_handle(&format!("config:new:cell:{column}"), response.rect.center()); + let completed = crate::params_dialog::complete(ui, &response, &mut value); + if response.changed() || completed || picked { + rule_editor.new_values.insert(column.clone(), value); + } + let focused = response.has_focus() || response.lost_focus(); + left |= response.lost_focus(); + commit |= !completed && focused && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Enter)); + }); + } + row.cell(|ui| { + discard = ui_kit::bin_button(ui, "Delete the row").clicked(); + }); + }) + }) + .inner; + if reveal { + table.reveal_row(response.rect); + } + let abandoned = new_row.trim().is_empty() + && !menu_was_open + && ui.input(|i| i.pointer.any_click() && i.pointer.interact_pos().is_some_and(|p| !response.rect.contains(p))); + if discard || abandoned { + rule_editor.adding_row = false; + new_row.clear(); + rule_editor.new_values.clear(); + } else if commit || (left && !new_row.trim().is_empty()) { + let name = if new_row.trim().is_empty() { + (tb.rows.len() + 1..).map(|k| format!("Configuration{k}")).find(|n| !tb.rows.iter().any(|r| &r.0 == n)).unwrap_or_default() + } else { + new_row.trim().to_string() + }; + let values: serde_json::Map = + rule_editor.new_values.iter().map(|(col, text)| (col.clone(), cell_value(col, text))).collect(); + let mut params = json!({"name":name}); + if !values.is_empty() { + params["values"] = Value::Object(values); + } + if let Some(active) = &tb.active { + params["from"] = json!(active); + } + run(app, error, "config.row", params); + new_row.clear(); + rule_editor.adding_row = false; + rule_editor.new_values.clear(); + } + } + if table.add_prompt(ui, "Add configuration", "A new configuration, a copy of the active one").clicked() { + rule_editor.adding_row = true; + rule_editor.focus_new_row = true; + } + }); + configuration_rules_ui::show(ui, app, error, rule_editor); + if let Some(styles) = v["style_status"].as_array() { + for style in styles.iter().filter(|style| style["dormant"].as_bool() == Some(true)) { + ui_kit::text( + ui, + &format!("{}: appearance dormant while its feature is suppressed", style["target"].as_str().unwrap_or("Face")), + ui_kit::Text::Report("dormant", false), + ); + } + } + if let Some(message) = v["style_error"].as_str() { + ui_kit::text(ui, message, ui_kit::Text::Error("style_error")); + } + if let Some(message) = error { + ui_kit::text(ui, message, ui_kit::Text::Error("config_error")); + } + // Table cells and rule editors own Enter. As in the legacy wide manager, it + // commits an edited cell without also submitting the enclosing card. + ui.input_mut(|input| input.consume_key(egui::Modifiers::NONE, egui::Key::Enter)); +} + +fn column_menu(app: &mut SolveApp, ui: &mut egui::Ui, error: &mut Option, columns: &[String]) { + let run = |app: &mut SolveApp, error: &mut Option, id: &str, params: Value| { + *error = app.run(id, params).err(); + }; + // A column: a parameter's value or a feature's suppression. + let mut options: Vec<(String, Value)> = Vec::new(); + for p in app.session.doc.all_param_exprs() { + if !columns.contains(&format!("param:{}", p.0)) { + options.push((format!("{} (parameter)", p.0), json!({ "param": p.0 }))); + } + } + for f in &app.session.doc.features { + if !columns.contains(&format!("suppress:{}", f.name)) { + options.push((format!("Suppress {}", f.name), json!({ "feature": f.name }))); + } + } + for body in &app.session.model.state().bodies { + for kind in ["material", "appearance"] { + if !columns.contains(&format!("{kind}:{}", body.name)) { + options.push((format!("{} ({kind})", body.name), json!({kind: body.name}))); + } + } + } + for component in &app.session.doc.components { + if component.id != 0 && !columns.contains(&format!("component_appearance:{}", component.id)) { + options.push((format!("{} (component appearance)", component.name), json!({"component_appearance":component.id}))); + } + } + for selected in &app.session.selection { + if let solidcraft_engine::Sel::Face { body, point, .. } = selected { + options.push((format!("Selected face on {body} (shared appearance)"), json!({"face_appearance":{"body":body,"point":point}}))); + } + } + let mut pick: Option = None; + ui_kit::plus_header(ui, |ui| { + egui::ScrollArea::vertical().max_height(crate::theme::Tokens::get().space_24 * 12.0).show(ui, |ui| { + for (label, p) in &options { + if ui_kit::button(ui, label.as_str(), ui_kit::Button::Normal).clicked() { + pick = Some(p.clone()); + ui.close(); + } + } + ui_kit::action_menu(ui, "Face Appearance", |ui| { + ui_kit::text( + ui, + crate::i18n::tr_label("Semantic faces share their appearance across instances."), + ui_kit::Text::Note("semantic_faces"), + ); + let state = app.session.model.state(); + for body in &state.bodies { + if body.body.is_mesh() { + continue; + } + ui_kit::action_menu(ui, &format!("Faces on {}", body.name), |ui| { + if let Ok(faces) = body.body.faces((body.body.size() * 2e-3).max(1e-3)) { + let mut seen = std::collections::BTreeSet::new(); + let mesh = body.mesh(); + let mut points = std::collections::BTreeMap::new(); + for (triangle, index) in mesh.triangles.iter().zip(&mesh.tri_face) { + if let std::collections::btree_map::Entry::Vacant(entry) = points.entry(*index as usize) + && let Some([a, b, c]) = mesh.tri(triangle) + { + entry.insert((a + b + c) * (1. / 3.)); + } + } + for face in faces { + // A curved face centroid can lie on another surface. + // Pick an interior triangle point on this actual face. + let Some(point) = points.get(&face.index).copied() else { continue }; + let Ok(target) = app.session.doc.configuration_face_target(body, face.index, point) else { continue }; + let supported = if target.certificate.is_some() { + // Capture read this exact face's current source certificate. + // Enumerate its pieces once in the command, not per menu face. + app.session.doc.certify_config_face(&target).is_ok() + } else { + app.session.doc.configuration_face_indices(body, &target).is_ok() + }; + if !supported || !seen.insert(target.name.clone()) { + continue; + } + let Some((_, role)) = target.name.split_once(':') else { continue }; + if ui_kit::button(ui, role, ui_kit::Button::Normal).clicked() { + pick = Some(json!({"face_appearance":{"target":target}})); + ui.close(); + } + } + } + }); + } + }); + }); + }); + if let Some(p) = pick { + // The first column starts the table (with a Default row of the current values). + let (id, p) = if columns.is_empty() { + ( + "config.configure", + if let Some(n) = p.get("param") { + json!({ "params": [n] }) + } else if let Some(n) = p.get("feature") { + json!({ "features": [n] }) + } else if let Some(n) = p.get("material") { + json!({ "materials": [n] }) + } else if let Some(n) = p.get("component_appearance") { + json!({ "component_appearances": [n] }) + } else if let Some(n) = p.get("face_appearance") { + json!({ "face_appearances": [n] }) + } else { + json!({ "appearances": [p.get("appearance")] }) + }, + ) + } else { + ("config.column", p) + }; + run(app, error, id, p); + } +} + +fn display_cell(kind: &str, value: &str) -> String { + if (kind == "material" || matches!(kind, "appearance" | "component_appearance" | "face_appearance")) && value.eq_ignore_ascii_case("Default") { + return String::new(); + } + if matches!(kind, "appearance" | "component_appearance" | "face_appearance") { + solidcraft_engine::doc::config::appearance_cell(value) + .ok() + .flatten() + .filter(|look| solidcraft_engine::doc::appearance::library(&look.name).as_ref() == Some(look)) + .map(|look| look.name) + .unwrap_or_else(|| value.to_owned()) + } else { + value.to_owned() + } +} + +fn value_control(ui: &mut egui::Ui, id: egui::Id, text: &mut String, kind: &str, material_names: &[String]) -> (egui::Response, Option) { + let appearance = matches!(kind, "appearance" | "component_appearance" | "face_appearance"); + let mut choice = None; + let response = if kind == "material" || appearance { + let follow = if kind == "appearance" { + "Follow material" + } else if appearance { + "Follow inherited appearance" + } else { + "Default material" + }; + ui_kit::library_cell(ui, id, text, follow, |ui| { + if ui_kit::button(ui, crate::i18n::tr_label(follow), ui_kit::Button::Normal).clicked() { + choice = Some("Default".to_owned()); + ui.close(); + } + let choices: Vec<_> = if kind == "material" { + material_names.iter().filter(|n| *n != "Default").map(String::as_str).collect() + } else { + solidcraft_engine::doc::appearance::LIBRARY.iter().map(|(name, _, _)| *name).collect() + }; + for name in choices { + if ui_kit::button(ui, name, ui_kit::Button::Normal).clicked() { + choice = Some(name.to_owned()); + ui.close(); + } + } + }) + } else { + ui_kit::cell_editor(ui, id, text, "", false) + }; + (response, choice) +} diff --git a/crates/ui-egui/src/context_menu.rs b/crates/ui-egui/src/context_menu.rs index cd6bcc6fad..da64bd1832 100644 --- a/crates/ui-egui/src/context_menu.rs +++ b/crates/ui-egui/src/context_menu.rs @@ -11,14 +11,12 @@ //! state; [`items`] lists them and [`run_item`] runs one, so the control channel (`ui.menu`, //! `ui.menuPick`) and tests reach everything a click does. -use egui::{Align2, Color32, FontId, Pos2, Rect, Sense, Stroke, Vec2, pos2, vec2}; +use egui::{Color32, Pos2, Rect, Sense, Vec2, pos2, vec2}; use serde::Serialize; use serde_json::{Value, json}; use solidcraft_engine::Sel; -use crate::theme::Tokens; - -use crate::{SolveApp, icons}; +use crate::SolveApp; /// What a menu is about. #[derive(Clone, Debug, Default, PartialEq, Serialize, serde::Deserialize)] @@ -103,6 +101,10 @@ pub struct Item { pub icon: String, #[serde(skip_serializing_if = "Option::is_none")] pub params: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub children: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub swatch: Option<[u8; 3]>, } impl Item { @@ -111,10 +113,28 @@ impl Item { act(id, label, icon) } pub fn sep() -> Item { - Item { id: "-".into(), label: String::new(), shortcut: String::new(), enabled: false, icon: String::new(), params: None } + Item { + id: "-".into(), + label: String::new(), + shortcut: String::new(), + enabled: false, + icon: String::new(), + params: None, + children: Vec::new(), + swatch: None, + } } fn heading(label: &str) -> Item { - Item { id: format!("#{label}"), label: label.into(), shortcut: String::new(), enabled: false, icon: String::new(), params: None } + Item { + id: format!("#{label}"), + label: label.into(), + shortcut: String::new(), + enabled: false, + icon: String::new(), + params: None, + children: Vec::new(), + swatch: None, + } } fn is_sep(&self) -> bool { self.id == "-" @@ -154,12 +174,23 @@ pub(crate) fn cmd(app: &SolveApp, id: &str, label: &str) -> Item { enabled: info.as_ref().is_some_and(|i| i.enabled), icon: info.map(|i| i.icon.to_string()).unwrap_or_default(), params: None, + children: Vec::new(), + swatch: None, } } /// A view action (`ui.*`). pub(crate) fn act(id: &str, label: &str, icon: &str) -> Item { - Item { id: id.into(), label: label.into(), shortcut: String::new(), enabled: true, icon: icon.into(), params: None } + Item { + id: id.into(), + label: label.into(), + shortcut: String::new(), + enabled: true, + icon: icon.into(), + params: None, + children: Vec::new(), + swatch: None, + } } /// Menu state kept between frames. The anchor is `app.viewport.context_menu`. @@ -168,6 +199,8 @@ pub struct MenuState { pub target: Target, /// The list row chosen with the arrow keys (Enter runs it). pub key_row: Option, + submenu: Option, + submenu_key: Option, /// The radial ring is shown (viewport menus). pub radial: bool, /// The right button is held through the menu: releasing over a direction picks it. @@ -214,8 +247,8 @@ pub struct Rename { /// Radial slots, clockwise from the top. const DIRS: usize = 8; /// Half axes of the ring of radial items. -const RX: f32 = 118.0; -const RY: f32 = 84.0; +const RX: f32 = crate::ui_kit::RADIAL_RADIUS; +const RY: f32 = crate::ui_kit::RADIAL_RADIUS; // --------------------------------------------------------------------------------------------- // What the menus offer @@ -265,32 +298,6 @@ fn body_feature(app: &SolveApp, body: &str) -> Option { app.session.world_state().body(body).map(|b| b.feature) } -/// The bodies a target stands for. -pub fn target_bodies(app: &SolveApp, target: &Target) -> Vec { - match target { - Target::Viewport => touched_bodies(app), - Target::Body { name } => vec![name.clone()], - Target::Component { id } => component_bodies(app, *id), - Target::Occurrence { path } => app.session.occurrence_bodies(path, true).unwrap_or_default(), - Target::Group { id, .. } => crate::browser::group_bodies(app, *id), - Target::Sketch { .. } - | Target::Feature { .. } - | Target::Plane { .. } - | Target::OriginPlane { .. } - | Target::Folder { .. } - | Target::Canvas { .. } - | Target::Origin - | Target::Units - | Target::Joint { .. } - | Target::ToolbarCommand { .. } - | Target::Toolbar - | Target::Workspace - | Target::NamedViews - | Target::NamedView { .. } - | Target::Section => Vec::new(), - } -} - /// Bodies in a component and its subcomponents. pub fn component_bodies(app: &SolveApp, id: u64) -> Vec { let doc = &app.session.doc; @@ -335,8 +342,8 @@ pub fn items(app: &SolveApp, target: &Target) -> Vec { Target::Body { name } => body_items(app, std::slice::from_ref(name), true), Target::Sketch { id } => sketch_items(app, *id), Target::Feature { id } => vec![ - act("ui.editFeature", "Edit Feature", "").with(json!({"feature":id})).on(app.session.doc.feature(*id).is_some()), - act("ui.findTimeline", "Find in Timeline", "").with(json!({"feature":id})).on(app.session.doc.feature(*id).is_some()), + act("ui.editFeature", "Edit Feature", "edit").with(json!({"feature":id})).on(app.session.doc.feature(*id).is_some()), + act("ui.findTimeline", "Find in Timeline", "rollback").with(json!({"feature":id})).on(app.session.doc.feature(*id).is_some()), ], Target::Plane { id } => plane_items(app, *id), Target::OriginPlane { name } => origin_plane_items(app, name), @@ -379,6 +386,43 @@ pub fn items(app: &SolveApp, target: &Target) -> Vec { v.push(Item::sep()); v.push(act("ui.groupSelected", "Group Selected", "folder").with(p)); } + for item in &mut v { + if item.id != "ui.appearance" { + continue; + } + let original = item.clone(); + let params = item.params.clone().unwrap_or_else(|| { + let faces: Vec<_> = app + .session + .selection + .iter() + .filter_map(|s| match s { + Sel::Face { body, point, .. } => Some(json!({"body":body,"point":[point.x, point.y, point.z]})), + _ => None, + }) + .collect(); + if faces.is_empty() { json!({"bodies": touched_bodies(app)}) } else { json!({"faces":faces}) } + }); + let targets = if let Some(component) = params.get("component").and_then(Value::as_u64).filter(|id| *id != 0) { + json!({"components": [component.to_string()]}) + } else { + params + }; + item.children.push(original); + if ["bodies", "components", "faces"].iter().any(|key| targets.get(key).and_then(Value::as_array).is_some_and(|v| !v.is_empty())) { + for look in crate::dialogs_appearance::builtin() { + let mut params = targets.clone(); + params["appearance"] = json!(look.name); + params["color"] = json!(crate::dialogs_appearance::hex(look.color)); + params["opacity"] = json!(look.opacity); + let mut child = cmd(app, "appearance.assign", &look.name).with(params); + child.swatch = Some(look.color); + item.children.push(child); + } + item.children.push(Item::sep()); + item.children.push(cmd(app, "appearance.assign", "Remove override").with(targets)); + } + } v } @@ -425,7 +469,7 @@ fn viewport_items(app: &SolveApp) -> Vec { } // Everything under the cursor, for what lies beneath something else. if app.menu.over_geometry { - v.push(act("ui.selectOther", "Select Other…", "").key("Hold click")); + v.push(act("ui.selectOther", "Select Other…", "select").key("Hold click")); v.push(Item::sep()); } if app.session.active_sketch.is_some() { @@ -457,7 +501,7 @@ fn viewport_items(app: &SolveApp) -> Vec { v.push(cmd(app, "solid.extrude", "Extrude")); v.push(cmd(app, "construct.plane.offset", "Offset Plane")); v.push(cmd(app, "solid.shell", "Shell")); - v.push(act("ui.editFeature", "Edit Feature", "").with(json!({ "feature": feature })).on(feature.is_some())); + v.push(act("ui.editFeature", "Edit Feature", "edit").with(json!({ "feature": feature })).on(feature.is_some())); v.push(act("ui.editSketch", "Edit Profile Sketch", "sketch").with(json!({ "sketch": sketch })).on(sketch.is_some())); v.push(Item::sep()); v.push(act("ui.appearance", "Appearance", "appearance").key("A")); @@ -467,8 +511,8 @@ fn viewport_items(app: &SolveApp) -> Vec { v.push(act("ui.delete", "Delete", "delete").key("Del")); v.push(act(if hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": [face] }))); v.push(Item::sep()); - v.push(act("ui.findBrowser", "Find in Browser", "").with(json!({ "body": face, "feature": feature })).on(feature.is_some())); - v.push(act("ui.findTimeline", "Find in Timeline", "").with(json!({ "feature": feature })).on(feature.is_some())); + v.push(act("ui.findBrowser", "Find in Browser", "list").with(json!({ "body": face, "feature": feature })).on(feature.is_some())); + v.push(act("ui.findTimeline", "Find in Timeline", "rollback").with(json!({ "feature": feature })).on(feature.is_some())); v.push(Item::sep()); v.push(cmd(app, "solid.press_pull", "Press Pull")); v.push(cmd(app, "inspect.measure", "Measure")); @@ -481,10 +525,10 @@ fn viewport_items(app: &SolveApp) -> Vec { v.push(act("ui.delete", "Delete", "delete").key("Del")); v.push(act(if hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": [body] }))); v.push(Item::sep()); - v.push(act("ui.findBrowser", "Find in Browser", "").with(json!({ "body": body }))); + v.push(act("ui.findBrowser", "Find in Browser", "list").with(json!({ "body": body }))); v.push(act("ui.saveMesh", "Save As Mesh", "export").with(json!({ "bodies": [body] }))); v.push(Item::sep()); - v.push(act("ui.tangentChain", "Select Tangent Chain", "")); + v.push(act("ui.tangentChain", "Select Tangent Chain", "chain")); v.push(cmd(app, "inspect.measure", "Measure")); } else if has(|s| matches!(s, Sel::Body { .. })) { v.extend(body_items(app, &sel_bodies(app), false)); @@ -507,7 +551,7 @@ fn viewport_items(app: &SolveApp) -> Vec { || !app.session.visibility.hidden_sketches.is_empty() || !app.session.visibility.hidden_folders.is_empty(); v.push(act("ui.showAll", "Show All", "eye").on(anything_hidden)); - v.push(act("ui.showAll", "Unisolate", "").on(!app.session.visibility.hidden_bodies.is_empty())); + v.push(act("ui.showAll", "Unisolate", "eye").on(!app.session.visibility.hidden_bodies.is_empty())); v.push(Item::sep()); v.push(cmd(app, "solid.extrude", "Extrude")); v.push(cmd(app, "solid.fillet", "Fillet")); @@ -528,7 +572,7 @@ fn viewport_items(app: &SolveApp) -> Vec { v.push(cmd(app, "parts.resize", "Change Part Size").with(json!({"component":component}))); } v.push(Item::sep()); - v.push(act("ui.clear", "Clear Selection", "").key("Esc")); + v.push(act("ui.clear", "Clear Selection", "close").key("Esc")); } v } @@ -543,7 +587,7 @@ fn body_items(app: &SolveApp, bodies: &[String], browser: bool) -> Vec { act("ui.moveBodies", "Move/Copy", "move").key("M").with(json!({ "bodies": names })).on(!locked), cmd(app, "component.from_bodies", "Create Components from Bodies").with(json!({ "bodies": names })), Item::sep(), - act("ui.material", "Physical Material", "").with(json!({ "bodies": names })), + act("ui.material", "Physical Material", "material").with(json!({ "bodies": names })), act("ui.appearance", "Appearance", "appearance").key("A").with(json!({ "bodies": names })), act("ui.properties", "Properties", "measure").with(json!({ "bodies": names })), Item::sep(), @@ -552,11 +596,11 @@ fn body_items(app: &SolveApp, bodies: &[String], browser: bool) -> Vec { Item::sep(), act("ui.delete", "Delete", "delete").key("Del").with(json!({ "items": items })).on(!locked), cmd(app, "solid.remove", "Remove").with(json!({ "bodies": names })).on(!locked), - act("ui.rename", "Rename", "").with(json!({ "body": bodies.first() })).on(one), + act("ui.rename", "Rename", "rename").with(json!({ "body": bodies.first() })).on(one), Item::sep(), act(if all_hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": names })), - act("ui.lock", if locked { "Unlock" } else { "Lock" }, "").with(json!({ "bodies": names })), - act("ui.isolate", "Isolate", "").with(json!({ "bodies": names })), + act("ui.lock", if locked { "Unlock" } else { "Lock" }, "ground").with(json!({ "bodies": names })), + act("ui.isolate", "Isolate", "body").with(json!({ "bodies": names })), ]; if one { let b = bodies.first().cloned().unwrap_or_default(); @@ -565,9 +609,9 @@ fn body_items(app: &SolveApp, bodies: &[String], browser: bool) -> Vec { } v.push(Item::sep()); if !browser { - v.push(act("ui.findBrowser", "Find in Browser", "").with(json!({ "body": b }))); + v.push(act("ui.findBrowser", "Find in Browser", "list").with(json!({ "body": b }))); } - v.push(act("ui.findTimeline", "Find in Timeline", "").with(json!({ "body": b }))); + v.push(act("ui.findTimeline", "Find in Timeline", "rollback").with(json!({ "body": b }))); } v } @@ -586,14 +630,14 @@ fn sketch_items(app: &SolveApp, id: u64) -> Vec { act("ui.exportDxf", "Export DXF…", "export").with(json!({ "sketch": id })), Item::sep(), act("ui.delete", "Delete", "delete").key("Del").with(json!({ "items": [{"type": "feature", "id": id}] })), - act("ui.rename", "Rename", "").with(json!({ "feature": id })), + act("ui.rename", "Rename", "rename").with(json!({ "feature": id })), Item::sep(), - act("ui.lookAt", "Look At", "").with(json!({ "sketch": id })), - act("ui.sketchProfile", if profiles { "Hide Profile" } else { "Show Profile" }, "").with(json!({ "sketch": id })), + act("ui.lookAt", "Look At", "lookat").with(json!({ "sketch": id })), + act("ui.sketchProfile", if profiles { "Hide Profile" } else { "Show Profile" }, "sketch").with(json!({ "sketch": id })), act("ui.sketchDims", if dims { "Hide Dimension" } else { "Show Dimension" }, "dimension").with(json!({ "sketch": id })), act(if visible { "ui.hideSketch" } else { "ui.showSketch" }, "Show/Hide", "eye").key("V").with(json!({ "sketch": id })), Item::sep(), - act("ui.findTimeline", "Find in Timeline", "").with(json!({ "feature": id })), + act("ui.findTimeline", "Find in Timeline", "rollback").with(json!({ "feature": id })), ] } @@ -607,11 +651,11 @@ fn plane_items(app: &SolveApp, id: u64) -> Vec { act("ui.editFeature", "Edit Feature", "plane").with(json!({ "feature": id })), Item::sep(), act("ui.delete", "Delete", "delete").key("Del").with(json!({ "items": [{"type": "feature", "id": id}] })), - act("ui.rename", "Rename", "").with(json!({ "feature": id })), + act("ui.rename", "Rename", "rename").with(json!({ "feature": id })), Item::sep(), act("ui.originPart", "Show/Hide", "eye").key("V").with(json!({ "keys": [f.name], "show": !visible })), Item::sep(), - act("ui.findTimeline", "Find in Timeline", "").with(json!({ "feature": id })), + act("ui.findTimeline", "Find in Timeline", "rollback").with(json!({ "feature": id })), ] } @@ -637,14 +681,14 @@ fn joint_items(app: &SolveApp, id: u64) -> Vec { let hidden = !joints.iter().any(|j| app.session.joint_visible(*j)); vec![ act("ui.editJoint", "Edit Joint", "joint").with(json!({ "joint": id })), - act("ui.editJoint", "Edit Joint Limits", "").with(json!({ "joint": id })).on(!rigid), - act("ui.driveJoint", "Drive Joint", "").with(json!({ "joint": id })).on(!rigid), - act("ui.animateJoint", "Animate Joint", "").with(json!({ "joint": id })).on(!rigid && !j.suppressed), - act("ui.findJoint", "Find in Window", "").with(json!({ "joint": id })), + act("ui.editJoint", "Edit Joint Limits", "joint").with(json!({ "joint": id })).on(!rigid), + act("ui.driveJoint", "Drive Joint", "drive").with(json!({ "joint": id })).on(!rigid), + act("ui.animateJoint", "Animate Joint", "play").with(json!({ "joint": id })).on(!rigid && !j.suppressed), + act("ui.findJoint", "Find in Window", "fit").with(json!({ "joint": id })), cmd(app, "joint.edit", if j.suppressed { "Unsuppress" } else { "Suppress" }).with(json!({ "joint": id, "suppressed": !j.suppressed })), Item::sep(), act("ui.delete", "Delete", "delete").key("Del").with(json!({ "joints": [id] })), - act("ui.rename", "Rename", "").with(json!({ "joint": id })), + act("ui.rename", "Rename", "rename").with(json!({ "joint": id })), Item::sep(), // Hiding a joint hides only its glyph: it still constrains and moves the parts. act("browser.visibility", "Show/Hide", "eye").key("V").with(json!({ "joints": joints, "visible": hidden })), @@ -733,12 +777,12 @@ fn component_items(app: &SolveApp, id: u64) -> Vec { } v.push(cmd(app, "component.create", "New Component").with(json!({ "parent": id }))); if id == 0 { - v.push(act("ui.copyDesign", "Copy", "").key("Ctrl+C")); - v.push(act("ui.paste", "Paste", "").key("Ctrl+V").on(crate::edit_clipboard::can_paste(app))); + v.push(act("ui.copyDesign", "Copy", "copy").key("Ctrl+C")); + v.push(act("ui.paste", "Paste", "paste").key("Ctrl+V").on(crate::edit_clipboard::can_paste(app))); v.push(cmd(app, "component.from_bodies", "Create Components from Bodies").with(json!({ "bodies": names })).on(!bodies.is_empty())); } v.push(Item::sep()); - v.push(act("ui.material", "Physical Material", "").with(json!({ "bodies": names })).on(!bodies.is_empty())); + v.push(act("ui.material", "Physical Material", "material").with(json!({ "bodies": names })).on(!bodies.is_empty())); v.push( act("ui.appearance", "Appearance", "appearance") .key("A") @@ -761,11 +805,11 @@ fn component_items(app: &SolveApp, id: u64) -> Vec { v.push(cmd(app, "component.paste_new", "Paste New").with(json!({ "component": id, "translate": [10, 10, 0] }))); v.push(act("ui.delete", "Delete", "delete").key("Del").with(json!({ "occurrences": [occ.map(|o| o.id)] })).on(occ.is_some())); } - v.push(act("ui.rename", "Rename", "").with(json!({ "component": id }))); + v.push(act("ui.rename", "Rename", "rename").with(json!({ "component": id }))); v.push(Item::sep()); v.push(act(if hidden { "ui.show" } else { "ui.hide" }, "Show/Hide", "eye").key("V").with(json!({ "bodies": names })).on(!bodies.is_empty())); v.push(act("ui.showAll", "Show All Bodies", "eye")); - v.push(act("ui.isolate", "Isolate", "").with(json!({ "bodies": names })).on(!bodies.is_empty() && id != 0)); + v.push(act("ui.isolate", "Isolate", "body").with(json!({ "bodies": names })).on(!bodies.is_empty() && id != 0)); v.push(act("ui.newGroup", "New Group", "folder").with(json!({ "component": id }))); v } @@ -819,7 +863,7 @@ fn sketch_entity_items(app: &SolveApp) -> Vec { v.push(cmd(app, "sketch.circle.center", "Circle")); v.push(cmd(app, "sketch.dimension", "Sketch Dimension")); v.push(Item::sep()); - v.push(act("ui.paste", "Paste", "").key("Ctrl+V").on(app.session.sketch_clipboard.is_some())); + v.push(act("ui.paste", "Paste", "paste").key("Ctrl+V").on(app.session.sketch_clipboard.is_some())); return v; } let ents: Vec = curves.iter().chain(points.iter()).cloned().collect(); @@ -835,9 +879,9 @@ fn sketch_entity_items(app: &SolveApp) -> Vec { v.extend(explode_text(app)); } v.push(cmd(app, "sketch.move", "Move/Copy").key("M")); - v.push(act("ui.cut", "Cut", "").key("Ctrl+X")); - v.push(act("ui.copy", "Copy", "").key("Ctrl+C")); - v.push(act("ui.paste", "Paste", "").key("Ctrl+V").on(app.session.sketch_clipboard.is_some())); + v.push(act("ui.cut", "Cut", "cut").key("Ctrl+X")); + v.push(act("ui.copy", "Copy", "copy").key("Ctrl+C")); + v.push(act("ui.paste", "Paste", "paste").key("Ctrl+V").on(app.session.sketch_clipboard.is_some())); if !curves.is_empty() { v.push(cmd(app, "sketch.construction", "Normal / Construction").with(json!({ "curves": curves }))); } @@ -1446,6 +1490,8 @@ pub fn open(app: &mut SolveApp, at: Pos2) { /// Open a menu for a target (the radial ring only for the viewport). pub fn open_for(app: &mut SolveApp, at: Pos2, target: Target) { app.menu.key_row = None; + app.menu.submenu = None; + app.menu.submenu_key = None; app.menu.radial = target == Target::Viewport && app.preferences.radial_context_menu; app.menu.target = target; app.menu.flick = false; @@ -1482,12 +1528,23 @@ pub fn describe(app: &SolveApp) -> Value { return json!({ "open": false, "pending": app.menu.pending.is_some() }); }; let radial = if app.menu.radial { radial_items(app) } else { Vec::new() }; + let describe_items = |items: Vec| { + items + .into_iter() + .map(|item| { + let placeholder = !item.icon.is_empty() && crate::icon_svg::resolve(&item.icon).placeholder; + let mut value = json!(item); + value["placeholder"] = json!(placeholder); + value + }) + .collect::>() + }; json!({ "open": true, "at": [at.x, at.y], "target": app.menu.target, - "radial": radial, - "items": items(app, &app.menu.target), + "radial": describe_items(radial), + "items": describe_items(items(app, &app.menu.target)), "rename": app.menu.rename.as_ref().map(|r| r.text.clone()), }) } @@ -1497,7 +1554,9 @@ pub fn pick(app: &mut SolveApp, key: &str) -> Result { let Some(at) = app.viewport.context_menu else { return Err("no menu is open".into()) }; let target = app.menu.target.clone(); let mut all = if app.menu.radial { radial_items(app) } else { Vec::new() }; - all.extend(items(app, &target)); + let roots = items(app, &target); + all.extend(roots.iter().cloned()); + all.extend(roots.into_iter().flat_map(|item| item.children)); let item = all.into_iter().find(|i| !i.is_sep() && !i.is_heading() && (i.id == key || i.label == key)).ok_or_else(|| format!("no menu item `{key}`"))?; if !item.enabled { @@ -1510,8 +1569,8 @@ pub fn pick(app: &mut SolveApp, key: &str) -> Result { /// Where the radial menu goes so its ring stays on screen. fn radial_center(at: Pos2, screen: Rect) -> Pos2 { - let mx = RX + 165.0; - let my = RY + 34.0; + let mx = RX + 4.0; + let my = RY + 4.0; let x = if screen.width() > 2.0 * mx { at.x.clamp(screen.left() + mx, screen.right() - mx) } else { screen.center().x }; let y = if screen.height() > 2.0 * my { at.y.clamp(screen.top() + my, screen.bottom() - my) } else { screen.center().y }; pos2(x, y) @@ -1519,15 +1578,15 @@ fn radial_center(at: Pos2, screen: Rect) -> Pos2 { /// Height of a list of items as [`list_row`] draws them in [`menu_frame`] (margins and border). fn list_height(items: &[Item]) -> f32 { - 10.0 + items + 14.0 + items .iter() .map(|i| { if i.is_sep() { - 7.0 + 11.0 } else if i.is_heading() { - 20.0 - } else { 24.0 + } else { + 30.0 } }) .sum::() @@ -1536,16 +1595,16 @@ fn list_height(items: &[Item]) -> f32 { /// Where the list goes next to a ring centred at `c`: below it when it fits, otherwise beside /// it (right, else left), kept on screen. fn list_place(c: Pos2, h: f32, screen: Rect) -> Pos2 { - let below = c.y + RY + 26.0; + let below = c.y + RY + 12.0; if below + h <= screen.bottom() - 4.0 { return pos2((c.x - LIST_W / 2.0).clamp(screen.left() + 4.0, (screen.right() - LIST_W - 4.0).max(screen.left())), below); } let y = (c.y - h / 2.0).clamp(screen.top() + 4.0, (screen.bottom() - h - 4.0).max(screen.top())); - let right = c.x + RX + 170.0; + let right = c.x + RX + 12.0; if right + LIST_W <= screen.right() - 4.0 { return pos2(right, y); } - pos2((c.x - RX - 170.0 - LIST_W).max(screen.left() + 4.0), y) + pos2((c.x - RX - 12.0 - LIST_W).max(screen.left() + 4.0), y) } /// Width of list menus. @@ -1579,28 +1638,6 @@ fn sector(d: Vec2) -> Option { Some((k.rem_euclid(DIRS as i64)) as usize) } -/// The rectangle of radial slot `k` with a label of `size`. -fn slot_rect(c: Pos2, k: usize, size: Vec2) -> Rect { - let a = (k as f32 * 45.0).to_radians(); - let p = c + vec2(a.sin() * RX, -a.cos() * RY); - let (sx, sy) = (a.sin(), -a.cos()); - let ax = if sx > 0.3 { - 0.0 - } else if sx < -0.3 { - 1.0 - } else { - 0.5 - }; - let ay = if sy > 0.9 { - 0.0 - } else if sy < -0.9 { - 1.0 - } else { - 0.5 - }; - Rect::from_min_size(p - vec2(size.x * ax, size.y * ay), size) -} - /// Press-and-hold of the right button in the viewport opens the menu; releasing over a /// direction picks it. fn track_hold(app: &mut SolveApp, ctx: &egui::Context) { @@ -1662,9 +1699,8 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { crate::browser::dims_overlay(app, ctx); track_hold(app, ctx); show_rename(app, ctx); - show_props(app, ctx); + crate::design_properties::show_object(app, ctx); let Some(at) = app.viewport.context_menu else { return }; - let t = Tokens::get(); let screen = ctx.content_rect(); let target = app.menu.target.clone(); let list = items(app, &target); @@ -1677,43 +1713,12 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { } else if app.menu.radial { let c = radial_center(at, screen); let radial = radial_items(app); - let area = Rect::from_center_size(c, vec2(2.0 * (RX + 165.0), 2.0 * (RY + 34.0))); + let area = Rect::from_center_size(c, vec2(2.0 * (RX + 4.0), 2.0 * (RY + 4.0))); inside |= pointer.is_some_and(|p| area.contains(p)); let hot = pointer.and_then(|p| sector(p - c)); let resp = crate::frame::area(egui::Id::new("sc_marking_menu")).fixed_pos(area.min).order(egui::Order::Foreground).show(ctx, |ui| { - let (r, _) = ui.allocate_exact_size(area.size(), Sense::hover()); - let painter = ui.painter_at(r.expand(4.0)); - let font = FontId::proportional(13.0); - // Centre: a ring with a pointer toward the hot direction. - let direction = hot.zip(pointer).map(|(k, p)| ((p - c).normalized(), radial.get(k).is_some_and(|i| i.enabled))); - crate::icons::glyphs::radial_centre(&painter, c, direction, t); - for (k, item) in radial.iter().enumerate() { - let display_label = crate::i18n::tr_label(&item.label); - let galley = painter.layout_no_wrap(display_label.to_string(), font.clone(), if item.enabled { t.text } else { t.text_dim }); - let size = vec2(galley.size().x + 40.0, 28.0); - let rr = slot_rect(c, k, size); - let resp = ui.interact(rr, ui.id().with(("radial", k)), Sense::click()); - let lit = item.enabled && (resp.hovered() || hot == Some(k)); - painter.rect( - rr, - 14.0, - if lit { t.accent_soft } else { t.panel }, - Stroke::new(1.0, if lit { t.accent } else { t.border }), - egui::StrokeKind::Inside, - ); - let ir = Rect::from_center_size(pos2(rr.left() + 16.0, rr.center().y), vec2(16.0, 16.0)); - let ink = if item.enabled { t.icon } else { t.border }; - if !item.icon.is_empty() { - icons::paint(&painter, ir, &item.icon, ink, t.icon_fill, t.accent); - } - painter.galley(pos2(rr.left() + 30.0, rr.center().y - galley.size().y / 2.0), galley, t.text); - if resp.clicked() && item.enabled { - picked = Some(item.clone()); - } - if item.enabled { - resp.on_hover_text(crate::i18n::tr_label(&item.shortcut).to_string()); - } - } + let _ = ui.allocate_exact_size(area.size(), Sense::hover()); + picked = crate::ui_kit::radial_menu(ui, c, &radial, hot).or(picked.take()); }); inside |= resp.response.contains_pointer(); list_place(c, list_height(&list), screen) @@ -1721,32 +1726,49 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { place(at, Vec2::ZERO, vec2(LIST_W, list_height(&list)), screen) }; - // Up/Down choose a row of the list, Enter runs it. - let (up, down, enter) = ctx.input(|i| (i.key_pressed(egui::Key::ArrowUp), i.key_pressed(egui::Key::ArrowDown), i.key_pressed(egui::Key::Enter))); - let usable: Vec = list.iter().enumerate().filter(|(_, i)| i.enabled && !i.is_sep() && !i.is_heading()).map(|(k, _)| k).collect(); - if up || down { - let at = app.menu.key_row.and_then(|r| usable.iter().position(|u| *u == r)); - let next = match (at, down) { - (None, true) => Some(0), - (None, false) => usable.len().checked_sub(1), - (Some(k), true) => Some((k + 1) % usable.len().max(1)), - (Some(k), false) => Some((k + usable.len().max(1) - 1) % usable.len().max(1)), - }; - app.menu.key_row = next.and_then(|k| usable.get(k).copied()); - } - if enter && let Some(item) = app.menu.key_row.and_then(|r| list.get(r)) { - picked = Some(item.clone()); + let had_submenu = app.menu.submenu.is_some(); + let enabled: Vec<_> = list.iter().map(|i| i.enabled && !i.is_sep() && !i.is_heading()).collect(); + if app.menu.submenu.is_none() { + let enter = crate::ui_kit::Menu::navigate(ctx, &enabled, &mut app.menu.key_row); + let right = ctx.input(|i| i.key_pressed(egui::Key::ArrowRight)); + if let Some(k) = app.menu.key_row + && let Some(item) = list.get(k) + { + if (right || enter) && !item.children.is_empty() { + app.menu.submenu = Some(k); + app.menu.submenu_key = item.children.iter().position(|c| c.enabled); + } else if enter { + picked = Some(item.clone()); + } + } + } else if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft)) { + app.menu.submenu = None; + app.menu.submenu_key = None; } + let mut submenu_row = None; let key_row = app.menu.key_row; let resp = crate::frame::area(egui::Id::new("sc_context_menu")).fixed_pos(list_at).constrain(true).order(egui::Order::Foreground).show(ctx, |ui| { if target == Target::Workspace { - picked = workspace_menu(ui, &list, key_row, (screen.bottom() - list_at.y).max(160.0)).or(picked.take()); + picked = crate::ui_kit::workspace_menu(ui, &list, key_row, (screen.bottom() - list_at.y).max(160.0)).or(picked.take()); } else { menu_frame(ui, |ui| { for (k, item) in list.iter().enumerate() { - if let Some(p) = list_row_lit(ui, item, key_row == Some(k)) { - picked = Some(p); + if item.children.is_empty() { + if let Some(p) = list_row_lit(ui, item, key_row == Some(k)) { + picked = Some(p); + } + } else { + let row = kit_item(item, key_row == Some(k) || app.menu.submenu == Some(k)).show(ui); + if item.enabled && ((row.hovered() && ctx.input(|i| i.pointer.delta() != Vec2::ZERO)) || row.clicked()) { + if app.menu.submenu != Some(k) { + app.menu.submenu_key = None; + } + app.menu.submenu = Some(k); + } + if app.menu.submenu == Some(k) { + submenu_row = Some((k, row.rect)); + } } } }); @@ -1754,6 +1776,41 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { }); inside |= resp.response.contains_pointer() || resp.response.rect.contains(pointer.unwrap_or(Pos2::ZERO)); + if let Some((k, row)) = submenu_row + && let Some(parent) = list.get(k) + { + let enabled: Vec<_> = parent.children.iter().map(|i| i.enabled && !i.is_sep() && !i.is_heading()).collect(); + if had_submenu + && crate::ui_kit::Menu::navigate(ctx, &enabled, &mut app.menu.submenu_key) + && let Some(child) = app.menu.submenu_key.and_then(|r| parent.children.get(r)) + { + picked = Some(child.clone()); + } + let sub = crate::ui_kit::Menu::submenu(ctx, egui::Id::new("sc_context_submenu"), row, list_height(&parent.children), |ui| { + egui::ScrollArea::vertical().max_height((screen.height() - 28.0).max(30.0)).show(ui, |ui| { + for (n, child) in parent.children.iter().enumerate() { + if child.is_sep() { + crate::ui_kit::Menu::separator(ui); + continue; + } + if child.is_heading() { + crate::ui_kit::Menu::heading(ui, &child.label); + continue; + } + // Child commands may share an id (swatches); preserve the parent handle + // and publish a distinct identity for every adjacent submenu action. + let handle = format!("{}:{}", child.id, child.label); + let mut row = kit_item(child, app.menu.submenu_key == Some(n)); + row.id = &handle; + if row.show(ui).clicked() && child.enabled { + picked = Some(child.clone()); + } + } + }); + }); + inside |= sub.response.contains_pointer() || pointer.is_some_and(|p| sub.response.rect.contains(p)); + } + let (primary, secondary, esc) = ctx.input(|i| { ( i.pointer.button_pressed(egui::PointerButton::Primary), @@ -1773,174 +1830,51 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { } } -/// Workspace cards retain the context-menu items and dispatch, including keyboard and -/// control-channel picks. Only the presentation differs from ordinary command lists. -fn workspace_menu(ui: &mut egui::Ui, items: &[Item], key_row: Option, height: f32) -> Option { - let t = Tokens::get(); - let mut picked = None; - egui::Frame::popup(ui.style()).fill(t.panel).stroke(Stroke::new(1.0, t.border)).corner_radius(10.0).inner_margin(9).show(ui, |ui| { - ui.set_width(450.0); - ui.spacing_mut().item_spacing.y = 0.0; - ui.add_space(4.0); - ui.label(egui::RichText::new(crate::i18n::tr("ui.ribbon_workspace").to_uppercase()).size(11.0).strong().color(t.text_dim)); - ui.add_space(8.0); - egui::ScrollArea::vertical().id_salt("workspace_menu_rows").max_height((height - 116.0).max(60.0)).show(ui, |ui| { - for (k, item) in items.iter().enumerate() { - let (slug, description, tabs) = match item.label.as_str() { - "Design" => ("design", "ui.workspace_design_description", Some("ui.workspace_design_tabs")), - "Render" => ("render", "ui.workspace_render_description", Some("ui.workspace_render_tabs")), - "Animation" => ("animation", "ui.workspace_animation_description", None), - "Simulation" => ("simulation", "ui.workspace_simulation_description", None), - "Manufacture" => ("manufacture", "ui.workspace_manufacture_description", None), - "Drawing" => ("drawing", "ui.workspace_drawing_description", None), - _ => continue, - }; - let planned = tabs.is_none(); - let current = item.shortcut == "current"; - let accent = if planned { - t.text_dim - } else if slug == "render" { - t.render_accent - } else { - t.accent - }; - let soft = t.panel.lerp_to_gamma(accent, 0.10); - let (r, resp) = ui - .allocate_exact_size(vec2(450.0, if planned { 60.0 } else { 72.0 }), if item.enabled { Sense::click() } else { Sense::hover() }); - for handle in [format!("menu:{}", item.id), format!("workspace:menu:{slug}")] { - crate::scenario::publish_handle(&handle, r.center()); - crate::scenario::publish_rect(&handle, r); - } - if current || (item.enabled && (resp.hovered() || key_row == Some(k))) { - ui.painter().rect_filled(r, 5.0, soft); - } - ui.painter().rect_filled(Rect::from_min_max(r.min + vec2(2.0, 7.0), pos2(r.left() + 6.0, r.bottom() - 7.0)), 2.0, accent); - let tile = Rect::from_center_size(pos2(r.left() + 40.0, r.center().y), vec2(44.0, 44.0)); - ui.painter().rect_filled(tile, 9.0, if planned { t.panel_header } else { soft }); - if !item.icon.is_empty() { - icons::paint_icon(ui.painter(), tile.shrink(7.0), &item.icon, if item.enabled { 1.0 } else { 0.35 }); - } - let text = if item.enabled { t.text } else { t.text_dim }; - let name = crate::i18n::tr_label(&item.label); - let x = r.left() + 74.0; - let y = r.top() + 17.0; - let galley = egui::WidgetText::from(egui::RichText::new(name).strong().size(14.0).color(text)).into_galley( - ui, - Some(egui::TextWrapMode::Extend), - f32::INFINITY, - egui::TextStyle::Button, - ); - let badge_x = x + galley.size().x + 9.0; - let origin = pos2(x, y - galley.size().y / 2.0); - ui.painter().galley(origin - vec2(0.2, 0.0), galley.clone(), text); - ui.painter().galley(origin + vec2(0.2, 0.0), galley, text); - let badge = crate::i18n::tr(if planned { - "ui.workspace_planned" - } else if current { - "ui.workspace_current" - } else { - "ui.workspace_available" - }); - let badge = ui.painter().layout_no_wrap(badge.to_string(), FontId::proportional(11.0), if current { t.on_accent } else { accent }); - let badge_rect = Rect::from_min_size(pos2(badge_x, y - 8.0), badge.size() + vec2(12.0, 3.0)); - ui.painter().rect_filled(badge_rect, 8.0, if current { accent } else { soft }); - ui.painter().galley(badge_rect.min + vec2(6.0, 1.5), badge, accent); - let line = |label: &str, y: f32| { - let mut job = egui::text::LayoutJob::simple_singleline(label.to_string(), FontId::proportional(12.0), text); - job.wrap.max_width = r.right() - x - 6.0; - job.wrap.max_rows = 1; - job.wrap.break_anywhere = false; - let galley = ui.painter().layout_job(job); - ui.painter().galley(pos2(x, y), galley, text); - }; - line(crate::i18n::tr(description), r.top() + 29.0); - if let Some(tabs) = tabs { - // Reuse existing tab-name translations when a partial catalog falls back - // to the English summary; complete catalogs keep their translated summary. - let labels = crate::i18n::tr(tabs).split('·').map(|label| crate::i18n::tr_label(label.trim())).collect::>().join(" · "); - line(&labels, r.top() + 47.0); - } - resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, item.enabled, name)); - if resp.clicked() && item.enabled { - picked = Some(item.clone()); - } - } - }); - ui.add_space(6.0); - ui.separator(); - ui.add_space(6.0); - ui.add(egui::Label::new(egui::RichText::new(crate::i18n::tr("ui.workspace_footer")).size(11.0).color(t.text_dim)).wrap()); - ui.add_space(4.0); - }); - picked -} - -/// The frame every menu uses. +/// Shared kit surface; legacy callers retain their menu positioning and dispatch. pub fn menu_frame(ui: &mut egui::Ui, add: impl FnOnce(&mut egui::Ui) -> R) -> R { - let t = Tokens::get(); - egui::Frame::new() - .fill(t.panel) - .stroke(Stroke::new(1.0, t.border)) - .corner_radius(6.0) - .inner_margin(egui::Margin::symmetric(4, 4)) - .shadow(egui::Shadow { - offset: [0, 3], - blur: 12, - spread: 0, - color: Color32::from_black_alpha(if crate::theme::is_dark() { 110 } else { 50 }), - }) - .show(ui, |ui| { - ui.set_width(LIST_W - 10.0); - ui.spacing_mut().item_spacing.y = 0.0; - add(ui) - }) - .inner + crate::ui_kit::Menu::show(ui, LIST_W, add) +} + +pub fn button(ui: &mut egui::Ui, label: &str, icon: &str) -> egui::Response { + button_enabled(ui, label, icon, true) +} + +pub fn button_enabled(ui: &mut egui::Ui, label: &str, icon: &str, enabled: bool) -> egui::Response { + let mut item = crate::ui_kit::MenuItem::new(label, label, icon); + item.enabled = enabled; + item.show(ui) } -/// One list row; the item when it was clicked. pub fn list_row(ui: &mut egui::Ui, item: &Item) -> Option { list_row_lit(ui, item, false) } -/// A list row, drawn highlighted when `lit` (the keyboard's choice). pub fn list_row_lit(ui: &mut egui::Ui, item: &Item, lit: bool) -> Option { - let t = Tokens::get(); - let w = LIST_W - 10.0; if item.is_sep() { - let (r, _) = ui.allocate_exact_size(vec2(w, 7.0), Sense::hover()); - ui.painter().hline(r.x_range().shrink(6.0), r.center().y, Stroke::new(1.0, t.border)); + crate::ui_kit::Menu::separator(ui); return None; } if item.is_heading() { - let text = ui.painter().layout(crate::i18n::tr_label(&item.label).to_uppercase(), FontId::proportional(10.5), t.text_dim, w - 16.0); - let (r, _) = ui.allocate_exact_size(vec2(w, (text.size().y + 8.0).max(20.0)), Sense::hover()); - ui.painter().galley(pos2(r.left() + 8.0, r.top() + 4.0), text, t.text_dim); + crate::ui_kit::Menu::heading(ui, &item.label); return None; } - let (r, resp) = ui.allocate_exact_size(vec2(w, 24.0), if item.enabled { Sense::click() } else { Sense::hover() }); - crate::scenario::publish_handle(&format!("menu:{}", item.id), r.center()); - if item.enabled && (resp.hovered() || lit) { - ui.painter().rect_filled(r.shrink2(vec2(2.0, 1.0)), 4.0, t.hover); + let row = kit_item(item, lit); + (row.show(ui).clicked() && item.enabled).then(|| item.clone()) +} + +fn kit_item(item: &Item, lit: bool) -> crate::ui_kit::MenuItem<'_> { + crate::ui_kit::MenuItem { + id: &item.id, + label: &item.label, + icon: &item.icon, + shortcut: if item.shortcut == "current" { "" } else { &item.shortcut }, + enabled: item.enabled, + highlighted: lit, + danger: item.icon == "delete", + checked: (item.shortcut == "current").then_some(true), + swatch: item.swatch.map(|c| Color32::from_rgb(c[0], c[1], c[2])), + submenu: !item.children.is_empty(), } - let ink = if item.enabled { t.icon } else { t.border }; - if !item.icon.is_empty() { - icons::paint( - ui.painter(), - Rect::from_center_size(pos2(r.left() + 16.0, r.center().y), vec2(16.0, 16.0)), - &item.icon, - ink, - t.icon_fill, - t.accent, - ); - } - let text = if item.enabled { t.text } else { t.text_dim.gamma_multiply(0.75) }; - let display_label = crate::i18n::tr_label(&item.label); - ui.painter().text(pos2(r.left() + 32.0, r.center().y), Align2::LEFT_CENTER, display_label, FontId::proportional(13.0), text); - if !item.shortcut.is_empty() { - let display_shortcut = crate::i18n::tr_label(&item.shortcut); - ui.painter().text(pos2(r.right() - 10.0, r.center().y), Align2::RIGHT_CENTER, display_shortcut, FontId::proportional(11.5), t.text_dim); - } - (item.enabled && resp.clicked()).then(|| item.clone()) } fn show_rename(app: &mut SolveApp, ctx: &egui::Context) { @@ -1950,31 +1884,9 @@ fn show_rename(app: &mut SolveApp, ctx: &egui::Context) { return; } let Some(mut r) = app.menu.rename.take() else { return }; - let t = Tokens::get(); let mut done: Option = None; let resp = crate::frame::area(egui::Id::new("sc_rename")).fixed_pos(r.at).constrain(true).order(egui::Order::Foreground).show(ctx, |ui| { - menu_frame(ui, |ui| { - ui.add_space(2.0); - ui.label(egui::RichText::new(crate::i18n::tr_label("Rename")).size(11.0).color(t.text_dim)); - let te = ui.add(egui::TextEdit::singleline(&mut r.text).desired_width(200.0).id(egui::Id::new("sc_rename_text"))); - if !r.focused { - // Start with the whole name selected, ready to type over. - let id = egui::Id::new("sc_rename_text"); - if let Some(mut st) = egui::TextEdit::load_state(ui.ctx(), id) { - let n = r.text.chars().count(); - st.cursor.set_char_range(Some(egui::text::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(n)))); - st.store(ui.ctx(), id); - } - te.request_focus(); - r.focused = true; - } - if te.lost_focus() { - done = Some(ui.input(|i| i.key_pressed(egui::Key::Enter))); - } - if ui.input(|i| i.key_pressed(egui::Key::Escape)) { - done = Some(false); - } - }) + done = crate::ui_kit::rename_menu(ui, &mut r.text, &mut r.focused); }); if ctx.input(|i| i.pointer.any_pressed()) && !resp.response.contains_pointer() && r.focused { done = Some(true); @@ -1998,66 +1910,6 @@ pub fn finish_rename(app: &mut SolveApp, text: Option<&str>, commit: bool) -> bo true } -fn show_props(app: &mut SolveApp, ctx: &egui::Context) { - let Some((title, v)) = app.menu.props.clone() else { return }; - let mut open = true; - let f = |x: &Value, k: &str| x.get(k).and_then(Value::as_f64).unwrap_or(f64::NAN); - crate::frame::window(ctx, format!("Properties: {title}"), crate::frame::Width::Normal) - .id(egui::Id::new("sc_props")) - .open(&mut open) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - egui::Grid::new("sc_props_grid").num_columns(2).spacing([16.0, 4.0]).show(ui, |ui| { - let total = &v["total"]; - let bodies = v["bodies"].as_array().cloned().unwrap_or_default(); - if let [b] = bodies.as_slice() { - ui.label(crate::i18n::tr_label("Material")); - ui.label(b["material"].as_str().unwrap_or("Default")); - ui.end_row(); - ui.label(crate::i18n::tr_label("Density")); - ui.label(format!("{:.3} g/cm³", f(b, "density_g_cm3"))); - ui.end_row(); - } - ui.label(crate::i18n::tr_label("Mass")); - ui.label(format!("{:.3} g", f(total, "mass_g"))); - ui.end_row(); - ui.label(crate::i18n::tr_label("Volume")); - ui.label(crate::prefs::show_mm(app, f(total, "volume_mm3"), 3)); - ui.end_row(); - ui.label(crate::i18n::tr_label("Area")); - ui.label(crate::prefs::show_mm(app, f(total, "area_mm2"), 2)); - ui.end_row(); - let geometry = if total.get("center_of_mass").is_some() { Some(total) } else { bodies.first().filter(|_| bodies.len() == 1) }; - if let Some(b) = geometry { - if let Some(c) = b["center_of_mass"].as_array() { - let c: Vec = c.iter().map(|x| crate::prefs::show_mm_bare(app, x.as_f64().unwrap_or(f64::NAN))).collect(); - ui.label(crate::i18n::tr_label("Center of mass")); - ui.label(format!("({}) {}", c.join(", "), crate::prefs::unit_scale(&app.session.doc.units).1)); - ui.end_row(); - } - let (mn, mx) = (&b["bbox"]["min"], &b["bbox"]["max"]); - if let (Some(a), Some(z)) = (mn.as_array(), mx.as_array()) { - let d: Vec = a - .iter() - .zip(z) - .map(|(a, z)| crate::prefs::show_mm_bare(app, z.as_f64().unwrap_or(0.0) - a.as_f64().unwrap_or(0.0))) - .collect(); - ui.label(crate::i18n::tr_label("Bounding box")); - ui.label(format!("{} {}", d.join(" × "), crate::prefs::unit_scale(&app.session.doc.units).1)); - ui.end_row(); - } - } - ui.label(crate::i18n::tr_label("Faces / edges / vertices")); - ui.label(format!("{} / {} / {}", total["faces"], total["edges"], total["vertices"])); - ui.end_row(); - }); - }); - if !open { - app.menu.props = None; - } -} - #[cfg(test)] mod tests { use super::*; @@ -2085,7 +1937,7 @@ mod tests { #[test] fn disabled_radial_menu_does_not_capture_right_button_hold() { let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default()); - let ctx = egui::Context::default(); + let ctx = crate::fonts::test_context(); let point = pos2(600.0, 400.0); app.viewport.mouse = Some(point); for (time, events) in [ @@ -2130,7 +1982,7 @@ mod tests { let p = list_place(c, 400.0, screen); let r = Rect::from_min_size(p, vec2(LIST_W, 400.0)); assert!(screen.contains_rect(r), "{at:?}: {r:?}"); - let ring = Rect::from_center_size(c, vec2(2.0 * (RX + 160.0), 2.0 * RY)); + let ring = Rect::from_center_size(c, vec2(2.0 * RX, 2.0 * RY)); assert!(!ring.intersects(r), "{at:?}: list {r:?} over ring {ring:?}"); } } @@ -2152,10 +2004,7 @@ mod tests { let screen = Rect::from_min_size(Pos2::ZERO, vec2(1200.0, 800.0)); for at in [pos2(2.0, 2.0), pos2(1199.0, 799.0), pos2(600.0, 400.0), pos2(-50.0, 900.0)] { let c = radial_center(at, screen); - for k in 0..DIRS { - let r = slot_rect(c, k, vec2(160.0, 28.0)); - assert!(screen.contains_rect(r), "{at:?} slot {k}: {r:?}"); - } + assert!(screen.contains_rect(Rect::from_center_size(c, vec2(2.0 * RX, 2.0 * RY)))); } } } @@ -2165,6 +2014,27 @@ mod workspace_menu_tests { use super::*; use crate::scenario::{Harness, rect_of}; + #[test] + fn workspace_titles_use_one_semibold_text_run() { + let ctx = crate::fonts::test_context(); + let app = SolveApp::new(Default::default(), Default::default()); + let list = items(&app, &Target::Workspace); + let mut output = ctx.run_ui(egui::RawInput::default(), |ui| { + crate::ui_kit::workspace_menu(ui, &list, None, 1000.0); + }); + output.textures_delta.clear(); + let titles: Vec<_> = output + .shapes + .iter() + .filter_map(|s| match &s.shape { + egui::Shape::Text(text) if text.galley.text() == "Design" => Some(text), + _ => None, + }) + .collect(); + assert_eq!(titles.len(), 1, "workspace titles must never be drawn twice"); + assert_eq!(titles[0].galley.job.sections[0].format.font_id, crate::fonts::semibold(14.0)); + } + #[test] fn workspace_rows_keep_switching_handles_and_disabled_render_behavior() { let mut h = Harness::new(); diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index f33db5648f..932bb7f7fc 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -24,6 +24,7 @@ //! headless CPU render of the model with the current camera, in the window's theme, with the //! section the viewport shows (no window needed); the ground grid and the origin's axes as the //! window shows them, unless `grid` or `axes` says +//! - `ui.icon_audit {reset?, stop?}`: collect/drain requested icon names, resolved masters and placeholder flags; //! - `ui.menu {x?, y?, target?, close?}`: open a context menu at a point (the viewport's marking //! menu, or a browser menu with `target: {type: body|sketch|component, …}`); without a point, //! the open menu and its items. `ui.menuPick {item}` runs an item by id or label; @@ -407,6 +408,18 @@ fn dispatch(app: &mut SolveApp, ctx: &egui::Context, method: &str, p: &Value) -> ok(json!(app.cam)) } "ui.camera" => wrap(camera(app, p)), + "ui.palette" => { + if p.get("open").and_then(Value::as_bool) == Some(false) { + app.ui.palette_open = false; + app.sbox.text.clear(); + } else { + app.start("ui.palette"); + if let Some(query) = s("text") { + app.sbox.text = query.chars().take(crate::shortcut_box::MAX_QUERY).collect(); + } + } + ok(json!({"open": app.ui.palette_open, "text": app.sbox.text})) + } "ui.start" => { let Some(id) = s("command") else { return err("missing `command`") }; // `params`: what the dialog opens on. Refused: nothing starts, or the dialog closes. @@ -668,6 +681,16 @@ fn dispatch(app: &mut SolveApp, ctx: &egui::Context, method: &str, p: &Value) -> app.synthetic.push(egui::Event::Text(s("text").unwrap_or("").to_string())); ok(Value::Null) } + "ui.icon_audit" => { + if b("reset") { + crate::icon_svg::audit::start(); + } + let requested = crate::icon_svg::audit::take(); + if b("stop") { + crate::icon_svg::audit::stop(); + } + ok(json!({"requested": requested})) + } "ui.menu" => { if b("close") { crate::context_menu::close(app); @@ -957,7 +980,7 @@ mod tests { #[test] fn touchpad_text_prefix_does_not_collapse_queued_pointer_frames() { let mut app = SolveApp::new(Session::default(), Services::default()); - let ctx = egui::Context::default(); + let ctx = crate::fonts::test_context(); let (req, _) = ControlRequest::new("ui.text", json!({"text":"prefix"})); assert!(matches!(handle(&mut app, &ctx, &req), Outcome::Done(_))); for params in [json!({"x":10,"y":10,"shift":true}), json!({"x":20,"y":20,"alt":true}), json!({"x":30,"y":30})] { @@ -981,7 +1004,7 @@ mod tests { #[test] fn touchpad_automation_preserves_each_queued_moves_modifiers() { let mut app = SolveApp::new(Session::default(), Services::default()); - let ctx = egui::Context::default(); + let ctx = crate::fonts::test_context(); for params in [json!({"x":10,"y":10,"shift":true}), json!({"x":20,"y":20,"alt":true}), json!({"x":30,"y":30})] { let (req, _) = ControlRequest::new("ui.move", params); assert!(matches!(handle(&mut app, &ctx, &req), Outcome::Done(_))); @@ -1003,7 +1026,7 @@ mod tests { fn render(app: &mut SolveApp, path: &std::path::Path) -> Vec { let (req, _rx) = ControlRequest::new("ui.render", json!({"path": path.to_string_lossy(), "width": 200, "height": 140})); - match handle(app, &egui::Context::default(), &req) { + match handle(app, &crate::fonts::test_context(), &req) { Outcome::Done(v) => assert_eq!(v["ok"], true, "{v}"), Outcome::Screenshot { .. } => panic!("ui.render took a screenshot"), } @@ -1073,7 +1096,7 @@ mod tests { params[k.as_str()] = v.clone(); } let (req, _rx) = ControlRequest::new("ui.render", params); - match handle(app, &egui::Context::default(), &req) { + match handle(app, &crate::fonts::test_context(), &req) { Outcome::Done(v) => assert_eq!(v["ok"], true, "{v}"), Outcome::Screenshot { .. } => panic!("ui.render took a screenshot"), } @@ -1348,7 +1371,7 @@ mod tests { params["allow_paths"] = json!([root.to_string_lossy()]); } let (req, _rx) = ControlRequest::new(method, params); - match handle(&mut app, &egui::Context::default(), &req) { + match handle(&mut app, &crate::fonts::test_context(), &req) { Outcome::Done(v) => v, Outcome::Screenshot { path } => json!({"ok": true, "screenshot": path}), } diff --git a/crates/ui-egui/src/crash_ui.rs b/crates/ui-egui/src/crash_ui.rs index b1194c35bb..44f8a8ebe3 100644 --- a/crates/ui-egui/src/crash_ui.rs +++ b/crates/ui-egui/src/crash_ui.rs @@ -1,11 +1,10 @@ //! The Crash Reports window (Help > Crash Reports…): the reports `solidcraft_engine::crash` //! keeps on this computer, each with a reviewed issue draft. It opens by itself after a crash. -use egui::{RichText, vec2}; use solidcraft_engine::crash::{self, Summary}; use crate::SolveApp; -use crate::theme::Tokens; +use crate::ui_kit; #[derive(Default)] pub struct CrashUi { @@ -33,18 +32,22 @@ impl CrashUi { } fn kind_label(kind: &str) -> &'static str { - match kind { - "command_panic" => "Internal error in a command", - "kernel_panic_recovered" => "Kernel error (recovered)", - "unclean_exit" => "Closed unexpectedly", - _ => "Internal error", - } + crate::i18n::tr(match kind { + "command_panic" => "crash.kind_command", + "kernel_panic_recovered" => "crash.kind_kernel", + "unclean_exit" => "crash.kind_unclean", + _ => "crash.kind_internal", + }) +} + +fn report_time(r: &Summary) -> String { + let time = r.time.replace('T', " "); + let time = time.trim_end_matches('Z'); + if r.kind == "unclean_exit" { crate::i18n::format("crash.detected_time", &[("time", time)]) } else { format!("{time} UTC") } } fn report_heading(r: &Summary) -> String { - let time = r.time.replace('T', " "); - let time = time.trim_end_matches('Z'); - let time = if r.kind == "unclean_exit" { crate::i18n::format("crash.detected_time", &[("time", time)]) } else { format!("{time} UTC") }; + let time = report_time(r); let times = if r.count > 1 { format!(" ×{}", r.count) } else { String::new() }; format!("{time} {}{times}", kind_label(&r.kind)) } @@ -66,73 +69,83 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { if !app.crashes.open { return; } - let t = Tokens::get(); - let mut open = true; let mut report: Option = None; let mut delete: Option> = None; - crate::frame::window(ctx, crate::i18n::tr_label("Crash Reports"), crate::frame::Width::Wide) - .id(egui::Id::new("sc_crash_reports")) - .open(&mut open) - .default_size(vec2(620.0, 420.0)) - .show(ctx, |ui| { - ui.label( - RichText::new( - "Crash reports stay on this computer and are never sent anywhere. Report… opens a draft you review before anything is shared.", - ) - .color(t.text_dim), - ); + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_crash_reports"), "bug_report", crate::i18n::tr_label("Crash Reports")) + .cards(2.0) + .actions("", crate::i18n::tr("ui.close"), None) + .show(ctx, true, |ui| { + ui.spacing_mut().item_spacing.y = crate::theme::Tokens::get().space_10; + let intro = ui_kit::text(ui, crate::i18n::tr("crash.intro"), ui_kit::Text::Hint("intro")); + crate::scenario::publish_rect("dialog:note:intro", intro.rect); if let Some(d) = app.crashes.dir.clone() { - // A long path is cut short (hover shows it all): it must not widen the window - // past its title bar. - ui.horizontal(|ui| { - if ui.small_button("Copy folder path").clicked() { + // A long path is cut short (hover shows it all): it must not widen the card. + ui_kit::path_chip(ui, &d, |ui| { + if ui_kit::icon_label_button(ui, crate::i18n::tr("crash.copy_folder"), "copy", true, false).clicked() { ctx.copy_text(d.clone()); } - ui.add(egui::Label::new(RichText::new(&d).monospace().size(11.0)).truncate()).on_hover_text(&d); }); } - ui.separator(); + // The list/empty-box top border replaces the old divider; retain its geometry alias. + crate::scenario::publish_rect( + "dialog:divider:reports", + egui::Rect::from_min_size(ui.next_widget_position(), egui::vec2(ui.available_width(), 1.0)), + ); if app.crashes.reports.is_empty() { - ui.label(crate::i18n::tr_label("No crash reports.")); + ui_kit::empty_box(ui, "none", crate::i18n::tr_label("No crash reports.")); } - egui::ScrollArea::vertical().max_height(320.0).show(ui, |ui| { - for r in &app.crashes.reports { - ui.horizontal(|ui| { - ui.label(RichText::new(report_heading(r)).strong()); - if let Some(c) = &r.command { - ui.label(RichText::new(c).monospace().size(11.0).color(t.text_dim)); + if !app.crashes.reports.is_empty() { + ui_kit::report_list( + ui, + "reports", + crate::theme::Tokens::get().control_height * 10.0 + crate::theme::Tokens::get().space_10 * 4.0, + |ui| { + let last = app.crashes.reports.len().saturating_sub(1); + for (index, r) in app.crashes.reports.iter().enumerate() { + let title = report_heading(r); + ui.push_id(&r.id, |ui| { + ui.spacing_mut().item_spacing.y = crate::theme::Tokens::get().space_6; + ui.add_space(crate::theme::Tokens::get().space_10); + ui_kit::report_header(ui, kind_label(&r.kind), r.count, &report_time(r), &title, |ui| { + if ui_kit::bin_button(ui, crate::i18n::tr("ui.delete")).clicked() { + delete = Some(Some(r.id.clone())); + } + ui_kit::icon_label_button(ui, crate::i18n::tr("crash.copy_path"), "copy", true, false) + .clicked() + .then(|| ctx.copy_text(r.path.clone())); + if ui_kit::icon_label_button(ui, crate::i18n::tr("crash.report"), "bug_report", true, false).clicked() { + report = Some(r.id.clone()); + } + }); + let mut detail = r.command.clone().unwrap_or_default(); + if r.replayable { + let sep = if detail.is_empty() { "" } else { " · " }; + detail = format!("{detail}{sep}{}", crate::i18n::tr("crash.replayable")); + } + if !detail.is_empty() { + ui_kit::text(ui, &detail, ui_kit::Text::MonoNote(&format!("detail:{}", r.id))); + } + ui_kit::text(ui, &r.message, ui_kit::Text::Report(&format!("message:{}", r.id), false)); + if r.kind == "unclean_exit" { + ui_kit::text(ui, crate::i18n::tr("crash.unclean_explanation"), ui_kit::Text::Note(&format!("unclean:{}", r.id))); + } + ui.add_space(crate::theme::Tokens::get().space_6); + if index != last { + ui_kit::report_divider(ui, &format!("end:{}", r.id)); + } + }); } - }); - ui.label(RichText::new(&r.message).size(12.0)); - if r.kind == "unclean_exit" { - ui.label(RichText::new(crate::i18n::tr("crash.unclean_explanation")).size(12.0).color(t.text_dim)); - } - ui.horizontal(|ui| { - if ui.small_button("Report…").clicked() { - report = Some(r.id.clone()); - } - if ui.small_button("Copy path").clicked() { - ctx.copy_text(r.path.clone()); - } - if ui.small_button("Delete").clicked() { - delete = Some(Some(r.id.clone())); - } - if r.replayable { - ui.label(RichText::new("replayable").size(11.0).color(t.text_dim)); - } - }); - ui.add_space(6.0); - } - }); + }, + ); + } if !app.crashes.note.is_empty() { - ui.label(RichText::new(&app.crashes.note).color(t.text_dim)); + ui_kit::text(ui, &app.crashes.note, ui_kit::Text::Note("note")); } - ui.separator(); - if !app.crashes.reports.is_empty() && ui.button(crate::i18n::tr_label("Delete all")).clicked() { + if ui_kit::icon_label_button(ui, crate::i18n::tr_label("Delete all"), "delete", !app.crashes.reports.is_empty(), true).clicked() { delete = Some(None); } }); - app.crashes.open = open; + app.crashes.open = !(closed || action == ui_kit::Action::Cancel); if let Some(id) = report && let Some(dir) = app.crashes.dir_path() { @@ -145,7 +158,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { && let Some(dir) = app.crashes.dir_path() { let n = crash::clear(&dir, which.as_deref()); - app.crashes.note = format!("Deleted {n} report(s)."); + app.crashes.note = crate::i18n::format("crash.deleted", &[("count", &n.to_string())]); app.crashes.refresh(); } } @@ -265,7 +278,7 @@ mod tests { h.frames(6); let window = h.ctx.memory(|m| m.area_rect(egui::Id::new("sc_crash_reports"))).unwrap(); // The frame's margins come on top of the content's maximum width. - assert!(window.width() <= crate::frame::WINDOW_WIDE_MAX_W + 40.0, "{} wide", window.width()); + assert!(window.width() <= 760.0 + 40.0, "{} wide", window.width()); let _ = std::fs::remove_dir_all(&d); } @@ -286,4 +299,51 @@ mod tests { super::announce(&mut h.app, &[quiet]); assert!(!h.app.crashes.open); } + + #[test] + fn inventory_actions_details_empty_state_and_keyboard_remain_reachable() { + let d = folder("inventory"); + let mut h = crate::scenario::Harness::new(); + h.app.crashes.folder = Some(d.clone()); + super::open(&mut h.app); + h.frames(5); + for handle in [ + "dialog:close", + "dialog:cancel", + "dialog:Close", + "dialog:button::Copy folder path", + "dialog:button::Report…", + "dialog:button::Copy path", + "dialog:button::Delete", + "dialog:button::Delete all", + ] { + assert!(crate::scenario::handle_at(handle).is_some(), "missing {handle}"); + } + for rect in [ + "dialog:note:intro", + "dialog:divider:reports", + "dialog:list:reports", + "dialog:note:detail:20261012-162341-bb60791ed6692f1d", + "dialog:report:message:20261012-162341-bb60791ed6692f1d", + ] { + assert!(crate::scenario::rect_of(rect).is_some(), "missing {rect}"); + } + let title = "Detected 2026-10-12 16:23:41 UTC Closed unexpectedly ×2"; + assert!(crate::scenario::handle_at(&format!("dialog:cell:{title}")).is_some()); + assert!(crate::scenario::handle_at(&format!("dialog:cell:{}", d.display())).is_some()); + h.step(&json!({"click":{"handle":"dialog:button::Copy folder path"}})).unwrap(); + h.step(&json!({"click":{"handle":"dialog:button::Copy path"}})).unwrap(); + h.step(&json!({"key":"Enter"})).unwrap(); + assert!(h.app.crashes.open, "Enter has no primary action"); + h.step(&json!({"click":{"handle":"dialog:button::Delete"}})).unwrap(); + h.frames(3); + assert!(h.app.crashes.reports.is_empty()); + assert!(crate::scenario::rect_of("dialog:note:none").is_some()); + assert!(crate::scenario::rect_of("dialog:note:note").is_some()); + h.step(&json!({"click":{"handle":"dialog:button::Delete all"}})).unwrap(); + assert!(h.app.crashes.reports.is_empty()); + h.step(&json!({"key":"Escape"})).unwrap(); + assert!(!h.app.crashes.open); + let _ = std::fs::remove_dir_all(d); + } } diff --git a/crates/ui-egui/src/delete.rs b/crates/ui-egui/src/delete.rs index 571e75ceac..706b5ee202 100644 --- a/crates/ui-egui/src/delete.rs +++ b/crates/ui-egui/src/delete.rs @@ -2,11 +2,13 @@ //! the viewport or the browser goes in one undoable step through `selection.delete`. When it //! takes other features with it or makes some fail, a confirmation lists them first. -use egui::{RichText, vec2}; use serde_json::{Value, json}; use crate::SolveApp; -use crate::theme::Tokens; +use crate::ui_kit; + +/// Longest list of names the confirmation shows before "and n more". +const MAX_LISTED: usize = 20; /// A delete waiting for confirmation: its parameters and the dry run's report. #[derive(Clone, Debug)] @@ -110,55 +112,49 @@ pub fn confirm(app: &mut SolveApp, accept: bool) -> bool { true } -/// The confirmation window. +/// The confirmation card. pub fn show(app: &mut SolveApp, ctx: &egui::Context) { let Some(p) = app.menu.confirm.clone() else { return }; - let t = Tokens::get(); let list = |k: &str| -> Vec { p.report[k].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default() }; let (deleted, fails) = (list("deleted"), list("would_fail")); - let mut answer: Option = None; - crate::frame::window(ctx, crate::i18n::tr_label("Delete"), crate::frame::Width::Normal) - .id(egui::Id::new("sc_delete_confirm")) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, vec2(0.0, -60.0)) - .show(ctx, |ui| { - ui.set_max_width(360.0); - if shared_occurrences(app, &p.params) { - ui.label(crate::i18n::tr_label("This child is shared. Deleting it removes it from every copy of its parent.")); + let shared = shared_occurrences(app, &p.params); + let (closed, action) = ui_kit::Confirm::new(egui::Id::new("sc_delete_confirm"), "delete", crate::i18n::tr_label("Delete")) + .tone(ui_kit::Tone::Danger) + .actions("Delete", "Cancel", None) + .show(ctx, true, |ui| { + if shared { + ui_kit::text( + ui, + crate::i18n::tr_label("This child is shared. Deleting it removes it from every copy of its parent."), + ui_kit::Text::Report("shared", false), + ); } - if !deleted.is_empty() { - ui.label(crate::i18n::tr_label("These features will be deleted:")); - for d in deleted.iter().take(20) { - ui.label(RichText::new(format!(" • {d}")).color(t.text)); + for (key, heading, names, error) in + [("deleted", "These features will be deleted:", &deleted, false), ("fails", "These features will fail:", &fails, true)] + { + if names.is_empty() { + continue; } - if deleted.len() > 20 { - ui.label(format!(" … and {} more", deleted.len() - 20)); + ui_kit::text(ui, crate::i18n::tr_label(heading), ui_kit::Text::Report(key, error)); + for name in names.iter().take(MAX_LISTED) { + ui_kit::text(ui, &format!(" • {name}"), ui_kit::Text::Report(&format!("{key}:{name}"), error)); } - } - if !fails.is_empty() { - ui.add_space(6.0); - ui.label(RichText::new(crate::i18n::tr_label("These features will fail:")).color(t.warning)); - for f in fails.iter().take(20) { - ui.label(RichText::new(format!(" • {f}")).color(t.warning)); + if names.len() > MAX_LISTED { + let more = (names.len() - MAX_LISTED).to_string(); + ui_kit::text( + ui, + &crate::i18n::format("delete.and_more", &[("count", &more)]), + ui_kit::Text::Report(&format!("{key}:more"), error), + ); } } - ui.add_space(8.0); - ui.horizontal(|ui| { - if ui.button("Delete").clicked() { - answer = Some(true); - } - if ui.button("Cancel").clicked() { - answer = Some(false); - } - }); - if ui.input(|i| i.key_pressed(egui::Key::Escape)) { - answer = Some(false); - } }); - if let Some(a) = answer { - confirm(app, a); + match action { + ui_kit::Action::Ok => drop(confirm(app, true)), + ui_kit::Action::Cancel => drop(confirm(app, false)), + _ if closed => drop(confirm(app, false)), + _ => {} } } diff --git a/crates/ui-egui/src/design_properties.rs b/crates/ui-egui/src/design_properties.rs index 7a5503205e..696e0c4d2e 100644 --- a/crates/ui-egui/src/design_properties.rs +++ b/crates/ui-egui/src/design_properties.rs @@ -3,17 +3,20 @@ //! the design. OK applies the edited fields through one `document.metadata.set` (one undo step); //! Cancel and the close button change nothing. File → Design Properties…, the Browser's //! Document Settings, the root component's menu and `ui.start` of `document.metadata` or -//! `document.metadata.set` open it. +//! `document.metadata.set` open it. [`show_object`] draws the Properties card (mass, volume, area, +//! centre of mass, bounding box, counts) of the body or component a menu asked about; both are +//! `ui_kit::Window` cards. -use egui::RichText; use serde_json::{Map, Value}; use solidcraft_engine::doc::metadata::{DESCRIPTION, WELL_KNOWN}; use crate::SolveApp; -use crate::theme::Tokens; +use crate::ui_kit; /// Longest preview of another tool's value in the list. const PREVIEW_CHARS: usize = 80; +/// Rows of other tools' data shown before the list scrolls. +const OTHERS_ROWS: f32 = 6.0; /// What is being typed; the properties themselves live in the document. pub struct DesignProperties { @@ -85,68 +88,86 @@ fn preview(v: &Value) -> String { /// Draw the window when it is open. Called once per frame. pub fn show(app: &mut SolveApp, ctx: &egui::Context) { let Some(mut d) = app.design_properties.take() else { return }; - let t = Tokens::get(); - let mut open = true; - let (mut ok, mut cancel) = (false, false); let metadata = app.session.doc.metadata.clone(); - let title = RichText::new(crate::i18n::tr_label("Design Properties")).strong().size(13.0); - crate::frame::window(ctx, title, crate::frame::Width::Normal) - .id(egui::Id::new("sc_design_properties")) - .open(&mut open) - .resizable(false) - .min_width(420.0) - .show(ctx, |ui| { - egui::Grid::new("sc_design_properties_grid").num_columns(2).spacing([12.0, 6.0]).show(ui, |ui| { - for (i, key) in WELL_KNOWN.iter().enumerate() { - ui.label(label(key)); - let Some(field) = d.fields.get_mut(i) else { continue }; - ui.vertical(|ui| { - let edit = - if *key == DESCRIPTION { egui::TextEdit::multiline(field).desired_rows(3) } else { egui::TextEdit::singleline(field) }; - ui.add(edit.desired_width(280.0)); - // Saved by another tool as something other than text: kept unless typed over. - if metadata.get(key).is_some() && metadata.text(key).is_none() && d.opened.get(i).is_some_and(String::is_empty) { - ui.label(RichText::new(crate::i18n::tr("ui.design_properties.not_text")).small().color(t.text_dim)); - } - }); - ui.end_row(); - } - }); - ui.add_space(4.0); - ui.label(RichText::new(crate::i18n::tr("ui.design_properties.exports")).small().color(t.text_dim)); - let others: Vec<(&String, &Value)> = metadata.others().collect(); - if !others.is_empty() { - ui.add_space(4.0); - egui::CollapsingHeader::new(format!("{} ({})", crate::i18n::tr("ui.design_properties.others"), others.len())) - .id_salt("sc_design_properties_others") - .show(ui, |ui| { - ui.label(RichText::new(crate::i18n::tr("ui.design_properties.others_note")).small().color(t.text_dim)); - egui::ScrollArea::vertical().max_height(160.0).show(ui, |ui| { - for (k, v) in others { - ui.label(RichText::new(k).monospace().size(12.0).color(t.text)); - ui.label(RichText::new(preview(v)).monospace().small().color(t.text_dim)); - } - }); - }); + let title = crate::i18n::tr_label("Design Properties"); + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_design_properties"), "settings", title).fields().show(ctx, true, |ui| { + for (i, key) in WELL_KNOWN.iter().enumerate() { + let label = label(key); + let Some(field) = d.fields.get_mut(i) else { continue }; + if *key == DESCRIPTION { + ui_kit::multiline_text_field(ui, label, egui::Id::new(("sc_design_properties", key)), field); + } else { + ui_kit::row(ui, label, |ui| ui_kit::text_field(ui, field, "")); } - if let Some(e) = &d.error { - ui.label(RichText::new(e).color(t.error)); + // Saved by another tool as something other than text: kept unless typed over. + if metadata.get(key).is_some() && metadata.text(key).is_none() && d.opened.get(i).is_some_and(String::is_empty) { + ui_kit::caption(ui, label, crate::i18n::tr("ui.design_properties.not_text")); } - ui.separator(); - // OK and Cancel flush right, as in every dialog. - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - cancel = ui.button(crate::i18n::tr("ui.cancel")).clicked(); - ok = ui.button(crate::i18n::tr("ui.heading_ok")).clicked(); + } + ui_kit::text(ui, crate::i18n::tr("ui.design_properties.exports"), ui_kit::Text::Note("exports")); + let others: Vec<(&String, &Value)> = metadata.others().collect(); + if !others.is_empty() { + let heading = format!("{} ({})", crate::i18n::tr("ui.design_properties.others"), others.len()); + ui_kit::section(ui, "sc_design_properties_others", &heading, false, |ui| { + ui_kit::text(ui, crate::i18n::tr("ui.design_properties.others_note"), ui_kit::Text::Note("others")); + egui::ScrollArea::vertical().max_height(ui_kit::row_height() * OTHERS_ROWS).show(ui, |ui| { + for (key, value) in others { + ui_kit::row(ui, key, |ui| ui_kit::text(ui, &preview(value), ui_kit::Text::Cell)); + } }); }); - }); - if !open || cancel || (ok && apply(app, &mut d)) { + } + if let Some(error) = &d.error { + ui_kit::text(ui, error, ui_kit::Text::Report("error", true)); + } + }); + if closed || action == ui_kit::Action::Cancel || (action == ui_kit::Action::Ok && apply(app, &mut d)) { return; } app.design_properties = Some(d); } +/// Draw the Properties window of the body or component the menu asked about (mass, volume, +/// area, centre of mass, bounding box and counts), when it is open. Close dismisses it. +pub fn show_object(app: &mut SolveApp, ctx: &egui::Context) { + let Some((title, v)) = app.menu.props.clone() else { return }; + let number = |x: &Value, k: &str| x.get(k).and_then(Value::as_f64).unwrap_or(f64::NAN); + let mut rows: Vec<(&str, String)> = Vec::new(); + let total = &v["total"]; + let bodies = v["bodies"].as_array().cloned().unwrap_or_default(); + if let [b] = bodies.as_slice() { + rows.push(("Material", b["material"].as_str().unwrap_or("Default").to_string())); + rows.push(("Density", format!("{:.3} g/cm³", number(b, "density_g_cm3")))); + } + rows.push(("Mass", format!("{:.3} g", number(total, "mass_g")))); + rows.push(("Volume", crate::prefs::show_mm(app, number(total, "volume_mm3"), 3))); + rows.push(("Area", crate::prefs::show_mm(app, number(total, "area_mm2"), 2))); + let geometry = if total.get("center_of_mass").is_some() { Some(total) } else { bodies.first().filter(|_| bodies.len() == 1) }; + if let Some(b) = geometry { + let unit = crate::prefs::unit_scale(&app.session.doc.units).1; + if let Some(c) = b["center_of_mass"].as_array() { + let c: Vec = c.iter().map(|x| crate::prefs::show_mm_bare(app, x.as_f64().unwrap_or(f64::NAN))).collect(); + rows.push(("Center of mass", format!("({}) {unit}", c.join(", ")))); + } + let (mn, mx) = (&b["bbox"]["min"], &b["bbox"]["max"]); + if let (Some(a), Some(z)) = (mn.as_array(), mx.as_array()) { + let d: Vec = + a.iter().zip(z).map(|(a, z)| crate::prefs::show_mm_bare(app, z.as_f64().unwrap_or(0.0) - a.as_f64().unwrap_or(0.0))).collect(); + rows.push(("Bounding box", format!("{} {unit}", d.join(" × ")))); + } + } + rows.push(("Faces / edges / vertices", format!("{} / {} / {}", total["faces"], total["edges"], total["vertices"]))); + let heading = crate::i18n::format("properties.title", &[("name", &title)]); + let (closed, action) = ui_kit::Window::new(egui::Id::new("sc_props"), "settings", &heading).actions("", "Close", None).show(ctx, true, |ui| { + for (label, value) in &rows { + ui_kit::row(ui, crate::i18n::tr_label(label), |ui| ui_kit::text(ui, value, ui_kit::Text::Cell)); + } + }); + if closed || action == ui_kit::Action::Cancel { + app.menu.props = None; + } +} + #[cfg(test)] mod tests { use serde_json::json; diff --git a/crates/ui-egui/src/dialog_descriptor.rs b/crates/ui-egui/src/dialog_descriptor.rs new file mode 100644 index 0000000000..e04c04b108 --- /dev/null +++ b/crates/ui-egui/src/dialog_descriptor.rs @@ -0,0 +1,69 @@ +//! Typed records for dialogs whose defaults and scalar restoration are mechanical. +//! Selection, geometry, optional parameters and form rows stay with their callers. + +macro_rules! dialog_descriptors { + ( + $(#[$attr:meta])* $vis:vis enum $name:ident { + $( $variant:ident ($command:literal, $title:literal, + [$(($label:literal, $accept:expr, $many:expr)),* $(,)?], $feature:pat) { + $( $field:ident: $ty:ty = $default:expr => $restore:expr, )* + } => $params:expr; )* + ; custom { $($custom:tt)* } + } + ) => { + $(#[$attr])* $vis enum $name { + $( $variant { $($field: $ty,)* }, )* + $($custom)* + } + + impl $name { + pub(crate) fn simple_start(id: &str) -> Option<(Self, Vec)> { + let kind = match id { + $( $command => Self::$variant { $($field: $default,)* }, )* + _ => return None, + }; + let inputs = kind.simple_inputs(); + Some((kind, inputs)) + } + + pub(crate) fn simple_inputs(&self) -> Vec { + match self { + $( Self::$variant { .. } => vec![$(crate::selection::SelInput::new($label, $accept, $many)),*], )* + _ => Vec::new(), + } + } + + pub(crate) fn simple_metadata(&self) -> Option<(&'static str, &'static str)> { + match self { + $( Self::$variant { .. } => Some(($command, $title)), )* + _ => None, + } + } + + pub(crate) fn fill_simple_params(&self, params: &mut serde_json::Value) { + let scalars = match self { + $( Self::$variant { $($field,)* } => { + // Some fields have conditional emission in the explicit command builder. + let _ = ($($field,)*); + $params + }, )* + _ => return, + }; + if let (serde_json::Value::Object(params), serde_json::Value::Object(scalars)) = (params, scalars) { + for (key, value) in scalars { + params.entry(key).or_insert(value); + } + } + } + + pub(crate) fn simple_restore(feature: &solidcraft_engine::doc::FeatureKind) -> Option { + Some(match feature { + $( $feature => Self::$variant { $($field: $restore,)* }, )* + _ => return None, + }) + } + } + }; +} + +pub(crate) use dialog_descriptors; diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 529cc1fa13..59593d37d5 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -2,9 +2,10 @@ //! inputs (geometry picked in the viewport, never typed), then runs its command; it never //! changes the design itself. Things selected before the command starts become its input. +use crate::ui_kit::choice_row; use std::hash::{Hash, Hasher}; -use egui::{Color32, RichText, Stroke, vec2}; +use egui::{RichText, vec2}; use serde_json::{Value, json}; use solidcraft_engine::Sel; use solidcraft_engine::Session; @@ -13,11 +14,16 @@ use solidcraft_engine::doc::{AxisRef, Direction, FeatureKind, FilletStyle, HoleK use solidcraft_engine::geom::{Aabb3, Vec3}; use crate::SolveApp; -use crate::selection::{self, AXES, Accept, BODIES, CURVES, EDGES, FACES, PLANAR_FACES, PLANES, PROFILES, SelInput}; +use crate::selection::{ + self, AXES, Accept, BODIES, CURVES, EDGES, FACES, PLANAR_FACES, PLANES, PROFILES, SelInput, SelectionExt as _, point_value as pt, polyline_mid, +}; use crate::theme::Tokens; use crate::tool_modifiers::{Audit, Scope, ToolModifiers}; use crate::viewport::Hit; +#[path = "dialogs_dense.rs"] +mod dense; + const SELECT_ONLY: &[Scope] = &[Scope::Selection, Scope::FeaturePreview]; const SELECT_MANIP: &[Scope] = &[Scope::Selection, Scope::Manipulator, Scope::FeaturePreview]; @@ -88,277 +94,278 @@ const THIN_ACCEPT: Accept = PROFILES | CURVES; const HOLE_TYPES: [&str; 3] = ["simple", "counterbore", "countersink"]; const HOLE_LABELS: [&str; 3] = ["Simple", "Counterbore", "Countersink"]; -#[derive(Clone, Debug)] -pub enum Kind { - /// Pick a plane or planar face; the sketch starts on it at once. - Sketch, - Extrude { - distance: String, - direction: usize, - operation: usize, - /// Into a body: cut; additions: join when a solid exists, otherwise new. - /// until the user picks one. - auto_op: bool, - /// The direction from the picked side and the latched operation (#1257). - smart: crate::smart_extrude::Smart, - /// The other side's distance (two sides). - distance2: String, - /// Taper angle (empty or zero: none). - taper: String, - /// An explicitly authored zero taper is still a model parameter. - taper_present: bool, - /// Optional distance beyond the selected end object. - to_offset: String, - /// Start offset from the profile plane (empty: start on the profile plane). - start: String, - /// Extent type All (through everything) instead of a distance. - all: bool, - /// Thin Extrude: a wall along the picked curves (or profiles' outlines). - thin: bool, - /// The wall thickness (inside, with Two Sides). - wall: String, - /// Wall location: an index into [`WALL_SIDES`]. - wall_side: usize, - /// The thickness outside, with Two Sides. - wall2: String, - }, - Revolve { - angle: String, - operation: usize, - /// An arbitrary stored line until the user deliberately picks a replacement. - axis: Option, - }, - Fillet { - radius: String, - chamfer: bool, - chain: bool, - /// Chamfer type: 0 equal distance, 1 two distances, 2 distance and angle; with the - /// second distance, the angle and which face the first distance is on. - ctype: usize, - distance2: String, - angle: String, - flip: bool, - }, - Shell { - thickness: String, - }, - Draft { - angle: String, - }, - Mirror, - /// Rectangular pattern of bodies: along one direction, optionally a second. - PatternRect { - count: String, - spacing: String, - count2: String, - spacing2: String, - }, - /// Circular pattern of bodies around an axis. - PatternCirc { - count: String, - angle: String, - }, - /// Loft through profiles of different sketches, in pick order; `rail_fit` how its guide - /// rails reshape the sections (an index into the sweep dialog's rail fits). - Loft { - operation: usize, - rail_fit: usize, - }, - /// Sweep a profile along a path of sketch curves. - Sweep { - operation: usize, - opts: crate::dialogs_sweep::SweepOpts, - }, - /// A construction plane offset from a plane. - OffsetPlane { - offset: String, - }, - /// Halfway between two planes or planar faces. - Midplane, - /// An imported mesh body rebuilt as editable faces (#3001). - MeshConvert { - tolerance: String, - }, - /// A construction axis, point or plane made from picks alone (#2474): input `i` is the - /// command's parameter `keys[i]`, and `t` (along a path) when it takes one. - Construct { - command: &'static str, - title: &'static str, - keys: Vec<&'static str>, - t: Option, - }, - /// A construction plane through an axis at an angle to a plane. - AnglePlane { - angle: String, - }, - /// A plane perpendicular to one selected path at a fractional distance. - PathPlane { - t: String, - sketch: Option, - }, - /// Split a body by a plane. - Split, - /// Scale bodies uniformly. - Scale { - factor: String, - }, - /// Move planar faces along their normals. - OffsetFaces { - distance: String, - }, - /// A cosmetic thread on a cylindrical face. - Thread { - designation: String, - length: String, - /// The last automatic size; a different value is a manual override. Keep automatic - /// features automatic when applying, even though their resolved size is visible. - suggested: Option, - /// The picks used for the suggestion, so an unchanged dialog does no geometry work. - of: Vec, - }, - /// A rib (one wall along open curves) or a web (a wall per curve). - Rib { - web: bool, - thickness: String, - depth: String, - flip: bool, - /// Grow along the sketch normal rather than across the plane. - normal: bool, - }, - /// Raise or sink profiles of a sketch on a face. - Emboss { - depth: String, - deboss: bool, - }, - /// Move planar faces onto a target plane or face. - ReplaceFace, - /// Take faces off a body; the neighbouring faces close the gap (#2167). - DeleteFaces, - /// Boundary Fill: the cells the tool bodies make (a point inside, a description, filled - /// or not), found again when the tools change. - BoundaryFill { - operation: usize, - cells: Vec<(Vec3, String, bool)>, - of: Vec, - }, - /// Move bodies so a face or point meets another. - Align { - flip: bool, - }, - /// Remove bodies from here on in the timeline. - Remove, - /// Pattern bodies along a path of sketch curves. - PathPattern { - count: String, - spacing: String, - }, - /// A pipe (round, optionally hollow) along sketch curves. - Pipe { - diameter: String, - wall: String, - }, - /// A stock block around bodies. - Stock { - margin: String, - }, - /// Physical material (and with it the appearance) of bodies. - Material { - index: usize, - }, - /// Section Analysis: cut the view by a plane, moved along its normal. - Section { - offset: String, - flip: bool, - }, - /// Measure one or two picked items (vertices, edges, faces): the result of the last - /// measurement and the items it was for. - Measure { - result: Option, - of: Vec, - /// The measure chosen in the panel (centre, min or max); `None` follows the result's - /// own default (centre where a round item gives one). - show: Option, - }, - /// Move bodies by a distance along X, Y and Z. - /// Move/Copy (see `dialogs_move`): the move type, X/Y/Z distances (Point to Position: the - /// target), X/Y/Z angles (Free Move) and the angle (Rotate). - Move { - mode: usize, - x: String, - y: String, - z: String, - rx: String, - ry: String, - rz: String, - angle: String, - }, - Hole { - diameter: String, - depth: String, - kind: usize, - cb_diameter: String, - cb_depth: String, - cs_diameter: String, - cs_angle: String, - opts: HoleOpts, - }, - Primitive { - cmd: &'static str, - fields: Vec<(&'static str, String)>, - operation: usize, - /// Where it goes: a picked point on a face or vertex, with the face's normal when the - /// face is planar. `None` keeps the command's own placement (the origin, or the edited - /// feature's). - at: Option<(Vec3, Option)>, - }, - Combine { - operation: usize, - keep_tools: bool, - }, - Params { - new_name: String, - new_expr: String, - }, - EditParam { - name: String, - expr: String, - }, - Rename { - feature: u64, - name: String, - }, - /// Delete a feature that others depend on. - ConfirmDelete { - features: Vec, - with: Vec, - fail: Vec, - }, - /// Joints and components (`dialogs_assembly`). - Assembly(crate::dialogs_assembly::Asm), - /// Sheet metal (`dialogs_sheet`). - Sheet(crate::dialogs_sheet::Sm), - /// Surface tools and Split Face (`dialogs_surface`). - Surface(crate::dialogs_surface::Surf), - SaveMesh(crate::dialogs_save_mesh::SaveMesh), - ViewDxf { - dashed: bool, - tolerance: f64, - }, - /// Plastic features (`dialogs_plastic`). - Plastic(crate::dialogs_plastic::Pl), - /// Appearance (`dialogs_appearance`). - Appearance(crate::dialogs_appearance::Ap), - /// Contact sets, motion studies, exploded views, configurations (`dialogs_motion`). - Motion(crate::dialogs_motion::Mo), - /// Insert Part from the standard parts library (`dialogs_parts`). - Part(crate::dialogs_parts::Pt), - PartSize(crate::dialogs_part_size::Resize), - /// Edit Text: a sketch text's content and placement (`sketch_text`). - Text(Box), - TextMove(crate::sketch_text_move::TextMove), - /// Sketch-space modify tools: scale and offset, and later fillet, chamfer, the patterns, - /// mirror and move (`sketch_modify_ui`). - SketchModify(crate::sketch_modify_ui::Mod), +crate::dialog_descriptor::dialog_descriptors! { + #[derive(Clone, Debug)] + pub enum Kind { + Shell("solid.shell", "SHELL", [("Faces/Body", FACES, true)], + FeatureKind::Shell { thickness, .. }) { + thickness: String = "2 mm".into() => thickness.to_string(), + } => json!({"thickness": thickness}); + Draft("solid.draft", "DRAFT", [("Faces", FACES, true), ("Neutral plane", PLANES | PLANAR_FACES, false)], + FeatureKind::Draft { angle, .. }) { + angle: String = "5 deg".into() => angle.to_string(), + } => json!({"angle": angle}); + OffsetPlane("construct.plane.offset", "OFFSET PLANE", [("Plane", PLANES | PLANAR_FACES, false)], + FeatureKind::ConstructionPlane { plane: PlaneRef::Offset { distance, .. } }) { + offset: String = "0 mm".into() => distance.to_string(), + } => json!({"offset": offset}); + AnglePlane("construct.plane.at_angle", "PLANE AT ANGLE", [("Plane", PLANES, false), ("Axis", AXES, false)], + FeatureKind::ConstructionPlane { plane: PlaneRef::AtAngle { angle, .. } }) { + angle: String = "45 deg".into() => angle.to_string(), + } => json!({"angle": angle}); + Scale("solid.scale", "SCALE", [("Bodies", BODIES, true)], + FeatureKind::Scale { factor, .. }) { + factor: String = "2".into() => factor.to_string(), + } => json!({"factor": factor}); + OffsetFaces("solid.offset_face", "OFFSET FACE", [("Faces", PLANAR_FACES, true)], + FeatureKind::OffsetFace { distance, .. }) { + distance: String = "2 mm".into() => distance.to_string(), + } => json!({"distance": distance}); + Stock("solid.bounding_solid", "STOCK", [("Bodies (all if none)", BODIES, true)], + FeatureKind::BoundingSolid { margin, .. }) { + margin: String = "2 mm".into() => margin.to_string(), + } => json!({"margin": margin}); + Pipe("solid.pipe", "PIPE", [("Path", CURVES, true)], + FeatureKind::Pipe { diameter, wall, .. }) { + diameter: String = "5 mm".into() => diameter.to_string(), + wall: String = String::new() => wall.as_deref().map(str::to_string).unwrap_or_default(), + } => json!({"diameter": diameter}); + Emboss("solid.emboss", "EMBOSS", [("Profiles", PROFILES, true), ("Target Face (optional)", FACES, false)], + FeatureKind::Emboss { depth, deboss, .. }) { + depth: String = "1 mm".into() => depth.to_string(), + deboss: bool = false => *deboss, + } => json!({"depth": depth, "mode": if *deboss { "deboss" } else { "emboss" }}); + ; custom { + /// Pick a plane or planar face; the sketch starts on it at once. + Sketch, + Extrude { + distance: String, + direction: usize, + operation: usize, + /// Into a body: cut; additions: join when a solid exists, otherwise new. + /// until the user picks one. + auto_op: bool, + /// The direction from the picked side and the latched operation (#1257). + smart: crate::smart_extrude::Smart, + /// The other side's distance (two sides). + distance2: String, + /// Taper angle (empty or zero: none). + taper: String, + /// An explicitly authored zero taper is still a model parameter. + taper_present: bool, + /// Optional distance beyond the selected end object. + to_offset: String, + /// Start offset from the profile plane (empty: start on the profile plane). + start: String, + /// Extent type All (through everything) instead of a distance. + all: bool, + /// Thin Extrude: a wall along the picked curves (or profiles' outlines). + thin: bool, + /// The wall thickness (inside, with Two Sides). + wall: String, + /// Wall location: an index into [`WALL_SIDES`]. + wall_side: usize, + /// The thickness outside, with Two Sides. + wall2: String, + }, + Revolve { + angle: String, + operation: usize, + /// An arbitrary stored line until the user deliberately picks a replacement. + axis: Option, + }, + Fillet { + radius: String, + chamfer: bool, + chain: bool, + /// Chamfer type: 0 equal distance, 1 two distances, 2 distance and angle; with the + /// second distance, the angle and which face the first distance is on. + ctype: usize, + distance2: String, + angle: String, + flip: bool, + }, + Mirror, + /// Rectangular pattern of bodies: along one direction, optionally a second. + PatternRect { + count: String, + spacing: String, + count2: String, + spacing2: String, + }, + /// Circular pattern of bodies around an axis. + PatternCirc { + count: String, + angle: String, + }, + /// Loft through profiles of different sketches, in pick order; `rail_fit` how its guide + /// rails reshape the sections (an index into the sweep dialog's rail fits). + Loft { + operation: usize, + rail_fit: usize, + }, + /// Sweep a profile along a path of sketch curves. + Sweep { + operation: usize, + opts: crate::dialogs_sweep::SweepOpts, + }, + /// Halfway between two planes or planar faces. + Midplane, + /// An imported mesh body rebuilt as editable faces (#3001). + MeshConvert { + tolerance: String, + }, + /// A construction axis, point or plane made from picks alone (#2474): input `i` is the + /// command's parameter `keys[i]`, and `t` (along a path) when it takes one. + Construct { + command: &'static str, + title: &'static str, + keys: Vec<&'static str>, + t: Option, + }, + /// A plane perpendicular to one selected path at a fractional distance. + PathPlane { + t: String, + sketch: Option, + }, + /// Split a body by a plane. + Split, + /// A cosmetic thread on a cylindrical face. + Thread { + designation: String, + length: String, + /// The last automatic size; a different value is a manual override. Keep automatic + /// features automatic when applying, even though their resolved size is visible. + suggested: Option, + /// The picks used for the suggestion, so an unchanged dialog does no geometry work. + of: Vec, + }, + /// A rib (one wall along open curves) or a web (a wall per curve). + Rib { + web: bool, + thickness: String, + depth: String, + flip: bool, + /// Grow along the sketch normal rather than across the plane. + normal: bool, + }, + /// Move planar faces onto a target plane or face. + DeleteFaces, + ReplaceFace, + /// Boundary Fill: the cells the tool bodies make (a point inside, a description, filled + /// or not), found again when the tools change. + BoundaryFill { + operation: usize, + cells: Vec<(Vec3, String, bool)>, + of: Vec, + }, + /// Move bodies so a face or point meets another. + Align { + flip: bool, + }, + /// Remove bodies from here on in the timeline. + Remove, + /// Pattern bodies along a path of sketch curves. + PathPattern { + count: String, + spacing: String, + }, + /// Physical material (and with it the appearance) of bodies. + Material { + index: usize, + }, + /// Section Analysis: cut the view by a plane, moved along its normal. + Section { + offset: String, + flip: bool, + }, + /// Measure one or two picked items (vertices, edges, faces): the result of the last + /// measurement and the items it was for. + Measure { + result: Option, + of: Vec, + /// The measure chosen in the panel (centre, min or max); `None` follows the result's + /// own default (centre where a round item gives one). + show: Option, + }, + /// Move bodies by a distance along X, Y and Z. + /// Move/Copy (see `dialogs_move`): the move type, X/Y/Z distances (Point to Position: the + /// target), X/Y/Z angles (Free Move) and the angle (Rotate). + Move { + mode: usize, + x: String, + y: String, + z: String, + rx: String, + ry: String, + rz: String, + angle: String, + }, + Hole { + diameter: String, + depth: String, + kind: usize, + cb_diameter: String, + cb_depth: String, + cs_diameter: String, + cs_angle: String, + opts: HoleOpts, + }, + Primitive { + cmd: &'static str, + fields: Vec<(&'static str, String)>, + operation: usize, + /// Where it goes: a picked point on a face or vertex, with the face's normal when the + /// face is planar. `None` keeps the command's own placement (the origin, or the edited + /// feature's). + at: Option<(Vec3, Option)>, + }, + Combine { + operation: usize, + keep_tools: bool, + }, + EditParam { + name: String, + expr: String, + }, + Rename { + feature: u64, + name: String, + }, + /// Delete a feature that others depend on. + ConfirmDelete { + features: Vec, + with: Vec, + fail: Vec, + }, + /// Joints and components (`dialogs_assembly`). + Assembly(crate::dialogs_assembly::Asm), + /// Sheet metal (`dialogs_sheet`). + Sheet(crate::dialogs_sheet::Sm), + /// Surface tools and Split Face (`dialogs_surface`). + Surface(crate::dialogs_surface::Surf), + SaveMesh(crate::dialogs_save_mesh::SaveMesh), + ViewDxf { + dashed: bool, + tolerance: f64, + }, + /// Plastic features (`dialogs_plastic`). + Plastic(crate::dialogs_plastic::Pl), + /// Appearance (`dialogs_appearance`). + Appearance(crate::dialogs_appearance::Ap), + /// Contact sets, motion studies, exploded views, configurations (`dialogs_motion`). + Motion(crate::dialogs_motion::Mo), + /// Insert Part from the standard parts library (`dialogs_parts`). + Part(crate::dialogs_parts::Pt), + PartSize(crate::dialogs_part_size::Resize), + /// Edit Text: a sketch text's content and placement (`sketch_text`). + Text(Box), + TextMove(crate::sketch_text_move::TextMove), + /// Sketch-space modify tools: scale and offset, and later fillet, chamfer, the patterns, + /// mirror and move (`sketch_modify_ui`). + SketchModify(crate::sketch_modify_ui::Mod), + } + } } /// The Single hole placement's input: places on planar faces. A pick on or near a sketch point @@ -400,8 +407,13 @@ pub struct Dialog { pub profile_selection_changed: bool, /// Give the on-canvas value box the keyboard (and select its text) on the next frame. pub focus: bool, - /// Folded to its header (the "−" glyph). + /// Folded to its header (the shared disclosure chevron). pub collapsed: bool, + reset_scroll: bool, + /// Transient display preference; never part of a command or a saved feature. + pub preview_enabled: bool, + /// Preserve Apply intent across asynchronous evaluation; never a command parameter. + repeat_after_apply: bool, /// Parameters the dialog doesn't show but the command needs (kept when editing). pub extra: serde_json::Map, /// What the inputs were when OK was refused: `error` stays only while they still are. @@ -527,6 +539,9 @@ impl Dialog { profile_selection_changed: false, focus: true, collapsed: false, + reset_scroll: true, + preview_enabled: true, + repeat_after_apply: false, extra: serde_json::Map::new(), refused: None, then_start: None, @@ -567,15 +582,12 @@ impl Dialog { "solid.fillet" => Dialog::new(fillet_kind("2 mm", false), vec![SelInput::new("Edges", EDGES | FACES, true)]), "solid.chamfer" => Dialog::new(fillet_kind("1 mm", true), vec![SelInput::new("Edges", EDGES | FACES, true)]), "solid.shell" => { - let mut d = Dialog::new(Kind::Shell { thickness: "2 mm".into() }, vec![SelInput::new("Faces/Body", FACES, true)]); + let (kind, inputs) = Kind::simple_start(id)?; + let mut d = Dialog::new(kind, inputs); // Faces joined smoothly to the picked ones too. d.extra.insert("tangent_chain".into(), json!(true)); d } - "solid.draft" => Dialog::new( - Kind::Draft { angle: "5 deg".into() }, - vec![SelInput::new("Faces", FACES, true), SelInput::new("Neutral plane", PLANES | PLANAR_FACES, false)], - ), "solid.mirror" => { Dialog::new(Kind::Mirror, vec![SelInput::new("Objects", BODIES, true), SelInput::new("Mirror Plane", PLANES | PLANAR_FACES, false)]) } @@ -583,10 +595,6 @@ impl Dialog { Kind::Rib { web: id == "solid.web", thickness: "2 mm".into(), depth: String::new(), flip: false, normal: false }, vec![SelInput::new("Curves", CURVES, true)], ), - "solid.emboss" => Dialog::new( - Kind::Emboss { depth: "1 mm".into(), deboss: false }, - vec![SelInput::new("Profiles", PROFILES, true), SelInput::new("Target Face (optional)", FACES, false)], - ), "solid.replace_face" => Dialog::new( Kind::ReplaceFace, vec![SelInput::new("Faces", PLANAR_FACES, true), SelInput::new("Target", PLANES | PLANAR_FACES, false)], @@ -608,11 +616,6 @@ impl Dialog { Kind::PathPattern { count: "4".into(), spacing: "20 mm".into() }, vec![SelInput::new("Objects", BODIES, true), SelInput::new("Path", CURVES, true)], ), - "solid.pipe" => Dialog::new(Kind::Pipe { diameter: "5 mm".into(), wall: String::new() }, vec![SelInput::new("Path", CURVES, true)]), - "solid.bounding_solid" => Dialog::new(Kind::Stock { margin: "2 mm".into() }, vec![SelInput::new("Bodies (all if none)", BODIES, true)]), - "construct.plane.offset" => { - Dialog::new(Kind::OffsetPlane { offset: "0 mm".into() }, vec![SelInput::new("Plane", PLANES | PLANAR_FACES, false)]) - } "mesh.convert" => Dialog::new(Kind::MeshConvert { tolerance: "0.05 mm".into() }, vec![SelInput::new("Mesh body", BODIES, false)]), "construct.plane.midplane" => Dialog::new( Kind::Midplane, @@ -628,16 +631,10 @@ impl Dialog { "construct.plane.along_path" => { Dialog::new(Kind::PathPlane { t: "0.5".into(), sketch: None }, vec![SelInput::new("Path", CURVES | EDGES, false)]) } - "construct.plane.at_angle" => Dialog::new( - Kind::AnglePlane { angle: "45 deg".into() }, - vec![SelInput::new("Plane", PLANES, false), SelInput::new("Axis", AXES, false)], - ), "solid.split_body" => Dialog::new( Kind::Split, vec![SelInput::new("Body to split", BODIES, false), SelInput::new("Splitting tool", PLANES | FACES | AXES, false)], ), - "solid.scale" => Dialog::new(Kind::Scale { factor: "2".into() }, vec![SelInput::new("Bodies", BODIES, true)]), - "solid.offset_face" => Dialog::new(Kind::OffsetFaces { distance: "2 mm".into() }, vec![SelInput::new("Faces", PLANAR_FACES, true)]), "solid.thread" => Dialog::new( Kind::Thread { designation: String::new(), length: String::new(), suggested: None, of: Vec::new() }, vec![SelInput::new("Cylindrical face", FACES, false)], @@ -727,9 +724,9 @@ impl Dialog { Kind::Combine { operation: 1, keep_tools: false }, vec![SelInput::new("Target Body", BODIES, false), SelInput::new("Tool Bodies", BODIES, true)], ), - "parameters.change" => Dialog::new(Kind::Params { new_name: String::new(), new_expr: String::new() }, vec![]), _ => { - let (kind, inputs) = crate::dialogs_assembly::start(app, id) + let (kind, inputs) = Kind::simple_start(id) + .or_else(|| crate::dialogs_assembly::start(app, id)) .or_else(|| crate::dialogs_sheet::start(app, id)) .or_else(|| crate::dialogs_surface::start(app, id)) .or_else(|| crate::dialogs_save_mesh::start(app, id)) @@ -844,6 +841,7 @@ impl Dialog { Dialog::new(Kind::Rename { feature, name: name.to_string() }, vec![]) } + #[cfg(test)] pub fn confirm_delete(feature: u64, with: Vec, fail: Vec) -> Dialog { Dialog::confirm_delete_many(vec![feature], with, fail) } @@ -962,8 +960,7 @@ impl Dialog { /// Dialogs holding a table get the wide limit. pub fn is_wide(&self) -> bool { - matches!(self.kind, Kind::Params { .. } | Kind::SketchModify(crate::sketch_modify_ui::Mod::PatternRect { .. })) - || (matches!(self.kind, Kind::PatternRect { .. }) && self.inputs.first().is_some_and(|i| i.accept == selection::FEATURES)) + (matches!(self.kind, Kind::PatternRect { .. }) && self.inputs.first().is_some_and(|i| i.accept == selection::FEATURES)) || matches!(&self.kind, Kind::Motion(k) if k.wide()) } @@ -1511,23 +1508,6 @@ thread_local! { const { std::cell::RefCell::new((None, Vec::new())) }; } -fn polyline_mid(pts: &[Vec3]) -> Vec3 { - let len: f64 = pts.windows(2).map(|w| w[0].dist(w[1])).sum(); - let mut acc = 0.0; - for w in pts.windows(2) { - let l = w[0].dist(w[1]); - if acc + l >= len * 0.5 && l > 0.0 { - return w[0].lerp(w[1], (len * 0.5 - acc) / l); - } - acc += l; - } - pts.first().copied().unwrap_or_default() -} - -fn pt(v: Vec3) -> Value { - json!([v.x, v.y, v.z]) -} - /// A plane input as a command parameter: an origin/construction plane name or a face plane. fn plane_value(s: &Session, sel: Option<&Sel>) -> Option { match sel? { @@ -1540,79 +1520,32 @@ fn plane_value(s: &Session, sel: Option<&Sel>) -> Option { } } -/// A value field; true when Enter was pressed in it. -pub(crate) fn field(ui: &mut egui::Ui, s: &mut String) -> bool { - let r = ui.text_edit_singleline(s); - crate::theme::TabAccent::number_focus(ui, &r); - publish_row(&r); - crate::params_dialog::complete(ui, &r, s); - r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) -} - -/// A checkbox row. Its row handle (`dialog:`) lets a scenario click it by the label -/// beside it, the way `field` already publishes one for a value field. -pub(crate) fn check(ui: &mut egui::Ui, on: &mut bool) { - let r = ui.checkbox(on, ""); - publish_row(&r); -} - -pub(crate) fn combo(ui: &mut egui::Ui, id: &str, labels: &[&str], sel: &mut usize) { - let row = ROW.with(|r| r.borrow().clone()); - let selected = labels.get(*sel).copied().map(tr).unwrap_or(""); - let r = egui::ComboBox::from_id_salt(id).selected_text(selected).width(FIELD_W).show_ui(ui, |ui| { - for (i, l) in labels.iter().enumerate() { - let item = ui.selectable_value(sel, i, tr(l)); - crate::scenario::publish_handle(&format!("dialog:{row}:{l}"), item.rect.center()); +/// Put non-empty optional values into the parameters. +pub(crate) fn put(p: &mut Value, list: &[(&str, &str)]) { + for (k, v) in list { + if !v.trim().is_empty() { + p[*k] = json!(v); } - }); - publish_row(&r.response); -} - -/// A mode choice as a row of icon buttons rather than a drop-down: every mode is on screen at -/// once, each drawn as the shape it makes, with its name as the tooltip. `labels` and `icons` -/// run in step; a mode with no icon gets the fallback square. Publishes the handles a combo -/// does, `dialog::