mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-07 20:41:50 +00:00
Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0160e67ea1 | |||
| c29f396f72 | |||
| 46edecca67 | |||
| a36c1743dc | |||
| 7b3ca00054 | |||
| 91a06a546e | |||
| fd02123b3e | |||
| 13ab728d07 | |||
| 4a07865288 | |||
| b73d6e6e6f | |||
| 3b2ca62c86 | |||
| 24c7bb7d70 | |||
| 670636ecd6 | |||
| 3623466acf | |||
| ecbd05ef5e | |||
| c1bd94e937 | |||
| 28fed4d6dd | |||
| be648ef742 | |||
| a8d775c12a | |||
| e02e1d40b5 | |||
| 136b62fa47 | |||
| a2c4a1267b | |||
| 735688dbc5 | |||
| 0debd9759f | |||
| 2c6230eafc | |||
| 850509120b | |||
| 72d2beff71 | |||
| 5395387dad | |||
| 0e83a0e494 | |||
| 9387a1b93c | |||
| 304aba2a49 | |||
| b9270a6884 | |||
| ea46e07300 | |||
| eaf014eb54 | |||
| 1c03f2be06 | |||
| 3dc258c203 | |||
| 565ba4a37e | |||
| 1c1018aa6e | |||
| 1bd05a2c51 | |||
| 5d8cd60f70 | |||
| 046eba5ba3 | |||
| 96a6629ed1 | |||
| 203ce2672b | |||
| 43c14bdbdf | |||
| 6b5b49b285 | |||
| 3d49d0cb0b | |||
| 9ecf28834f | |||
| 2e4b728fca | |||
| 5524224222 | |||
| 2c4e3fe4d6 | |||
| 5acf7b679a | |||
| 7f0ef575a2 | |||
| 849edb692a | |||
| 6f5894683c | |||
| a6c0e8a959 | |||
| 46438b14a7 | |||
| 3900dc742c | |||
| 6b235ff0d1 | |||
| 4774efe1ee | |||
| 4f687454fc | |||
| 7f257866d9 | |||
| feeac232f0 | |||
| 4f77836834 | |||
| af21f2aa13 | |||
| 142285bff3 | |||
| ea4513728b | |||
| 9325e3ac8c | |||
| 8c32875160 | |||
| 871014f955 | |||
| 684a4f4285 | |||
| 73e188df52 | |||
| c571a133dc | |||
| a2218e8a8d | |||
| c36860e773 | |||
| 5faacb55d6 | |||
| 6c2ac96a09 | |||
| cccef6dccd | |||
| 5a630606a4 | |||
| 7139ee9d20 | |||
| 5818c3eb3b | |||
| 9c2b888b40 | |||
| 0678ec98c3 | |||
| b8aa83c6d9 | |||
| 8ad0cce768 | |||
| 3bcd93e2ca | |||
| 3a70b43708 | |||
| 1bbb18494e | |||
| 8e55033d9a | |||
| a1d2b6d05b | |||
| 89191a1278 | |||
| dad363980b | |||
| 55d07a258c | |||
| 1589e18f2a | |||
| ee43b7d478 | |||
| ed30a59795 | |||
| c66752e8d3 | |||
| 429520088a | |||
| 54b4bb3f44 | |||
| d8bf4c9bed | |||
| d68970dd97 | |||
| a6644ef0d2 | |||
| 48bab1a65a | |||
| 25d7a6b0c3 | |||
| 8505c77f5b | |||
| 7ecb3934a6 | |||
| b5a5412d48 | |||
| c4e1a3a756 | |||
| b432b4484b | |||
| 068c46cf8b | |||
| d7cad1005a | |||
| 8564006ffb | |||
| 708d5a149d | |||
| 0c094142d3 | |||
| 1939adba6b | |||
| 207679e261 | |||
| 4cbbccb935 | |||
| 22f8da2689 | |||
| 51d8842e8a | |||
| 95fd121bb3 | |||
| 1444801d96 | |||
| 28dcbbc862 | |||
| 7e96aabd6d | |||
| 52893744a5 | |||
| 4699b9cc89 | |||
| 4eb0c6f5ec | |||
| a07b60cd81 | |||
| bd3053f57b | |||
| 49ab9179a7 | |||
| 8c4caf9372 | |||
| 202b775de4 | |||
| 2c35a027f5 | |||
| 146a43f38e | |||
| e04bc060f6 | |||
| 464da90238 | |||
| 0b32d3619d | |||
| eb10552c61 | |||
| e95f65fd54 | |||
| 1fdc56835b | |||
| 3fde538320 | |||
| 283ccd0b21 | |||
| 6b222aedf7 | |||
| bcdbb91f26 | |||
| 21a1269d5c | |||
| d580c4256c | |||
| 1b52186524 | |||
| 3e6eadc118 | |||
| 87358ef724 | |||
| 3d73822442 | |||
| 51f4074506 | |||
| 796b7c40d3 | |||
| 9cdee588f2 | |||
| 90d9c6488a | |||
| edb7882815 | |||
| db3558d820 | |||
| b2f5da6ca7 | |||
| f1159a5d51 | |||
| ffc84dbe2e | |||
| 942e1540db | |||
| 6500c42c9f | |||
| 655c1f3336 | |||
| 9f2e117934 | |||
| c6fe613d32 | |||
| d5e953871b | |||
| c7dcab172f | |||
| 8312bf0cb8 | |||
| 8f20ad4532 | |||
| abdf195132 | |||
| 7e002f182d | |||
| f0d305a783 | |||
| dc33c4a24d | |||
| 2405f75c38 | |||
| de07905f78 | |||
| 1cf7ae8e5c | |||
| e9efcf4c0b | |||
| 7546e84ef4 | |||
| 278ab40a0f | |||
| ed7206cc0d | |||
| 16f3b939b3 | |||
| ad230eb4a4 | |||
| c1760d4857 | |||
| 4d69d8c87a | |||
| fa2faf99e2 | |||
| 20467e39e2 | |||
| 020e3bebe3 | |||
| 6029a38ea0 | |||
| 9c93a6c340 | |||
| 9fdb7fdefc | |||
| 718d5a4383 | |||
| 1c10f82b34 | |||
| ca4f3ed1a0 | |||
| a1d66c238e | |||
| 70603de654 | |||
| db20c8be23 | |||
| 8f4fe740c7 | |||
| aae4282f1e | |||
| 18b698ffa8 | |||
| 051e6236c3 | |||
| 8691cefbbf | |||
| 6d2465b52d | |||
| c517ca0393 | |||
| ee1bdea97b | |||
| dc54d2f229 | |||
| b916f9bf33 | |||
| 733f0b2254 | |||
| ee02080a25 | |||
| 99d96482af | |||
| a5be592b7d | |||
| d92923a964 | |||
| 71598c0354 | |||
| 06a8a9b7d2 | |||
| 4d2691e636 | |||
| 6e17874cb9 | |||
| bf22dad8e8 | |||
| 731029cd87 | |||
| 6b5c5dc6e8 | |||
| e7ce6c282a | |||
| 67c58cbd28 | |||
| 7c89f7445f | |||
| 26adcfa997 | |||
| 1e41dddae2 | |||
| a9b219bc9f | |||
| 32dc8750df | |||
| ceeff58a7f | |||
| a55286953a | |||
| 9a0846ef68 | |||
| 06749ac464 | |||
| a6e7126bf1 | |||
| 4a906699ee | |||
| a2a7102859 |
@@ -33,6 +33,12 @@ slow-timeout = { period = "60s" }
|
|||||||
[profile.ci.junit]
|
[profile.ci.junit]
|
||||||
path = "junit.xml"
|
path = "junit.xml"
|
||||||
|
|
||||||
|
# Bound the whole test process, including Vulkan host initialization and teardown.
|
||||||
|
# Cold runs can warm multiple pipelines before the first image starts rendering.
|
||||||
|
[[profile.ci.overrides]]
|
||||||
|
filter = "package(=solidcraft-ui-egui) & test(=render_workspace::tests::progressive_image_controls_route_to_the_registered_host)"
|
||||||
|
slow-timeout = { period = "60s", terminate-after = 20 }
|
||||||
|
|
||||||
# The action matrix sample runs its designs in parallel workers (ACTION_MATRIX_JOBS, set to match in
|
# The action matrix sample runs its designs in parallel workers (ACTION_MATRIX_JOBS, set to match in
|
||||||
# ci.yml). Its workers reserve that many of nextest's threads, so the cores are not oversubscribed
|
# ci.yml). Its workers reserve that many of nextest's threads, so the cores are not oversubscribed
|
||||||
# (a pi-case hole that takes 8 s alone went past the matrix's 90 s action limit when they were),
|
# (a pi-case hole that takes 8 s alone went past the matrix's 90 s action limit when they were),
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
|
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
|
||||||
|
|
||||||
## Start here
|
## Start here
|
||||||
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Act when the code, docs and rules settle a question; ask only when it changes what users see. Local `plan/` notes are optional.
|
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Local `plan/` notes are optional.
|
||||||
|
|
||||||
## Hard rules (what enforces each)
|
## Hard rules (what enforces each)
|
||||||
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
|
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
|
||||||
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
|
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
|
||||||
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
|
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
|
||||||
- Never crash: no unwrap/expect/panic/unsafe in non-test code (unsafe allowlist in crash-safety), errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
|
- Never crash: no unwrap/expect/panic/unsafe in non-test code, errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
|
||||||
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
|
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
|
||||||
- New kernel cases go to solidcore: `cargo xtask lists`.
|
- New kernel cases go to solidcore: `cargo xtask lists`.
|
||||||
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
|
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
|
||||||
@@ -21,12 +21,14 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
|
|||||||
- A design-format change is ONE new file in `crates/doc/src/format/pending/`: `scripts/test-format-pending.sh`.
|
- A design-format change is ONE new file in `crates/doc/src/format/pending/`: `scripts/test-format-pending.sh`.
|
||||||
- A command has input and output schemas (will become required; `schema/unconverted.txt` only shrinks): `skills/solidcraft-commands/SKILL.md`.
|
- A command has input and output schemas (will become required; `schema/unconverted.txt` only shrinks): `skills/solidcraft-commands/SKILL.md`.
|
||||||
- New UI strings go in `assets/locales/pending/<slug>.json`, ATTRIBUTION rows in `attribution/pending/<slug>.md`: `cargo xtask lists`.
|
- New UI strings go in `assets/locales/pending/<slug>.json`, ATTRIBUTION rows in `attribution/pending/<slug>.md`: `cargo xtask lists`.
|
||||||
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line, and no second PR for an issue a lower-numbered one closes: `pr-checks.yml`.
|
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line: `pr-checks.yml`.
|
||||||
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
|
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
|
||||||
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR, never force-push `main`, no pushes just to catch up with `main`: `skills/solidcraft-contributing/SKILL.md`.
|
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR: `skills/solidcraft-contributing/SKILL.md`.
|
||||||
- A PR's title and first sentence make sense to someone who never saw your chat or the issue: `skills/solidcraft-contributing/SKILL.md`.
|
- A PR's title and first sentence make sense to someone who never saw your chat or the issue: `skills/solidcraft-contributing/SKILL.md`.
|
||||||
|
- Code, comments, commit messages and docs are in English, always: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
|
||||||
|
- Independent work runs in parallel, dependent work stays sequential: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
|
||||||
- Show observable changes with real screenshots or a recording, captured yourself.
|
- Show observable changes with real screenshots or a recording, captured yourself.
|
||||||
- Run `cargo xtask ci` before every commit, without switching branches while it runs. `cargo xtask skills` keeps this file and `skills/` honest.
|
- Run `cargo xtask ci` before every commit. `cargo xtask skills` keeps this file and `skills/` honest.
|
||||||
|
|
||||||
## Working on X: read the skill first
|
## Working on X: read the skill first
|
||||||
| Working on | Read |
|
| Working on | Read |
|
||||||
|
|||||||
+46
-4
@@ -82,7 +82,6 @@ Editable drawing code, native projects and shipped exports are collected under `
|
|||||||
| 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-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 |
|
| 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 |
|
| docs/book/src/generated/tool-preview.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/tool-preview.json`; gitignored) | MIT OR Apache-2.0 |
|
||||||
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
|
|
||||||
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
|
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
|
||||||
| assets/fonts/craft-ui-sc/CraftUI-SC.otf | Adobe / Google Inc. (Noto Sans SC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/SC Regular, glyph-subset for assets/locales/zh.json and renamed Craft UI SC (OFL reserved-name); licence text in assets/fonts/craft-ui-sc/OFL.txt; web UI Simplified Chinese | SIL OFL 1.1 |
|
| assets/fonts/craft-ui-sc/CraftUI-SC.otf | Adobe / Google Inc. (Noto Sans SC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/SC Regular, glyph-subset for assets/locales/zh.json and renamed Craft UI SC (OFL reserved-name); licence text in assets/fonts/craft-ui-sc/OFL.txt; web UI Simplified Chinese | SIL OFL 1.1 |
|
||||||
| assets/fonts/craft-ui-tc/CraftUI-TC.otf | Adobe / Google Inc. (Noto Sans TC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/TC Regular, glyph-subset for assets/locales/zh-TW.json and renamed Craft UI TC (OFL reserved-name); licence text in assets/fonts/craft-ui-tc/OFL.txt; web UI Traditional Chinese | SIL OFL 1.1 |
|
| assets/fonts/craft-ui-tc/CraftUI-TC.otf | Adobe / Google Inc. (Noto Sans TC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/TC Regular, glyph-subset for assets/locales/zh-TW.json and renamed Craft UI TC (OFL reserved-name); licence text in assets/fonts/craft-ui-tc/OFL.txt; web UI Traditional Chinese | SIL OFL 1.1 |
|
||||||
@@ -147,7 +146,6 @@ Editable drawing code, native projects and shipped exports are collected under `
|
|||||||
| assets/fonts/craft-ui-tc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-TC.otf | SIL OFL 1.1 |
|
| assets/fonts/craft-ui-tc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-TC.otf | SIL OFL 1.1 |
|
||||||
| assets/fonts/ibm-plex-mono/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
|
| assets/fonts/ibm-plex-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/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/io.tangencylabs.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/128x128/apps/io.tangencylabs.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/io.tangencylabs.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/io.tangencylabs.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/24x24/apps/io.tangencylabs.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/24x24/apps/io.tangencylabs.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 |
|
||||||
@@ -215,6 +213,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
|||||||
| assets/styles/ui/themes/one-dark.json | SolidCraft contributors | original SolidCraft theme file with colours adapted from the One Dark palette (Atom One Dark by GitHub Atom, https://github.com/atom/atom, MIT) | MIT OR Apache-2.0 |
|
| assets/styles/ui/themes/one-dark.json | SolidCraft contributors | original SolidCraft theme file with colours adapted from the One Dark palette (Atom One Dark by GitHub Atom, https://github.com/atom/atom, MIT) | MIT OR Apache-2.0 |
|
||||||
| assets/styles/ui/themes/tokyo-night.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Tokyo Night by enkia, https://github.com/enkia/tokyo-night-vscode-theme, MIT) | MIT OR Apache-2.0 |
|
| assets/styles/ui/themes/tokyo-night.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Tokyo Night by enkia, https://github.com/enkia/tokyo-night-vscode-theme, MIT) | MIT OR Apache-2.0 |
|
||||||
| assets/styles/ui/themes/solarized-light.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
|
| assets/styles/ui/themes/solarized-light.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
|
||||||
|
| assets/styles/ui/themes/solarized-dark.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
|
||||||
| assets/styles/ui/themes/nord.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Nord by Sven Greb and the Nord contributors, https://github.com/nordtheme/nord, MIT) | MIT OR Apache-2.0 |
|
| assets/styles/ui/themes/nord.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Nord by Sven Greb and the Nord contributors, https://github.com/nordtheme/nord, MIT) | MIT OR Apache-2.0 |
|
||||||
| assets/styles/ui/themes/high-contrast.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
| assets/styles/ui/themes/high-contrast.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||||
| assets/styles/ui/themes/graphite.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
| assets/styles/ui/themes/graphite.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||||
@@ -799,6 +798,50 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
|||||||
| docs/book/src/media/ui-tour/navigate.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (`--sample`, default dark theme, software renderer) orbiting, panning and zooming, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
| docs/book/src/media/ui-tour/navigate.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (`--sample`, default dark theme, software renderer) orbiting, panning and zooming, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
| assets/locales/pending/sketch-tool-hints.json | carlymx | Original sketch tool hints and engine error translations | MIT OR Apache-2.0 |
|
| assets/locales/pending/sketch-tool-hints.json | carlymx | Original sketch tool hints and engine error translations | MIT OR Apache-2.0 |
|
||||||
| assets/locales/pending/primitive-pick-field-tips.json | SolidCraft contributors | Original primitive pick-field tooltip translations | MIT OR Apache-2.0 |
|
| assets/locales/pending/primitive-pick-field-tips.json | SolidCraft contributors | Original primitive pick-field tooltip translations | MIT OR Apache-2.0 |
|
||||||
|
| assets/locales/pending/ribbon-panel-hover.json | SolidCraft contributors | Original ribbon panel menu preference labels and translations | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/box-insert-designer.gif | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (2.5× playback, 800 px wide) | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/box-insert-designer.mp4 | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (1280×720) | MIT OR Apache-2.0 |
|
||||||
|
| assets/locales/pending/agent-http-consent.json | SolidCraft contributors | Original endpoint-specific HTTP consent messages and translations | MIT OR Apache-2.0 |
|
||||||
|
| assets/locales/pending/ground-shadow-placement.json | SolidCraft contributors | Original ground-shadow placement preference messages and translations | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/assemblies/components.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making components from two original hinge-leaf bodies, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/assemblies/joint.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) creating a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/assemblies/drag.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dragging a jointed hinge leaf, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/assemblies/drive.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) driving a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/features/extrude.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) extruding a sketch's profile, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/features/shell.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) shelling a block, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/features/fillet.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) filleting four edges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/features/edit.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) editing a feature from the timeline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/features/hole.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) placing a hole, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/features/pattern.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) patterning a hole around an axis, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/parameters/dialog.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer) showing the Parameters dialog, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/files/menu.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`--sample`, default dark theme, software renderer) showing the File menu, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/getting-started/start.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing the start page with its built-in sample thumbnails, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/getting-started/sample.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) opening the built-in Sample Plate from the start page, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sheet-metal/base.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making a sheet metal base flange, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sheet-metal/flange.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) adding sheet metal edge flanges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sheet-metal/flat.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) showing a sheet metal flat pattern, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sketching/create.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) starting a sketch on the Top plane, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sketching/draw.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) drawing a rectangle and a circle with typed values, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sketching/dimension.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dimensioning a sketch until it is fully constrained, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sketching/parts.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing a fully constrained sketch, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sketching/offset.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) offsetting a sketch outline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| docs/book/src/media/sketching/trim.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) trimming a notch in a sketch, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||||
|
| assets/locales/ca.json | carlymx | Catalan translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||||
|
| assets/locales/pending/sketch-mirror-auto-line.json | SolidCraft contributors | Original Mirror preference label and translations | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-01-extrude5-edge-frame.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-04-preview-outer-l11.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-05-preview-inner-l8.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-06-result-outer-120.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-07-preview-360.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-08-cancelled.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-13-colliding-id.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-14-surface-l1.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/demo-15-surface-result.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| docs/screenshots/7452/revolve-selected-edge.mp4 | SamD98 | screen recording of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||||
|
| assets/locales/bg.json | Yasen-55 | Original translation | MIT OR Apache-2.0 |
|
||||||
|
| crates/sketch/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 crates/sketch/assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
|
||||||
|
| crates/sketch/assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||||
|
| assets/locales/pending/drag-to-zoom.json | SolidCraft contributors | Original drag-to-zoom labels and translations | MIT OR Apache-2.0 |
|
||||||
|
|
||||||
## Code adapted from sibling projects
|
## Code adapted from sibling projects
|
||||||
|
|
||||||
@@ -868,11 +911,10 @@ copied into SolidCraft.
|
|||||||
|
|
||||||
| Directory | Source | Licence | Changes |
|
| Directory | Source | Licence | Changes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| vendor/accesskit_macos | accesskit_macos 0.26.3, https://github.com/AccessKit/accesskit (The AccessKit contributors) | MIT OR Apache-2.0 | the subclassing adapter and its tests from 0.27.2: dropping the adapter keeps AppKit's key-value observers (vendor/README.md) |
|
|
||||||
|
|
||||||
## Vendored renderer compatibility fix
|
## Vendored renderer compatibility fix
|
||||||
|
|
||||||
`vendor/egui-wgpu` is egui-wgpu 0.36.2 from https://github.com/emilk/egui
|
`vendor/egui-wgpu` is egui-wgpu 0.37.0 from https://github.com/emilk/egui
|
||||||
(MIT OR Apache-2.0; both original licence texts are included). Rust sources and
|
(MIT OR Apache-2.0; both original licence texts are included). Rust sources and
|
||||||
its original `egui.wgsl` shader are retained; no images or other artwork are copied.
|
its original `egui.wgsl` shader are retained; no images or other artwork are copied.
|
||||||
SolidCraft's narrow patch selects a safe 1x MSAA path on the chosen OpenGL adapter
|
SolidCraft's narrow patch selects a safe 1x MSAA path on the chosen OpenGL adapter
|
||||||
|
|||||||
+1
-1
@@ -279,7 +279,7 @@ mean something else, it needs a format change. **Add one file, and edit nothing
|
|||||||
change's file, or any `v<N>/` folder: those are fixed once released.
|
change's file, or any `v<N>/` folder: those are fixed once released.
|
||||||
|
|
||||||
That's all. The build counts the files: while any are pending it writes the next format number,
|
That's all. The build counts the files: while any are pending it writes the next format number,
|
||||||
which no release reads, so a release refuses a design that has your change instead of losing it.
|
which no release reads, so a release warns about a design that has your change and keeps what it can't read of it.
|
||||||
A file also lists the pending changes it has (`format_pending`), so a later development build
|
A file also lists the pending changes it has (`format_pending`), so a later development build
|
||||||
runs only the steps that file is missing. Two pull requests that each add a change merge without
|
runs only the steps that file is missing. Two pull requests that each add a change merge without
|
||||||
a conflict. `crates/doc/src/format/pending/README.md` has an example.
|
a conflict. `crates/doc/src/format/pending/README.md` has an example.
|
||||||
|
|||||||
Generated
+173
-165
@@ -4,9 +4,9 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit"
|
name = "accesskit"
|
||||||
version = "0.24.1"
|
version = "0.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320"
|
checksum = "4e883d1d28c5fd5568c79ed2d2bd48d9f2fd3945c8974ed0f53690ceddb051be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"enumn",
|
"enumn",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -15,12 +15,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_atspi_common"
|
name = "accesskit_atspi_common"
|
||||||
version = "0.18.1"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954"
|
checksum = "24f5baf62e34a4a6c30e3d697e4782f821a7337049c44c3b40576a27dfbb580d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_consumer 0.36.0",
|
"accesskit_consumer",
|
||||||
"atspi-common",
|
"atspi-common",
|
||||||
"phf",
|
"phf",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -29,41 +29,37 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_consumer"
|
name = "accesskit_consumer"
|
||||||
version = "0.35.0"
|
version = "0.39.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10"
|
checksum = "f3da8f27636015191b2264ee1df681503170a632cafd832fdc1edfdd4e3a2fd3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_consumer"
|
name = "accesskit_ios"
|
||||||
version = "0.36.0"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60"
|
checksum = "a5280724673bbfa8b30a838c013500b719392989be3b97c5a9964062c252c232"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"hashbrown 0.16.1",
|
"accesskit_consumer",
|
||||||
]
|
"hashbrown 0.17.1",
|
||||||
|
"objc2 0.5.3",
|
||||||
[[package]]
|
"objc2-foundation 0.2.2",
|
||||||
name = "accesskit_consumer"
|
"objc2-ui-kit 0.2.2",
|
||||||
version = "0.38.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245"
|
|
||||||
dependencies = [
|
|
||||||
"accesskit",
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_macos"
|
name = "accesskit_macos"
|
||||||
version = "0.26.3"
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "123828ceeec755b05255e3976cb0852993202de98a0626c97dd8d34246991f84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_consumer 0.38.0",
|
"accesskit_consumer",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.1",
|
||||||
"objc2 0.5.3",
|
"objc2 0.5.3",
|
||||||
"objc2-app-kit 0.2.2",
|
"objc2-app-kit 0.2.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -71,9 +67,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_unix"
|
name = "accesskit_unix"
|
||||||
version = "0.21.1"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c"
|
checksum = "30d5192871228c54f1b0fb4a85cef000e11a3804f25d2165f14afa0daa67f3f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_atspi_common",
|
"accesskit_atspi_common",
|
||||||
@@ -89,13 +85,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_windows"
|
name = "accesskit_windows"
|
||||||
version = "0.32.1"
|
version = "0.35.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21"
|
checksum = "498668b90da9995302a0055aaecad4756932333da2ddef6baee1c00844c430fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_consumer 0.35.0",
|
"accesskit_consumer",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.1",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
@@ -103,11 +99,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "accesskit_winit"
|
name = "accesskit_winit"
|
||||||
version = "0.32.2"
|
version = "0.34.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313"
|
checksum = "cd4ab4437b520ff03088fbe48676a11e7d44a929535e64b31432c1073e48dbcc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
|
"accesskit_ios",
|
||||||
"accesskit_macos",
|
"accesskit_macos",
|
||||||
"accesskit_unix",
|
"accesskit_unix",
|
||||||
"accesskit_windows",
|
"accesskit_windows",
|
||||||
@@ -203,6 +200,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
|
"wl-clipboard-rs",
|
||||||
"x11rb",
|
"x11rb",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -687,42 +685,18 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "calloop"
|
|
||||||
version = "0.14.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.13.2",
|
|
||||||
"polling",
|
|
||||||
"rustix 1.1.5",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calloop-wayland-source"
|
name = "calloop-wayland-source"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"calloop 0.13.0",
|
"calloop",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "calloop-wayland-source"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
|
|
||||||
dependencies = [
|
|
||||||
"calloop 0.14.5",
|
|
||||||
"rustix 1.1.5",
|
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cbc"
|
name = "cbc"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1123,9 +1097,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecolor"
|
name = "ecolor"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34"
|
checksum = "31bcbef0d4263a63eb32c4834e5d711613035ce1333468e107d928923e21e6e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"emath",
|
"emath",
|
||||||
@@ -1134,9 +1108,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2"
|
checksum = "9c5642501a65ca657d05f00c643a5c14ff2346cb3818032bfe23d58e1f8e5f84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -1159,6 +1133,7 @@ dependencies = [
|
|||||||
"profiling",
|
"profiling",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"ron",
|
"ron",
|
||||||
|
"send_wrapper",
|
||||||
"serde",
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -1171,9 +1146,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui"
|
name = "egui"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba"
|
checksum = "bec9144f49b7a28b5f395d3147e325b4fe3e77ba686eac11d1bdf0a7da3f375f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"ahash",
|
"ahash",
|
||||||
@@ -1193,7 +1168,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-wgpu"
|
name = "egui-wgpu"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -1211,32 +1186,34 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-winit"
|
name = "egui-winit"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc"
|
checksum = "e4e449f4c4a2a60c7ccb4ba7fa4078417bfe1f9ddc03b5e05ce75748e33a70de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit_winit",
|
"accesskit_winit",
|
||||||
"arboard",
|
"arboard",
|
||||||
|
"block2 0.6.2",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"egui",
|
"egui",
|
||||||
"log",
|
"log",
|
||||||
"objc2 0.6.5",
|
"objc2 0.6.5",
|
||||||
|
"objc2-app-kit 0.3.2",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"objc2-ui-kit 0.3.2",
|
"objc2-ui-kit 0.3.2",
|
||||||
"profiling",
|
"profiling",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"serde",
|
"serde",
|
||||||
"smithay-clipboard",
|
|
||||||
"web-time",
|
"web-time",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_glow"
|
name = "egui_glow"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "606ed4167daf341e5753d703285c4c1d562324b2c07aa8f405cd7d066fe58f3b"
|
checksum = "9275ca5cc35a6b7c94e0e8cedf22542f66cb0cf45ed8ab2657c78ff49fec7268"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"egui",
|
"egui",
|
||||||
@@ -1254,9 +1231,9 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1"
|
checksum = "e73ce90f54750be8672c02be0743fe47530e6f251e066f0ee2869b59eb297af5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1330,9 +1307,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint"
|
name = "epaint"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3"
|
checksum = "97bac86ae3dde93fc3951e790951caea739ff39a6c89d909cdaf1d9278dbb1bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -1350,15 +1327,16 @@ dependencies = [
|
|||||||
"skrifa",
|
"skrifa",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"unicode-general-category",
|
"unicode-general-category",
|
||||||
|
"unicode-properties",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"vello_cpu",
|
"vello_cpu",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint_default_fonts"
|
name = "epaint_default_fonts"
|
||||||
version = "0.36.2"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13"
|
checksum = "cfc1cc505827f4c23eedc297975a0fd038268d41293f389dd8ff3b574aefab19"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
@@ -1444,6 +1422,12 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
@@ -1455,6 +1439,12 @@ dependencies = [
|
|||||||
"zlib-rs",
|
"zlib-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1651,12 +1641,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glifo"
|
name = "glifo"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a"
|
checksum = "282a26c1e23de04bdab3e34a21b6f877a479b96737792516b9b8b8f69b6661be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"foldhash",
|
"foldhash 0.2.0",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.17.1",
|
||||||
"log",
|
"log",
|
||||||
"peniko",
|
"peniko",
|
||||||
@@ -1826,6 +1816,15 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash 0.1.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
@@ -1834,7 +1833,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"foldhash",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1843,7 +1842,7 @@ version = "0.17.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foldhash",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2500,6 +2499,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -2956,6 +2964,16 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_pipe"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
@@ -3005,10 +3023,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "petgraph"
|
||||||
version = "0.13.1"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros",
|
"phf_macros",
|
||||||
"phf_shared",
|
"phf_shared",
|
||||||
@@ -3017,9 +3046,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_generator"
|
name = "phf_generator"
|
||||||
version = "0.13.1"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"phf_shared",
|
"phf_shared",
|
||||||
@@ -3027,9 +3056,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_macros"
|
name = "phf_macros"
|
||||||
version = "0.13.1"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
|
checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_generator",
|
"phf_generator",
|
||||||
"phf_shared",
|
"phf_shared",
|
||||||
@@ -3040,9 +3069,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.13.1"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
@@ -3762,6 +3791,12 @@ version = "1.0.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "send_wrapper"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.229"
|
version = "1.0.229"
|
||||||
@@ -3986,8 +4021,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"calloop 0.13.0",
|
"calloop",
|
||||||
"calloop-wayland-source 0.3.0",
|
"calloop-wayland-source",
|
||||||
"cursor-icon",
|
"cursor-icon",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -4004,44 +4039,6 @@ dependencies = [
|
|||||||
"xkeysym",
|
"xkeysym",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smithay-client-toolkit"
|
|
||||||
version = "0.20.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.13.2",
|
|
||||||
"calloop 0.14.5",
|
|
||||||
"calloop-wayland-source 0.4.1",
|
|
||||||
"cursor-icon",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"memmap2",
|
|
||||||
"rustix 1.1.5",
|
|
||||||
"thiserror 2.0.21",
|
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
|
||||||
"wayland-csd-frame",
|
|
||||||
"wayland-cursor",
|
|
||||||
"wayland-protocols",
|
|
||||||
"wayland-protocols-experimental",
|
|
||||||
"wayland-protocols-misc",
|
|
||||||
"wayland-protocols-wlr",
|
|
||||||
"wayland-scanner",
|
|
||||||
"xkeysym",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smithay-clipboard"
|
|
||||||
version = "0.7.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"smithay-client-toolkit 0.20.0",
|
|
||||||
"wayland-backend",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smol_str"
|
name = "smol_str"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -4054,15 +4051,16 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "solidcore"
|
name = "solidcore"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "git+https://github.com/tangency-labs/solidcore?rev=8ddce901671f1cebd9214a31d21895d725e886c8#8ddce901671f1cebd9214a31d21895d725e886c8"
|
source = "git+https://github.com/tangency-labs/solidcore?rev=a445bdaac60a980b0e9d1dba3ee28eb7ada44f50#a445bdaac60a980b0e9d1dba3ee28eb7ada44f50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"thiserror 2.0.21",
|
"thiserror 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solidcore-record"
|
name = "solidcore-record"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/tangency-labs/solidcore?rev=f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f#f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f"
|
source = "git+https://github.com/tangency-labs/solidcore?rev=a445bdaac60a980b0e9d1dba3ee28eb7ada44f50#a445bdaac60a980b0e9d1dba3ee28eb7ada44f50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4286,9 +4284,11 @@ dependencies = [
|
|||||||
name = "solidcraft-render"
|
name = "solidcraft-render"
|
||||||
version = "0.40.0-rc.2"
|
version = "0.40.0-rc.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
"image",
|
"image",
|
||||||
"moxcms",
|
"moxcms",
|
||||||
"png",
|
"png",
|
||||||
|
"robust",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
@@ -4663,7 +4663,6 @@ version = "0.1.44"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
@@ -4689,6 +4688,17 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree_magic_mini"
|
||||||
|
version = "3.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"nom",
|
||||||
|
"petgraph",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
@@ -4763,6 +4773,12 @@ version = "1.0.26"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "993ad287eb20e8ba755312801cfe1413e0f25d604075863acaddca10b04c1833"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.13.3"
|
version = "1.13.3"
|
||||||
@@ -4846,9 +4862,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vello_common"
|
name = "vello_common"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2"
|
checksum = "bbb2141a2bca6e6d598e471fd4d1d7eed8e020aad6a28187edda07f091a325dd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"fearless_simd",
|
"fearless_simd",
|
||||||
@@ -4861,9 +4877,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vello_cpu"
|
name = "vello_cpu"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea"
|
checksum = "ace506ca414548966fcf3c283be94eb1cad8254de488791ee698bf438b0890a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"glifo",
|
"glifo",
|
||||||
@@ -5038,32 +5054,6 @@ dependencies = [
|
|||||||
"wayland-scanner",
|
"wayland-scanner",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wayland-protocols-experimental"
|
|
||||||
version = "20250721.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.13.2",
|
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
|
||||||
"wayland-protocols",
|
|
||||||
"wayland-scanner",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wayland-protocols-misc"
|
|
||||||
version = "0.3.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.13.2",
|
|
||||||
"wayland-backend",
|
|
||||||
"wayland-client",
|
|
||||||
"wayland-protocols",
|
|
||||||
"wayland-scanner",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-protocols-plasma"
|
name = "wayland-protocols-plasma"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@@ -5641,7 +5631,7 @@ dependencies = [
|
|||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"calloop 0.13.0",
|
"calloop",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.9.4",
|
||||||
@@ -5662,7 +5652,7 @@ dependencies = [
|
|||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall 0.4.1",
|
"redox_syscall 0.4.1",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"smithay-client-toolkit 0.19.2",
|
"smithay-client-toolkit",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
@@ -5705,6 +5695,24 @@ version = "0.57.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wl-clipboard-rs"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "959c7ac3b8afde864f7a7448ac16f29b2b83350b7eccbf77fb781c3999aa6848"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"os_pipe",
|
||||||
|
"rustix 1.1.5",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
"tree_magic_mini",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-wlr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
|||||||
+7
-9
@@ -36,10 +36,10 @@ solidcraft-3dx-client = { path = "crates/threedx-client" }
|
|||||||
# move the rev on its own commit. For a
|
# move the rev on its own commit. For a
|
||||||
# local checkout, add `[patch."https://github.com/tangency-labs/solidcore"] solidcore = { path =
|
# local checkout, add `[patch."https://github.com/tangency-labs/solidcore"] solidcore = { path =
|
||||||
# "../solidcore/crates/solidcore" }` to a `.cargo/config.toml` outside the repository.
|
# "../solidcore/crates/solidcore" }` to a `.cargo/config.toml` outside the repository.
|
||||||
solidcore = { git = "https://github.com/tangency-labs/solidcore", rev = "8ddce901671f1cebd9214a31d21895d725e886c8" }
|
solidcore = { git = "https://github.com/tangency-labs/solidcore", rev = "a445bdaac60a980b0e9d1dba3ee28eb7ada44f50" }
|
||||||
# Geometry as data (solidcore's layers §1, "Record"): the B-rep record, its structural validation
|
# Geometry as data (solidcore's layers §1, "Record"): the B-rep record, its structural validation
|
||||||
# and the native body file. Pinned like solidcore; for a local checkout, patch it as above.
|
# and the native body file. Pinned like solidcore; for a local checkout, patch it as above.
|
||||||
solidcore-record = { git = "https://github.com/tangency-labs/solidcore", rev = "f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f" }
|
solidcore-record = { git = "https://github.com/tangency-labs/solidcore", rev = "a445bdaac60a980b0e9d1dba3ee28eb7ada44f50" }
|
||||||
# test tooling: a dev-dependency only (xtask/src/layers.rs)
|
# test tooling: a dev-dependency only (xtask/src/layers.rs)
|
||||||
solidcraft-testkit = { path = "crates/testkit" }
|
solidcraft-testkit = { path = "crates/testkit" }
|
||||||
|
|
||||||
@@ -103,11 +103,12 @@ tiny-skia = { version = "0.12", default-features = false, features = ["std", "si
|
|||||||
toml = "1"
|
toml = "1"
|
||||||
|
|
||||||
# ui
|
# ui
|
||||||
egui = "0.36"
|
# default_fonts: the built-in Hack is the UI's symbol fallback (#2955); 0.37 no longer turns it on.
|
||||||
eframe = { version = "0.36", default-features = false, features = ["persistence"] }
|
egui = { version = "0.37", features = ["default_fonts"] }
|
||||||
|
eframe = { version = "0.37", default-features = false, features = ["persistence"] }
|
||||||
# Instant/SystemTime that also work in the browser (std panics on wasm32-unknown-unknown).
|
# Instant/SystemTime that also work in the browser (std panics on wasm32-unknown-unknown).
|
||||||
web-time = "1"
|
web-time = "1"
|
||||||
egui-wgpu = "0.36"
|
egui-wgpu = "0.37"
|
||||||
# the wgpu egui-wgpu uses, for compute (crates/gpu); backends come from the apps' features
|
# the wgpu egui-wgpu uses, for compute (crates/gpu); backends come from the apps' features
|
||||||
wgpu = { version = "30", default-features = false, features = ["std", "wgsl"] }
|
wgpu = { version = "30", default-features = false, features = ["std", "wgsl"] }
|
||||||
naga = { version = "30", default-features = false }
|
naga = { version = "30", default-features = false }
|
||||||
@@ -162,12 +163,9 @@ debug = "line-tables-only"
|
|||||||
egui-wgpu = { path = "vendor/egui-wgpu" }
|
egui-wgpu = { path = "vendor/egui-wgpu" }
|
||||||
|
|
||||||
# Deliver trackpad pinch and rotation gestures on Wayland (zwp_pointer_gestures_v1), which
|
# Deliver trackpad pinch and rotation gestures on Wayland (zwp_pointer_gestures_v1), which
|
||||||
# winit 0.30 never binds and eframe 0.36 still requires winit 0.30.
|
# winit 0.30 never binds and eframe 0.37 still requires winit 0.30.
|
||||||
winit = { path = "vendor/winit" }
|
winit = { path = "vendor/winit" }
|
||||||
|
|
||||||
# macOS: dropping the accessibility adapter leaves AppKit's key-value observers intact (#7535).
|
|
||||||
accesskit_macos = { path = "vendor/accesskit_macos" }
|
|
||||||
|
|
||||||
# Mutation testing (cargo-mutants, docs/mutation-testing.md): the ci profile with incremental
|
# Mutation testing (cargo-mutants, docs/mutation-testing.md): the ci profile with incremental
|
||||||
# builds, since every mutant rebuilds the crates above the one it changes.
|
# builds, since every mutant rebuilds the crates above the one it changes.
|
||||||
[profile.mutants]
|
[profile.mutants]
|
||||||
|
|||||||
@@ -225,6 +225,12 @@ completed drawings publish atomically. The browser still exports synchronously.
|
|||||||
|
|
||||||
## Render foundation
|
## Render foundation
|
||||||
|
|
||||||
|
- Wood surface images (#1345): native CPU and GPU opaque rendering share physical
|
||||||
|
repeat frames and filtered mip chains. Imported maps retain exact bytes and
|
||||||
|
declared provenance in the design, reopen offline, and expose grain-angle
|
||||||
|
controls. Scalar face appearances suppress body maps. Planar projection and
|
||||||
|
CPU encoding/optical limits remain explicit; see [portable surfaces](docs/render-portable-surfaces-and-stages.md).
|
||||||
|
|
||||||
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
|
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
|
||||||
|
|
||||||
- Bounded linear film accumulation (#1696) supplies caller-admitted mean/variance storage and complete-pass views; actual progressive consumers remain separate. See [the film contract](docs/render-film.md).
|
- Bounded linear film accumulation (#1696) supplies caller-admitted mean/variance storage and complete-pass views; actual progressive consumers remain separate. See [the film contract](docs/render-film.md).
|
||||||
|
|||||||
+275
-116
@@ -1,18 +1,17 @@
|
|||||||
Third-party software in SolidCraft
|
Third-party software in SolidCraft
|
||||||
|
|
||||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 537 crates below.
|
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 540 crates below.
|
||||||
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
|
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
|
||||||
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
|
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
|
||||||
|
|
||||||
accesskit 0.24.1 (MIT OR Apache-2.0)
|
accesskit 0.25.2 (MIT OR Apache-2.0)
|
||||||
accesskit_atspi_common 0.18.1 (MIT OR Apache-2.0)
|
accesskit_atspi_common 0.21.1 (MIT OR Apache-2.0)
|
||||||
accesskit_consumer 0.35.0 (MIT OR Apache-2.0)
|
accesskit_consumer 0.39.2 (MIT OR Apache-2.0)
|
||||||
accesskit_consumer 0.36.0 (MIT OR Apache-2.0)
|
accesskit_ios 0.2.2 (MIT OR Apache-2.0)
|
||||||
accesskit_consumer 0.38.0 (MIT OR Apache-2.0)
|
accesskit_macos 0.27.2 (MIT OR Apache-2.0)
|
||||||
accesskit_macos 0.26.3 (MIT OR Apache-2.0)
|
accesskit_unix 0.24.1 (MIT OR Apache-2.0)
|
||||||
accesskit_unix 0.21.1 (MIT OR Apache-2.0)
|
accesskit_windows 0.35.2 (MIT OR Apache-2.0)
|
||||||
accesskit_windows 0.32.1 (MIT OR Apache-2.0)
|
accesskit_winit 0.34.2 (Apache-2.0)
|
||||||
accesskit_winit 0.32.2 (Apache-2.0)
|
|
||||||
adler2 2.0.1 (0BSD OR MIT OR Apache-2.0)
|
adler2 2.0.1 (0BSD OR MIT OR Apache-2.0)
|
||||||
aes 0.8.4 (MIT OR Apache-2.0)
|
aes 0.8.4 (MIT OR Apache-2.0)
|
||||||
ahash 0.8.12 (MIT OR Apache-2.0)
|
ahash 0.8.12 (MIT OR Apache-2.0)
|
||||||
@@ -65,9 +64,7 @@ cadmpeg-core 0.6.7 (Apache-2.0)
|
|||||||
cadmpeg-ir 0.6.7 (Apache-2.0)
|
cadmpeg-ir 0.6.7 (Apache-2.0)
|
||||||
cadmpeg-registry 0.6.7 (Apache-2.0)
|
cadmpeg-registry 0.6.7 (Apache-2.0)
|
||||||
calloop 0.13.0 (MIT)
|
calloop 0.13.0 (MIT)
|
||||||
calloop 0.14.5 (MIT)
|
|
||||||
calloop-wayland-source 0.3.0 (MIT)
|
calloop-wayland-source 0.3.0 (MIT)
|
||||||
calloop-wayland-source 0.4.1 (MIT)
|
|
||||||
cbc 0.1.2 (MIT OR Apache-2.0)
|
cbc 0.1.2 (MIT OR Apache-2.0)
|
||||||
cfg-if 1.0.5 (MIT OR Apache-2.0)
|
cfg-if 1.0.5 (MIT OR Apache-2.0)
|
||||||
cgl 0.3.2 (MIT / Apache-2.0)
|
cgl 0.3.2 (MIT / Apache-2.0)
|
||||||
@@ -109,21 +106,21 @@ document-features 0.2.12 (MIT OR Apache-2.0)
|
|||||||
downcast-rs 1.2.1 (MIT/Apache-2.0)
|
downcast-rs 1.2.1 (MIT/Apache-2.0)
|
||||||
dpi 0.1.2 (Apache-2.0 AND MIT)
|
dpi 0.1.2 (Apache-2.0 AND MIT)
|
||||||
dyn-clone 1.0.20 (MIT OR Apache-2.0)
|
dyn-clone 1.0.20 (MIT OR Apache-2.0)
|
||||||
ecolor 0.36.2 (MIT OR Apache-2.0)
|
ecolor 0.37.0 (MIT OR Apache-2.0)
|
||||||
eframe 0.36.2 (MIT OR Apache-2.0)
|
eframe 0.37.0 (MIT OR Apache-2.0)
|
||||||
egui 0.36.2 (MIT OR Apache-2.0)
|
egui 0.37.0 (MIT OR Apache-2.0)
|
||||||
egui-wgpu 0.36.2 (MIT OR Apache-2.0)
|
egui-wgpu 0.37.0 (MIT OR Apache-2.0)
|
||||||
egui-winit 0.36.2 (MIT OR Apache-2.0)
|
egui-winit 0.37.0 (MIT OR Apache-2.0)
|
||||||
egui_glow 0.36.2 (MIT OR Apache-2.0)
|
egui_glow 0.37.0 (MIT OR Apache-2.0)
|
||||||
either 1.19.0 (MIT OR Apache-2.0)
|
either 1.19.0 (MIT OR Apache-2.0)
|
||||||
emath 0.36.2 (MIT OR Apache-2.0)
|
emath 0.37.0 (MIT OR Apache-2.0)
|
||||||
encoding_rs 0.8.42 ((Apache-2.0 OR MIT) AND BSD-3-Clause)
|
encoding_rs 0.8.42 ((Apache-2.0 OR MIT) AND BSD-3-Clause)
|
||||||
endi 1.1.1 (MIT)
|
endi 1.1.1 (MIT)
|
||||||
enumflags2 0.7.12 (MIT OR Apache-2.0)
|
enumflags2 0.7.12 (MIT OR Apache-2.0)
|
||||||
enumflags2_derive 0.7.12 (MIT OR Apache-2.0)
|
enumflags2_derive 0.7.12 (MIT OR Apache-2.0)
|
||||||
enumn 0.1.14 (MIT OR Apache-2.0)
|
enumn 0.1.14 (MIT OR Apache-2.0)
|
||||||
epaint 0.36.2 (MIT OR Apache-2.0)
|
epaint 0.37.0 (MIT OR Apache-2.0)
|
||||||
epaint_default_fonts 0.36.2 ((MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0)
|
epaint_default_fonts 0.37.0 ((MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0)
|
||||||
equivalent 1.0.2 (Apache-2.0 OR MIT)
|
equivalent 1.0.2 (Apache-2.0 OR MIT)
|
||||||
errno 0.3.14 (MIT OR Apache-2.0)
|
errno 0.3.14 (MIT OR Apache-2.0)
|
||||||
error-code 3.4.0 (BSL-1.0)
|
error-code 3.4.0 (BSL-1.0)
|
||||||
@@ -134,7 +131,9 @@ fastrand 2.5.0 (Apache-2.0 OR MIT)
|
|||||||
fax 0.2.7 (MIT)
|
fax 0.2.7 (MIT)
|
||||||
fdeflate 0.3.7 (MIT OR Apache-2.0)
|
fdeflate 0.3.7 (MIT OR Apache-2.0)
|
||||||
fearless_simd 0.4.1 (Apache-2.0 OR MIT)
|
fearless_simd 0.4.1 (Apache-2.0 OR MIT)
|
||||||
|
fixedbitset 0.5.7 (MIT OR Apache-2.0)
|
||||||
flate2 1.1.10 (MIT OR Apache-2.0)
|
flate2 1.1.10 (MIT OR Apache-2.0)
|
||||||
|
foldhash 0.1.5 (Zlib)
|
||||||
foldhash 0.2.0 (Zlib)
|
foldhash 0.2.0 (Zlib)
|
||||||
font-types 0.12.6 (MIT OR Apache-2.0)
|
font-types 0.12.6 (MIT OR Apache-2.0)
|
||||||
foreign-types 0.5.0 (MIT/Apache-2.0)
|
foreign-types 0.5.0 (MIT/Apache-2.0)
|
||||||
@@ -154,7 +153,7 @@ getrandom 0.2.17 (MIT OR Apache-2.0)
|
|||||||
getrandom 0.3.4 (MIT OR Apache-2.0)
|
getrandom 0.3.4 (MIT OR Apache-2.0)
|
||||||
getrandom 0.4.3 (MIT OR Apache-2.0)
|
getrandom 0.4.3 (MIT OR Apache-2.0)
|
||||||
gif 0.14.2 (MIT OR Apache-2.0)
|
gif 0.14.2 (MIT OR Apache-2.0)
|
||||||
glifo 0.2.0 (Apache-2.0 OR MIT)
|
glifo 0.3.0 (Apache-2.0 OR MIT)
|
||||||
glow 0.17.0 (MIT OR Apache-2.0 OR Zlib)
|
glow 0.17.0 (MIT OR Apache-2.0 OR Zlib)
|
||||||
gltf 1.4.1 (MIT OR Apache-2.0)
|
gltf 1.4.1 (MIT OR Apache-2.0)
|
||||||
gltf-derive 1.4.1 (MIT OR Apache-2.0)
|
gltf-derive 1.4.1 (MIT OR Apache-2.0)
|
||||||
@@ -168,6 +167,7 @@ gpu-allocator 0.28.0 (MIT OR Apache-2.0)
|
|||||||
guillotiere 0.7.0 (MIT/Apache-2.0)
|
guillotiere 0.7.0 (MIT/Apache-2.0)
|
||||||
half 2.7.1 (MIT OR Apache-2.0)
|
half 2.7.1 (MIT OR Apache-2.0)
|
||||||
harfrust 0.12.0 (MIT)
|
harfrust 0.12.0 (MIT)
|
||||||
|
hashbrown 0.15.5 (MIT OR Apache-2.0)
|
||||||
hashbrown 0.16.1 (MIT OR Apache-2.0)
|
hashbrown 0.16.1 (MIT OR Apache-2.0)
|
||||||
hashbrown 0.17.1 (MIT OR Apache-2.0)
|
hashbrown 0.17.1 (MIT OR Apache-2.0)
|
||||||
hermit-abi 0.5.3 (MIT OR Apache-2.0)
|
hermit-abi 0.5.3 (MIT OR Apache-2.0)
|
||||||
@@ -234,6 +234,7 @@ ndk-sys 0.6.0+11769913 (MIT OR Apache-2.0)
|
|||||||
nix 0.29.0 (MIT)
|
nix 0.29.0 (MIT)
|
||||||
nix 0.31.3 (MIT)
|
nix 0.31.3 (MIT)
|
||||||
nohash-hasher 0.2.0 (Apache-2.0 OR MIT)
|
nohash-hasher 0.2.0 (Apache-2.0 OR MIT)
|
||||||
|
nom 8.0.0 (MIT)
|
||||||
num 0.4.3 (MIT OR Apache-2.0)
|
num 0.4.3 (MIT OR Apache-2.0)
|
||||||
num-bigint 0.4.8 (MIT OR Apache-2.0)
|
num-bigint 0.4.8 (MIT OR Apache-2.0)
|
||||||
num-complex 0.4.6 (MIT OR Apache-2.0)
|
num-complex 0.4.6 (MIT OR Apache-2.0)
|
||||||
@@ -275,15 +276,17 @@ orbclient 0.3.55 (MIT)
|
|||||||
ordered-float 2.10.1 (MIT)
|
ordered-float 2.10.1 (MIT)
|
||||||
ordered-float 5.5.0 (MIT)
|
ordered-float 5.5.0 (MIT)
|
||||||
ordered-stream 0.2.0 (MIT OR Apache-2.0)
|
ordered-stream 0.2.0 (MIT OR Apache-2.0)
|
||||||
|
os_pipe 1.2.3 (MIT)
|
||||||
parking 2.2.1 (Apache-2.0 OR MIT)
|
parking 2.2.1 (Apache-2.0 OR MIT)
|
||||||
parking_lot 0.12.5 (MIT OR Apache-2.0)
|
parking_lot 0.12.5 (MIT OR Apache-2.0)
|
||||||
parking_lot_core 0.9.12 (MIT OR Apache-2.0)
|
parking_lot_core 0.9.12 (MIT OR Apache-2.0)
|
||||||
peniko 0.6.1 (Apache-2.0 OR MIT)
|
peniko 0.6.1 (Apache-2.0 OR MIT)
|
||||||
percent-encoding 2.3.2 (MIT OR Apache-2.0)
|
percent-encoding 2.3.2 (MIT OR Apache-2.0)
|
||||||
phf 0.13.1 (MIT)
|
petgraph 0.8.3 (MIT OR Apache-2.0)
|
||||||
phf_generator 0.13.1 (MIT)
|
phf 0.14.0 (MIT)
|
||||||
phf_macros 0.13.1 (MIT)
|
phf_generator 0.14.0 (MIT)
|
||||||
phf_shared 0.13.1 (MIT)
|
phf_macros 0.14.0 (MIT)
|
||||||
|
phf_shared 0.14.0 (MIT)
|
||||||
pin-project 1.1.13 (Apache-2.0 OR MIT)
|
pin-project 1.1.13 (Apache-2.0 OR MIT)
|
||||||
pin-project-internal 1.1.13 (Apache-2.0 OR MIT)
|
pin-project-internal 1.1.13 (Apache-2.0 OR MIT)
|
||||||
pin-project-lite 0.2.17 (Apache-2.0 OR MIT)
|
pin-project-lite 0.2.17 (Apache-2.0 OR MIT)
|
||||||
@@ -352,6 +355,7 @@ security-framework 3.7.0 (MIT OR Apache-2.0)
|
|||||||
security-framework-sys 2.17.0 (MIT OR Apache-2.0)
|
security-framework-sys 2.17.0 (MIT OR Apache-2.0)
|
||||||
self_cell 1.3.0 (Apache-2.0 OR GPL-2.0-only)
|
self_cell 1.3.0 (Apache-2.0 OR GPL-2.0-only)
|
||||||
semver 1.0.28 (MIT OR Apache-2.0)
|
semver 1.0.28 (MIT OR Apache-2.0)
|
||||||
|
send_wrapper 0.6.0 (MIT/Apache-2.0)
|
||||||
serde 1.0.229 (MIT OR Apache-2.0)
|
serde 1.0.229 (MIT OR Apache-2.0)
|
||||||
serde-value 0.7.0 (MIT)
|
serde-value 0.7.0 (MIT)
|
||||||
serde_core 1.0.229 (MIT OR Apache-2.0)
|
serde_core 1.0.229 (MIT OR Apache-2.0)
|
||||||
@@ -375,8 +379,6 @@ slab 0.4.12 (MIT)
|
|||||||
slotmap 1.1.1 (Zlib)
|
slotmap 1.1.1 (Zlib)
|
||||||
smallvec 1.16.2 (MIT OR Apache-2.0)
|
smallvec 1.16.2 (MIT OR Apache-2.0)
|
||||||
smithay-client-toolkit 0.19.2 (MIT)
|
smithay-client-toolkit 0.19.2 (MIT)
|
||||||
smithay-client-toolkit 0.20.0 (MIT)
|
|
||||||
smithay-clipboard 0.7.3 (MIT)
|
|
||||||
smol_str 0.2.2 (MIT OR Apache-2.0)
|
smol_str 0.2.2 (MIT OR Apache-2.0)
|
||||||
solidcore 1.0.1 (Apache-2.0)
|
solidcore 1.0.1 (Apache-2.0)
|
||||||
solidcore-record 0.1.0 (Apache-2.0)
|
solidcore-record 0.1.0 (Apache-2.0)
|
||||||
@@ -409,6 +411,7 @@ toml_writer 1.1.3+spec-1.1.0 (MIT OR Apache-2.0)
|
|||||||
tracing 0.1.44 (MIT)
|
tracing 0.1.44 (MIT)
|
||||||
tracing-attributes 0.1.31 (MIT)
|
tracing-attributes 0.1.31 (MIT)
|
||||||
tracing-core 0.1.36 (MIT)
|
tracing-core 0.1.36 (MIT)
|
||||||
|
tree_magic_mini 3.2.2 (MIT)
|
||||||
ttf-parser 0.25.1 (MIT OR Apache-2.0)
|
ttf-parser 0.25.1 (MIT OR Apache-2.0)
|
||||||
twox-hash 2.1.5 (MIT)
|
twox-hash 2.1.5 (MIT)
|
||||||
type-map 0.5.1 (MIT/Apache-2.0)
|
type-map 0.5.1 (MIT/Apache-2.0)
|
||||||
@@ -419,6 +422,7 @@ uds_windows 1.2.1 (MIT)
|
|||||||
unicode-bidi 0.3.18 (MIT OR Apache-2.0)
|
unicode-bidi 0.3.18 (MIT OR Apache-2.0)
|
||||||
unicode-general-category 1.1.0 (Apache-2.0)
|
unicode-general-category 1.1.0 (Apache-2.0)
|
||||||
unicode-ident 1.0.26 ((MIT OR Apache-2.0) AND Unicode-3.0)
|
unicode-ident 1.0.26 ((MIT OR Apache-2.0) AND Unicode-3.0)
|
||||||
|
unicode-properties 0.1.5 ((MIT OR Apache-2.0) AND Unicode-3.0)
|
||||||
unicode-segmentation 1.13.3 (MIT OR Apache-2.0)
|
unicode-segmentation 1.13.3 (MIT OR Apache-2.0)
|
||||||
unicode-width 0.2.2 (MIT OR Apache-2.0)
|
unicode-width 0.2.2 (MIT OR Apache-2.0)
|
||||||
untrusted 0.9.0 (ISC)
|
untrusted 0.9.0 (ISC)
|
||||||
@@ -428,8 +432,8 @@ url 2.5.8 (MIT OR Apache-2.0)
|
|||||||
utf8-zero 0.8.1 (MIT OR Apache-2.0)
|
utf8-zero 0.8.1 (MIT OR Apache-2.0)
|
||||||
utf8_iter 1.0.4 (Apache-2.0 OR MIT)
|
utf8_iter 1.0.4 (Apache-2.0 OR MIT)
|
||||||
uuid 1.27.0 (Apache-2.0 OR MIT)
|
uuid 1.27.0 (Apache-2.0 OR MIT)
|
||||||
vello_common 0.1.0 (Apache-2.0 OR MIT)
|
vello_common 0.2.0 (Apache-2.0 OR MIT)
|
||||||
vello_cpu 0.1.0 (Apache-2.0 OR MIT)
|
vello_cpu 0.2.0 (Apache-2.0 OR MIT)
|
||||||
wasi 0.11.1+wasi-snapshot-preview1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
wasi 0.11.1+wasi-snapshot-preview1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
||||||
wasip2 1.0.4+wasi-0.2.12 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
wasip2 1.0.4+wasi-0.2.12 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
||||||
wasm-bindgen 0.2.129 (MIT OR Apache-2.0)
|
wasm-bindgen 0.2.129 (MIT OR Apache-2.0)
|
||||||
@@ -442,8 +446,6 @@ wayland-client 0.31.15 (MIT)
|
|||||||
wayland-csd-frame 0.3.0 (MIT)
|
wayland-csd-frame 0.3.0 (MIT)
|
||||||
wayland-cursor 0.31.14 (MIT)
|
wayland-cursor 0.31.14 (MIT)
|
||||||
wayland-protocols 0.32.13 (MIT)
|
wayland-protocols 0.32.13 (MIT)
|
||||||
wayland-protocols-experimental 20250721.0.1 (MIT)
|
|
||||||
wayland-protocols-misc 0.3.12 (MIT)
|
|
||||||
wayland-protocols-plasma 0.3.12 (MIT)
|
wayland-protocols-plasma 0.3.12 (MIT)
|
||||||
wayland-protocols-wlr 0.3.12 (MIT)
|
wayland-protocols-wlr 0.3.12 (MIT)
|
||||||
wayland-scanner 0.31.11 (MIT)
|
wayland-scanner 0.31.11 (MIT)
|
||||||
@@ -500,6 +502,7 @@ winit 0.30.13 (Apache-2.0)
|
|||||||
winnow 1.0.4 (MIT)
|
winnow 1.0.4 (MIT)
|
||||||
winreg 0.56.0 (MIT)
|
winreg 0.56.0 (MIT)
|
||||||
wit-bindgen 0.57.1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
wit-bindgen 0.57.1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
||||||
|
wl-clipboard-rs 0.9.5 (MIT OR Apache-2.0)
|
||||||
writeable 0.6.4 (Unicode-3.0)
|
writeable 0.6.4 (Unicode-3.0)
|
||||||
x11-dl 2.21.0 (MIT)
|
x11-dl 2.21.0 (MIT)
|
||||||
x11rb 0.13.2 (MIT OR Apache-2.0)
|
x11rb 0.13.2 (MIT OR Apache-2.0)
|
||||||
@@ -570,13 +573,14 @@ num_enum 0.7.6, num_enum_derive 0.7.6, once_cell 1.21.4, ordered-stream 0.2.0, p
|
|||||||
pin-project 1.1.13, pin-project-internal 1.1.13, pin-project-lite 0.2.17, piper 0.2.5, polling
|
pin-project 1.1.13, pin-project-internal 1.1.13, pin-project-lite 0.2.17, piper 0.2.5, polling
|
||||||
3.11.0, portable-atomic 1.15.0, portable-atomic-util 0.2.8, proc-macro-crate 3.5.0, proc-macro2
|
3.11.0, portable-atomic 1.15.0, portable-atomic-util 0.2.8, proc-macro-crate 3.5.0, proc-macro2
|
||||||
1.0.107, quote 1.0.47, ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 1.1.0, rustc-hash 2.1.3,
|
1.0.107, quote 1.0.47, ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 1.1.0, rustc-hash 2.1.3,
|
||||||
rustix 0.38.44, rustix 1.1.5, rustversion 1.0.23, semver 1.0.28, serde 1.0.229, serde_core 1.0.229,
|
rustix 0.38.44, rustix 1.1.5, rustversion 1.0.23, semver 1.0.28, send_wrapper 0.6.0, serde 1.0.229,
|
||||||
serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json 1.0.151, serde_path_to_error 0.1.20,
|
serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json 1.0.151,
|
||||||
serde_repr 0.1.21, simd_cesu8 1.2.0, smol_str 0.2.2, syn 2.0.119, syn 3.0.6, thiserror 1.0.69,
|
serde_path_to_error 0.1.20, serde_repr 0.1.21, simd_cesu8 1.2.0, smol_str 0.2.2, syn 2.0.119, syn
|
||||||
thiserror 2.0.21, thiserror-impl 1.0.69, thiserror-impl 2.0.21, typed-path 0.13.0, typeid 1.0.3,
|
3.0.6, thiserror 1.0.69, thiserror 2.0.21, thiserror-impl 1.0.69, thiserror-impl 2.0.21, typed-path
|
||||||
unicode-ident 1.0.26, utf8-zero 0.8.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12,
|
0.13.0, typeid 1.0.3, unicode-ident 1.0.26, utf8-zero 0.8.1, wasi 0.11.1+wasi-snapshot-preview1,
|
||||||
wit-bindgen 0.57.1, x11-dl 2.21.0, xdg-home 1.3.0, zbus-lockstep 0.5.2, zbus-lockstep-macros 0.5.2,
|
wasip2 1.0.4+wasi-0.2.12, wit-bindgen 0.57.1, x11-dl 2.21.0, xdg-home 1.3.0, zbus-lockstep 0.5.2,
|
||||||
zbus_names 3.0.0, zmij 1.0.23, zvariant_utils 2.1.0, zvariant_utils 4.2.0
|
zbus-lockstep-macros 0.5.2, zbus_names 3.0.0, zmij 1.0.23, zvariant_utils 2.1.0, zvariant_utils
|
||||||
|
4.2.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
Permission is hereby granted, free of charge, to any
|
||||||
@@ -1086,25 +1090,26 @@ ahash 0.8.12, arrayvec 0.7.8, async-channel 2.5.0, async-executor 1.14.0, async-
|
|||||||
0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, core_detect
|
0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, core_detect
|
||||||
1.0.0, crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils
|
1.0.0, crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils
|
||||||
0.8.23, dbus-secret-service 4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14,
|
0.8.23, dbus-secret-service 4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14,
|
||||||
euclid 0.22.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10,
|
euclid 0.22.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, fixedbitset
|
||||||
form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow 0.17.0, gltf 1.4.1,
|
0.5.7, flate2 1.1.10, form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow
|
||||||
gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, httparse
|
0.17.0, gltf 1.4.1, gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.15.5, hashbrown 0.16.1,
|
||||||
1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.2, itertools 0.15.0, js-sys 0.3.106,
|
hashbrown 0.17.1, hermit-abi 0.5.3, httparse 1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap
|
||||||
keyboard-types 0.8.3, khronos-egl 6.0.0, lazy_static 1.5.1, linux-raw-sys 0.12.1, linux-raw-sys
|
2.14.2, itertools 0.15.0, js-sys 0.3.106, keyboard-types 0.8.3, khronos-egl 6.0.0, lazy_static
|
||||||
0.4.15, lock_api 0.4.14, log 0.4.34, muda 0.21.2, num 0.4.3, num-bigint 0.4.8, num-complex 0.4.6,
|
1.5.1, linux-raw-sys 0.12.1, linux-raw-sys 0.4.15, lock_api 0.4.14, log 0.4.34, muda 0.21.2, num
|
||||||
num-integer 0.1.47, num-iter 0.1.46, num-rational 0.4.2, num-traits 0.2.19, once_cell 1.21.4,
|
0.4.3, num-bigint 0.4.8, num-complex 0.4.6, num-integer 0.1.47, num-iter 0.1.46, num-rational 0.4.2,
|
||||||
openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5, parking_lot_core
|
num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1,
|
||||||
0.9.12, percent-encoding 2.3.2, piper 0.2.5, plain 0.2.3, png 0.18.1, polling 3.11.0, pollster
|
parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, petgraph 0.8.3, piper 0.2.5,
|
||||||
0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core 1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, ron
|
plain 0.2.3, png 0.18.1, polling 3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core
|
||||||
0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix 0.38.44, rustix 1.1.5, rustls 0.23.46,
|
1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, ron 0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix
|
||||||
rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard 1.2.0, secret-service 4.0.0,
|
0.38.44, rustix 1.1.5, rustls 0.23.46, rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard
|
||||||
security-framework 2.11.1, security-framework 3.7.0, security-framework-sys 2.17.0,
|
1.2.0, secret-service 4.0.0, security-framework 2.11.1, security-framework 3.7.0,
|
||||||
signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.16.2, smol_str 0.2.2, stable_deref_trait
|
security-framework-sys 2.17.0, send_wrapper 0.6.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0,
|
||||||
1.2.1, tempfile 3.27.0, ttf-parser 0.25.1, unicode-bidi 0.3.18, unicode-segmentation 1.13.3,
|
smallvec 1.16.2, smol_str 0.2.2, stable_deref_trait 1.2.1, tempfile 3.27.0, ttf-parser 0.25.1,
|
||||||
unicode-width 0.2.2, url 2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2
|
unicode-bidi 0.3.18, unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2, url
|
||||||
1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129,
|
2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen
|
||||||
wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12,
|
0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129, wasm-bindgen-macro-support
|
||||||
wit-bindgen 0.57.1
|
0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12, wit-bindgen 0.57.1,
|
||||||
|
wl-clipboard-rs 0.9.5
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
@@ -1310,7 +1315,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
allocator-api2 0.2.21, async-trait 0.1.92, dyn-clone 1.0.20, enumn 0.1.14, glifo 0.2.0, itoa 1.0.18,
|
allocator-api2 0.2.21, async-trait 0.1.92, dyn-clone 1.0.20, enumn 0.1.14, glifo 0.3.0, itoa 1.0.18,
|
||||||
libc 0.2.190, litrs 1.0.0, num_enum 0.7.6, num_enum_derive 0.7.6, proc-macro2 1.0.107, quote 1.0.47,
|
libc 0.2.190, litrs 1.0.0, num_enum 0.7.6, num_enum_derive 0.7.6, proc-macro2 1.0.107, quote 1.0.47,
|
||||||
ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 2.1.3, rustversion 1.0.23, semver 1.0.28, serde
|
ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 2.1.3, rustversion 1.0.23, semver 1.0.28, serde
|
||||||
1.0.229, serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json
|
1.0.229, serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json
|
||||||
@@ -1530,7 +1535,7 @@ android-activity 0.6.1, arboard 3.6.1, color 0.3.3, fdeflate 0.3.7, fearless_sim
|
|||||||
image 0.25.10, image-webp 0.2.4, linebender_resource_handle 0.1.1, miniz_oxide 0.8.9, miniz_oxide
|
image 0.25.10, image-webp 0.2.4, linebender_resource_handle 0.1.1, miniz_oxide 0.8.9, miniz_oxide
|
||||||
0.9.1, naga 30.0.1, naga-types 30.0.1, pin-project 1.1.13, pin-project-internal 1.1.13,
|
0.9.1, naga 30.0.1, naga-types 30.0.1, pin-project 1.1.13, pin-project-internal 1.1.13,
|
||||||
pin-project-lite 0.2.17, portable-atomic 1.15.0, portable-atomic-util 0.2.8, range-alloc 0.1.5,
|
pin-project-lite 0.2.17, portable-atomic 1.15.0, portable-atomic-util 0.2.8, range-alloc 0.1.5,
|
||||||
raw-window-handle 0.6.2, simdutf8 0.1.5, vello_common 0.1.0, vello_cpu 0.1.0, wgpu 30.0.1, wgpu-core
|
raw-window-handle 0.6.2, simdutf8 0.1.5, vello_common 0.2.0, vello_cpu 0.2.0, wgpu 30.0.1, wgpu-core
|
||||||
30.0.1, wgpu-core-deps-apple 30.0.1, wgpu-core-deps-emscripten 30.0.1, wgpu-core-deps-wasm 30.0.1,
|
30.0.1, wgpu-core-deps-apple 30.0.1, wgpu-core-deps-emscripten 30.0.1, wgpu-core-deps-wasm 30.0.1,
|
||||||
wgpu-core-deps-windows-linux-android 30.0.1, wgpu-hal 30.0.1, wgpu-naga-bridge 30.0.1, wgpu-types
|
wgpu-core-deps-windows-linux-android 30.0.1, wgpu-hal 30.0.1, wgpu-naga-bridge 30.0.1, wgpu-types
|
||||||
30.0.1
|
30.0.1
|
||||||
@@ -2588,7 +2593,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
bit-set 0.10.0, bit-vec 0.9.1, downcast-rs 1.2.1, egui-wgpu 0.36.2, presser 0.3.1
|
bit-set 0.10.0, bit-vec 0.9.1, downcast-rs 1.2.1, egui-wgpu 0.37.0, presser 0.3.1
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
@@ -3134,7 +3139,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
calloop 0.13.0, calloop 0.14.5, smithay-client-toolkit 0.19.2, smithay-client-toolkit 0.20.0
|
calloop 0.13.0, smithay-client-toolkit 0.19.2
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2018 Victor Berger
|
Copyright (c) 2018 Victor Berger
|
||||||
@@ -3158,7 +3163,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
calloop-wayland-source 0.3.0, calloop-wayland-source 0.4.1
|
calloop-wayland-source 0.3.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2023 Kirill Chibisov
|
Copyright (c) 2023 Kirill Chibisov
|
||||||
@@ -5231,7 +5236,7 @@ have been licensed under extremely permissive terms.
|
|||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
egui-wgpu 0.36.2
|
egui-wgpu 0.37.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
|
Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
|
||||||
@@ -5261,7 +5266,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
either 1.19.0, itertools 0.15.0
|
either 1.19.0, itertools 0.15.0, petgraph 0.8.3
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2015
|
Copyright (c) 2015
|
||||||
@@ -5709,6 +5714,36 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
fixedbitset 0.5.7
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Copyright (c) 2015-2017
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
flate2 1.1.10
|
flate2 1.1.10
|
||||||
================================================================================
|
================================================================================
|
||||||
@@ -5740,7 +5775,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
foldhash 0.2.0
|
foldhash 0.1.5, foldhash 0.2.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2024 Orson Peters
|
Copyright (c) 2024 Orson Peters
|
||||||
@@ -6511,7 +6546,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
glifo 0.2.0
|
glifo 0.3.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright 2025 the Vello Authors
|
Copyright 2025 the Vello Authors
|
||||||
@@ -7138,7 +7173,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
hashbrown 0.16.1, hashbrown 0.17.1
|
hashbrown 0.15.5, hashbrown 0.16.1, hashbrown 0.17.1
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2016 Amanieu d'Antras
|
Copyright (c) 2016 Amanieu d'Antras
|
||||||
@@ -9459,6 +9494,31 @@ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
nom 8.0.0
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Copyright (c) 2014-2019 Geoffroy Couprie
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
num_enum 0.7.6, num_enum_derive 0.7.6
|
num_enum 0.7.6, num_enum_derive 0.7.6
|
||||||
================================================================================
|
================================================================================
|
||||||
@@ -9547,6 +9607,30 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
os_pipe 1.2.3
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
parking 2.2.1
|
parking 2.2.1
|
||||||
================================================================================
|
================================================================================
|
||||||
@@ -9562,7 +9646,7 @@ option. All files in the project carrying such notice may not be
|
|||||||
copied, modified, or distributed except according to those terms.
|
copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
phf 0.13.1, phf_generator 0.13.1, phf_macros 0.13.1, phf_shared 0.13.1
|
phf 0.14.0, phf_generator 0.14.0, phf_macros 0.14.0, phf_shared 0.14.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
@@ -12377,30 +12461,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
|
||||||
smithay-clipboard 0.7.3
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
Copyright (c) 2018 Lucas Timmins & Victor Berger
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
solidcore 1.0.1
|
solidcore 1.0.1
|
||||||
================================================================================
|
================================================================================
|
||||||
@@ -12973,6 +13033,32 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
tree_magic_mini 3.2.2
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Aaron Hancock
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
ttf-parser 0.25.1
|
ttf-parser 0.25.1
|
||||||
================================================================================
|
================================================================================
|
||||||
@@ -13795,7 +13881,7 @@ carrying such notice may not be copied, modified, or distributed except
|
|||||||
according to those terms.
|
according to those terms.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
unicode-bidi 0.3.18, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
unicode-bidi 0.3.18, unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2015 The Rust Project Developers
|
Copyright (c) 2015 The Rust Project Developers
|
||||||
@@ -13869,7 +13955,51 @@ dealings in these Data Files or Software without prior written
|
|||||||
authorization of the copyright holder.
|
authorization of the copyright holder.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
unicode-segmentation 1.13.3, unicode-width 0.2.2
|
unicode-properties 0.1.5
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
UNICODE LICENSE V3
|
||||||
|
|
||||||
|
COPYRIGHT AND PERMISSION NOTICE
|
||||||
|
|
||||||
|
Copyright © 1991-2026 Unicode, Inc.
|
||||||
|
|
||||||
|
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||||
|
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||||
|
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||||
|
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||||
|
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of data files and any associated documentation (the "Data Files") or
|
||||||
|
software and any associated documentation (the "Software") to deal in the
|
||||||
|
Data Files or Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||||
|
copies of the Data Files or Software, and to permit persons to whom the
|
||||||
|
Data Files or Software are furnished to do so, provided that either (a)
|
||||||
|
this copyright and permission notice appear with all copies of the Data
|
||||||
|
Files or Software, or (b) this copyright and permission notice appear in
|
||||||
|
associated Documentation.
|
||||||
|
|
||||||
|
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||||
|
THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||||
|
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||||
|
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||||
|
FILES OR SOFTWARE.
|
||||||
|
|
||||||
|
Except as contained in this notice, the name of a copyright holder shall
|
||||||
|
not be used in advertising or otherwise to promote the sale, use or other
|
||||||
|
dealings in these Data Files or Software without prior written
|
||||||
|
authorization of the copyright holder.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0
|
Licensed under the Apache License, Version 2.0
|
||||||
@@ -14015,7 +14145,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
vello_common 0.1.0, vello_cpu 0.1.0
|
vello_common 0.2.0, vello_cpu 0.2.0
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright 2020 the Vello Authors
|
Copyright 2020 the Vello Authors
|
||||||
@@ -14046,8 +14176,8 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
wayland-backend 0.3.17, wayland-client 0.31.15, wayland-cursor 0.31.14, wayland-protocols 0.32.13,
|
wayland-backend 0.3.17, wayland-client 0.31.15, wayland-cursor 0.31.14, wayland-protocols 0.32.13,
|
||||||
wayland-protocols-experimental 20250721.0.1, wayland-protocols-misc 0.3.12, wayland-protocols-plasma
|
wayland-protocols-plasma 0.3.12, wayland-protocols-wlr 0.3.12, wayland-scanner 0.31.11, wayland-sys
|
||||||
0.3.12, wayland-protocols-wlr 0.3.12, wayland-scanner 0.31.11, wayland-sys 0.31.11
|
0.31.11
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
Copyright (c) 2015 Elinor Berger
|
Copyright (c) 2015 Elinor Berger
|
||||||
@@ -14942,6 +15072,36 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
wl-clipboard-rs 0.9.5
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Copyright (c) 2019 Ivan Molodetskikh
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
x11rb 0.13.2, x11rb-protocol 0.13.2
|
x11rb 0.13.2, x11rb-protocol 0.13.2
|
||||||
================================================================================
|
================================================================================
|
||||||
@@ -15967,15 +16127,14 @@ Crates whose package carries no licence file (their licence is named in Cargo.to
|
|||||||
texts are as in SolidCraft's own LICENSE-MIT and LICENSE-APACHE)
|
texts are as in SolidCraft's own LICENSE-MIT and LICENSE-APACHE)
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
accesskit 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit 0.25.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_atspi_common 0.18.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_atspi_common 0.21.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_consumer 0.35.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_consumer 0.39.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_consumer 0.36.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_ios 0.2.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_consumer 0.38.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_macos 0.27.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_macos 0.26.3: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_unix 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_unix 0.21.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_windows 0.35.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_windows 0.32.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
accesskit_winit 0.34.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||||
accesskit_winit 0.32.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
|
|
||||||
block2 0.5.1: MIT (https://github.com/madsmtm/objc2)
|
block2 0.5.1: MIT (https://github.com/madsmtm/objc2)
|
||||||
block2 0.6.2: MIT (https://github.com/madsmtm/objc2)
|
block2 0.6.2: MIT (https://github.com/madsmtm/objc2)
|
||||||
cadmpeg-codec-freecad 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
cadmpeg-codec-freecad 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||||
@@ -15988,14 +16147,14 @@ cadmpeg-registry 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
|||||||
clipboard-win 5.4.1: BSL-1.0 (https://github.com/DoumanAsh/clipboard-win)
|
clipboard-win 5.4.1: BSL-1.0 (https://github.com/DoumanAsh/clipboard-win)
|
||||||
dispatch 0.2.0: MIT (http://github.com/SSheldon/rust-dispatch)
|
dispatch 0.2.0: MIT (http://github.com/SSheldon/rust-dispatch)
|
||||||
dispatch2 0.3.1: Zlib OR Apache-2.0 OR MIT (https://github.com/madsmtm/objc2)
|
dispatch2 0.3.1: Zlib OR Apache-2.0 OR MIT (https://github.com/madsmtm/objc2)
|
||||||
ecolor 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
ecolor 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
||||||
eframe 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/eframe)
|
eframe 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/eframe)
|
||||||
egui 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
egui 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
||||||
egui-winit 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui-winit)
|
egui-winit 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui-winit)
|
||||||
egui_glow 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui_glow)
|
egui_glow 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui_glow)
|
||||||
emath 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/emath)
|
emath 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/emath)
|
||||||
epaint 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/epaint)
|
epaint 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/epaint)
|
||||||
epaint_default_fonts 0.36.2: (MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0 (https://github.com/emilk/egui/tree/main/crates/epaint_default_fonts)
|
epaint_default_fonts 0.37.0: (MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0 (https://github.com/emilk/egui/tree/main/crates/epaint_default_fonts)
|
||||||
jni 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
|
jni 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
|
||||||
jni-macros 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
|
jni-macros 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
|
||||||
jni-sys-macros 0.4.1: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-sys)
|
jni-sys-macros 0.4.1: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-sys)
|
||||||
@@ -16039,7 +16198,7 @@ spirv 0.4.0+sdk-1.4.341.0: Apache-2.0 (https://github.com/gfx-rs/rspirv)
|
|||||||
Bundled fonts (SIL Open Font License, Version 1.1)
|
Bundled fonts (SIL Open Font License, Version 1.1)
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- LiberationSans-Regular.ttf (assets/fonts/liberation-sans/LiberationSans-Regular.ttf)
|
- LiberationSans-Regular.ttf (crates/sketch/assets/fonts/liberation-sans/LiberationSans-Regular.ttf)
|
||||||
Copyright 2012 Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google)
|
Copyright 2012 Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google)
|
||||||
Source: https://github.com/liberationfonts
|
Source: https://github.com/liberationfonts
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use std::process::{Command, ExitCode};
|
|||||||
const TARGET: &str = "solidcraft-cli";
|
const TARGET: &str = "solidcraft-cli";
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
eprintln!("solvecraft-cli is deprecated and will be removed in a later release: use solidcraft-cli");
|
solidcraft_engine::note!("solvecraft-cli is deprecated and will be removed in a later release: use solidcraft-cli");
|
||||||
let sibling = std::env::current_exe()
|
let sibling = std::env::current_exe()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|exe| exe.parent().map(|dir| dir.join(format!("{TARGET}{}", std::env::consts::EXE_SUFFIX))))
|
.and_then(|exe| exe.parent().map(|dir| dir.join(format!("{TARGET}{}", std::env::consts::EXE_SUFFIX))))
|
||||||
@@ -23,7 +23,7 @@ fn main() -> ExitCode {
|
|||||||
None => ExitCode::FAILURE,
|
None => ExitCode::FAILURE,
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("solvecraft-cli: could not run {}: {e}", program.to_string_lossy());
|
solidcraft_engine::note!("solvecraft-cli: could not run {}: {e}", program.to_string_lossy());
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ pub fn one(args: &[String]) -> Result<(), String> {
|
|||||||
std::panic::set_hook(Box::new(|_| {
|
std::panic::set_hook(Box::new(|_| {
|
||||||
PANICS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
PANICS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
}));
|
}));
|
||||||
eprintln!("design {seed}");
|
solidcraft_engine::note!("design {seed}");
|
||||||
let run = fuzz::run_with(seed, steps, &mut |st| eprintln!("step {}", serde_json::to_string(st).unwrap_or_default()));
|
let run = fuzz::run_with(seed, steps, &mut |st| solidcraft_engine::note!("step {}", serde_json::to_string(st).unwrap_or_default()));
|
||||||
let mut design = json!({
|
let mut design = json!({
|
||||||
"outcome": run.outcome,
|
"outcome": run.outcome,
|
||||||
"steps": run.steps.iter().map(|s| s.kind).collect::<Vec<_>>(),
|
"steps": run.steps.iter().map(|s| s.kind).collect::<Vec<_>>(),
|
||||||
@@ -45,13 +45,13 @@ pub fn one(args: &[String]) -> Result<(), String> {
|
|||||||
if run.outcome.is_bug() {
|
if run.outcome.is_bug() {
|
||||||
design["script"] = run.script();
|
design["script"] = run.script();
|
||||||
if minimise {
|
if minimise {
|
||||||
eprintln!("minimise");
|
solidcraft_engine::note!("minimise");
|
||||||
let m = fuzz::minimise(&run);
|
let m = fuzz::minimise(&run);
|
||||||
design["minimal"] = json!({"outcome": m.outcome, "steps": m.steps.iter().map(|s| s.kind).collect::<Vec<_>>(), "script": m.script()});
|
design["minimal"] = json!({"outcome": m.outcome, "steps": m.steps.iter().map(|s| s.kind).collect::<Vec<_>>(), "script": m.script()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (a, b) = solidcraft_engine::fuzz::boolean_shapes(seed);
|
let (a, b) = solidcraft_engine::fuzz::boolean_shapes(seed);
|
||||||
eprintln!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default());
|
solidcraft_engine::note!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default());
|
||||||
let b = boolean_case(seed);
|
let b = boolean_case(seed);
|
||||||
let bp = PANICS.swap(0, std::sync::atomic::Ordering::SeqCst);
|
let bp = PANICS.swap(0, std::sync::atomic::Ordering::SeqCst);
|
||||||
println!("{}", json!({"seed": seed, "design": design, "boolean": b, "boolean_panics": bp}));
|
println!("{}", json!({"seed": seed, "design": design, "boolean": b, "boolean_panics": bp}));
|
||||||
@@ -162,11 +162,14 @@ fn record(v: &Value, out_dir: Option<&str>) {
|
|||||||
let d = &v["design"];
|
let d = &v["design"];
|
||||||
let bug = matches!(d["outcome"]["kind"].as_str(), Some("failed" | "panic" | "invalid" | "volume"));
|
let bug = matches!(d["outcome"]["kind"].as_str(), Some("failed" | "panic" | "invalid" | "volume"));
|
||||||
if bug || v.get("timeout").is_some() || v.get("crash").is_some() {
|
if bug || v.get("timeout").is_some() || v.get("crash").is_some() {
|
||||||
eprintln!("seed {seed}: {}", if bug { format!("{} {}", d["outcome"]["kind"], d["outcome"]["detail"]) } else { format!("{v}") });
|
solidcraft_engine::note!(
|
||||||
|
"seed {seed}: {}",
|
||||||
|
if bug { format!("{} {}", d["outcome"]["kind"], d["outcome"]["detail"]) } else { format!("{v}") }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let bk = v["boolean"]["outcome"].as_str().unwrap_or("ok");
|
let bk = v["boolean"]["outcome"].as_str().unwrap_or("ok");
|
||||||
if bk != "ok" {
|
if bk != "ok" {
|
||||||
eprintln!("seed {seed}: boolean {bk} {}", v["boolean"]["detail"]);
|
solidcraft_engine::note!("seed {seed}: boolean {bk} {}", v["boolean"]["detail"]);
|
||||||
}
|
}
|
||||||
if let Some(dir) = out_dir {
|
if let Some(dir) = out_dir {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! `kernel-cases --export=DIR`: one kernel rig case as a corpus entry for solidcore's rig. The
|
//! `kernel-cases --export=DIR`: one kernel rig case as a corpus entry for solidcore's rig. The
|
||||||
//! case's commands run as the rig runs them while the kernel's operations are traced
|
//! case's commands run as the rig runs them while the kernel's operations are traced
|
||||||
//! (`smlib_trace`); the entry holds the operation (or the chain of operations) that made the
|
//! (`smlib_trace`); the entry holds every operation in execution order, including discarded
|
||||||
//! bodies the case ends with, each operand that came in as a record given as that record (the
|
//! tool construction and handled refusals, each operand that came in as a record (the
|
||||||
//! B-rep record, solidcore-record's JSON), the fix set, the case's expected values and the rig's
|
//! B-rep record, solidcore-record's JSON), the fix set, the case's expected values and the rig's
|
||||||
//! own result. A case whose result no kernel operation alone accounts for is written too, with
|
//! own result. A case whose result no kernel operation alone accounts for is written too, with
|
||||||
//! the reasons, so nothing is dropped silently.
|
//! the reasons, so nothing is dropped silently.
|
||||||
@@ -98,32 +98,6 @@ impl<'a> Trace<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The operations `ends` need, in the order they ran: each body's maker, and its operands' makers
|
|
||||||
/// in turn, back to bodies made from records. Operands made by nothing traced are listed in
|
|
||||||
/// `unknown`.
|
|
||||||
fn chain(t: &Trace<'_>, ends: &[u64], unknown: &mut BTreeSet<u64>) -> Vec<usize> {
|
|
||||||
let mut need = BTreeSet::new();
|
|
||||||
let mut todo: Vec<u64> = ends.to_vec();
|
|
||||||
while let Some(id) = todo.pop() {
|
|
||||||
if t.imports.contains_key(&id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match t.producer.get(&id) {
|
|
||||||
Some(&i) => {
|
|
||||||
if need.insert(i)
|
|
||||||
&& let Some(c) = t.calls.get(i)
|
|
||||||
{
|
|
||||||
todo.extend(c.1.iter().copied());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
unknown.insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
need.into_iter().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The entry of case `dir`, run with result `out`, into `root/<area>/<id>.json`, and the fix set
|
/// The entry of case `dir`, run with result `out`, into `root/<area>/<id>.json`, and the fix set
|
||||||
/// into `root/fixes.json`.
|
/// into `root/fixes.json`.
|
||||||
pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<(), String> {
|
pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<(), String> {
|
||||||
@@ -220,14 +194,18 @@ pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<()
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut unknown = BTreeSet::new();
|
let mut unknown = BTreeSet::new();
|
||||||
let mut order = chain(&t, &ends, &mut unknown);
|
let mut available: BTreeSet<u64> = t.imports.keys().copied().collect();
|
||||||
|
for (_, inputs, _, outputs) in &t.calls {
|
||||||
|
unknown.extend(inputs.iter().filter(|id| !available.contains(id)).copied());
|
||||||
|
if let Ok(made) = outputs {
|
||||||
|
available.extend(made.iter().copied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unknown.extend(ends.iter().filter(|id| !available.contains(id)).copied());
|
||||||
if !unknown.is_empty() {
|
if !unknown.is_empty() {
|
||||||
why_not.push(format!("operands made by nothing traced: {unknown:?}"));
|
why_not.push(format!("operands made by nothing traced: {unknown:?}"));
|
||||||
}
|
}
|
||||||
if let Some(i) = failed_call.filter(|i| !order.contains(i)) {
|
// Operand records: every body a call takes, or the case ends with, that came in as a record.
|
||||||
order.push(i);
|
|
||||||
}
|
|
||||||
// Operand records: every body the chain takes, or the case ends with, that came in as a record.
|
|
||||||
let mut bodies = BTreeMap::new();
|
let mut bodies = BTreeMap::new();
|
||||||
let given = |id: u64, bodies: &mut BTreeMap<String, Value>| {
|
let given = |id: u64, bodies: &mut BTreeMap<String, Value>| {
|
||||||
if let Some((record, trusted, splines)) = t.imports.get(&id) {
|
if let Some((record, trusted, splines)) = t.imports.get(&id) {
|
||||||
@@ -235,14 +213,21 @@ pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<()
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut calls: Vec<Value> = Vec::new();
|
let mut calls: Vec<Value> = Vec::new();
|
||||||
for (op, inputs, params, outputs) in order.iter().filter_map(|&i| t.calls.get(i)) {
|
for (i, (op, inputs, params, outputs)) in t.calls.iter().enumerate() {
|
||||||
for &id in inputs.iter() {
|
for &id in inputs.iter() {
|
||||||
given(id, &mut bodies);
|
given(id, &mut bodies);
|
||||||
}
|
}
|
||||||
let mut c = json!({"op": op, "inputs": inputs.iter().map(|i| format!("b{i}")).collect::<Vec<_>>(), "params": params});
|
let mut c = json!({"op": op, "inputs": inputs.iter().map(|i| format!("b{i}")).collect::<Vec<_>>(), "params": params});
|
||||||
match outputs {
|
match outputs {
|
||||||
Ok(made) => c["outputs"] = json!(made.iter().map(|i| format!("b{i}")).collect::<Vec<_>>()),
|
Ok(made) => c["outputs"] = json!(made.iter().map(|i| format!("b{i}")).collect::<Vec<_>>()),
|
||||||
Err(e) => c["error"] = json!(e),
|
Err(e) => {
|
||||||
|
c["error"] = json!(e);
|
||||||
|
// The application handled this refusal. Replay must continue to any later
|
||||||
|
// calls and grade the final bodies, even when this was the last attempt.
|
||||||
|
if failed_call != Some(i) {
|
||||||
|
c["continued"] = json!(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
calls.push(c);
|
calls.push(c);
|
||||||
}
|
}
|
||||||
@@ -290,6 +275,134 @@ pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<()
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn empty_record() -> Brep {
|
||||||
|
Brep { version: 4, tolerance: 1e-6, regions: Vec::new(), shells: Vec::new(), faces: Vec::new(), edges: Vec::new(), vertices: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(op: &'static str, inputs: &[u64], outputs: Result<Vec<u64>, String>) -> Event {
|
||||||
|
Event::Call { op, inputs: inputs.to_vec(), params: json!({}), outputs }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exported_case(name: &str, events: Vec<Event>, ran: Result<Vec<(String, Brep)>, String>) -> Value {
|
||||||
|
let root = std::env::temp_dir().join(format!("solidcraft-export-{}-{name}", std::process::id()));
|
||||||
|
let dir = root.join("tests/kernel-cases/traces").join(name);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::write(dir.join("expected.json"), "{}").unwrap();
|
||||||
|
let output = root.join("export");
|
||||||
|
write(&output, dir.to_str().unwrap(), &json!({"status": "pass"}), Capture { events, ran: Some(ran) }).unwrap();
|
||||||
|
let entry = output.join("traces").join(format!("{name}.json"));
|
||||||
|
serde_json::from_str(&std::fs::read_to_string(entry).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discarded_tool_construction_is_exported_in_execution_order() {
|
||||||
|
let record = empty_record();
|
||||||
|
let e = exported_case(
|
||||||
|
"discarded-tool",
|
||||||
|
vec![call("extrude", &[], Ok(vec![1])), call("extrude", &[], Ok(vec![2])), Event::Export { id: 1, record: Box::new(record.clone()) }],
|
||||||
|
Ok(vec![("Part".into(), record)]),
|
||||||
|
);
|
||||||
|
assert_eq!(e["calls"].as_array().unwrap().len(), 2);
|
||||||
|
assert_eq!(e["calls"][0]["outputs"], json!(["b1"]));
|
||||||
|
assert_eq!(e["calls"][1]["outputs"], json!(["b2"]));
|
||||||
|
assert_eq!(e["traced_ops"], json!(["extrude", "extrude"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handled_refusals_are_exported_without_becoming_terminal() {
|
||||||
|
let record = empty_record();
|
||||||
|
let e = exported_case(
|
||||||
|
"handled-refusal",
|
||||||
|
vec![
|
||||||
|
call("extrude", &[], Ok(vec![1])),
|
||||||
|
call("fillet", &[1], Err("too large".into())),
|
||||||
|
call("fillet", &[1], Ok(vec![2])),
|
||||||
|
Event::Export { id: 2, record: Box::new(record.clone()) },
|
||||||
|
],
|
||||||
|
Ok(vec![("Part".into(), record)]),
|
||||||
|
);
|
||||||
|
assert_eq!(e["calls"].as_array().unwrap().len(), 3);
|
||||||
|
assert_eq!(e["calls"][1]["error"], "too large");
|
||||||
|
assert_eq!(e["calls"][1]["continued"], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_handled_last_attempt_keeps_the_retained_body_as_the_result() {
|
||||||
|
let record = empty_record();
|
||||||
|
let e = exported_case(
|
||||||
|
"handled-last-attempt",
|
||||||
|
vec![
|
||||||
|
call("extrude", &[], Ok(vec![1])),
|
||||||
|
call("fillet", &[1], Err("too large".into())),
|
||||||
|
Event::Export { id: 1, record: Box::new(record.clone()) },
|
||||||
|
],
|
||||||
|
Ok(vec![("Part".into(), record)]),
|
||||||
|
);
|
||||||
|
assert_eq!(e["calls"][1]["continued"], true);
|
||||||
|
assert_eq!(e["result"], json!([{"body": "Part", "made": "b1"}]));
|
||||||
|
assert_eq!(e["operation_expressible"], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_final_refusal_still_ends_replay() {
|
||||||
|
let e = exported_case(
|
||||||
|
"terminal-refusal",
|
||||||
|
vec![
|
||||||
|
call("extrude", &[], Ok(vec![1])),
|
||||||
|
call("fillet", &[1], Err("too large".into())),
|
||||||
|
call("fillet", &[1], Err("no room".into())),
|
||||||
|
call("copy", &[1], Ok(vec![2])),
|
||||||
|
],
|
||||||
|
Err("no room".into()),
|
||||||
|
);
|
||||||
|
assert_eq!(e["calls"].as_array().unwrap().len(), 4);
|
||||||
|
assert_eq!(e["calls"][1]["continued"], true);
|
||||||
|
assert!(e["calls"][2].get("continued").is_none());
|
||||||
|
assert_eq!(e["outcome"]["kernel_error"], "no room");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discarded_operations_still_require_their_operand_records() {
|
||||||
|
let record = empty_record();
|
||||||
|
let e = exported_case(
|
||||||
|
"missing-tool-operand",
|
||||||
|
vec![call("extrude", &[], Ok(vec![1])), call("copy", &[99], Ok(vec![2])), Event::Export { id: 1, record: Box::new(record.clone()) }],
|
||||||
|
Ok(vec![("Part".into(), record)]),
|
||||||
|
);
|
||||||
|
assert_eq!(e["operation_expressible"], false);
|
||||||
|
assert!(e["why_not"].as_array().unwrap().iter().any(|why| why.as_str().unwrap().contains("99")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discarded_operations_keep_their_imported_operands() {
|
||||||
|
let record = empty_record();
|
||||||
|
let e = exported_case(
|
||||||
|
"imported-tool-operand",
|
||||||
|
vec![
|
||||||
|
call("extrude", &[], Ok(vec![1])),
|
||||||
|
Event::Import { id: 9, record: Box::new(record.clone()), trusted: false, keeping_splines: true },
|
||||||
|
call("copy", &[9], Ok(vec![2])),
|
||||||
|
Event::Export { id: 1, record: Box::new(record.clone()) },
|
||||||
|
],
|
||||||
|
Ok(vec![("Part".into(), record.clone())]),
|
||||||
|
);
|
||||||
|
assert_eq!(e["bodies"]["b9"], json!({"record": record, "trusted": false, "keeping_splines": true}));
|
||||||
|
assert_eq!(e["calls"][1]["inputs"], json!(["b9"]));
|
||||||
|
assert_eq!(e["operation_expressible"], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_later_output_cannot_supply_an_earlier_discarded_operation() {
|
||||||
|
let record = empty_record();
|
||||||
|
let e = exported_case(
|
||||||
|
"future-tool-operand",
|
||||||
|
vec![call("copy", &[9], Ok(vec![2])), call("extrude", &[], Ok(vec![9])), Event::Export { id: 9, record: Box::new(record.clone()) }],
|
||||||
|
Ok(vec![("Part".into(), record)]),
|
||||||
|
);
|
||||||
|
assert_eq!(e["operation_expressible"], false);
|
||||||
|
assert!(e["why_not"].as_array().unwrap().iter().any(|why| why.as_str().unwrap().contains("9")));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_area_is_the_path_under_kernel_cases() {
|
fn the_area_is_the_path_under_kernel_cases() {
|
||||||
assert_eq!(area_and_id("tests/kernel-cases/booleans/1894-x").unwrap(), ("booleans".into(), "1894-x".into()));
|
assert_eq!(area_and_id("tests/kernel-cases/booleans/1894-x").unwrap(), ("booleans".into(), "1894-x".into()));
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
|
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
|
||||||
//! solidcraft-cli kernel-cases [--json] [--dumps=DIR] [--validation=MODE] <case-dir>... the kernel regression rig (tests/kernel-cases)
|
//! solidcraft-cli kernel-cases [--json] [--dumps=DIR] [--validation=MODE] <case-dir>... the kernel regression rig (tests/kernel-cases)
|
||||||
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
||||||
//! [--param-check off|warn|strict]
|
//! [--param-check off|warn|strict] [--compact]
|
||||||
//! MCP server on stdio (docs/mcp.md)
|
//! MCP server on stdio (docs/mcp.md)
|
||||||
//! solidcraft-cli mcp setup [--dry-run] [--client NAME] [--live] (alias: mcp install) add it to OpenCode, Claude Desktop, Claude Code, Cursor
|
//! solidcraft-cli mcp setup [--dry-run] [--client NAME] [--live] (alias: mcp install) add it to OpenCode, Claude Desktop, Claude Code, Cursor
|
||||||
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
|
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
|
||||||
@@ -62,9 +62,9 @@ struct Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `run`'s lines in the usage.
|
/// `run`'s lines in the usage.
|
||||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
|
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict] [--quality standard|fine|finest]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--quality: how closely an STL, OBJ or 3MF --out follows curved faces, as file.export's quality: a chord of\n 2e-4, 5e-5 or 1e-5 of each body's size, never under 1 µm, default standard)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
|
||||||
/// `eval`'s lines in the usage.
|
/// `eval`'s lines in the usage.
|
||||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile and --profile-out as for run)\n";
|
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] [--validation each_step|each_feature|off] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile, --profile-out and --validation as for run)\n";
|
||||||
/// `script`'s lines in the usage.
|
/// `script`'s lines in the usage.
|
||||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
||||||
/// `bench-open`'s lines in the usage.
|
/// `bench-open`'s lines in the usage.
|
||||||
@@ -91,7 +91,7 @@ const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [-
|
|||||||
const USAGE_FUZZ: &str =
|
const USAGE_FUZZ: &str =
|
||||||
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
|
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
|
||||||
/// `mcp`'s lines in the usage.
|
/// `mcp`'s lines in the usage.
|
||||||
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu]\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
|
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu] [--compact]\n (--compact, SOLIDCRAFT_MCP_COMPACT=1 or the app's Preferences › AI (with --connect): compact tool output for\n agents: inspect_design summarises sketches and takes `sketches` by name and a `bodies` filter, and tool JSON\n is not pretty-printed; off by default)\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
|
||||||
/// `crashes`'s lines in the usage.
|
/// `crashes`'s lines in the usage.
|
||||||
const USAGE_CRASHES: &str = " solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
|
const USAGE_CRASHES: &str = " solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
|
||||||
/// `licences`'s lines in the usage.
|
/// `licences`'s lines in the usage.
|
||||||
@@ -154,7 +154,7 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Err(e) = max_file_size(&mut args) {
|
if let Err(e) = max_file_size(&mut args) {
|
||||||
eprintln!("error: {e}");
|
solidcraft_engine::note!("error: {e}");
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
let r = match args.first().map(String::as_str) {
|
let r = match args.first().map(String::as_str) {
|
||||||
@@ -235,7 +235,7 @@ fn main() -> ExitCode {
|
|||||||
match r {
|
match r {
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
Ok(()) => ExitCode::SUCCESS,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error: {e}");
|
solidcraft_engine::note!("error: {e}");
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,6 +318,19 @@ fn load_checked(path: &str, check: solidcraft_engine::schema::ParamCheck) -> Res
|
|||||||
/// `load_checked`, the features computed under `validation` (`diagnostics.validation`), which
|
/// `load_checked`, the features computed under `validation` (`diagnostics.validation`), which
|
||||||
/// the session keeps.
|
/// the session keeps.
|
||||||
fn load_with(path: &str, check: solidcraft_engine::schema::ParamCheck, validation: solidcraft_engine::kernel::Validation) -> Result<Session, String> {
|
fn load_with(path: &str, check: solidcraft_engine::schema::ParamCheck, validation: solidcraft_engine::kernel::Validation) -> Result<Session, String> {
|
||||||
|
let s = read_session(path, check, validation)?;
|
||||||
|
// A newer SolidCraft's file opens all the same; stderr says so, stdout stays the caller's.
|
||||||
|
for warning in s.doc.newer_file.iter().flat_map(|file| file.warnings()) {
|
||||||
|
eprintln!("{path}: warning: {warning}");
|
||||||
|
}
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_session(
|
||||||
|
path: &str,
|
||||||
|
check: solidcraft_engine::schema::ParamCheck,
|
||||||
|
validation: solidcraft_engine::kernel::Validation,
|
||||||
|
) -> Result<Session, String> {
|
||||||
let mut s = Session::default();
|
let mut s = Session::default();
|
||||||
s.param_check = check;
|
s.param_check = check;
|
||||||
s.set_validation(validation);
|
s.set_validation(validation);
|
||||||
@@ -351,7 +364,7 @@ fn run_script_warning(s: &mut Session, path: &str, script: &Value) -> Result<Vec
|
|||||||
/// Step `step`'s `param_warnings` on stderr, one per line, named after the script at `path`.
|
/// Step `step`'s `param_warnings` on stderr, one per line, named after the script at `path`.
|
||||||
fn print_param_warnings(path: &str, step: usize, r: &Value) {
|
fn print_param_warnings(path: &str, step: usize, r: &Value) {
|
||||||
for w in r.get("param_warnings").and_then(Value::as_array).into_iter().flatten() {
|
for w in r.get("param_warnings").and_then(Value::as_array).into_iter().flatten() {
|
||||||
eprintln!("{path}: step {}: {}", step + 1, w.as_str().unwrap_or_default());
|
solidcraft_engine::note!("{path}: step {}: {}", step + 1, w.as_str().unwrap_or_default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,11 +451,12 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
|||||||
known_args(
|
known_args(
|
||||||
"run",
|
"run",
|
||||||
args,
|
args,
|
||||||
&["--in", "--out", "--save", "--json-out", "--param-check", "--profile-out", "--validation"],
|
&["--in", "--out", "--save", "--json-out", "--param-check", "--profile-out", "--validation", "--quality"],
|
||||||
&["--quiet", "--results", "--timings", "--profile", "--allow-errors"],
|
&["--quiet", "--results", "--timings", "--profile", "--allow-errors"],
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
let validation = validation_flag("run", args)?;
|
let validation = validation_flag("run", args)?;
|
||||||
|
let quality = export_quality("run", args)?;
|
||||||
let base = flag(args, "--in");
|
let base = flag(args, "--in");
|
||||||
let path = positional(args);
|
let path = positional(args);
|
||||||
if path.is_none() && base.is_none() {
|
if path.is_none() && base.is_none() {
|
||||||
@@ -484,11 +498,17 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
|||||||
// A feature that fails leaves the bodies as they were before it (a parameter change can
|
// A feature that fails leaves the bodies as they were before it (a parameter change can
|
||||||
// break a feature without failing the command that made it): writing them would hand out
|
// break a feature without failing the command that made it): writing them would hand out
|
||||||
// the wrong part with success, so fail the run unless asked to write it anyway.
|
// the wrong part with success, so fail the run unless asked to write it anyway.
|
||||||
|
// A feature only a newer SolidCraft can build isn't a broken design: saving the design keeps it
|
||||||
|
// and hands out no part. Exporting bodies without it would, so that still stops.
|
||||||
if !args.iter().any(|a| a == "--allow-errors") {
|
if !args.iter().any(|a| a == "--allow-errors") {
|
||||||
timeline_ok(&s)?;
|
timeline_ok(&s, flags(args, "--out").is_empty())?;
|
||||||
}
|
}
|
||||||
for out in flags(args, "--out") {
|
for out in flags(args, "--out") {
|
||||||
s.execute("file.export", &json!({"path": out})).map_err(|e| e.to_string())?;
|
let mut params = json!({"path": out});
|
||||||
|
if let Some(q) = quality {
|
||||||
|
params["quality"] = json!(q);
|
||||||
|
}
|
||||||
|
s.execute("file.export", ¶ms).map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(p) = flag(args, "--save") {
|
if let Some(p) = flag(args, "--save") {
|
||||||
s.execute("file.save_as", &json!({"path": p})).map_err(|e| e.to_string())?;
|
s.execute("file.save_as", &json!({"path": p})).map_err(|e| e.to_string())?;
|
||||||
@@ -529,6 +549,20 @@ fn open_validating(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `--quality standard|fine|finest`: how closely the meshes `--out` writes (STL, OBJ, 3MF)
|
||||||
|
/// follow curved faces, as `file.export`'s `quality` (#12270). Checked before anything runs;
|
||||||
|
/// given twice it is refused, since only one quality can apply.
|
||||||
|
fn export_quality<'a>(cmd: &str, args: &'a [String]) -> Result<Option<&'a str>, String> {
|
||||||
|
if flags(args, "--quality").len() > 1 {
|
||||||
|
return Err(format!("{cmd}: --quality is given more than once"));
|
||||||
|
}
|
||||||
|
match flag(args, "--quality") {
|
||||||
|
Some(q) if solidcraft_engine::io::MeshQuality::from_name(q).is_some() => Ok(Some(q)),
|
||||||
|
Some(q) => Err(format!("{cmd}: --quality must be standard, fine or finest, not `{q}`")),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `--validation MODE` (`diagnostics.validation`): the default, each_feature, when not given.
|
/// `--validation MODE` (`diagnostics.validation`): the default, each_feature, when not given.
|
||||||
fn validation_flag(cmd: &str, args: &[String]) -> Result<solidcraft_engine::kernel::Validation, String> {
|
fn validation_flag(cmd: &str, args: &[String]) -> Result<solidcraft_engine::kernel::Validation, String> {
|
||||||
match flag(args, "--validation") {
|
match flag(args, "--validation") {
|
||||||
@@ -561,8 +595,12 @@ fn known_args(cmd: &str, args: &[String], valued: &[&str], switches: &[&str], po
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Every failing timeline feature, one per line, as an error; Ok when none fails.
|
/// Every failing timeline feature, one per line, as an error; Ok when none fails.
|
||||||
fn timeline_ok(s: &Session) -> Result<(), String> {
|
fn timeline_ok(s: &Session, skip_newer_features: bool) -> Result<(), String> {
|
||||||
let failed: Vec<String> = s.model.results.iter().filter_map(|r| r.error.as_ref().map(|e| format!(" {}: {e}", r.name))).collect();
|
let newer = |id: u64| {
|
||||||
|
skip_newer_features && s.doc.feature(id).is_some_and(|f| matches!(f.kind, solidcraft_engine::doc::FeatureKind::NewerFileFeature { .. }))
|
||||||
|
};
|
||||||
|
let failed: Vec<String> =
|
||||||
|
s.model.results.iter().filter(|r| !newer(r.id)).filter_map(|r| r.error.as_ref().map(|e| format!(" {}: {e}", r.name))).collect();
|
||||||
if failed.is_empty() {
|
if failed.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -573,13 +611,15 @@ fn timeline_ok(s: &Session) -> Result<(), String> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs `solidcraft-cli eval` (and its `inspect` alias), opening the input design or script with the requested `--validation` mode and reporting both `inspect.measure` and `document.inspect`.
|
||||||
fn cmd_eval(args: &[String]) -> Result<(), String> {
|
fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||||
let json_out = json_out(args)?;
|
let json_out = json_out(args)?;
|
||||||
known_args("eval", args, &["--in", "--json-out", "--profile-out"], &["--profile"], true)?;
|
known_args("eval", args, &["--in", "--json-out", "--profile-out", "--validation"], &["--profile"], true)?;
|
||||||
let mut s = open(flag(args, "--in"), positional(args), solidcraft_engine::schema::ParamCheck::Off)?;
|
let validation = validation_flag("eval", args)?;
|
||||||
|
let mut s = open_validating(flag(args, "--in"), positional(args), solidcraft_engine::schema::ParamCheck::Off, validation)?;
|
||||||
let m = s.execute("inspect.measure", &json!({})).map_err(|e| e.to_string())?;
|
let m = s.execute("inspect.measure", &json!({})).map_err(|e| e.to_string())?;
|
||||||
let d = s.execute("document.inspect", &json!({})).map_err(|e| e.to_string())?;
|
let d = s.execute("document.inspect", &json!({})).map_err(|e| e.to_string())?;
|
||||||
let out = json!({
|
let mut out = json!({
|
||||||
"body_count": m["body_count"],
|
"body_count": m["body_count"],
|
||||||
"bodies": m["bodies"],
|
"bodies": m["bodies"],
|
||||||
"total": m["total"],
|
"total": m["total"],
|
||||||
@@ -587,6 +627,10 @@ fn cmd_eval(args: &[String]) -> Result<(), String> {
|
|||||||
"params": d["params"],
|
"params": d["params"],
|
||||||
"sketches": d["sketches"],
|
"sketches": d["sketches"],
|
||||||
});
|
});
|
||||||
|
if let Some(file) = &s.doc.newer_file {
|
||||||
|
out["newer_file"] = file.report();
|
||||||
|
out["warnings"] = json!(file.warnings());
|
||||||
|
}
|
||||||
print_json(&out, json_out)
|
print_json(&out, json_out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,10 +642,10 @@ fn cmd_script(args: &[String]) -> Result<(), String> {
|
|||||||
let out = flag(args, "--out");
|
let out = flag(args, "--out");
|
||||||
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
|
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
|
||||||
for what in r["omitted"].as_array().into_iter().flatten().filter_map(Value::as_str) {
|
for what in r["omitted"].as_array().into_iter().flatten().filter_map(Value::as_str) {
|
||||||
eprintln!("note: the script leaves out {what}");
|
solidcraft_engine::note!("note: the script leaves out {what}");
|
||||||
}
|
}
|
||||||
match out {
|
match out {
|
||||||
Some(out) => eprintln!("{out}: {} steps", r["steps"]),
|
Some(out) => solidcraft_engine::note!("{out}: {} steps", r["steps"]),
|
||||||
None => println!("{}", pretty(&r["script"])),
|
None => println!("{}", pretty(&r["script"])),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -632,7 +676,7 @@ fn cmd_gpu(args: &[String]) -> Result<(), String> {
|
|||||||
);
|
);
|
||||||
match s.lands_on {
|
match s.lands_on {
|
||||||
Some(solidcraft_gpu::select::Landing::Cpu) => {
|
Some(solidcraft_gpu::select::Landing::Cpu) => {
|
||||||
eprintln!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
|
solidcraft_engine::note!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Some(_) => Ok(()),
|
Some(_) => Ok(()),
|
||||||
@@ -709,7 +753,7 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
|||||||
std::fs::write(out, &png.bytes).map_err(|e| format!("{out}: {e}"))?;
|
std::fs::write(out, &png.bytes).map_err(|e| format!("{out}: {e}"))?;
|
||||||
let mut reply = json!({"path": out, "width": w, "height": h, "bytes": png.bytes.len(), "renderer": png.renderer});
|
let mut reply = json!({"path": out, "width": w, "height": h, "bytes": png.bytes.len(), "renderer": png.renderer});
|
||||||
if let Some(w) = solidcraft_engine::capture::warning(png.renderer) {
|
if let Some(w) = solidcraft_engine::capture::warning(png.renderer) {
|
||||||
eprintln!("warning: {w}");
|
solidcraft_engine::note!("warning: {w}");
|
||||||
reply["warning"] = json!(w);
|
reply["warning"] = json!(w);
|
||||||
}
|
}
|
||||||
println!("{}", pretty(&reply));
|
println!("{}", pretty(&reply));
|
||||||
@@ -792,11 +836,15 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
|||||||
return Err("mcp: --render-gpu installs a headless image host; a connected application owns its own device".into());
|
return Err("mcp: --render-gpu installs a headless image host; a connected application owns its own device".into());
|
||||||
}
|
}
|
||||||
install_renderer();
|
install_renderer();
|
||||||
|
let options = solidcraft_mcp::Options::from_args_and_env(args).map_err(|e| format!("mcp: {e}"))?;
|
||||||
|
if options.compact {
|
||||||
|
eprintln!("solidcraft mcp: compact output (inspect_design summarises sketches; tool JSON is not pretty-printed)");
|
||||||
|
}
|
||||||
let scope = (!args.iter().any(|a| a == "--allow-any-path"))
|
let scope = (!args.iter().any(|a| a == "--allow-any-path"))
|
||||||
.then(|| solidcraft_mcp::agent_scope(flags(args, "--allow-path").into_iter().map(std::path::PathBuf::from)));
|
.then(|| solidcraft_mcp::agent_scope(flags(args, "--allow-path").into_iter().map(std::path::PathBuf::from)));
|
||||||
if let Some(s) = &scope {
|
if let Some(s) = &scope {
|
||||||
let roots: Vec<String> = s.roots().iter().map(|r| r.display().to_string()).collect();
|
let roots: Vec<String> = s.roots().iter().map(|r| r.display().to_string()).collect();
|
||||||
eprintln!(
|
solidcraft_engine::note!(
|
||||||
"solidcraft mcp: files limited to {} and the folders of designs opened or saved here (--allow-path DIR adds one, --allow-any-path lifts the limit)",
|
"solidcraft mcp: files limited to {} and the folders of designs opened or saved here (--allow-path DIR adds one, --allow-any-path lifts the limit)",
|
||||||
roots.join(", ")
|
roots.join(", ")
|
||||||
);
|
);
|
||||||
@@ -804,7 +852,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
|||||||
.and_then(|p| std::path::Path::new(p).parent())
|
.and_then(|p| std::path::Path::new(p).parent())
|
||||||
.map(|d| if d.as_os_str().is_empty() { std::path::Path::new(".") } else { d });
|
.map(|d| if d.as_os_str().is_empty() { std::path::Path::new(".") } else { d });
|
||||||
if let Some(why) = design_dir.and_then(solidcraft_engine::io::vfs::design_folder_refusal) {
|
if let Some(why) = design_dir.and_then(solidcraft_engine::io::vfs::design_folder_refusal) {
|
||||||
eprintln!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
|
solidcraft_engine::note!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// How calls that break a command's parameter contract are treated, headless or bridged.
|
// How calls that break a command's parameter contract are treated, headless or bridged.
|
||||||
@@ -818,7 +866,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
|||||||
// Serve without the app too: the tools connect once it runs, and say it isn't until then.
|
// Serve without the app too: the tools connect once it runs, and say it isn't until then.
|
||||||
let mut remote = solidcraft_mcp::Remote::new(addr, flag(args, "--token")).with_param_check(param_check);
|
let mut remote = solidcraft_mcp::Remote::new(addr, flag(args, "--token")).with_param_check(param_check);
|
||||||
if let Err(e) = remote.try_connect() {
|
if let Err(e) = remote.try_connect() {
|
||||||
eprintln!(
|
solidcraft_engine::note!(
|
||||||
"solidcraft mcp: no SolidCraft app at {addr} yet ({e}); start it with `solidcraft --control {port}` \
|
"solidcraft mcp: no SolidCraft app at {addr} yet ({e}); start it with `solidcraft --control {port}` \
|
||||||
(or SOLIDCRAFT_CONTROL_PORT={port}), tool calls connect once it runs"
|
(or SOLIDCRAFT_CONTROL_PORT={port}), tool calls connect once it runs"
|
||||||
);
|
);
|
||||||
@@ -835,9 +883,9 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
|||||||
(None, None) => {
|
(None, None) => {
|
||||||
let auto = !args.iter().any(|a| a == "--headless");
|
let auto = !args.iter().any(|a| a == "--headless");
|
||||||
if auto && let Some(port) = discover_app() {
|
if auto && let Some(port) = discover_app() {
|
||||||
eprintln!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
|
solidcraft_engine::note!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
|
||||||
let remote = solidcraft_mcp::Remote::new(&format!("127.0.0.1:{port}"), flag(args, "--token")).with_param_check(param_check);
|
let remote = solidcraft_mcp::Remote::new(&format!("127.0.0.1:{port}"), flag(args, "--token")).with_param_check(param_check);
|
||||||
return serve_mcp(Box::new(remote), scope);
|
return serve_mcp(Box::new(remote), scope, options);
|
||||||
}
|
}
|
||||||
let mut headless = solidcraft_mcp::Headless::default();
|
let mut headless = solidcraft_mcp::Headless::default();
|
||||||
headless.session.param_check = param_check;
|
headless.session.param_check = param_check;
|
||||||
@@ -852,13 +900,17 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
serve_mcp(backend, scope)
|
serve_mcp(backend, scope, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_mcp(backend: Box<dyn solidcraft_mcp::Backend>, scope: Option<solidcraft_engine::io::vfs::PathScope>) -> Result<(), String> {
|
fn serve_mcp(
|
||||||
|
backend: Box<dyn solidcraft_mcp::Backend>,
|
||||||
|
scope: Option<solidcraft_engine::io::vfs::PathScope>,
|
||||||
|
options: solidcraft_mcp::Options,
|
||||||
|
) -> Result<(), String> {
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
solidcraft_mcp::Server::new(backend).with_path_scope(scope).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
|
solidcraft_mcp::Server::new(backend).with_path_scope(scope).with_options(options).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The port of the running desktop app: the one its discovery file names, if something listens
|
/// The port of the running desktop app: the one its discovery file names, if something listens
|
||||||
|
|||||||
@@ -188,3 +188,26 @@ fn max_file_size_raises_the_limit_and_refuses_bad_sizes() {
|
|||||||
assert!(!output.status.success() && !stderr.contains("too large"), "{stderr}");
|
assert!(!output.status.success() && !stderr.contains("too large"), "{stderr}");
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// #10111: a line for a stderr whose reader has gone (the pipe closed before it, as an agent may
|
||||||
|
/// leave the MCP server's, and the app that one starts) is dropped, and the command ends as it
|
||||||
|
/// would with stderr open: here a script that isn't there fails with 1, where `eprintln!`
|
||||||
|
/// panicked with "failed printing to stderr" (101).
|
||||||
|
#[test]
|
||||||
|
fn a_closed_stderr_does_not_stop_the_cli() {
|
||||||
|
for _ in 0..5 {
|
||||||
|
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
||||||
|
.args(["run", "no-such-script.json"])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
drop(child.stderr.take());
|
||||||
|
assert_eq!(child.wait().unwrap().code(), Some(1));
|
||||||
|
}
|
||||||
|
// The old name's alias writes its deprecation notice first: an agent still configured with
|
||||||
|
// `solvecraft-cli mcp` must get the server, not that panic.
|
||||||
|
let mut child = Command::new(env!("CARGO_BIN_EXE_solvecraft-cli")).arg("--help").stdout(Stdio::null()).stderr(Stdio::piped()).spawn().unwrap();
|
||||||
|
drop(child.stderr.take());
|
||||||
|
assert_eq!(child.wait().unwrap().code(), Some(0));
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//! `mcp --compact` and `SOLIDCRAFT_MCP_COMPACT` turn on compact tool output; without them the
|
||||||
|
//! server's tools are as they always were.
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
/// `inspect_design`'s input schema as `solidcraft-cli mcp` with `args` and `env` lists it, or
|
||||||
|
/// the server's stderr when it does not start.
|
||||||
|
fn inspect_schema(args: &[&str], env: Option<&str>) -> Result<Value, String> {
|
||||||
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
|
||||||
|
// --headless: never bridge to (and read the preference of) an app the developer has running.
|
||||||
|
cmd.args(["mcp", "--headless", "--allow-any-path"]).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||||
|
match env {
|
||||||
|
Some(v) => cmd.env("SOLIDCRAFT_MCP_COMPACT", v),
|
||||||
|
None => cmd.env_remove("SOLIDCRAFT_MCP_COMPACT"),
|
||||||
|
};
|
||||||
|
let mut child = cmd.spawn().unwrap();
|
||||||
|
{
|
||||||
|
let mut stdin = child.stdin.take().unwrap();
|
||||||
|
// The server may refuse to start and close stdin first.
|
||||||
|
let _ = writeln!(stdin, "{}", json!({"jsonrpc": "2.0", "id": 1, "method": "tools/list"}));
|
||||||
|
}
|
||||||
|
let out = child.wait_with_output().unwrap();
|
||||||
|
if !out.status.success() {
|
||||||
|
return Err(String::from_utf8_lossy(&out.stderr).into_owned());
|
||||||
|
}
|
||||||
|
let reply: Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||||
|
let tools = reply["result"]["tools"].as_array().cloned().unwrap_or_default();
|
||||||
|
Ok(tools.into_iter().find(|t| t["name"] == "inspect_design").map(|t| t["inputSchema"].clone()).unwrap_or(Value::Null))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_output_is_off_unless_asked() {
|
||||||
|
let plain = inspect_schema(&[], None).unwrap();
|
||||||
|
assert_eq!(plain["properties"]["sketches"]["type"], "boolean", "{plain}");
|
||||||
|
assert!(plain["properties"].get("bodies").is_none(), "{plain}");
|
||||||
|
assert_eq!(inspect_schema(&[], Some("0")).unwrap(), plain);
|
||||||
|
|
||||||
|
for (args, env) in [(&["--compact"][..], None), (&[][..], Some("1")), (&[][..], Some("true"))] {
|
||||||
|
let compact = inspect_schema(args, env).unwrap();
|
||||||
|
assert!(compact["properties"]["bodies"].is_object(), "{args:?} {env:?}: {compact}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let e = inspect_schema(&[], Some("maybe")).unwrap_err();
|
||||||
|
assert!(e.contains("SOLIDCRAFT_MCP_COMPACT"), "{e}");
|
||||||
|
}
|
||||||
@@ -60,15 +60,25 @@ fn mcp_connect_serves_before_the_app_runs_and_reaches_it_once_it_does() {
|
|||||||
let listener = TcpListener::bind(&addr).unwrap();
|
let listener = TcpListener::bind(&addr).unwrap();
|
||||||
let app = std::thread::spawn(move || {
|
let app = std::thread::spawn(move || {
|
||||||
let (stream, _) = listener.accept().unwrap();
|
let (stream, _) = listener.accept().unwrap();
|
||||||
let mut line = String::new();
|
let mut lines = BufReader::new(stream.try_clone().unwrap());
|
||||||
BufReader::new(stream.try_clone().unwrap()).read_line(&mut line).unwrap();
|
loop {
|
||||||
let req: Value = serde_json::from_str(&line).unwrap();
|
let mut line = String::new();
|
||||||
let reply = if req["token"] == json!(token) {
|
lines.read_line(&mut line).unwrap();
|
||||||
json!({"id": req["id"], "ok": true, "result": {"bodies": [], "method": req["method"]}})
|
let req: Value = serde_json::from_str(&line).unwrap();
|
||||||
} else {
|
// The server first asks for the agents' output options (the compact preference: off).
|
||||||
json!({"id": req["id"], "ok": false, "error": format!("unauthorized: token {}", req["token"])})
|
let options = req["method"] == "mcp.options";
|
||||||
};
|
let reply = if req["token"] != json!(token) {
|
||||||
writeln!(&stream, "{reply}").unwrap();
|
json!({"id": req["id"], "ok": false, "error": format!("unauthorized: token {}", req["token"])})
|
||||||
|
} else if options {
|
||||||
|
json!({"id": req["id"], "ok": true, "result": {"compact": false}})
|
||||||
|
} else {
|
||||||
|
json!({"id": req["id"], "ok": true, "result": {"bodies": [], "method": req["method"]}})
|
||||||
|
};
|
||||||
|
writeln!(&stream, "{reply}").unwrap();
|
||||||
|
if !options {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let r = ask(4, "tools/call", measure);
|
let r = ask(4, "tools/call", measure);
|
||||||
assert_ne!(r["result"]["isError"], true, "{r}");
|
assert_ne!(r["result"]["isError"], true, "{r}");
|
||||||
@@ -111,10 +121,21 @@ fn mcp_without_arguments_finds_the_running_app() {
|
|||||||
break (stream, line);
|
break (stream, line);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let req: Value = serde_json::from_str(&line).unwrap();
|
let mut lines = BufReader::new(stream.try_clone().unwrap());
|
||||||
let ok = req["token"] == json!(token);
|
let mut line = line;
|
||||||
let reply = json!({"id": req["id"], "ok": ok, "result": {"bodies": [], "method": req["method"]}, "error": "unauthorized"});
|
// The server first asks for the agents' output options (the compact preference: off).
|
||||||
writeln!(&stream, "{reply}").unwrap();
|
loop {
|
||||||
|
let req: Value = serde_json::from_str(&line).unwrap();
|
||||||
|
let ok = req["token"] == json!(token);
|
||||||
|
let options = req["method"] == "mcp.options";
|
||||||
|
let result = if options { json!({"compact": false}) } else { json!({"bodies": [], "method": req["method"]}) };
|
||||||
|
writeln!(&stream, "{}", json!({"id": req["id"], "ok": ok, "result": result, "error": "unauthorized"})).unwrap();
|
||||||
|
if !options {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
line.clear();
|
||||||
|
lines.read_line(&mut line).unwrap();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
||||||
.args(["mcp", "--allow-any-path"])
|
.args(["mcp", "--allow-any-path"])
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ fn a_model_saved_under_another_check_mode_is_evaluated_again() {
|
|||||||
/// opened from its model or evaluated anew. Two edges of a 20 mm box rounded r2 (a mitre at the
|
/// opened from its model or evaluated anew. Two edges of a 20 mm box rounded r2 (a mitre at the
|
||||||
/// corner (20, 0, 20)), saved; opened, the upright edge there rounded r3: the corner all three
|
/// corner (20, 0, 20)), saved; opened, the upright edge there rounded r3: the corner all three
|
||||||
/// rounded at once make (SMLib's setback corner, kernel `three_rounds_at_a_corner`), 15 faces and
|
/// rounded at once make (SMLib's setback corner, kernel `three_rounds_at_a_corner`), 15 faces and
|
||||||
/// 7928.142563938 mm³, the same body as the design evaluated anew.
|
/// 7927.72428251348 mm³, the same body as the design evaluated anew.
|
||||||
#[test]
|
#[test]
|
||||||
fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
||||||
let mut built = Session::default();
|
let mut built = Session::default();
|
||||||
@@ -336,7 +336,7 @@ fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
|||||||
let b = &st.body("Box").unwrap().body;
|
let b = &st.body("Box").unwrap().body;
|
||||||
let v = solidcraft_engine::kernel::measure(b).unwrap().volume;
|
let v = solidcraft_engine::kernel::measure(b).unwrap().volume;
|
||||||
assert_eq!(b.face_count(), 15);
|
assert_eq!(b.face_count(), 15);
|
||||||
assert!((v - 7928.142563938).abs() < 1e-6, "{v}");
|
assert!((v - 7927.72428251348).abs() < 1e-6, "{v}"); // solidcore#2382's setback corner
|
||||||
}
|
}
|
||||||
assert_eq!(model("later round", &cached), model("later round", &rebuilt));
|
assert_eq!(model("later round", &cached), model("later round", &rebuilt));
|
||||||
let _ = std::fs::remove_file(&path);
|
let _ = std::fs::remove_file(&path);
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ fn run_rejects_invalid_options_before_script_execution_or_output_writes() {
|
|||||||
(&["--out", "--quiet"][..], "--out needs a value"),
|
(&["--out", "--quiet"][..], "--out needs a value"),
|
||||||
(&["--ouut", "part.step"][..], "doesn't take --ouut"),
|
(&["--ouut", "part.step"][..], "doesn't take --ouut"),
|
||||||
(&["--unknown"][..], "doesn't take --unknown"),
|
(&["--unknown"][..], "doesn't take --unknown"),
|
||||||
|
(&["--quality"][..], "--quality needs a value"),
|
||||||
|
(&["--quality", "best"][..], "--quality must be standard, fine or finest, not `best`"),
|
||||||
|
(&["--quality", "standard", "--quality", "best"][..], "--quality is given more than once"),
|
||||||
];
|
];
|
||||||
for existing in [false, true] {
|
for existing in [false, true] {
|
||||||
let saved = dir.0.join(format!("{existing}.solidcraft"));
|
let saved = dir.0.join(format!("{existing}.solidcraft"));
|
||||||
@@ -159,7 +162,7 @@ fn cli_loads_both_design_encodings_by_contents() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// #1369: a pending concept that did not land in the numbered release must not be silently
|
/// #1369: a pending concept that did not land in the numbered release must not be silently
|
||||||
/// dropped on open/resave, in either encoding or before replacing an existing destination.
|
/// dropped on open/resave, in either encoding: it opens with a warning, and saving keeps it.
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
||||||
use solidcraft_engine::{Session, doc, io};
|
use solidcraft_engine::{Session, doc, io};
|
||||||
@@ -173,34 +176,29 @@ fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
|||||||
let source = dir.0.join(format!("experimental-{version}.{extension}"));
|
let source = dir.0.join(format!("experimental-{version}.{extension}"));
|
||||||
std::fs::write(&source, &bytes).unwrap();
|
std::fs::write(&source, &bytes).unwrap();
|
||||||
let mut session = Session::default();
|
let mut session = Session::default();
|
||||||
session.execute("solid.box", &json!({"length": 2, "width": 3, "height": 4})).unwrap();
|
// It opens, and the reply says what it is.
|
||||||
let original = session.doc.clone();
|
let reply = session.execute("doc.open", &json!({"path": source})).unwrap();
|
||||||
let original_path = session.path.clone();
|
let notice = reply["newer_file"]["notice"].as_str().unwrap().to_string();
|
||||||
let undo = session.undo.len();
|
assert!(notice.contains("experimental-card") && notice.starts_with("Saved by a newer SolidCraft"), "{notice}");
|
||||||
let dirty = session.is_dirty();
|
assert_eq!(reply["warnings"][0], notice.as_str());
|
||||||
let err = session.execute("doc.open", &json!({"path": source})).unwrap_err().to_string();
|
assert_eq!(session.doc.name, "Experimental");
|
||||||
assert!(err.contains("experimental-card") && err.contains("update SolidCraft"), "{err}");
|
|
||||||
assert_eq!(session.doc, original);
|
|
||||||
assert_eq!(session.path, original_path);
|
|
||||||
assert_eq!(session.undo.len(), undo);
|
|
||||||
assert_eq!(session.is_dirty(), dirty);
|
|
||||||
|
|
||||||
for existing in [false, true] {
|
for existing in [false, true] {
|
||||||
let destination = dir.0.join(format!("output-{version}-{extension}-{existing}.{extension}"));
|
let destination = dir.0.join(format!("output-{version}-{extension}-{existing}.{extension}"));
|
||||||
let backup = io::backup_path(&destination);
|
let backup = io::backup_path(&destination);
|
||||||
if existing {
|
if existing {
|
||||||
std::fs::write(&destination, b"existing design").unwrap();
|
std::fs::write(&destination, b"existing design").unwrap();
|
||||||
std::fs::write(&backup, b"existing backup").unwrap();
|
|
||||||
}
|
}
|
||||||
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
|
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
|
||||||
assert_eq!(output.status.code(), Some(1));
|
|
||||||
let error = String::from_utf8_lossy(&output.stderr);
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
assert!(error.contains("experimental-card") && error.contains("update SolidCraft"), "{error}");
|
assert_eq!(output.status.code(), Some(0), "{error}");
|
||||||
|
assert!(error.contains("warning: Saved by a newer SolidCraft") && error.contains("experimental-card"), "{error}");
|
||||||
|
// Saved, it still says it has the change, and opens the same way.
|
||||||
|
let saved = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
|
||||||
|
assert!(saved.format_pending.iter().any(|name| name == "experimental-card"));
|
||||||
|
assert!(saved.newer_file.is_some());
|
||||||
if existing {
|
if existing {
|
||||||
assert_eq!(std::fs::read(&destination).unwrap(), b"existing design");
|
assert_eq!(std::fs::read(&backup).unwrap(), b"existing design");
|
||||||
assert_eq!(std::fs::read(&backup).unwrap(), b"existing backup");
|
|
||||||
} else {
|
|
||||||
assert!(!destination.exists() && !backup.exists());
|
|
||||||
}
|
}
|
||||||
assert_eq!(std::fs::read(&source).unwrap(), bytes);
|
assert_eq!(std::fs::read(&source).unwrap(), bytes);
|
||||||
}
|
}
|
||||||
@@ -208,6 +206,43 @@ fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `eval` and `run` on a design one format newer, with a feature kind this version doesn't have:
|
||||||
|
/// the rest builds, the reply names it, and the saved file still holds it, under the file's number.
|
||||||
|
#[test]
|
||||||
|
fn the_cli_opens_a_newer_design_and_saving_keeps_what_it_cant_read() {
|
||||||
|
use solidcraft_engine::{Session, doc, io};
|
||||||
|
|
||||||
|
let dir = Scratch::new();
|
||||||
|
let mut session = Session::default();
|
||||||
|
session.execute("solid.box", &json!({"length": 2, "width": 3, "height": 4})).unwrap();
|
||||||
|
let mut file: Value = serde_json::from_slice(&io::write_design(&session.doc)).unwrap();
|
||||||
|
let newer = doc::format::format_string(doc::format::FORMAT + 1);
|
||||||
|
file["format"] = json!(newer);
|
||||||
|
let future = json!({"id": 900, "name": "Lattice1", "type": "gyroid_lattice", "cell": 2.5});
|
||||||
|
file["features"].as_array_mut().unwrap().push(future.clone());
|
||||||
|
let source = dir.0.join("newer.json");
|
||||||
|
std::fs::write(&source, file.to_string()).unwrap();
|
||||||
|
|
||||||
|
let output = cli().args(["eval"]).arg(&source).output().unwrap();
|
||||||
|
let error = String::from_utf8_lossy(&output.stderr);
|
||||||
|
assert_eq!(output.status.code(), Some(0), "{error}");
|
||||||
|
assert!(error.contains("warning: Saved by a newer SolidCraft") && error.contains("Lattice1"), "{error}");
|
||||||
|
let reply: Value = serde_json::from_slice(&output.stdout).unwrap();
|
||||||
|
assert_eq!(reply["body_count"], 1);
|
||||||
|
assert!(reply["newer_file"]["not_understood"][0].as_str().unwrap().contains("gyroid_lattice"), "{reply}");
|
||||||
|
assert!(reply["warnings"][0].as_str().unwrap().starts_with("Saved by a newer SolidCraft"), "{reply}");
|
||||||
|
|
||||||
|
for extension in ["json", "sc"] {
|
||||||
|
let destination = dir.0.join(format!("saved.{extension}"));
|
||||||
|
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
|
||||||
|
assert_eq!(output.status.code(), Some(0), "{}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
let saved = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
|
||||||
|
assert_eq!(saved.format, newer, "never a lower number");
|
||||||
|
let again: Value = serde_json::from_slice(&io::write_design(&saved)).unwrap();
|
||||||
|
assert_eq!(again["features"].as_array().unwrap().last().unwrap(), &future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn known_pending_concepts_and_released_files_round_trip_with_inert_metadata() {
|
fn known_pending_concepts_and_released_files_round_trip_with_inert_metadata() {
|
||||||
use solidcraft_engine::{doc, io};
|
use solidcraft_engine::{doc, io};
|
||||||
@@ -256,6 +291,38 @@ fn results_still_save_and_export_the_script_once() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The triangles of a binary STL: the count after its 80-byte header.
|
||||||
|
fn stl_triangles(path: &Path) -> u32 {
|
||||||
|
let bytes = std::fs::read(path).unwrap();
|
||||||
|
u32::from_le_bytes(bytes[80..84].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// #12270: `run --out part.stl --quality fine` writes the mesh at that quality, as
|
||||||
|
/// `file.export`'s `quality` does, without a script to say so.
|
||||||
|
#[test]
|
||||||
|
fn run_exports_meshes_at_the_quality_asked_for() {
|
||||||
|
let dir = Scratch::new();
|
||||||
|
let script = dir.0.join("cylinder.json");
|
||||||
|
std::fs::write(&script, json!({"commands": [{"command": "solid.cylinder", "params": {"diameter": 40, "height": 10}}]}).to_string()).unwrap();
|
||||||
|
let export = |name: &str, quality: Option<&str>| {
|
||||||
|
let stl = dir.0.join(name);
|
||||||
|
let mut command = cli();
|
||||||
|
command.arg("run").arg(&script).arg("--quiet").arg("--out").arg(&stl);
|
||||||
|
if let Some(q) = quality {
|
||||||
|
command.args(["--quality", q]);
|
||||||
|
}
|
||||||
|
let output = command.output().unwrap();
|
||||||
|
assert!(output.status.success(), "{quality:?}: {}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
stl_triangles(&stl)
|
||||||
|
};
|
||||||
|
let default = export("default.stl", None);
|
||||||
|
let standard = export("standard.stl", Some("standard"));
|
||||||
|
let fine = export("fine.stl", Some("fine"));
|
||||||
|
let finest = export("finest.stl", Some("finest"));
|
||||||
|
assert_eq!(default, standard);
|
||||||
|
assert!(standard < fine && fine < finest, "standard {standard}, fine {fine}, finest {finest}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn results_with_base_design_and_quiet_still_report_and_save() {
|
fn results_with_base_design_and_quiet_still_report_and_save() {
|
||||||
let dir = Scratch::new();
|
let dir = Scratch::new();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<meta property="og:site_name" content="SolidCraft">
|
<meta property="og:site_name" content="SolidCraft">
|
||||||
<meta property="og:title" content="SolidCraft: try it in your browser">
|
<meta property="og:title" content="SolidCraft: try it in your browser">
|
||||||
<meta property="og:description" content="Open-source parametric CAD built in Rust. Desktop, browser, and MCP for AI agents.">
|
<meta property="og:description" content="Open-source parametric CAD built in Rust. Desktop, browser, and MCP for AI agents.">
|
||||||
<meta property="og:url" content="https://solidcraft.ai/">
|
<meta property="og:url" content="https://app.solidcraft.io/">
|
||||||
<meta property="og:image" content="https://docs.solidcraft.io/og.png">
|
<meta property="og:image" content="https://docs.solidcraft.io/og.png">
|
||||||
<meta property="og:image:width" content="1280">
|
<meta property="og:image:width" content="1280">
|
||||||
<meta property="og:image:height" content="640">
|
<meta property="og:image:height" content="640">
|
||||||
|
|||||||
@@ -676,7 +676,7 @@ pub fn take_drops(files: &Rc<Files>, raw: &mut egui::RawInput, theme: bool) {
|
|||||||
/// any later page.
|
/// any later page.
|
||||||
fn hold_page_lock(page: &str) {
|
fn hold_page_lock(page: &str) {
|
||||||
let Some(locks) = web_locks() else { return };
|
let Some(locks) = web_locks() else { return };
|
||||||
let Ok(request) = Reflect::get(&locks, &"request".into()).and_then(|f| f.dyn_into::<Function>().map_err(JsValue::from)) else { return };
|
let Ok(request) = Reflect::get(&locks, &"request".into()).and_then(|f| f.dyn_into::<Function>()) else { return };
|
||||||
// A promise that never settles: the lock is let go only when the page goes.
|
// A promise that never settles: the lock is let go only when the page goes.
|
||||||
let hold = Closure::<dyn FnMut(JsValue) -> js_sys::Promise>::new(|_lock: JsValue| js_sys::Promise::new(&mut |_, _| {}));
|
let hold = Closure::<dyn FnMut(JsValue) -> js_sys::Promise>::new(|_lock: JsValue| js_sys::Promise::new(&mut |_, _| {}));
|
||||||
let name = format!("{}{page}", keep::LOCK_PREFIX);
|
let name = format!("{}{page}", keep::LOCK_PREFIX);
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ fn prompt() {
|
|||||||
let _ = js_sys::Reflect::set(&window, &OFFER.into(), &JsValue::NULL);
|
let _ = js_sys::Reflect::set(&window, &OFFER.into(), &JsValue::NULL);
|
||||||
}
|
}
|
||||||
let shown = js_sys::Reflect::get(&offer, &"prompt".into())
|
let shown = js_sys::Reflect::get(&offer, &"prompt".into())
|
||||||
.and_then(|f| f.dyn_into::<js_sys::Function>().map_err(JsValue::from))
|
.and_then(|f| f.dyn_into::<js_sys::Function>())
|
||||||
.and_then(|f| f.call0(&offer))
|
.and_then(|f| f.call0(&offer))
|
||||||
.and_then(|p| p.dyn_into::<js_sys::Promise>().map_err(JsValue::from));
|
.and_then(|p| p.dyn_into::<js_sys::Promise>());
|
||||||
match shown {
|
match shown {
|
||||||
// Accepted or dismissed, the browser handles it; a refusal is only logged.
|
// Accepted or dismissed, the browser handles it; a refusal is only logged.
|
||||||
Ok(p) => wasm_bindgen_futures::spawn_local(async move {
|
Ok(p) => wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
|||||||
@@ -173,6 +173,35 @@ pub fn is_open(open: Option<&str>, wanted: &str) -> bool {
|
|||||||
open == Some(wanted)
|
open == Some(wanted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What follows once a design restored from a kept copy, or recovered, is open: it opens in
|
||||||
|
/// stages, a few features a frame with progress and Cancel (#10198), so this waits for the answer.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum AfterOpen {
|
||||||
|
/// A kept copy (#10220): it is its own design again (saving goes to `path`, it is named
|
||||||
|
/// `name`), with its changes unsaved; then the copy `id` goes when `drop` says so.
|
||||||
|
KeptCopy { opened: String, id: String, path: Option<String>, name: String, drop: bool },
|
||||||
|
/// The saved design offered beside a kept copy: the copy `id` goes when `drop` says so.
|
||||||
|
KeptSaved { opened: String, id: String, drop: bool },
|
||||||
|
/// Unsaved work from the page's last visit: it belongs to `path` again, named `name`, unsaved.
|
||||||
|
Recovery { opened: String, path: Option<String>, name: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AfterOpen {
|
||||||
|
/// The file the open reads.
|
||||||
|
pub fn opened(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
AfterOpen::KeptCopy { opened, .. } | AfterOpen::KeptSaved { opened, .. } | AfterOpen::Recovery { opened, .. } => opened,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the open of `wanted` answered with `reply` put it in: the answer says so, and it is
|
||||||
|
/// the design now open (`open`: the open design's path). A refusal, a Cancel or another file
|
||||||
|
/// leaves the copy or the recovered work offered.
|
||||||
|
pub fn opened(reply: &Value, open: Option<&str>, wanted: &str) -> bool {
|
||||||
|
reply["ok"].as_bool() == Some(true) && is_open(open, wanted)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether leaving the page asks through the browser's "leave site?" box: there is unsaved work,
|
/// Whether leaving the page asks through the browser's "leave site?" box: there is unsaved work,
|
||||||
/// and either the user still wants to be asked or it isn't all kept in the browser yet (with the
|
/// and either the user still wants to be asked or it isn't all kept in the browser yet (with the
|
||||||
/// keep preference off it never is, so there the box always stays).
|
/// keep preference off it never is, so there the box always stays).
|
||||||
@@ -224,6 +253,20 @@ pub fn answers(entry: &Entry, saved_exists: bool) -> Vec<Answer> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// A restored or recovered design opens in stages (#10198): only an answer that it is in, for
|
||||||
|
/// the file asked for, counts; a refusal, a Cancel or another design open leaves it offered.
|
||||||
|
#[test]
|
||||||
|
fn a_staged_restore_counts_only_when_its_file_is_in() {
|
||||||
|
let copy = "/opfs/.kept/a.sc";
|
||||||
|
assert!(opened(&json!({"ok": true}), Some(copy), copy));
|
||||||
|
assert!(!opened(&json!({"ok": false, "error": "cancelled"}), Some(copy), copy));
|
||||||
|
assert!(!opened(&json!({"ok": false, "error": "not a design"}), None, copy));
|
||||||
|
assert!(!opened(&json!({"ok": true}), Some("/opfs/other.sc"), copy));
|
||||||
|
assert!(!opened(&json!({}), Some(copy), copy));
|
||||||
|
let after = AfterOpen::KeptCopy { opened: copy.into(), id: "a".into(), path: None, name: "Bracket".into(), drop: true };
|
||||||
|
assert_eq!(after.opened(), copy);
|
||||||
|
}
|
||||||
|
|
||||||
fn entry(id: &str, changed_at: f64, path: Option<&str>) -> Entry {
|
fn entry(id: &str, changed_at: f64, path: Option<&str>) -> Entry {
|
||||||
Entry { id: id.into(), name: "Bracket".into(), path: path.map(str::to_string), changed_at }
|
Entry { id: id.into(), name: "Bracket".into(), path: path.map(str::to_string), changed_at }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl Download {
|
|||||||
}
|
}
|
||||||
if solidcraft_ui_egui::documents::any_dirty(app) {
|
if solidcraft_ui_egui::documents::any_dirty(app) {
|
||||||
let mut answer = None;
|
let mut answer = None;
|
||||||
let closed = ui_kit::Window::new(egui::Id::new("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
|
let closed = ui_kit::Window::new(egui::Id::unique("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
|
||||||
.required()
|
.required()
|
||||||
.show_body(ctx, |ui| {
|
.show_body(ctx, |ui| {
|
||||||
ui.label(solidcraft_ui_egui::i18n::tr("web.open_link_confirm"));
|
ui.label(solidcraft_ui_egui::i18n::tr("web.open_link_confirm"));
|
||||||
|
|||||||
+121
-37
@@ -376,24 +376,47 @@ mod web {
|
|||||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures,
|
wheel_gestures: solidcraft_ui_egui::WebWheelGestures,
|
||||||
/// The settings this tab wrote or loaded last, and those another tab wrote since (#8644).
|
/// The settings this tab wrote or loaded last, and those another tab wrote since (#8644).
|
||||||
prefs_writer: crate::PrefsWriter,
|
prefs_writer: crate::PrefsWriter,
|
||||||
|
prefs_warning: Option<String>,
|
||||||
other_tab_prefs: PrefsInbox,
|
other_tab_prefs: PrefsInbox,
|
||||||
/// The open designs followed for their kept copy, by `Session::uid` (#10220).
|
/// The open designs followed for their kept copy, by `Session::uid` (#10220).
|
||||||
keep_track: std::collections::HashMap<u64, Track>,
|
keep_track: std::collections::HashMap<u64, Track>,
|
||||||
/// Every unsaved design's kept copy is current (and the preference is on).
|
/// Every unsaved design's kept copy is current (and the preference is on).
|
||||||
all_kept: bool,
|
all_kept: bool,
|
||||||
|
/// A kept copy or recovered work being opened, in stages with progress and Cancel
|
||||||
|
/// (#10198): the answer to come and what follows it ([`Shell::restored`]).
|
||||||
|
restoring: Option<(std::sync::mpsc::Receiver<serde_json::Value>, keep::AfterOpen)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shell {
|
impl Shell {
|
||||||
/// The settings as they are kept between visits: with the user's own language, not a
|
/// The settings as they are kept between visits: with the user's own language, not a
|
||||||
/// session-only English fallback.
|
/// session-only English fallback.
|
||||||
fn persisted_prefs(&mut self) -> String {
|
fn persisted_prefs(&mut self, previous: impl FnOnce() -> Result<Option<String>, String>) -> Option<String> {
|
||||||
let saved = self.font.persisted_language(&self.app.preferences.language).to_owned();
|
let saved = self.font.persisted_language(&self.app.preferences.language).to_owned();
|
||||||
let live = std::mem::replace(&mut self.app.preferences.language, saved);
|
let live = std::mem::replace(&mut self.app.preferences.language, saved);
|
||||||
let prefs = self.app.prefs();
|
let (prefs, warning) = match self.app.try_prefs() {
|
||||||
|
Ok(prefs) => (Some(prefs), None),
|
||||||
|
Err(reason) => match previous().and_then(|previous| self.app.prefs_with_stored_panel_layout(previous.as_deref())) {
|
||||||
|
Ok(prefs) => (Some(prefs), Some(format!("Panel layout changes were not saved: {reason}"))),
|
||||||
|
Err(error) => (None, Some(format!("Settings were not written: {error}; panel layout: {reason}"))),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if self.prefs_warning != warning {
|
||||||
|
if let Some(warning) = &warning {
|
||||||
|
log::warn!("{warning}");
|
||||||
|
}
|
||||||
|
self.prefs_warning = warning;
|
||||||
|
}
|
||||||
self.app.preferences.language = live;
|
self.app.preferences.language = live;
|
||||||
prefs
|
prefs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stored_prefs() -> Result<Option<String>, String> {
|
||||||
|
let storage = web_sys::window()
|
||||||
|
.and_then(|window| window.local_storage().ok().flatten())
|
||||||
|
.ok_or_else(|| "browser settings storage is unavailable".to_owned())?;
|
||||||
|
storage.get_item(&crate::prefs_key()).map_err(|_| "browser settings could not be read".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
/// A settings change goes to the browser's storage in the frame it is made, and from
|
/// A settings change goes to the browser's storage in the frame it is made, and from
|
||||||
/// there to the other open tabs (#8644). Input is what changes settings, so a frame
|
/// there to the other open tabs (#8644). Input is what changes settings, so a frame
|
||||||
/// without any is skipped; eframe's own save still catches anything else.
|
/// without any is skipped; eframe's own save still catches anything else.
|
||||||
@@ -401,7 +424,7 @@ mod web {
|
|||||||
if !self.launch.persists() || ctx.input(|i| i.events.is_empty()) {
|
if !self.launch.persists() || ctx.input(|i| i.events.is_empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let prefs = self.persisted_prefs();
|
let Some(prefs) = self.persisted_prefs(Self::stored_prefs) else { return };
|
||||||
let Some(prefs) = self.prefs_writer.take(prefs) else { return };
|
let Some(prefs) = self.prefs_writer.take(prefs) else { return };
|
||||||
let stored = web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(&crate::prefs_key(), &prefs));
|
let stored = web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(&crate::prefs_key(), &prefs));
|
||||||
if !matches!(stored, Some(Ok(()))) {
|
if !matches!(stored, Some(Ok(()))) {
|
||||||
@@ -415,8 +438,9 @@ mod web {
|
|||||||
fn load_other_tabs_prefs(&mut self) {
|
fn load_other_tabs_prefs(&mut self) {
|
||||||
let Some(prefs) = self.other_tab_prefs.take() else { return };
|
let Some(prefs) = self.other_tab_prefs.take() else { return };
|
||||||
self.app.load_prefs(&prefs);
|
self.app.load_prefs(&prefs);
|
||||||
let held = self.persisted_prefs();
|
if let Some(held) = self.persisted_prefs(Self::stored_prefs) {
|
||||||
self.prefs_writer.loaded(held);
|
self.prefs_writer.loaded(held);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Files from the computer, writes to store, messages and the autosave.
|
/// Files from the computer, writes to store, messages and the autosave.
|
||||||
@@ -612,7 +636,7 @@ mod web {
|
|||||||
/// (when it has a file) and Discard.
|
/// (when it has a file) and Discard.
|
||||||
fn keep_prompt(&mut self, ctx: &egui::Context) {
|
fn keep_prompt(&mut self, ctx: &egui::Context) {
|
||||||
let offered = self.files.keep_offered.borrow().clone();
|
let offered = self.files.keep_offered.borrow().clone();
|
||||||
if offered.is_empty() || !self.files.ready.get() {
|
if offered.is_empty() || !self.files.ready.get() || self.restoring.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let now = (js_sys::Date::now() / 1000.0) as u64;
|
let now = (js_sys::Date::now() / 1000.0) as u64;
|
||||||
@@ -652,24 +676,21 @@ mod web {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.app.open_path(©);
|
// Finished in `restored` once it is in.
|
||||||
if self.app.session.path.as_deref() != Some(copy.as_str()) {
|
let after = keep::AfterOpen::KeptCopy {
|
||||||
// Not opened (the status says why): it stays offered.
|
opened: copy.clone(),
|
||||||
return;
|
id: entry.id.clone(),
|
||||||
}
|
path: entry.path.clone(),
|
||||||
// It is its own design again (saving goes to its file), with its changes unsaved.
|
name: entry.name.clone(),
|
||||||
self.app.session.path = entry.path.clone();
|
drop: plan.drop_copy,
|
||||||
self.app.session.doc_mut().name = entry.name.clone();
|
};
|
||||||
self.app.session.mark_unsaved();
|
self.restore(©, after);
|
||||||
self.app.home.open = false;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(path) = &plan.open_saved {
|
if let Some(path) = &plan.open_saved {
|
||||||
if vfs::exists(path) {
|
if vfs::exists(path) {
|
||||||
self.app.open_path(path);
|
self.restore(path, keep::AfterOpen::KeptSaved { opened: path.clone(), id: entry.id.clone(), drop: plan.drop_copy });
|
||||||
if !keep::is_open(self.app.session.path.as_deref(), path) {
|
return;
|
||||||
// Not opened (the status says why): the copy stays offered.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// A file on the computer: the browser asks for access again, in this click.
|
// A file on the computer: the browser asks for access again, in this click.
|
||||||
// The copy goes once the file has opened, not before: if access is refused,
|
// The copy goes once the file has opened, not before: if access is refused,
|
||||||
@@ -687,8 +708,62 @@ mod web {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if plan.drop_copy {
|
if plan.drop_copy {
|
||||||
self.files.keep_offered.borrow_mut().retain(|e| e.id != entry.id);
|
self.drop_kept(&entry.id);
|
||||||
files::remove_kept(&self.files, &entry.id);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_kept(&self, id: &str) {
|
||||||
|
self.files.keep_offered.borrow_mut().retain(|e| e.id != id);
|
||||||
|
files::remove_kept(&self.files, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a kept copy, its saved design or recovered work as any design opens, a few
|
||||||
|
/// features a frame with progress and Cancel (#10198), so a large design doesn't freeze the
|
||||||
|
/// page; `after` follows once it is in ([`Self::restored`]).
|
||||||
|
fn restore(&mut self, path: &str, after: keep::AfterOpen) {
|
||||||
|
self.restoring = Some((self.app.open_path_answered(path), after));
|
||||||
|
self.restored();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What follows a restore that is in: the design gets back its file and name, and the
|
||||||
|
/// copy or the recovered work it came from goes. One that didn't open (refused or
|
||||||
|
/// cancelled; the status says why) stays offered.
|
||||||
|
fn restored(&mut self) {
|
||||||
|
let Some((rx, _)) = &self.restoring else { return };
|
||||||
|
let reply = match rx.try_recv() {
|
||||||
|
Ok(reply) => reply,
|
||||||
|
Err(std::sync::mpsc::TryRecvError::Empty) => return,
|
||||||
|
Err(std::sync::mpsc::TryRecvError::Disconnected) => serde_json::json!({"ok": false}),
|
||||||
|
};
|
||||||
|
let Some((_, after)) = self.restoring.take() else { return };
|
||||||
|
if !keep::opened(&reply, self.app.session.path.as_deref(), after.opened()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match after {
|
||||||
|
keep::AfterOpen::KeptCopy { id, path, name, drop, .. } => {
|
||||||
|
// It is its own design again (saving goes to its file), with its changes unsaved.
|
||||||
|
self.app.session.path = path;
|
||||||
|
self.app.session.doc_mut().name = name;
|
||||||
|
self.app.session.mark_unsaved();
|
||||||
|
self.app.home.open = false;
|
||||||
|
if drop {
|
||||||
|
self.drop_kept(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keep::AfterOpen::KeptSaved { id, drop, .. } => {
|
||||||
|
if drop {
|
||||||
|
self.drop_kept(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keep::AfterOpen::Recovery { path, name, .. } => {
|
||||||
|
// It belongs to its own file again (saving goes there); it is unsaved.
|
||||||
|
self.app.session.path = path;
|
||||||
|
self.app.session.doc_mut().name = name;
|
||||||
|
self.app.session.mark_unsaved();
|
||||||
|
self.app.home.open = false;
|
||||||
|
*self.files.recovery.borrow_mut() = None;
|
||||||
|
files::autosave(&self.files, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +801,7 @@ mod web {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut act: Option<(&'static str, String)> = None;
|
let mut act: Option<(&'static str, String)> = None;
|
||||||
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_files"), "folder", "Files in this browser")
|
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::unique("sc_browser_files"), "folder", "Files in this browser")
|
||||||
// Room for long names; the kit still caps the window at the browser's width.
|
// Room for long names; the kit still caps the window at the browser's width.
|
||||||
.cards(4.0)
|
.cards(4.0)
|
||||||
.show_body(ctx, |ui| {
|
.show_body(ctx, |ui| {
|
||||||
@@ -828,7 +903,9 @@ mod web {
|
|||||||
self.files.panel.set(false);
|
self.files.panel.set(false);
|
||||||
}
|
}
|
||||||
"insert" => {
|
"insert" => {
|
||||||
self.app.insert_path(&path);
|
if !solidcraft_ui_egui::mesh_units::ask(&mut self.app, &path, true) {
|
||||||
|
self.app.insert_path(&path);
|
||||||
|
}
|
||||||
self.files.panel.set(false);
|
self.files.panel.set(false);
|
||||||
}
|
}
|
||||||
"download" => match vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES) {
|
"download" => match vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES) {
|
||||||
@@ -873,10 +950,16 @@ mod web {
|
|||||||
*r = new.clone();
|
*r = new.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(at) = self.app.home.opened_at.remove(&old) {
|
||||||
|
self.app.home.opened_at.insert(new.clone(), at);
|
||||||
|
}
|
||||||
// A forgotten design follows its new name.
|
// A forgotten design follows its new name.
|
||||||
if self.app.home.forgotten.remove(&old) {
|
if self.app.home.forgotten.remove(&old) {
|
||||||
self.app.home.forgotten.insert(new);
|
self.app.home.forgotten.insert(new.clone());
|
||||||
}
|
}
|
||||||
|
// An open tab of that file keeps its path and, when the name came
|
||||||
|
// from the file, the title that matches the file list (#11375).
|
||||||
|
solidcraft_ui_egui::documents::follow_file_rename(&mut self.app, &old, &new);
|
||||||
}
|
}
|
||||||
Err(e) => self.app.set_status(e, true),
|
Err(e) => self.app.set_status(e, true),
|
||||||
}
|
}
|
||||||
@@ -888,9 +971,12 @@ mod web {
|
|||||||
|
|
||||||
/// Offer the autosave an earlier session left.
|
/// Offer the autosave an earlier session left.
|
||||||
fn recovery_prompt(&mut self, ctx: &egui::Context) {
|
fn recovery_prompt(&mut self, ctx: &egui::Context) {
|
||||||
|
if self.restoring.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some((name, path)) = self.files.recovery.borrow().clone() else { return };
|
let Some((name, path)) = self.files.recovery.borrow().clone() else { return };
|
||||||
let mut answer = None;
|
let mut answer = None;
|
||||||
let _ = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_recovery"), "restore", "Recover unsaved work?")
|
let _ = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::unique("sc_browser_recovery"), "restore", "Recover unsaved work?")
|
||||||
.required()
|
.required()
|
||||||
.show_body(ctx, |ui| {
|
.show_body(ctx, |ui| {
|
||||||
ui.label(format!("“{name}” had unsaved changes when this page last closed."));
|
ui.label(format!("“{name}” had unsaved changes when this page last closed."));
|
||||||
@@ -912,15 +998,10 @@ mod web {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.app.open_path(&recovery);
|
// Finished in `restored` once it is in; until then the prompt stays away.
|
||||||
if self.app.session.path.as_deref() != Some(recovery.as_str()) {
|
let after = keep::AfterOpen::Recovery { opened: recovery.clone(), path, name };
|
||||||
return;
|
self.restore(&recovery, after);
|
||||||
}
|
return;
|
||||||
// It belongs to its own file again (saving goes there); it is unsaved.
|
|
||||||
self.app.session.path = path;
|
|
||||||
self.app.session.doc_mut().name = name;
|
|
||||||
self.app.session.mark_unsaved();
|
|
||||||
self.app.home.open = false;
|
|
||||||
}
|
}
|
||||||
*self.files.recovery.borrow_mut() = None;
|
*self.files.recovery.borrow_mut() = None;
|
||||||
files::autosave(&self.files, None);
|
files::autosave(&self.files, None);
|
||||||
@@ -1064,6 +1145,7 @@ mod web {
|
|||||||
}
|
}
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||||
self.linked.tick(&mut self.app, ui.ctx());
|
self.linked.tick(&mut self.app, ui.ctx());
|
||||||
|
self.restored();
|
||||||
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
||||||
if self.launch.embed {
|
if self.launch.embed {
|
||||||
ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Chrome).edge("embed_header", ui_kit::Edge::Top).show(ui, |ui| {
|
ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Chrome).edge("embed_header", ui_kit::Edge::Top).show(ui, |ui| {
|
||||||
@@ -1096,7 +1178,7 @@ mod web {
|
|||||||
if !self.launch.persists() {
|
if !self.launch.persists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let prefs = self.persisted_prefs();
|
let Some(prefs) = self.persisted_prefs(|| Ok(storage.get_string(&crate::prefs_key()))) else { return };
|
||||||
storage.set_string(&crate::prefs_key(), prefs);
|
storage.set_string(&crate::prefs_key(), prefs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1273,9 +1355,11 @@ mod web {
|
|||||||
space_mouse: SpaceMouse::default(),
|
space_mouse: SpaceMouse::default(),
|
||||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
|
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
|
||||||
prefs_writer: crate::PrefsWriter::default(),
|
prefs_writer: crate::PrefsWriter::default(),
|
||||||
|
prefs_warning: None,
|
||||||
other_tab_prefs,
|
other_tab_prefs,
|
||||||
keep_track: Default::default(),
|
keep_track: Default::default(),
|
||||||
all_kept: false,
|
all_kept: false,
|
||||||
|
restoring: None,
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
|
|||||||
Ok(l) => {
|
Ok(l) => {
|
||||||
if let Some(port) = listen_on_listener(l, token.clone(), tx, ctx, false) {
|
if let Some(port) = listen_on_listener(l, token.clone(), tx, ctx, false) {
|
||||||
if matches!(listen_on, control_auth::Listen::Default) {
|
if matches!(listen_on, control_auth::Listen::Default) {
|
||||||
eprintln!("solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)");
|
solidcraft_engine::note!(
|
||||||
|
"solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// The discovery file: where `solidcraft-cli mcp` finds this app with no arguments.
|
// The discovery file: where `solidcraft-cli mcp` finds this app with no arguments.
|
||||||
match control_auth::write_current_port(port) {
|
match control_auth::write_current_port(port) {
|
||||||
@@ -77,14 +79,14 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
|
|||||||
*p = Some((port, token));
|
*p = Some((port, token));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("solidcraft: could not write the control discovery file ({e})"),
|
Err(e) => solidcraft_engine::note!("solidcraft: could not write the control discovery file ({e})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
|
Err(e) => solidcraft_engine::note!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => eprintln!("solidcraft: control server not started: no random token available"),
|
None => solidcraft_engine::note!("solidcraft: control server not started: no random token available"),
|
||||||
}
|
}
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
@@ -95,13 +97,13 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
|
|||||||
/// requests go to `tx`, where the chat's gate reads them; one may wait long for the user's Allow.
|
/// requests go to `tx`, where the chat's gate reads them; one may wait long for the user's Allow.
|
||||||
pub fn listen_agent(tx: Sender<ControlRequest>, ctx: egui::Context) -> Option<u16> {
|
pub fn listen_agent(tx: Sender<ControlRequest>, ctx: egui::Context) -> Option<u16> {
|
||||||
let Some(token) = new_token() else {
|
let Some(token) = new_token() else {
|
||||||
eprintln!("solidcraft: agent chat endpoint not started: no random token available");
|
solidcraft_engine::note!("solidcraft: agent chat endpoint not started: no random token available");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
|
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
|
||||||
Ok(l) => l,
|
Ok(l) => l,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}");
|
solidcraft_engine::note!("solidcraft: control server failed to bind 127.0.0.1: {e}");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,19 +117,21 @@ fn listen_on_listener(listener: TcpListener, token: String, tx: Sender<ControlRe
|
|||||||
match control_auth::token_path(port) {
|
match control_auth::token_path(port) {
|
||||||
Some(path) => match control_auth::write_token_file(&path, &token) {
|
Some(path) => match control_auth::write_token_file(&path, &token) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
eprintln!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
|
solidcraft_engine::note!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
|
||||||
if let Ok(mut files) = TOKEN_FILES.lock() {
|
if let Ok(mut files) = TOKEN_FILES.lock() {
|
||||||
files.push((path, token.clone()));
|
files.push((path, token.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!(
|
Err(e) => solidcraft_engine::note!(
|
||||||
"solidcraft: control server listening on 127.0.0.1:{port}, but its token file {} could not be written ({e}); \
|
"solidcraft: control server listening on 127.0.0.1:{port}, but its token file {} could not be written ({e}); \
|
||||||
clients need SOLIDCRAFT_CONTROL_TOKEN",
|
clients need SOLIDCRAFT_CONTROL_TOKEN",
|
||||||
path.display()
|
path.display()
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
eprintln!("solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN")
|
solidcraft_engine::note!(
|
||||||
|
"solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let token = std::sync::Arc::new(token);
|
let token = std::sync::Arc::new(token);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ pub fn restart(software: bool, lost_at: u64, reason: &str) {
|
|||||||
pub fn end() -> ! {
|
pub fn end() -> ! {
|
||||||
let watchdog = std::thread::Builder::new().name("solidcraft-exit".into()).spawn(|| {
|
let watchdog = std::thread::Builder::new().name("solidcraft-exit".into()).spawn(|| {
|
||||||
std::thread::sleep(END_GRACE);
|
std::thread::sleep(END_GRACE);
|
||||||
eprintln!("SolidCraft graphics: still closing after the device was lost; aborting");
|
solidcraft_engine::note!("SolidCraft graphics: still closing after the device was lost; aborting");
|
||||||
std::process::abort();
|
std::process::abort();
|
||||||
});
|
});
|
||||||
if let Err(e) = watchdog {
|
if let Err(e) = watchdog {
|
||||||
|
|||||||
+31
-15
@@ -79,7 +79,7 @@ fn mark(step: &str) {
|
|||||||
static START: std::sync::OnceLock<(std::time::Instant, bool)> = std::sync::OnceLock::new();
|
static START: std::sync::OnceLock<(std::time::Instant, bool)> = std::sync::OnceLock::new();
|
||||||
let (start, on) = START.get_or_init(|| (std::time::Instant::now(), solidcraft_engine::geom::env::var_os("PROFILE_STARTUP").is_some()));
|
let (start, on) = START.get_or_init(|| (std::time::Instant::now(), solidcraft_engine::geom::env::var_os("PROFILE_STARTUP").is_some()));
|
||||||
if *on {
|
if *on {
|
||||||
eprintln!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
|
solidcraft_engine::note!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,13 +282,20 @@ impl eframe::App for App {
|
|||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
if self.prefs_writer.due(ui.input(|i| i.events.iter().any(changes_settings)), now)
|
if self.prefs_writer.due(ui.input(|i| i.events.iter().any(changes_settings)), now)
|
||||||
&& let Some(storage) = frame.storage_mut()
|
&& let Some(storage) = frame.storage_mut()
|
||||||
&& let Some(wait) = self.prefs_writer.store(storage, self.solve.prefs(), now)
|
&& let Ok(prefs) = self.prefs_for_storage(storage)
|
||||||
|
&& let Some(wait) = self.prefs_writer.store(storage, prefs, now)
|
||||||
{
|
{
|
||||||
ui.ctx().request_repaint_after(wait);
|
ui.ctx().request_repaint_after(wait);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||||
let prefs = self.solve.prefs();
|
let prefs = match self.prefs_for_storage(storage) {
|
||||||
|
Ok(prefs) => prefs,
|
||||||
|
Err(reason) => {
|
||||||
|
log::warn!("Settings were not written: {reason}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs.clone());
|
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs.clone());
|
||||||
self.prefs_writer.written = Some(prefs);
|
self.prefs_writer.written = Some(prefs);
|
||||||
}
|
}
|
||||||
@@ -361,6 +368,14 @@ struct LostWatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
fn prefs_for_storage(&self, storage: &dyn eframe::Storage) -> Result<String, String> {
|
||||||
|
self.solve.try_prefs().or_else(|reason| {
|
||||||
|
log::warn!("Panel layout changes were not saved: {reason}");
|
||||||
|
let previous = solidcraft_ui_egui::prefs::stored(|key| storage.get_string(key));
|
||||||
|
self.solve.prefs_with_stored_panel_layout(previous.as_deref())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The graphics device was lost (#4172): the window can't draw again, so close, keeping
|
/// The graphics device was lost (#4172): the window can't draw again, so close, keeping
|
||||||
/// unsaved changes for recovery as a forced quit does (the window can't ask about them), and
|
/// unsaved changes for recovery as a forced quit does (the window can't ask about them), and
|
||||||
/// start again once closed ([`device_lost::restart`]).
|
/// start again once closed ([`device_lost::restart`]).
|
||||||
@@ -368,7 +383,7 @@ impl App {
|
|||||||
let Some(gpu) = self.gpu.as_mut().filter(|g| g.lost_at.is_none() && g.lost.is_lost()) else { return };
|
let Some(gpu) = self.gpu.as_mut().filter(|g| g.lost_at.is_none() && g.lost.is_lost()) else { return };
|
||||||
let reason = gpu.lost.reason().unwrap_or_default();
|
let reason = gpu.lost.reason().unwrap_or_default();
|
||||||
log::error!("graphics: the device was lost ({reason}); closing to start again");
|
log::error!("graphics: the device was lost ({reason}); closing to start again");
|
||||||
eprintln!("SolidCraft graphics: the device was lost ({reason}); starting again");
|
solidcraft_engine::note!("SolidCraft graphics: the device was lost ({reason}); starting again");
|
||||||
solidcraft_engine::crash::phase(&format!("graphics device lost: {reason}"));
|
solidcraft_engine::crash::phase(&format!("graphics device lost: {reason}"));
|
||||||
gpu.lost_at = Some(device_lost::now_secs());
|
gpu.lost_at = Some(device_lost::now_secs());
|
||||||
gpu.reason = reason;
|
gpu.reason = reason;
|
||||||
@@ -993,7 +1008,7 @@ fn main() -> eframe::Result {
|
|||||||
if let Some(value) = max_file_size {
|
if let Some(value) = max_file_size {
|
||||||
let applied = startup::max_file_size(value.as_deref()).and_then(|bytes| solidcraft_engine::io::limits::set_override(Some(bytes)));
|
let applied = startup::max_file_size(value.as_deref()).and_then(|bytes| solidcraft_engine::io::limits::set_override(Some(bytes)));
|
||||||
if let Err(error) = applied {
|
if let Err(error) = applied {
|
||||||
eprintln!("SolidCraft: {error}");
|
solidcraft_engine::note!("SolidCraft: {error}");
|
||||||
return Err(eframe::Error::AppCreation(std::io::Error::other(error).into()));
|
return Err(eframe::Error::AppCreation(std::io::Error::other(error).into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1025,7 +1040,7 @@ fn main() -> eframe::Result {
|
|||||||
let fallback_note = std::env::var(startup::FALLBACK_NOTE_ENV).ok().map(|s| s.chars().take(4096).collect::<String>());
|
let fallback_note = std::env::var(startup::FALLBACK_NOTE_ENV).ok().map(|s| s.chars().take(4096).collect::<String>());
|
||||||
if let Some(note) = &fallback_note {
|
if let Some(note) = &fallback_note {
|
||||||
log::warn!("{note}");
|
log::warn!("{note}");
|
||||||
eprintln!("SolidCraft: {note}");
|
solidcraft_engine::note!("SolidCraft: {note}");
|
||||||
notes.push(note.clone());
|
notes.push(note.clone());
|
||||||
}
|
}
|
||||||
let saved_samples = if safe_mode { None } else { msaa() };
|
let saved_samples = if safe_mode { None } else { msaa() };
|
||||||
@@ -1138,13 +1153,13 @@ fn main() -> eframe::Result {
|
|||||||
// Windows: the taskbar button shows the mark even when slow first frames keep the
|
// Windows: the taskbar button shows the mark even when slow first frames keep the
|
||||||
// taskbar waiting for the window's icon.
|
// taskbar waiting for the window's icon.
|
||||||
if !native_frame::set_class_icon(cc) {
|
if !native_frame::set_class_icon(cc) {
|
||||||
eprintln!("SolidCraft window: no class icon; the taskbar may show a generic one");
|
solidcraft_engine::note!("SolidCraft window: no class icon; the taskbar may show a generic one");
|
||||||
}
|
}
|
||||||
// The OS hit test offers the resize border, at the window's DPI, from the first
|
// The OS hit test offers the resize border, at the window's DPI, from the first
|
||||||
// message (#1381).
|
// message (#1381).
|
||||||
app.native_resize_border = custom_titlebar && native_frame::install(cc);
|
app.native_resize_border = custom_titlebar && native_frame::install(cc);
|
||||||
if custom_titlebar && !app.native_resize_border {
|
if custom_titlebar && !app.native_resize_border {
|
||||||
eprintln!("SolidCraft window: no native resize border; egui edge zones instead");
|
solidcraft_engine::note!("SolidCraft window: no native resize border; egui edge zones instead");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.integrated_titlebar = cfg!(target_os = "macos");
|
app.integrated_titlebar = cfg!(target_os = "macos");
|
||||||
@@ -1175,9 +1190,10 @@ fn main() -> eframe::Result {
|
|||||||
.collect();
|
.collect();
|
||||||
let info = rs.adapter.get_info();
|
let info = rs.adapter.get_info();
|
||||||
if !app.preferences.graphics_adapter.is_empty() && !info.name.eq_ignore_ascii_case(app.preferences.graphics_adapter.as_str()) {
|
if !app.preferences.graphics_adapter.is_empty() && !info.name.eq_ignore_ascii_case(app.preferences.graphics_adapter.as_str()) {
|
||||||
eprintln!(
|
solidcraft_engine::note!(
|
||||||
"SolidCraft graphics: preferred adapter {:?} was not used; using {:?} instead",
|
"SolidCraft graphics: preferred adapter {:?} was not used; using {:?} instead",
|
||||||
app.preferences.graphics_adapter, info.name
|
app.preferences.graphics_adapter,
|
||||||
|
info.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let samples = scene_msaa_request(saved_samples);
|
let samples = scene_msaa_request(saved_samples);
|
||||||
@@ -1185,7 +1201,7 @@ fn main() -> eframe::Result {
|
|||||||
eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth)
|
eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth)
|
||||||
});
|
});
|
||||||
let scene_samples = scene_samples(info.backend, u32::from(samples), &supported);
|
let scene_samples = scene_samples(info.backend, u32::from(samples), &supported);
|
||||||
eprintln!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
|
solidcraft_engine::note!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
|
||||||
solidcraft_engine::crash::phase("viewport pipelines (shader compile)");
|
solidcraft_engine::crash::phase("viewport pipelines (shader compile)");
|
||||||
let scene_samples = app.set_wgpu_split(rs, depth_bits, STENCIL_BITS, scene_samples);
|
let scene_samples = app.set_wgpu_split(rs, depth_bits, STENCIL_BITS, scene_samples);
|
||||||
app.msaa_supported = supported;
|
app.msaa_supported = supported;
|
||||||
@@ -1346,7 +1362,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
|
|||||||
let retry = std::env::var(startup::RETRY_BACKEND_ENV).ok();
|
let retry = std::env::var(startup::RETRY_BACKEND_ENV).ok();
|
||||||
let preference = if cpu { preference } else { retry.as_deref().or(preference) };
|
let preference = if cpu { preference } else { retry.as_deref().or(preference) };
|
||||||
if let Some(backends) = startup::choose_backends(cfg!(target_os = "windows"), env_set, cpu, preference, startup::has_hardware_adapter) {
|
if let Some(backends) = startup::choose_backends(cfg!(target_os = "windows"), env_set, cpu, preference, startup::has_hardware_adapter) {
|
||||||
eprintln!("SolidCraft graphics: starting {backends:?}");
|
solidcraft_engine::note!("SolidCraft graphics: starting {backends:?}");
|
||||||
setup.instance_descriptor.backends = backends;
|
setup.instance_descriptor.backends = backends;
|
||||||
}
|
}
|
||||||
let intel_mac = cfg!(all(target_os = "macos", target_arch = "x86_64"));
|
let intel_mac = cfg!(all(target_os = "macos", target_arch = "x86_64"));
|
||||||
@@ -1372,7 +1388,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
|
|||||||
/// event loop or a failed device; each retry advances through the APIs, then software once.
|
/// event loop or a failed device; each retry advances through the APIs, then software once.
|
||||||
fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backends, settings: Option<&std::path::Path>) -> eframe::Result {
|
fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backends, settings: Option<&std::path::Path>) -> eframe::Result {
|
||||||
log::error!("the window could not open: {error}");
|
log::error!("the window could not open: {error}");
|
||||||
eprintln!("SolidCraft: the window could not open: {error}");
|
solidcraft_engine::note!("SolidCraft: the window could not open: {error}");
|
||||||
// This failure was handled, so neither the marker nor the journal should call it a crash.
|
// This failure was handled, so neither the marker nor the journal should call it a crash.
|
||||||
if let Some(dir) = settings {
|
if let Some(dir) = settings {
|
||||||
startup::started(dir);
|
startup::started(dir);
|
||||||
@@ -1385,7 +1401,7 @@ fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backen
|
|||||||
let previous = std::env::var(startup::FALLBACK_NOTE_ENV).unwrap_or_default();
|
let previous = std::env::var(startup::FALLBACK_NOTE_ENV).unwrap_or_default();
|
||||||
let note = startup::fallback_note(&previous, failed, next, &error.to_string());
|
let note = startup::fallback_note(&previous, failed, next, &error.to_string());
|
||||||
log::warn!("{note}");
|
log::warn!("{note}");
|
||||||
eprintln!("SolidCraft: {note}");
|
solidcraft_engine::note!("SolidCraft: {note}");
|
||||||
let mut command = std::process::Command::new(exe);
|
let mut command = std::process::Command::new(exe);
|
||||||
command.args(std::env::args_os().skip(1)).env(startup::FALLBACK_NOTE_ENV, note);
|
command.args(std::env::args_os().skip(1)).env(startup::FALLBACK_NOTE_ENV, note);
|
||||||
if next == "cpu" {
|
if next == "cpu" {
|
||||||
@@ -1791,7 +1807,7 @@ mod viewport_tests {
|
|||||||
viewport.fullscreen = Some(fullscreen);
|
viewport.fullscreen = Some(fullscreen);
|
||||||
viewport.native_pixels_per_point = Some(retina);
|
viewport.native_pixels_per_point = Some(retina);
|
||||||
super::fullscreen_content_insets(&mut raw, 32.0, zoom);
|
super::fullscreen_content_insets(&mut raw, 32.0, zoom);
|
||||||
let _ = ctx.run_ui(raw, |ui| super::content_ui(app, ui));
|
ctx.run_ui(raw, |ui| super::content_ui(app, ui)).textures_delta.clear();
|
||||||
};
|
};
|
||||||
// Enter, leave, and enter again: no stale inset when returning to a normal window.
|
// Enter, leave, and enter again: no stale inset when returning to a normal window.
|
||||||
for fullscreen in [false, true, false, true] {
|
for fullscreen in [false, true, false, true] {
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ mod tests {
|
|||||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), solidcraft_ui_egui::Services::default());
|
let mut app = SolveApp::new(solidcraft_engine::Session::default(), solidcraft_ui_egui::Services::default());
|
||||||
app.home.open = false;
|
app.home.open = false;
|
||||||
let ctx = Context::default();
|
let ctx = Context::default();
|
||||||
let _ = ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |_| {});
|
ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |_| {}).textures_delta.clear();
|
||||||
let mut bridge = NavBridge { shared: Arc::new(Mutex::new(Shared::default())) };
|
let mut bridge = NavBridge { shared: Arc::new(Mutex::new(Shared::default())) };
|
||||||
let queue = |bridge: &NavBridge| {
|
let queue = |bridge: &NavBridge| {
|
||||||
let mut shared = bridge.shared.lock().unwrap();
|
let mut shared = bridge.shared.lock().unwrap();
|
||||||
|
|||||||
@@ -1914,6 +1914,7 @@ mod tests {
|
|||||||
|
|
||||||
fn wheel() -> egui::Event {
|
fn wheel() -> egui::Event {
|
||||||
egui::Event::MouseWheel {
|
egui::Event::MouseWheel {
|
||||||
|
source: egui::MouseWheelSource::Unknown,
|
||||||
unit: egui::MouseWheelUnit::Line,
|
unit: egui::MouseWheelUnit::Line,
|
||||||
delta: egui::vec2(0.0, -1.0),
|
delta: egui::vec2(0.0, -1.0),
|
||||||
modifiers: Default::default(),
|
modifiers: Default::default(),
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ pub fn relaunch_with_version_compat() -> Result<bool, String> {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
log::info!("startup: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
log::info!("startup: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
||||||
eprintln!("SolidCraft: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
solidcraft_engine::note!("SolidCraft: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
||||||
let exe = std::env::current_exe().map_err(|e| format!("could not find own executable: {e}"))?;
|
let exe = std::env::current_exe().map_err(|e| format!("could not find own executable: {e}"))?;
|
||||||
let status = std::process::Command::new(&exe)
|
let status = std::process::Command::new(&exe)
|
||||||
.args(std::env::args_os().skip(1))
|
.args(std::env::args_os().skip(1))
|
||||||
|
|||||||
+4
-2
@@ -23,7 +23,7 @@ assets/
|
|||||||
appearances/library.rs # built-in visual presets
|
appearances/library.rs # built-in visual presets
|
||||||
materials/library.rs # physical defaults and colours
|
materials/library.rs # physical defaults and colours
|
||||||
shaders/viewport.wgsl # original viewport shader
|
shaders/viewport.wgsl # original viewport shader
|
||||||
fonts/{liberation-sans,biz-udp-gothic}/ # unmodified fonts and OFL licences
|
fonts/biz-udp-gothic/ # unmodified font and OFL licence
|
||||||
locales/{en,de,ja,ko}.json # shared offline catalogs
|
locales/{en,de,ja,ko}.json # shared offline catalogs
|
||||||
platforms/{macos,linux,windows}/ # bundle, freedesktop and WiX templates
|
platforms/{macos,linux,windows}/ # bundle, freedesktop and WiX templates
|
||||||
platforms/macos/dmg/ # disk image window: layout, dmgbuild settings, background art
|
platforms/macos/dmg/ # disk image window: layout, dmgbuild settings, background art
|
||||||
@@ -87,7 +87,9 @@ The CLI still supports arbitrary PNG/ICO exports with `solidcraft-cli icon`.
|
|||||||
|
|
||||||
Resources remain compiled by their owning layer through existing APIs:
|
Resources remain compiled by their owning layer through existing APIs:
|
||||||
`ui-egui::icons::paint_icon` and `Tokens` at L5, model presets in L2 `doc`, and sketch
|
`ui-egui::icons::paint_icon` and `Tokens` at L5, model presets in L2 `doc`, and sketch
|
||||||
fonts at L1. State, projection, interaction, commands and GPU bindings stay in
|
fonts at L1. The sketch crate owns its built-in Liberation Sans font and OFL
|
||||||
|
licence under `crates/sketch/assets/fonts/liberation-sans/`, so Cargo source
|
||||||
|
packages and vendored builds include both files. State, projection, interaction, commands and GPU bindings stay in
|
||||||
their current modules. Embedded data uses `CARGO_MANIFEST_DIR`; application code
|
their current modules. Embedded data uses `CARGO_MANIFEST_DIR`; application code
|
||||||
needs no runtime resource registry or repository lookup.
|
needs no runtime resource registry or repository lookup.
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
|
|||||||
"Smooth" => "c_smooth",
|
"Smooth" => "c_smooth",
|
||||||
"Equal" => "c_equal",
|
"Equal" => "c_equal",
|
||||||
"Concentric" => "c_concentric",
|
"Concentric" => "c_concentric",
|
||||||
"MidPoint" => "c_midpoint",
|
"MidPoint" | "PointAlong" => "c_midpoint",
|
||||||
"Symmetry" => "c_symmetry",
|
"Symmetry" => "c_symmetry",
|
||||||
"Fix" => "c_fix",
|
"Fix" => "c_fix",
|
||||||
_ => return None,
|
_ => return None,
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ pub(crate) fn face_text(painter: &egui::Painter, face: &Facet, ink: Color32, fon
|
|||||||
painter.ctx().add_plugin(LabelAtlas);
|
painter.ctx().add_plugin(LabelAtlas);
|
||||||
let text = Shape::Text(egui::epaint::TextShape::new(Pos2::ZERO, galley, ink));
|
let text = Shape::Text(egui::epaint::TextShape::new(Pos2::ZERO, galley, ink));
|
||||||
let atlas = painter.ctx().fonts(|f| f.font_image_size());
|
let atlas = painter.ctx().fonts(|f| f.font_image_size());
|
||||||
let primitives =
|
let primitives = painter.ctx().tessellate(vec![egui::epaint::ClippedShape::new(Rect::EVERYTHING, text)], painter.ctx().pixels_per_point());
|
||||||
painter.ctx().tessellate(vec![egui::epaint::ClippedShape { clip_rect: Rect::EVERYTHING, shape: text }], painter.ctx().pixels_per_point());
|
|
||||||
for primitive in primitives {
|
for primitive in primitives {
|
||||||
if let egui::epaint::Primitive::Mesh(mut mesh) = primitive.primitive {
|
if let egui::epaint::Primitive::Mesh(mut mesh) = primitive.primitive {
|
||||||
mesh.texture_id = LABEL_PIXELS;
|
mesh.texture_id = LABEL_PIXELS;
|
||||||
|
|||||||
+550
-22
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Učitava putanje SVG crteža u skicu, skalirane, zakrenute i postavljene gdje zadate. Koristite ga da logotip ili vektorski obris pretvorite u krive koje možete ekstrudirati.",
|
"help.sketch.insert_svg": "Učitava putanje SVG crteža u skicu, skalirane, zakrenute i postavljene gdje zadate. Koristite ga da logotip ili vektorski obris pretvorite u krive koje možete ekstrudirati.",
|
||||||
"help.sketch.inspect": "Otvara izvještaj o otvorenoj skici: koliko je stepeni slobode preostalo i je li potpuno ograničena. Koristite ga da saznate šta se još pomjera prije nego što završite skicu.",
|
"help.sketch.inspect": "Otvara izvještaj o otvorenoj skici: koliko je stepeni slobode preostalo i je li potpuno ograničena. Koristite ga da saznate šta se još pomjera prije nego što završite skicu.",
|
||||||
"help.sketch.intersect": "Dodaje krive ili tačke gdje tijela, lica, ivice ili druge skice sijeku ravan skice. Ostaju povezane i prate model kada se promijeni.",
|
"help.sketch.intersect": "Dodaje krive ili tačke gdje tijela, lica, ivice ili druge skice sijeku ravan skice. Ostaju povezane i prate model kada se promijeni.",
|
||||||
"help.sketch.intersection_curve": "Pravi 3D krive tamo gdje se dva tijela ili lica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
|
"help.sketch.intersection_curve": "Pravi 3D krive tamo gdje se dva tijela, lica ili krive drugih skica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
|
||||||
"help.sketch.isoparametric_curve": "Pravi 3D krivu u smjeru U ili V lica kroz tačku koju izaberete, povezanu s licem. Koristite je kao putanju ili liniju razdvajanja koja prati površ.",
|
"help.sketch.isoparametric_curve": "Pravi 3D krivu u smjeru U ili V lica kroz tačku koju izaberete, povezanu s licem. Koristite je kao putanju ili liniju razdvajanja koja prati površ.",
|
||||||
"help.sketch.line": "Crta povezane linije tačku po tačku, kao otvoreni lanac ili zatvoren obris. To je osnovni alat za većinu profila.",
|
"help.sketch.line": "Crta povezane linije tačku po tačku, kao otvoreni lanac ili zatvoren obris. To je osnovni alat za većinu profila.",
|
||||||
"help.sketch.line.midpoint": "Crta liniju od njene središnje tačke do jednog kraja, a drugi kraj se preslikava. Koristite je za linije centrirane na tački, npr. simetričnu ivicu.",
|
"help.sketch.line.midpoint": "Crta liniju od njene središnje tačke do jednog kraja, a drugi kraj se preslikava. Koristite je za linije centrirane na tački, npr. simetričnu ivicu.",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+354
-176
File diff suppressed because it is too large
Load Diff
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Læser stierne i en SVG-tegning ind i en skitse, skaleret, drejet og placeret, som du angiver. Brug den til at gøre et logo eller en vektorkontur til kurver, du kan ekstrudere.",
|
"help.sketch.insert_svg": "Læser stierne i en SVG-tegning ind i en skitse, skaleret, drejet og placeret, som du angiver. Brug den til at gøre et logo eller en vektorkontur til kurver, du kan ekstrudere.",
|
||||||
"help.sketch.inspect": "Åbner en rapport om den åbne skitse: hvor mange frihedsgrader der er tilbage, og om den er fuldt bundet. Brug den til at finde ud af, hvad der stadig kan bevæge sig, før du afslutter skitsen.",
|
"help.sketch.inspect": "Åbner en rapport om den åbne skitse: hvor mange frihedsgrader der er tilbage, og om den er fuldt bundet. Brug den til at finde ud af, hvad der stadig kan bevæge sig, før du afslutter skitsen.",
|
||||||
"help.sketch.intersect": "Tilføjer de kurver eller punkter, hvor emner, flader, kanter eller andre skitser krydser skitseplanet. De forbliver linkede og følger modellen, når den ændres.",
|
"help.sketch.intersect": "Tilføjer de kurver eller punkter, hvor emner, flader, kanter eller andre skitser krydser skitseplanet. De forbliver linkede og følger modellen, når den ændres.",
|
||||||
"help.sketch.intersection_curve": "Opretter 3D-kurver, hvor to emner eller flader mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
|
"help.sketch.intersection_curve": "Opretter 3D-kurver, hvor to emner, flader eller kurver fra andre skitser mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
|
||||||
"help.sketch.isoparametric_curve": "Opretter en 3D-kurve langs en flades U- eller V-retning gennem et punkt, du vælger, linket til fladen. Brug den som bane eller delelinje, der følger overfladen.",
|
"help.sketch.isoparametric_curve": "Opretter en 3D-kurve langs en flades U- eller V-retning gennem et punkt, du vælger, linket til fladen. Brug den som bane eller delelinje, der følger overfladen.",
|
||||||
"help.sketch.line": "Tegner forbundne linjer punkt for punkt som en åben kæde eller en lukket kontur. Det er det grundlæggende værktøj til de fleste profiler.",
|
"help.sketch.line": "Tegner forbundne linjer punkt for punkt som en åben kæde eller en lukket kontur. Det er det grundlæggende værktøj til de fleste profiler.",
|
||||||
"help.sketch.line.midpoint": "Tegner en linje fra dens midtpunkt ud til den ene ende, og den anden ende spejles. Brug den til linjer centreret om et punkt, f.eks. en symmetrisk kant.",
|
"help.sketch.line.midpoint": "Tegner en linje fra dens midtpunkt ud til den ene ende, og den anden ende spejles. Brug den til linjer centreret om et punkt, f.eks. en symmetrisk kant.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Eksempelplade",
|
"sample.plate.name": "Eksempelplade",
|
||||||
"sample.vents.about": "Slidser i et rektangulært mønster: Rækker, kolonner og deling er parametre",
|
"sample.vents.about": "Slidser i et rektangulært mønster: Rækker, kolonner og deling er parametre",
|
||||||
"sample.vents.name": "Ventilationsplade",
|
"sample.vents.name": "Ventilationsplade",
|
||||||
|
"scale.per_axis": "Per akse",
|
||||||
|
"scale.uniform": "Ensartet",
|
||||||
"search.command_keys": "Enter kør Shift+Enter kør og hold åben Esc luk",
|
"search.command_keys": "Enter kør Shift+Enter kør og hold åben Esc luk",
|
||||||
"search.commands": "Kommandoer",
|
"search.commands": "Kommandoer",
|
||||||
"search.design": "I dette design",
|
"search.design": "I dette design",
|
||||||
|
|||||||
+339
-21
@@ -206,6 +206,7 @@
|
|||||||
"Edge": "Kante",
|
"Edge": "Kante",
|
||||||
"Edges": "Kanten",
|
"Edges": "Kanten",
|
||||||
"Edit": "Bearbeiten",
|
"Edit": "Bearbeiten",
|
||||||
|
"Edit Holes": "Bohrungen bearbeiten",
|
||||||
"Edit Profile Sketch": "Profile-Skizze bearbeiten",
|
"Edit Profile Sketch": "Profile-Skizze bearbeiten",
|
||||||
"Edit shortcuts": "Tastenkürzel bearbeiten",
|
"Edit shortcuts": "Tastenkürzel bearbeiten",
|
||||||
"Enable rule": "Regel aktivieren",
|
"Enable rule": "Regel aktivieren",
|
||||||
@@ -264,8 +265,8 @@
|
|||||||
"Flat size": "Abwicklungsgröße",
|
"Flat size": "Abwicklungsgröße",
|
||||||
"Flat size X": "Abwicklungsmaß X",
|
"Flat size X": "Abwicklungsmaß X",
|
||||||
"Flat size Y": "Abwicklungsmaß Y",
|
"Flat size Y": "Abwicklungsmaß Y",
|
||||||
"Follow camera (Look At sketches)": "Kamera folgen (Skizzen ansehen)",
|
"Follow camera (Look At sketches)": "Kamera folgen (senkrecht auf Skizzen)",
|
||||||
"Follow the camera (Look At sketches)": "Der Kamera folgen (Skizzen ansehen)",
|
"Follow the camera (Look At sketches)": "Der Kamera folgen (senkrecht auf Skizzen)",
|
||||||
"Font": "Schriftart",
|
"Font": "Schriftart",
|
||||||
"Free Move": "Frei verschieben",
|
"Free Move": "Frei verschieben",
|
||||||
"From": "Von",
|
"From": "Von",
|
||||||
@@ -568,6 +569,7 @@
|
|||||||
"Select edges": "Kanten auswählen",
|
"Select edges": "Kanten auswählen",
|
||||||
"Select points of the sketch being edited": "Punkte der bearbeiteten Skizze auswählen",
|
"Select points of the sketch being edited": "Punkte der bearbeiteten Skizze auswählen",
|
||||||
"Select the centre the pattern turns around first": "Zuerst den Mittelpunkt auswählen, um den sich das Muster dreht",
|
"Select the centre the pattern turns around first": "Zuerst den Mittelpunkt auswählen, um den sich das Muster dreht",
|
||||||
|
"Select the curves to divide first": "Zuerst die zu teilenden Kurven auswählen",
|
||||||
"Select the curves to offset first": "Zuerst die zu versetzenden Kurven auswählen",
|
"Select the curves to offset first": "Zuerst die zu versetzenden Kurven auswählen",
|
||||||
"Select the mirror line": "Die Spiegellinie auswählen",
|
"Select the mirror line": "Die Spiegellinie auswählen",
|
||||||
"Select the sketch curves or points to mirror first": "Zuerst die zu spiegelnden Skizzenkurven oder -punkte auswählen",
|
"Select the sketch curves or points to mirror first": "Zuerst die zu spiegelnden Skizzenkurven oder -punkte auswählen",
|
||||||
@@ -628,6 +630,8 @@
|
|||||||
"Suppress Selected Folders": "Ausgewählte Ordner unterdrücken",
|
"Suppress Selected Folders": "Ausgewählte Ordner unterdrücken",
|
||||||
"Suppressed": "Unterdrückt",
|
"Suppressed": "Unterdrückt",
|
||||||
"Surface": "Flächenkörper",
|
"Surface": "Flächenkörper",
|
||||||
|
"Surface or face": "Flächenkörper oder Fläche",
|
||||||
|
"Surface or faces": "Flächenkörper oder Flächen",
|
||||||
"Surface, or faces to turn over": "Fläche oder umzudrehende Flächen",
|
"Surface, or faces to turn over": "Fläche oder umzudrehende Flächen",
|
||||||
"Surfaces": "Flächen",
|
"Surfaces": "Flächen",
|
||||||
"Swap Ends": "Enden vertauschen",
|
"Swap Ends": "Enden vertauschen",
|
||||||
@@ -641,6 +645,7 @@
|
|||||||
"Target": "Ziel",
|
"Target": "Ziel",
|
||||||
"Target Body": "Zielkörper",
|
"Target Body": "Zielkörper",
|
||||||
"Target Face (optional)": "Zielfläche (optional)",
|
"Target Face (optional)": "Zielfläche (optional)",
|
||||||
|
"Target edges": "Zielkanten",
|
||||||
"Tear": "Reißen",
|
"Tear": "Reißen",
|
||||||
"Technical details": "Technische Details",
|
"Technical details": "Technische Details",
|
||||||
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "Die gestrichelte Skizze ist eine gespeicherte Ebenenführung. Wählen Sie eine neue Fläche, um die Anbindung zu reparieren.",
|
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "Die gestrichelte Skizze ist eine gespeicherte Ebenenführung. Wählen Sie eine neue Fläche, um die Anbindung zu reparieren.",
|
||||||
@@ -648,6 +653,7 @@
|
|||||||
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Die Richtung muss eine Achse oder eine gerade Skizzenkurve in der Skizzenebene sein",
|
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Die Richtung muss eine Achse oder eine gerade Skizzenkurve in der Skizzenebene sein",
|
||||||
"The mirror line cannot also be mirrored": "Die Spiegellinie kann nicht gleichzeitig gespiegelt werden",
|
"The mirror line cannot also be mirrored": "Die Spiegellinie kann nicht gleichzeitig gespiegelt werden",
|
||||||
"The mirror line must be a straight curve of this sketch, not an origin axis": "Die Spiegellinie muss eine gerade Kurve dieser Skizze sein, keine Ursprungsachse",
|
"The mirror line must be a straight curve of this sketch, not an origin axis": "Die Spiegellinie muss eine gerade Kurve dieser Skizze sein, keine Ursprungsachse",
|
||||||
|
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Die Ursprungsachse steht senkrecht auf der Skizzenebene und hat daher keine Linie in ihr",
|
||||||
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Das gespeicherte Autorvorkommen fehlt. Wählen Sie vor dem erneuten Anbinden eine gültige Autorplatzierung.",
|
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Das gespeicherte Autorvorkommen fehlt. Wählen Sie vor dem erneuten Anbinden eine gültige Autorplatzierung.",
|
||||||
"Then": "Then",
|
"Then": "Then",
|
||||||
"These features will be deleted:": "Diese Merkmale werden gelöscht:",
|
"These features will be deleted:": "Diese Merkmale werden gelöscht:",
|
||||||
@@ -689,6 +695,7 @@
|
|||||||
"Unsuppress Selected Folders": "Unterdrückung der ausgewählten Ordner aufheben",
|
"Unsuppress Selected Folders": "Unterdrückung der ausgewählten Ordner aufheben",
|
||||||
"Updating the preview": "Vorschau wird aktualisiert",
|
"Updating the preview": "Vorschau wird aktualisiert",
|
||||||
"Use active author placement": "Aktive Autorplatzierung verwenden",
|
"Use active author placement": "Aktive Autorplatzierung verwenden",
|
||||||
|
"Use axis direction only": "Nur Achsenrichtung verwenden",
|
||||||
"Use fixed plane": "Feste Ebene verwenden",
|
"Use fixed plane": "Feste Ebene verwenden",
|
||||||
"Use saved fixed plane": "Gespeicherte feste Ebene verwenden",
|
"Use saved fixed plane": "Gespeicherte feste Ebene verwenden",
|
||||||
"Use selected face as fixed plane": "Ausgewählte Fläche als feste Ebene verwenden",
|
"Use selected face as fixed plane": "Ausgewählte Fläche als feste Ebene verwenden",
|
||||||
@@ -739,6 +746,9 @@
|
|||||||
"a picked edge's body is gone": "der Körper einer gewählten Kante existiert nicht mehr",
|
"a picked edge's body is gone": "der Körper einer gewählten Kante existiert nicht mehr",
|
||||||
"add a key for a joint first": "zuerst einen Schlüssel für ein Gelenk hinzufügen",
|
"add a key for a joint first": "zuerst einen Schlüssel für ein Gelenk hinzufügen",
|
||||||
"ai.connected_toast": "SolidCraft ist jetzt in {clients} verfügbar. Starten Sie diese neu, um es zu nutzen (Einstellungen > Assistent).",
|
"ai.connected_toast": "SolidCraft ist jetzt in {clients} verfügbar. Starten Sie diese neu, um es zu nutzen (Einstellungen > Assistent).",
|
||||||
|
"ai.http.allow": "Unsicheres HTTP erlauben",
|
||||||
|
"ai.http.confirm": "Für diesen Endpunkt erlauben",
|
||||||
|
"ai.http.warning": "Der API-Schlüssel und die Unterhaltung werden unverschlüsselt gesendet. Jeder mit Zugriff auf das Netzwerk kann sie lesen oder verändern. Verwenden Sie dies nur in einem vertrauenswürdigen Netzwerk.",
|
||||||
"ai.prefs.allow_control": "KI-Agenten verbinden lassen",
|
"ai.prefs.allow_control": "KI-Agenten verbinden lassen",
|
||||||
"ai.prefs.allow_control_note": "Ein lokaler Kanal (MCP). Nichts verlässt diesen Computer.\n\nÖffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
|
"ai.prefs.allow_control_note": "Ein lokaler Kanal (MCP). Nichts verlässt diesen Computer.\n\nÖffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
|
||||||
"ai.prefs.availability": "Verfügbarkeit",
|
"ai.prefs.availability": "Verfügbarkeit",
|
||||||
@@ -784,6 +794,7 @@
|
|||||||
"browser.calibrate_click_a": "Klicken Sie im Bild auf die Position von Punkt A oder geben Sie dessen Koordinaten ein.",
|
"browser.calibrate_click_a": "Klicken Sie im Bild auf die Position von Punkt A oder geben Sie dessen Koordinaten ein.",
|
||||||
"browser.calibrate_click_b": "Klicken Sie im Bild auf die Position von Punkt B oder geben Sie dessen Koordinaten ein.",
|
"browser.calibrate_click_b": "Klicken Sie im Bild auf die Position von Punkt B oder geben Sie dessen Koordinaten ein.",
|
||||||
"browser.calibrate_title": "Kalibrieren: {name}",
|
"browser.calibrate_title": "Kalibrieren: {name}",
|
||||||
|
"browser.canvas_locked": "Die Leinwand ist gesperrt. Entsperren Sie sie zuerst.",
|
||||||
"browser.capture_many": "{count} Komponenten wurden verschoben, ihre Positionen sind aber nicht erfasst.",
|
"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_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.capture_one": "Eine Komponente wurde verschoben, ihre Position ist aber nicht erfasst.",
|
||||||
@@ -1028,6 +1039,7 @@
|
|||||||
"cmd.occurrence_move": "Instanz verschieben",
|
"cmd.occurrence_move": "Instanz verschieben",
|
||||||
"cmd.occurrence_properties": "Physikalische Eigenschaften der Instanz",
|
"cmd.occurrence_properties": "Physikalische Eigenschaften der Instanz",
|
||||||
"cmd.occurrence_reparent": "In andere Komponente verschieben",
|
"cmd.occurrence_reparent": "In andere Komponente verschieben",
|
||||||
|
"cmd.panel.action": "Panelaktion ausführen",
|
||||||
"cmd.panel.activate": "Panel-Registerkarte aktivieren",
|
"cmd.panel.activate": "Panel-Registerkarte aktivieren",
|
||||||
"cmd.panel.close": "Panel schließen",
|
"cmd.panel.close": "Panel schließen",
|
||||||
"cmd.panel.collapse": "Panel-Bereich einklappen",
|
"cmd.panel.collapse": "Panel-Bereich einklappen",
|
||||||
@@ -1071,9 +1083,15 @@
|
|||||||
"cmd.pointcloud_edit": "Punktwolke bearbeiten",
|
"cmd.pointcloud_edit": "Punktwolke bearbeiten",
|
||||||
"cmd.pointcloud_insert": "Punktwolke einfügen",
|
"cmd.pointcloud_insert": "Punktwolke einfügen",
|
||||||
"cmd.pointcloud_list": "Punktwolken",
|
"cmd.pointcloud_list": "Punktwolken",
|
||||||
|
"cmd.pointcloud_reload": "Punktwolke neu laden",
|
||||||
|
"cmd.render.coat.bind": "Klarlack speichern",
|
||||||
|
"cmd.render.coat.unbind": "Klarlack entfernen",
|
||||||
"cmd.render.compare.divider": "Render-Vergleichstrenner verschieben",
|
"cmd.render.compare.divider": "Render-Vergleichstrenner verschieben",
|
||||||
"cmd.render.result.discard": "Renderergebnis verwerfen",
|
"cmd.render.result.discard": "Renderergebnis verwerfen",
|
||||||
"cmd.render.result.select": "Renderergebnis auswählen",
|
"cmd.render.result.select": "Renderergebnis auswählen",
|
||||||
|
"cmd.render.surface.import": "Oberflächentexturen importieren",
|
||||||
|
"cmd.render.texture.list": "Eingebettete Oberflächenbilder auflisten",
|
||||||
|
"cmd.render.texture.remove": "Eingebettetes Oberflächenbild entfernen",
|
||||||
"cmd.render_aging_describe": "Mehrschichtige Oberflächenalterung",
|
"cmd.render_aging_describe": "Mehrschichtige Oberflächenalterung",
|
||||||
"cmd.render_backends": "Render-Backends",
|
"cmd.render_backends": "Render-Backends",
|
||||||
"cmd.render_batch": "Alle Aufnahmen rendern",
|
"cmd.render_batch": "Alle Aufnahmen rendern",
|
||||||
@@ -1160,6 +1178,7 @@
|
|||||||
"cmd.sketch.construction_line.tangent_external": "Tangente durch äußeren Punkt",
|
"cmd.sketch.construction_line.tangent_external": "Tangente durch äußeren Punkt",
|
||||||
"cmd.sketch.construction_line.tangent_two": "Gemeinsame Tangente",
|
"cmd.sketch.construction_line.tangent_two": "Gemeinsame Tangente",
|
||||||
"cmd.sketch.construction_line.vertical": "Vertikale Konstruktionslinie",
|
"cmd.sketch.construction_line.vertical": "Vertikale Konstruktionslinie",
|
||||||
|
"cmd.sketch.replace": "Skizze ersetzen",
|
||||||
"cmd.sketch_arc3d": "3D-Bogen",
|
"cmd.sketch_arc3d": "3D-Bogen",
|
||||||
"cmd.sketch_arc_center_point": "Mittelpunkt-Kreisbogen",
|
"cmd.sketch_arc_center_point": "Mittelpunkt-Kreisbogen",
|
||||||
"cmd.sketch_arc_tangent": "Tangentialbogen",
|
"cmd.sketch_arc_tangent": "Tangentialbogen",
|
||||||
@@ -1216,6 +1235,7 @@
|
|||||||
"cmd.sketch_options": "Skizzenpalettenoptionen",
|
"cmd.sketch_options": "Skizzenpalettenoptionen",
|
||||||
"cmd.sketch_paste": "Skizzenelemente einfügen",
|
"cmd.sketch_paste": "Skizzenelemente einfügen",
|
||||||
"cmd.sketch_point3d": "3D-Punkt",
|
"cmd.sketch_point3d": "3D-Punkt",
|
||||||
|
"cmd.sketch_point_divide": "Kurve teilen",
|
||||||
"cmd.sketch_polygon_circumscribed": "Umgeschriebenes Polygon",
|
"cmd.sketch_polygon_circumscribed": "Umgeschriebenes Polygon",
|
||||||
"cmd.sketch_polygon_edge": "Polygon über eine Kante",
|
"cmd.sketch_polygon_edge": "Polygon über eine Kante",
|
||||||
"cmd.sketch_polygon_inscribed": "Eingeschriebenes Polygon",
|
"cmd.sketch_polygon_inscribed": "Eingeschriebenes Polygon",
|
||||||
@@ -1293,6 +1313,7 @@
|
|||||||
"cmd.view.render_browser": "Render-Browser",
|
"cmd.view.render_browser": "Render-Browser",
|
||||||
"cmd.view.render_dock": "Render-Panel verschieben",
|
"cmd.view.render_dock": "Render-Panel verschieben",
|
||||||
"cmd.view.render_focus": "Render-Fokusansicht",
|
"cmd.view.render_focus": "Render-Fokusansicht",
|
||||||
|
"cmd.view.render_live": "Live-Rendering pausieren oder fortsetzen",
|
||||||
"cmd.view.status_details": "Statusdetails des Ansichtsfensters",
|
"cmd.view.status_details": "Statusdetails des Ansichtsfensters",
|
||||||
"cmd.view.status_items": "Ansichtsstatus-Elemente",
|
"cmd.view.status_items": "Ansichtsstatus-Elemente",
|
||||||
"cmd.view_ambient_occlusion": "Umgebungsverdeckung",
|
"cmd.view_ambient_occlusion": "Umgebungsverdeckung",
|
||||||
@@ -1322,6 +1343,7 @@
|
|||||||
"cmd.view_shots_strip": "Aufnahmenleiste",
|
"cmd.view_shots_strip": "Aufnahmenleiste",
|
||||||
"cmd.view_spin": "Ansicht kontinuierlich drehen",
|
"cmd.view_spin": "Ansicht kontinuierlich drehen",
|
||||||
"cmd.view_tool": "Navigationswerkzeug",
|
"cmd.view_tool": "Navigationswerkzeug",
|
||||||
|
"cmd.view_tool_zoom": "Durch Ziehen zoomen",
|
||||||
"cmd.view_top": "Draufsicht",
|
"cmd.view_top": "Draufsicht",
|
||||||
"cmd.view_visual_style": "Darstellungsstil",
|
"cmd.view_visual_style": "Darstellungsstil",
|
||||||
"color.blue": "Blau",
|
"color.blue": "Blau",
|
||||||
@@ -1446,8 +1468,11 @@
|
|||||||
"file.open.portal_fallback.xwayland": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Unter XWayland lehnt das Portal das Elternfenster ab.",
|
"file.open.portal_fallback.xwayland": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Unter XWayland lehnt das Portal das Elternfenster ab.",
|
||||||
"file_load.built": "Bisher erstellt: {built} von {total} Merkmalen.",
|
"file_load.built": "Bisher erstellt: {built} von {total} Merkmalen.",
|
||||||
"file_load.converting": "Volumenkörper {done} von {total} wird konvertiert",
|
"file_load.converting": "Volumenkörper {done} von {total} wird konvertiert",
|
||||||
|
"file_load.inserted": "{name} eingefügt",
|
||||||
"file_load.inserting": "Geometrie wird eingefügt",
|
"file_load.inserting": "Geometrie wird eingefügt",
|
||||||
"file_load.link_item": "{name} ({done} von {total})",
|
"file_load.link_item": "{name} ({done} von {total})",
|
||||||
|
"file_load.opened": "{name} geöffnet",
|
||||||
|
"file_load.opened_in_tab": "{name} in neuem Tab geöffnet",
|
||||||
"file_load.opening": "Design wird geöffnet",
|
"file_load.opening": "Design wird geöffnet",
|
||||||
"file_load.preparing_thumbnail": "Vorschaubild wird vorbereitet...",
|
"file_load.preparing_thumbnail": "Vorschaubild wird vorbereitet...",
|
||||||
"file_load.preparing_view": "Ansicht wird vorbereitet...",
|
"file_load.preparing_view": "Ansicht wird vorbereitet...",
|
||||||
@@ -1456,6 +1481,7 @@
|
|||||||
"file_load.reading_percent": "Datei wird gelesen... {percent} %",
|
"file_load.reading_percent": "Datei wird gelesen... {percent} %",
|
||||||
"file_load.rebuilding": "Merkmal {done} von {total} wird neu aufgebaut",
|
"file_load.rebuilding": "Merkmal {done} von {total} wird neu aufgebaut",
|
||||||
"file_load.recovering": "Design wird wiederhergestellt",
|
"file_load.recovering": "Design wird wiederhergestellt",
|
||||||
|
"file_load.reloading_cloud": "Punktwolke wird neu geladen",
|
||||||
"file_load.step_build": "Modell wird aufgebaut",
|
"file_load.step_build": "Modell wird aufgebaut",
|
||||||
"file_load.step_feature": "Merkmal {done} von {total}",
|
"file_load.step_feature": "Merkmal {done} von {total}",
|
||||||
"file_load.step_part": "Teil {done} von {total}",
|
"file_load.step_part": "Teil {done} von {total}",
|
||||||
@@ -1683,7 +1709,7 @@
|
|||||||
"help.sketch.insert_svg": "Liest die Pfade einer SVG-Zeichnung in eine Skizze ein, skaliert, gedreht und an der gewünschten Stelle. Damit machen Sie aus einem Logo oder einer Vektorkontur Kurven, die sich extrudieren lassen.",
|
"help.sketch.insert_svg": "Liest die Pfade einer SVG-Zeichnung in eine Skizze ein, skaliert, gedreht und an der gewünschten Stelle. Damit machen Sie aus einem Logo oder einer Vektorkontur Kurven, die sich extrudieren lassen.",
|
||||||
"help.sketch.inspect": "Öffnet einen Bericht zur offenen Skizze: wie viele Freiheitsgrade übrig sind und ob sie vollständig bestimmt ist. Damit sehen Sie, was sich noch bewegt, bevor Sie die Skizze abschließen.",
|
"help.sketch.inspect": "Öffnet einen Bericht zur offenen Skizze: wie viele Freiheitsgrade übrig sind und ob sie vollständig bestimmt ist. Damit sehen Sie, was sich noch bewegt, bevor Sie die Skizze abschließen.",
|
||||||
"help.sketch.intersect": "Fügt die Kurven oder Punkte hinzu, an denen Körper, Flächen, Kanten oder andere Skizzen die Skizzenebene schneiden. Sie bleiben verknüpft und folgen Änderungen am Modell.",
|
"help.sketch.intersect": "Fügt die Kurven oder Punkte hinzu, an denen Körper, Flächen, Kanten oder andere Skizzen die Skizzenebene schneiden. Sie bleiben verknüpft und folgen Änderungen am Modell.",
|
||||||
"help.sketch.intersection_curve": "Erzeugt 3D-Kurven, wo sich zwei Körper oder Flächen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
|
"help.sketch.intersection_curve": "Erzeugt 3D-Kurven, wo sich zwei Körper, Flächen oder Kurven anderer Skizzen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
|
||||||
"help.sketch.isoparametric_curve": "Erzeugt eine 3D-Kurve in U- oder V-Richtung einer Fläche durch einen gewählten Punkt, mit der Fläche verknüpft. Als Pfad oder Trennlinie, die der Fläche folgt.",
|
"help.sketch.isoparametric_curve": "Erzeugt eine 3D-Kurve in U- oder V-Richtung einer Fläche durch einen gewählten Punkt, mit der Fläche verknüpft. Als Pfad oder Trennlinie, die der Fläche folgt.",
|
||||||
"help.sketch.line": "Zeichnet verbundene Linien Punkt für Punkt, als offene Kette oder geschlossene Kontur. Das Grundwerkzeug für die meisten Profile.",
|
"help.sketch.line": "Zeichnet verbundene Linien Punkt für Punkt, als offene Kette oder geschlossene Kontur. Das Grundwerkzeug für die meisten Profile.",
|
||||||
"help.sketch.line.midpoint": "Zeichnet eine Linie von ihrem Mittelpunkt zu einem Ende, das andere Ende gespiegelt. Für Linien, die auf einem Punkt mittig liegen, etwa eine symmetrische Kante.",
|
"help.sketch.line.midpoint": "Zeichnet eine Linie von ihrem Mittelpunkt zu einem Ende, das andere Ende gespiegelt. Für Linien, die auf einem Punkt mittig liegen, etwa eine symmetrische Kante.",
|
||||||
@@ -1695,6 +1721,7 @@
|
|||||||
"help.sketch.pattern.circular": "Kopiert Skizzengeometrie um einen Mittelpunkt, über einen vollen Kreis oder einen festen Winkel. Für Lochkreise, Speichen und Zähne.",
|
"help.sketch.pattern.circular": "Kopiert Skizzengeometrie um einen Mittelpunkt, über einen vollen Kreis oder einen festen Winkel. Für Lochkreise, Speichen und Zähne.",
|
||||||
"help.sketch.pattern.rectangular": "Kopiert Skizzengeometrie in Reihen und Spalten mit festen Abständen. Für Raster aus Bohrungen, Schlitzen oder Lüftungsöffnungen.",
|
"help.sketch.pattern.rectangular": "Kopiert Skizzengeometrie in Reihen und Spalten mit festen Abständen. Für Raster aus Bohrungen, Schlitzen oder Lüftungsöffnungen.",
|
||||||
"help.sketch.point": "Setzt einen Skizzenpunkt. Als Bezug für Maße, Bedingungen, Bohrungsmitten oder spätere Elemente.",
|
"help.sketch.point": "Setzt einen Skizzenpunkt. Als Bezug für Maße, Bedingungen, Bohrungsmitten oder spätere Elemente.",
|
||||||
|
"help.sketch.point.divide": "Setzt Punkte, die die gewählten Kurven in gleich lange Abschnitte teilen. Die Punkte bleiben auf jeder Kurve und behalten ihren Abstand, wenn sie sich ändert.",
|
||||||
"help.sketch.point3d": "Setzt bei eingeschalteter 3D-Skizze einen Punkt im Raum. Als Bezug für 3D-Kurven und Pfade.",
|
"help.sketch.point3d": "Setzt bei eingeschalteter 3D-Skizze einen Punkt im Raum. Als Bezug für 3D-Kurven und Pfade.",
|
||||||
"help.sketch.polygon.circumscribed": "Zeichnet ein regelmäßiges Polygon um einen Kreis, bemessen vom Mittelpunkt zur Mitte einer Seite. Wenn die Schlüsselweite bekannt ist, etwa bei einer Sechskantmutter.",
|
"help.sketch.polygon.circumscribed": "Zeichnet ein regelmäßiges Polygon um einen Kreis, bemessen vom Mittelpunkt zur Mitte einer Seite. Wenn die Schlüsselweite bekannt ist, etwa bei einer Sechskantmutter.",
|
||||||
"help.sketch.polygon.edge": "Zeichnet ein regelmäßiges Polygon aus den beiden Enden einer Seite. Wenn Länge und Lage einer Seite bekannt sind.",
|
"help.sketch.polygon.edge": "Zeichnet ein regelmäßiges Polygon aus den beiden Enden einer Seite. Wenn Länge und Lage einer Seite bekannt sind.",
|
||||||
@@ -1794,6 +1821,7 @@
|
|||||||
"help.view.visual_style": "Schaltet um, wie das Modell gezeichnet wird: schattiert mit oder ohne Kanten, Drahtmodell oder mit verdeckten Kanten. Drahtmodell zum Hineinschauen, schattiert für ein sauberes Bild.",
|
"help.view.visual_style": "Schaltet um, wie das Modell gezeichnet wird: schattiert mit oder ohne Kanten, Drahtmodell oder mit verdeckten Kanten. Drahtmodell zum Hineinschauen, schattiert für ein sauberes Bild.",
|
||||||
"history.sketch_under_constrained": "Unterbestimmt ({count} Freiheitsgrade)",
|
"history.sketch_under_constrained": "Unterbestimmt ({count} Freiheitsgrade)",
|
||||||
"history.sketch_under_constrained_one": "Unterbestimmt ({count} Freiheitsgrad)",
|
"history.sketch_under_constrained_one": "Unterbestimmt ({count} Freiheitsgrad)",
|
||||||
|
"hole_folder.applies_to": "Änderungen gelten für die {count} Bohrungen in {folder}; jede behält ihre Position.",
|
||||||
"hole_presets.confirm_delete": "Löschen bestätigen",
|
"hole_presets.confirm_delete": "Löschen bestätigen",
|
||||||
"hole_presets.delete": "Löschen",
|
"hole_presets.delete": "Löschen",
|
||||||
"hole_presets.delete_prompt": "Möchten Sie diese persönliche Vorlage wirklich löschen? Bereits vorhandene Bohrungen bleiben unverändert.",
|
"hole_presets.delete_prompt": "Möchten Sie diese persönliche Vorlage wirklich löschen? Bereits vorhandene Bohrungen bleiben unverändert.",
|
||||||
@@ -2123,7 +2151,7 @@
|
|||||||
"navbar.ground_shadow": "Bodenschatten",
|
"navbar.ground_shadow": "Bodenschatten",
|
||||||
"navbar.home": "Start",
|
"navbar.home": "Start",
|
||||||
"navbar.later": "Später",
|
"navbar.later": "Später",
|
||||||
"navbar.look_at": "Ansehen",
|
"navbar.look_at": "Senkrecht auf",
|
||||||
"navbar.more": "Mehr",
|
"navbar.more": "Mehr",
|
||||||
"navbar.orbit": "Drehen",
|
"navbar.orbit": "Drehen",
|
||||||
"navbar.orthographic": "Orthografisch",
|
"navbar.orthographic": "Orthografisch",
|
||||||
@@ -2143,6 +2171,7 @@
|
|||||||
"navbar.value_tip": "{name} · {value}",
|
"navbar.value_tip": "{name} · {value}",
|
||||||
"navbar.visual_style": "Darstellung",
|
"navbar.visual_style": "Darstellung",
|
||||||
"navbar.zoom": "Zoomen",
|
"navbar.zoom": "Zoomen",
|
||||||
|
"navbar.zoom_hold_tip": "Zoomen (Tastenkürzel gedrückt halten)",
|
||||||
"needs two joints": "benötigt zwei Gelenke",
|
"needs two joints": "benötigt zwei Gelenke",
|
||||||
"network.allow_once": "Einmalig erlauben",
|
"network.allow_once": "Einmalig erlauben",
|
||||||
"network.always_allow": "Diesen Server immer erlauben",
|
"network.always_allow": "Diesen Server immer erlauben",
|
||||||
@@ -2293,6 +2322,7 @@
|
|||||||
"prefs.carousel_previous": "Zurück",
|
"prefs.carousel_previous": "Zurück",
|
||||||
"prefs.choose_font": "Schriftdatei auswählen…",
|
"prefs.choose_font": "Schriftdatei auswählen…",
|
||||||
"prefs.click": "Klicken",
|
"prefs.click": "Klicken",
|
||||||
|
"prefs.command_dialog_values": "Befehlsdialoge",
|
||||||
"prefs.contact_shading": "Weiche Kontaktschattierung",
|
"prefs.contact_shading": "Weiche Kontaktschattierung",
|
||||||
"prefs.contact_shading_note": "Erzeugt weiche Schatten an Stellen, an denen sich Bauteile nahekommen.",
|
"prefs.contact_shading_note": "Erzeugt weiche Schatten an Stellen, an denen sich Bauteile nahekommen.",
|
||||||
"prefs.context_menu": "Kontextmenü",
|
"prefs.context_menu": "Kontextmenü",
|
||||||
@@ -2316,6 +2346,8 @@
|
|||||||
"prefs.design_tools.double_click_help": "Oder zum Bearbeiten einer verknüpften Komponente an Ort und Stelle.",
|
"prefs.design_tools.double_click_help": "Oder zum Bearbeiten einer verknüpften Komponente an Ort und Stelle.",
|
||||||
"prefs.design_tools.ghost": "Alte Position verschobener Teile als Geisterbild zeigen",
|
"prefs.design_tools.ghost": "Alte Position verschobener Teile als Geisterbild zeigen",
|
||||||
"prefs.design_tools.hole_moves_help": "Erneutes Auswählen der Fläche verschiebt die Bohrung, statt sie zu entfernen.",
|
"prefs.design_tools.hole_moves_help": "Erneutes Auswählen der Fläche verschiebt die Bohrung, statt sie zu entfernen.",
|
||||||
|
"prefs.design_tools.hole_several": "Mehrere Bohrungen auf einer Fläche",
|
||||||
|
"prefs.design_tools.hole_several_help": "Eine Auswahl an anderer Stelle der Fläche fügt eine weitere Bohrung hinzu; die Auswahl einer Bohrung entfernt sie.",
|
||||||
"prefs.design_tools.hole_snaps_help": "Die Mitte einer Fläche und die Mittelpunkte ihrer kreisförmigen Kanten.",
|
"prefs.design_tools.hole_snaps_help": "Die Mitte einer Fläche und die Mittelpunkte ihrer kreisförmigen Kanten.",
|
||||||
"prefs.design_tools.imported_files": "Importierte Dateien",
|
"prefs.design_tools.imported_files": "Importierte Dateien",
|
||||||
"prefs.design_tools.note": "Hinweis",
|
"prefs.design_tools.note": "Hinweis",
|
||||||
@@ -2339,6 +2371,8 @@
|
|||||||
"prefs.edges": "Kanten und Schattierung",
|
"prefs.edges": "Kanten und Schattierung",
|
||||||
"prefs.extend": "Erweitern",
|
"prefs.extend": "Erweitern",
|
||||||
"prefs.extra_navigation": "Mit Strg und gedrückter linker Maustaste auf einer freien Fläche drehen. Leertaste gedrückt halten, um die Ansicht zu verschieben.",
|
"prefs.extra_navigation": "Mit Strg und gedrückter linker Maustaste auf einer freien Fläche drehen. Leertaste gedrückt halten, um die Ansicht zu verschieben.",
|
||||||
|
"prefs.extrude_arrow_face_stops": "Extrusionspfeil hält an Flächen",
|
||||||
|
"prefs.extrude_arrow_face_stops_note": "Beim Ziehen des Abstandspfeils von Extrusion hält er an ebenen Flächen parallel zum Profil, etwa an der Rückseite eines Blocks, in den geschnitten wird, und hebt die Fläche hervor. Aus: Der Pfeil bewegt sich nur in runden Schritten.",
|
||||||
"prefs.extrude_view_isometric": "Beim Öffnen von Extrudieren isometrisch ausrichten",
|
"prefs.extrude_view_isometric": "Beim Öffnen von Extrudieren isometrisch ausrichten",
|
||||||
"prefs.extrude_view_return": "Beim Schließen von Extrudieren zur vorigen Ansicht zurück",
|
"prefs.extrude_view_return": "Beim Schließen von Extrudieren zur vorigen Ansicht zurück",
|
||||||
"prefs.follows_os": "Wie im Betriebssystem",
|
"prefs.follows_os": "Wie im Betriebssystem",
|
||||||
@@ -2388,9 +2422,15 @@
|
|||||||
"prefs.graphics_page.surfaces_edges": "Flächen und Kanten",
|
"prefs.graphics_page.surfaces_edges": "Flächen und Kanten",
|
||||||
"prefs.graphics_page.view_bar": "Auch in der Ansichtsleiste",
|
"prefs.graphics_page.view_bar": "Auch in der Ansichtsleiste",
|
||||||
"prefs.graphics_presets_note": "Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
|
"prefs.graphics_presets_note": "Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
|
||||||
|
"prefs.ground_shadow_follows_model": "Bodenschatten folgt dem Modell",
|
||||||
|
"prefs.ground_shadow_follows_model_help": "Den Schatten unter dem niedrigsten sichtbaren Körper platzieren. Wenn deaktiviert, bleibt der Schatten angehobener Modelle auf der Bodenebene durch den Ursprung.",
|
||||||
"prefs.hide_constraint_glyphs_zoomed_out": "Bindungssymbole bei starkem Herauszoomen ausblenden",
|
"prefs.hide_constraint_glyphs_zoomed_out": "Bindungssymbole bei starkem Herauszoomen ausblenden",
|
||||||
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
|
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
|
||||||
"prefs.history": "Verlauf",
|
"prefs.history": "Verlauf",
|
||||||
|
"prefs.history_bar_groups": "Verlaufsleiste in Gruppen falten",
|
||||||
|
"prefs.history_bar_groups_note": "In einem langen Verlauf wird eine Folge von Features einer Komponente zu einem Chip gefaltet, und Merkmalsordner beginnen gefaltet. Eine eingefügte STEP-Baugruppe kommt in einen Group-Ordner. Aus zeigt jedes Feature, öffnet Ordner und legt keinen Ordner an.",
|
||||||
|
"prefs.history_group_joints": "Gelenke im Verlauf gruppieren",
|
||||||
|
"prefs.history_group_joints_note": "Zwei oder mehr Gelenke erscheinen als eine Schaltfläche mit ihrer Anzahl; ein Klick zeigt jedes einzeln. Aus zeigt jedes Gelenk einzeln.",
|
||||||
"prefs.history_origin_bodies": "Ursprung und Körper oben zeigen",
|
"prefs.history_origin_bodies": "Ursprung und Körper oben zeigen",
|
||||||
"prefs.history_origin_bodies_note": "Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
|
"prefs.history_origin_bodies_note": "Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
|
||||||
"prefs.history_play_button": "Schaltfläche „Verlauf abspielen“ in den Verlaufssteuerelementen",
|
"prefs.history_play_button": "Schaltfläche „Verlauf abspielen“ in den Verlaufssteuerelementen",
|
||||||
@@ -2423,6 +2463,8 @@
|
|||||||
"prefs.layout_page.card": "Karte",
|
"prefs.layout_page.card": "Karte",
|
||||||
"prefs.layout_page.column": "Spalte",
|
"prefs.layout_page.column": "Spalte",
|
||||||
"prefs.layout_page.hover": "Bei Mauszeiger",
|
"prefs.layout_page.hover": "Bei Mauszeiger",
|
||||||
|
"prefs.layout_page.hover_panel_menus": "Bereichsmenüs beim Darüberfahren wechseln",
|
||||||
|
"prefs.layout_page.hover_panel_menus_help": "Klicken Sie auf einen Bereichsnamen, um das Menü zu öffnen. Bewegen Sie den Zeiger auf einen anderen Bereichsnamen, um das Menü zu wechseln. Außerhalb des Menübands und des geöffneten Menüs schließt es sich.",
|
||||||
"prefs.layout_page.icons_labels": "Symbole + Beschriftungen",
|
"prefs.layout_page.icons_labels": "Symbole + Beschriftungen",
|
||||||
"prefs.layout_page.ok_cancel": "OK und Abbrechen",
|
"prefs.layout_page.ok_cancel": "OK und Abbrechen",
|
||||||
"prefs.layout_page.on_hover": "Bei Mauszeiger",
|
"prefs.layout_page.on_hover": "Bei Mauszeiger",
|
||||||
@@ -2461,6 +2503,8 @@
|
|||||||
"prefs.mouse": "Maus und Trackpad",
|
"prefs.mouse": "Maus und Trackpad",
|
||||||
"prefs.mouse_heading": "Maus",
|
"prefs.mouse_heading": "Maus",
|
||||||
"prefs.mouse_mapping": "Mausbelegung",
|
"prefs.mouse_mapping": "Mausbelegung",
|
||||||
|
"prefs.move_drag_highlights": "Hervorheben, was unter dem Zeiger liegt, während die Pfeile von Verschieben eine Komponente ziehen",
|
||||||
|
"prefs.move_drag_highlights_tip": "Aus: Solange die Pfeile von Verschieben eine Komponente ziehen, wird nichts unter dem Zeiger hervorgehoben; so bleiben große Baugruppen flüssig. Mit einem Schnitt oder einer Analyse wird es trotzdem hervorgehoben, und ein entlang seiner Gelenke gezogenes Teil hebt nie etwas hervor.",
|
||||||
"prefs.navigation": "Navigation",
|
"prefs.navigation": "Navigation",
|
||||||
"prefs.navigation.axes_signed": "Achsen",
|
"prefs.navigation.axes_signed": "Achsen",
|
||||||
"prefs.navigation.cube_compact": "Würfel",
|
"prefs.navigation.cube_compact": "Würfel",
|
||||||
@@ -2484,6 +2528,7 @@
|
|||||||
"prefs.origin_plane_names": "Namen der Ursprungsebenen",
|
"prefs.origin_plane_names": "Namen der Ursprungsebenen",
|
||||||
"prefs.origin_plane_names_note": "Namen in Browser, Dialogen und Listen. {top} ist XY, {front} ist XZ und {right} ist YZ, mit Z nach oben. Befehle, Skripte und gespeicherte Konstruktionen behalten XY, XZ und YZ.",
|
"prefs.origin_plane_names_note": "Namen in Browser, Dialogen und Listen. {top} ist XY, {front} ist XZ und {right} ist YZ, mit Z nach oben. Befehle, Skripte und gespeicherte Konstruktionen behalten XY, XZ und YZ.",
|
||||||
"prefs.overall_speed": "Gesamtgeschwindigkeit",
|
"prefs.overall_speed": "Gesamtgeschwindigkeit",
|
||||||
|
"prefs.panel_layout.recovery": "Gespeicherte Panel-Layouts konnten nicht wiederhergestellt werden. Die gespeicherten Daten bleiben erhalten; Klassisch bleibt verfügbar.",
|
||||||
"prefs.parameter_list_rows": "Zeilen der Parameterliste",
|
"prefs.parameter_list_rows": "Zeilen der Parameterliste",
|
||||||
"prefs.parameter_list_rows_tip": "Maximale Anzahl der Parameter, die Pfeil abwärts in einem leeren Ausdrucksfeld auflistet.",
|
"prefs.parameter_list_rows_tip": "Maximale Anzahl der Parameter, die Pfeil abwärts in einem leeren Ausdrucksfeld auflistet.",
|
||||||
"prefs.persistent_ribbon_pressed": "Aktive Menübandbefehle hervorheben",
|
"prefs.persistent_ribbon_pressed": "Aktive Menübandbefehle hervorheben",
|
||||||
@@ -2525,6 +2570,10 @@
|
|||||||
"prefs.quality_smoother": "Stärker geglättet",
|
"prefs.quality_smoother": "Stärker geglättet",
|
||||||
"prefs.quick_measure": "Schnellmessung in der Ecke der Ansicht",
|
"prefs.quick_measure": "Schnellmessung in der Ecke der Ansicht",
|
||||||
"prefs.quick_measure_note": "Zeigt unten rechts in der Ansicht die Größe der Auswahl an: bei einer Kante die Länge, bei einer kreisförmigen Kante zusätzlich Durchmesser (Ø) und Radius (R), bei einer Fläche den Flächeninhalt, bei einem Punkt die Position, bei zwei Elementen Abstand und Winkel sowie bei mehreren Kanten oder Flächen die Gesamtlänge bzw. Gesamtfläche. Ein Klick kopiert den Messwert.",
|
"prefs.quick_measure_note": "Zeigt unten rechts in der Ansicht die Größe der Auswahl an: bei einer Kante die Länge, bei einer kreisförmigen Kante zusätzlich Durchmesser (Ø) und Radius (R), bei einer Fläche den Flächeninhalt, bei einem Punkt die Position, bei zwei Elementen Abstand und Winkel sowie bei mehreren Kanten oder Flächen die Gesamtlänge bzw. Gesamtfläche. Ein Klick kopiert den Messwert.",
|
||||||
|
"prefs.remember_dialog_values": "Befehlsdialogwerte merken",
|
||||||
|
"prefs.remember_dialog_values.help": "Standardmäßig aus. Wenn aktiviert, werden nach erfolgreicher Ausführung eines Befehls seine sicheren Werte und Optionen beim nächsten Öffnen desselben Befehls wiederverwendet, bis SolidCraft geschlossen wird.",
|
||||||
|
"prefs.render_legacy_image_defaults": "Bisherige Render-Bildvorgaben verwenden",
|
||||||
|
"prefs.render_legacy_image_defaults_help": "Den bisherigen manuellen Ablauf beibehalten: neue Setups mit CPU-Raster, feste Abtastung beim CPU-Raytracing und manuell geöffnete Ergebnisse. Beim Wechsel zu Render die Ansicht neu einpassen, Aufnahmen ohne Wiederherstellung ihrer Kamera wechseln und die bisherige Bildausrichtung und Navigation verwenden. Automatische Live-Bildrückmeldungen deaktivieren. Gespeicherte Setups bleiben unverändert.",
|
||||||
"prefs.render_previous_geometry_budget": "Bisheriges Geometriebudget für die Render-Vorschau verwenden",
|
"prefs.render_previous_geometry_budget": "Bisheriges Geometriebudget für die Render-Vorschau verwenden",
|
||||||
"prefs.render_previous_geometry_budget_help": "Stellt die bisherige feste Obergrenze für die Geometrie wieder her (512 MiB auf 64-Bit-Desktop-Systemen, andernfalls 128 MiB) anstelle des geräteabhängigen Budgets.",
|
"prefs.render_previous_geometry_budget_help": "Stellt die bisherige feste Obergrenze für die Geometrie wieder her (512 MiB auf 64-Bit-Desktop-Systemen, andernfalls 128 MiB) anstelle des geräteabhängigen Budgets.",
|
||||||
"prefs.reports_saved": "{n} gespeichert",
|
"prefs.reports_saved": "{n} gespeichert",
|
||||||
@@ -2568,6 +2617,8 @@
|
|||||||
"prefs.sketch.drawing": "Zeichnen",
|
"prefs.sketch.drawing": "Zeichnen",
|
||||||
"prefs.sketch.ellipse_axes": "Ellipsen mit ihren Achsen zeichnen",
|
"prefs.sketch.ellipse_axes": "Ellipsen mit ihren Achsen zeichnen",
|
||||||
"prefs.sketch.ellipse_axes_note": "Haupt- und Nebenachse als Konstruktionslinien, damit Halbachsen und Achsen bemaßt werden können. Aus: nur die Ellipse, und D darauf ergibt ihren Nebenradius, wohin der Wert auch gezogen wird, gezeichnet und mit R beschriftet wie bisher.",
|
"prefs.sketch.ellipse_axes_note": "Haupt- und Nebenachse als Konstruktionslinien, damit Halbachsen und Achsen bemaßt werden können. Aus: nur die Ellipse, und D darauf ergibt ihren Nebenradius, wohin der Wert auch gezogen wird, gezeichnet und mit R beschriftet wie bisher.",
|
||||||
|
"prefs.sketch.escape_twice_finishes": "Doppeltes Esc beendet die Skizze",
|
||||||
|
"prefs.sketch.escape_twice_finishes_note": "Zweimal schnell Esc drücken beendet die Skizze. Aus: Esc bricht nur das Werkzeug oder die Auswahl ab, und Skizze beenden schließt die Skizze.",
|
||||||
"prefs.sketch.exit_keep": "Beibehalten",
|
"prefs.sketch.exit_keep": "Beibehalten",
|
||||||
"prefs.sketch.exit_previous": "Zur Ansicht davor zurückkehren",
|
"prefs.sketch.exit_previous": "Zur Ansicht davor zurückkehren",
|
||||||
"prefs.sketch.exit_unless_rotated": "Außer bei Drehung",
|
"prefs.sketch.exit_unless_rotated": "Außer bei Drehung",
|
||||||
@@ -2601,8 +2652,9 @@
|
|||||||
"prefs.sketch_exit_view_note": "Legt fest, zu welcher Ansicht nach „Skizze beenden“, „Abbrechen“ oder zweimaligem Drücken der Esc-Taste gewechselt wird.",
|
"prefs.sketch_exit_view_note": "Legt fest, zu welcher Ansicht nach „Skizze beenden“, „Abbrechen“ oder zweimaligem Drücken der Esc-Taste gewechselt wird.",
|
||||||
"prefs.sketch_face_activates_part": "Skizzieren auf der Fläche eines Teils aktiviert dieses Teil",
|
"prefs.sketch_face_activates_part": "Skizzieren auf der Fläche eines Teils aktiviert dieses Teil",
|
||||||
"prefs.sketch_face_activates_part_note": "Eine Skizze, die auf einer Fläche am Körper einer anderen Komponente begonnen wird, aktiviert zuerst diese Komponente, sodass die Skizze zu ihr gehört und im Browser bei diesem Teil bleibt. Das Teil bleibt danach aktiv. Aus: Die Skizze gehört zur aktiven Komponente.",
|
"prefs.sketch_face_activates_part_note": "Eine Skizze, die auf einer Fläche am Körper einer anderen Komponente begonnen wird, aktiviert zuerst diese Komponente, sodass die Skizze zu ihr gehört und im Browser bei diesem Teil bleibt. Das Teil bleibt danach aktiv. Aus: Die Skizze gehört zur aktiven Komponente.",
|
||||||
|
"prefs.sketch_mirror_auto_line": "Vorausgewählte Konstruktionslinie zum Spiegeln verwenden",
|
||||||
"prefs.sketch_open_look_at": "Beim Öffnen einer Skizze auf sie ausrichten",
|
"prefs.sketch_open_look_at": "Beim Öffnen einer Skizze auf sie ausrichten",
|
||||||
"prefs.sketch_open_look_at_note": "Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Ansehen“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
|
"prefs.sketch_open_look_at_note": "Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Senkrecht auf“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
|
||||||
"prefs.sketch_recent_snap": "Automatisch an zuletzt verwendete Mittelpunkte fangen",
|
"prefs.sketch_recent_snap": "Automatisch an zuletzt verwendete Mittelpunkte fangen",
|
||||||
"prefs.sketch_recent_snap_note": "Fängt den Zeiger beim Zeichnen dort ein, wo sich die Hilfslinien der beiden zuletzt verwendeten Kreismittelpunkte oder Kantenmitten kreuzen. Aus: die Hilfslinien bleiben sichtbar und der Zeiger bewegt sich nicht.",
|
"prefs.sketch_recent_snap_note": "Fängt den Zeiger beim Zeichnen dort ein, wo sich die Hilfslinien der beiden zuletzt verwendeten Kreismittelpunkte oder Kantenmitten kreuzen. Aus: die Hilfslinien bleiben sichtbar und der Zeiger bewegt sich nicht.",
|
||||||
"prefs.sketch_selection": "Skizzen und Auswahl",
|
"prefs.sketch_selection": "Skizzen und Auswahl",
|
||||||
@@ -2652,6 +2704,8 @@
|
|||||||
"prefs.structure.work": "Arbeit",
|
"prefs.structure.work": "Arbeit",
|
||||||
"prefs.supersampling_brief": "Rendert die Ansicht größer und verkleinert sie dann: sehr scharf, aber rechenintensiver.",
|
"prefs.supersampling_brief": "Rendert die Ansicht größer und verkleinert sie dann: sehr scharf, aber rechenintensiver.",
|
||||||
"prefs.tab_accent_note": "Die Akzentfarbe richtet sich nach dem aktiven Tab.",
|
"prefs.tab_accent_note": "Die Akzentfarbe richtet sich nach dem aktiven Tab.",
|
||||||
|
"prefs.tab_double_click_renames": "Entwurfsregisterkarte per Doppelklick umbenennen",
|
||||||
|
"prefs.tab_double_click_renames_note": "Geben Sie den neuen Namen ein und drücken Sie Enter oder klicken Sie woanders hin, um ihn zu übernehmen, oder Esc zum Abbrechen. Aus: Ein Doppelklick auf eine Registerkarte maximiert das Fenster, wie auf dem leeren Teil der Leiste.",
|
||||||
"prefs.theme_file": "Designdatei",
|
"prefs.theme_file": "Designdatei",
|
||||||
"prefs.theme_file_or_paste": "Datei oder Einfügen",
|
"prefs.theme_file_or_paste": "Datei oder Einfügen",
|
||||||
"prefs.theme_follow": "Einer Designdatei folgen",
|
"prefs.theme_follow": "Einer Designdatei folgen",
|
||||||
@@ -2748,12 +2802,15 @@
|
|||||||
"refused.join": "Konnte nicht mit {body} vereinigt werden.",
|
"refused.join": "Konnte nicht mit {body} vereinigt werden.",
|
||||||
"refused.repair": "Teil reparieren",
|
"refused.repair": "Teil reparieren",
|
||||||
"refused.show_face": "Fläche zeigen",
|
"refused.show_face": "Fläche zeigen",
|
||||||
|
"render.adaptive.pilot_progress": "Adaptive Pilotphase · {current}/{target} Durchgänge",
|
||||||
|
"render.adaptive.preview_help": "Pilotbilder bestimmen die Verteilung und sind vom Endbild ausgeschlossen. Produktionsiterationen nutzen ein eigenes Budget pro Pixel; die Iterationszahl ist keine einheitliche Samplezahl pro Pixel.",
|
||||||
|
"render.adaptive.production_progress": "Adaptive Produktion · {current}/{target} Iterationen",
|
||||||
"render.adaptive_absolute": "Absolute RGB-Fehleruntergrenze",
|
"render.adaptive_absolute": "Absolute RGB-Fehleruntergrenze",
|
||||||
"render.adaptive_coverage": "Zielwert für den Abdeckungsfehler",
|
"render.adaptive_coverage": "Zielwert für den Abdeckungsfehler",
|
||||||
"render.adaptive_enable": "Samples mithilfe unabhängiger Pilot-Samples zuteilen",
|
"render.adaptive_enable": "Samples mithilfe unabhängiger Pilot-Samples zuteilen",
|
||||||
"render.adaptive_help": "Nur GPU. Unabhängige Pilot-Samples werden verworfen. Anschließend werden pro Pixel 16 oder mehr Produktions-Samples zugeteilt, bis zu den Samples pro Pixel. Die Zuteilung richtet sich nach der RGB- und Abdeckungsvarianz; seltene Pfade können übersehen werden, und Pixel, die ihr Limit erreicht haben, können verrauscht bleiben. Der Abschlussbericht zählt den tatsächlichen Aufwand. Keine zertifizierte Garantie für ein bestimmtes Rauschniveau oder eine bestimmte Renderzeit.",
|
"render.adaptive_help": "Unabhängige Pilotsamples werden verworfen und bestimmen dann mindestens 16 Produktionssamples pro Pixel bis zum Wert Samples pro Pixel. CPU-Ressourcengrenzen umfassen beide Phasen. RGB- und Abdeckungsvarianz steuern die Verteilung; Pixel am Limit können verrauscht bleiben. Der Abschlussbericht zählt die tatsächlichen Samples.",
|
||||||
"render.adaptive_minimum": "Minimale Produktions-Samples",
|
"render.adaptive_minimum": "Minimale Produktions-Samples",
|
||||||
"render.adaptive_pending": "Die adaptive Zuteilung mit unabhängigen GPU-Pilot-Samples und bedingte Fresnel-Schichten sind verfügbar. Zertifizierte Konvergenz, Garantien für seltene Ereignisse und adaptiver Lichttransport auf der CPU bleiben nicht verfügbar.",
|
"render.adaptive_pending": "Adaptive Verteilung mit unabhängigen Pilotsamples ist für CPU und GPU verfügbar. Bedingte Fresnel-Schichten erfordern die GPU. Die statistische Verteilung bestätigt keine Konvergenz.",
|
||||||
"render.adaptive_pilots": "Pilot-Samples pro Pixel",
|
"render.adaptive_pilots": "Pilot-Samples pro Pixel",
|
||||||
"render.adaptive_relative": "Zielwert für den relativen RGB-Fehler",
|
"render.adaptive_relative": "Zielwert für den relativen RGB-Fehler",
|
||||||
"render.add_ground": "Add fitted ground to setup",
|
"render.add_ground": "Add fitted ground to setup",
|
||||||
@@ -2776,6 +2833,7 @@
|
|||||||
"render.aging_metallic": "Metallanteil der Schicht",
|
"render.aging_metallic": "Metallanteil der Schicht",
|
||||||
"render.aging_origin": "Feldursprung · Weltkoordinaten (mm)",
|
"render.aging_origin": "Feldursprung · Weltkoordinaten (mm)",
|
||||||
"render.aging_patina": "Kupfergrüne Patina",
|
"render.aging_patina": "Kupfergrüne Patina",
|
||||||
|
"render.aging_raytrace_help": "Alterung wird beim CPU-Raytracing und GPU-Pathtracing angewendet. Raster bleibt eine schematische Darstellung.",
|
||||||
"render.aging_refresh_targets": "Aktuelle Körper prüfen",
|
"render.aging_refresh_targets": "Aktuelle Körper prüfen",
|
||||||
"render.aging_roughness": "Rauheit der Schicht",
|
"render.aging_roughness": "Rauheit der Schicht",
|
||||||
"render.aging_rust": "Oxidiertes Eisen / Rost",
|
"render.aging_rust": "Oxidiertes Eisen / Rost",
|
||||||
@@ -2796,6 +2854,7 @@
|
|||||||
"render.appearance_editor.closed": "Geschlossener Körper",
|
"render.appearance_editor.closed": "Geschlossener Körper",
|
||||||
"render.appearance_editor.coat": "Klarlack",
|
"render.appearance_editor.coat": "Klarlack",
|
||||||
"render.appearance_editor.cpu_reason": "Benötigt CPU-Strahlverfolgung.",
|
"render.appearance_editor.cpu_reason": "Benötigt CPU-Strahlverfolgung.",
|
||||||
|
"render.appearance_editor.discard_saved_edit": "Ungespeicherte Optikänderungen verwerfen",
|
||||||
"render.appearance_editor.glass": "Glas und Absorption",
|
"render.appearance_editor.glass": "Glas und Absorption",
|
||||||
"render.appearance_editor.glass_enable": "Physikalisches Glas für diesen Körper verwenden",
|
"render.appearance_editor.glass_enable": "Physikalisches Glas für diesen Körper verwenden",
|
||||||
"render.appearance_editor.glass_note": "Geschlossenes Glas verwendet geometrische Abstände. Es kann nicht mit Klarlack oder Texturen auf demselben Körper kombiniert werden.",
|
"render.appearance_editor.glass_note": "Geschlossenes Glas verwendet geometrische Abstände. Es kann nicht mit Klarlack oder Texturen auf demselben Körper kombiniert werden.",
|
||||||
@@ -2814,7 +2873,7 @@
|
|||||||
"render.appearance_editor.replace": "Gespeicherte Optik dieses Körpers ersetzen",
|
"render.appearance_editor.replace": "Gespeicherte Optik dieses Körpers ersetzen",
|
||||||
"render.appearance_editor.revalidate": "Diesen Körper erneut prüfen",
|
"render.appearance_editor.revalidate": "Diesen Körper erneut prüfen",
|
||||||
"render.appearance_editor.save": "Diese Optik speichern",
|
"render.appearance_editor.save": "Diese Optik speichern",
|
||||||
"render.appearance_editor.save_reason": "Zum Speichern wird physikalisches Glas auf einem aktuellen GPU-Körper benötigt. Klarlack und lokale Bildtexturen bleiben vorübergehend.",
|
"render.appearance_editor.save_reason": "Zum Speichern Glas an einem aktuellen CPU- oder GPU-Körper aktivieren. Für Klarlack „Klarlack in Aufnahme speichern“ verwenden. Lokale Bilddateien bleiben temporär.",
|
||||||
"render.appearance_editor.saved": "Mit dieser Aufnahme gespeichert",
|
"render.appearance_editor.saved": "Mit dieser Aufnahme gespeichert",
|
||||||
"render.appearance_editor.saved_state": "Diese Optik ist mit dieser Aufnahme gespeichert.",
|
"render.appearance_editor.saved_state": "Diese Optik ist mit dieser Aufnahme gespeichert.",
|
||||||
"render.appearance_editor.scatter_reason": "Innere Streuung benötigt einen geschlossenen Körper.",
|
"render.appearance_editor.scatter_reason": "Innere Streuung benötigt einen geschlossenen Körper.",
|
||||||
@@ -2860,9 +2919,13 @@
|
|||||||
"render.base_metallic": "Metallanteil",
|
"render.base_metallic": "Metallanteil",
|
||||||
"render.base_opaque_only": "Dieser Editor lässt Körper undurchsichtig. Für optische Transmission verwenden Sie die gesonderten Glaseinstellungen.",
|
"render.base_opaque_only": "Dieser Editor lässt Körper undurchsichtig. Für optische Transmission verwenden Sie die gesonderten Glaseinstellungen.",
|
||||||
"render.batch_all_shots": "Alle Ansichten ({count})",
|
"render.batch_all_shots": "Alle Ansichten ({count})",
|
||||||
|
"render.batch_current_settings_help": "Verwendet die aktuellen Bildeinstellungen mit jeder gespeicherten Aufnahmekamera, eine Aufnahme nach der anderen.",
|
||||||
"render.batch_empty": "Vor dem Rendern aller Ansichten eine Ansicht speichern.",
|
"render.batch_empty": "Vor dem Rendern aller Ansichten eine Ansicht speichern.",
|
||||||
"render.batch_limit": "Es können höchstens 32 Ansichten gerendert werden.",
|
"render.batch_limit": "Es können höchstens 32 Ansichten gerendert werden.",
|
||||||
|
"render.batch_owner_limit": "Es können höchstens 32 Dokument-Bildfolgen vorgehalten werden. Schließen Sie zuerst eine vorhandene Folge ab oder brechen Sie sie ab.",
|
||||||
|
"render.batch_preparing": "Nächste Aufnahme wird vorbereitet",
|
||||||
"render.batch_status": "{running} läuft · {waiting} warten",
|
"render.batch_status": "{running} läuft · {waiting} warten",
|
||||||
|
"render.batch_waiting_worker": "Warten auf die Rendering-Vorbereitung",
|
||||||
"render.bloom_enable": "Darstellungs-Bloom",
|
"render.bloom_enable": "Darstellungs-Bloom",
|
||||||
"render.bloom_help": "Kreativer Ausgabeglanz aus linearen Glanzlichtern mit drei normierten Durchläufen. Radius in Ausgabepixeln; Addition vor sRGB. Keine Linsen-, Volumen- oder Konvergenzsimulation. Das rohe EXR bleibt unverändert.",
|
"render.bloom_help": "Kreativer Ausgabeglanz aus linearen Glanzlichtern mit drei normierten Durchläufen. Radius in Ausgabepixeln; Addition vor sRGB. Keine Linsen-, Volumen- oder Konvergenzsimulation. Das rohe EXR bleibt unverändert.",
|
||||||
"render.bloom_opaque": "Bloom benötigt eine deckende Ausgabe. Transparentes PNG wird abgelehnt, nicht still abgeflacht.",
|
"render.bloom_opaque": "Bloom benötigt eine deckende Ausgabe. Transparentes PNG wird abgelehnt, nicht still abgeflacht.",
|
||||||
@@ -2892,6 +2955,16 @@
|
|||||||
"render.category_paint": "Lack",
|
"render.category_paint": "Lack",
|
||||||
"render.category_plastic": "Kunststoff",
|
"render.category_plastic": "Kunststoff",
|
||||||
"render.category_wood": "Holz",
|
"render.category_wood": "Holz",
|
||||||
|
"render.caustics_depth": "Kaustik-Pfadtiefe",
|
||||||
|
"render.caustics_enable": "Glaskaustiken durch Punkt- und Richtungslichter",
|
||||||
|
"render.caustics_help": "Die Photonenzahl bestimmt Rauschen und Aufwand. Der Sammelradius tauscht scharfe Details gegen eine glattere Schätzung. CPU benötigt indirekte Beleuchtung.",
|
||||||
|
"render.caustics_path_required": "CPU-Kaustiken benötigen indirekte Beleuchtung.",
|
||||||
|
"render.caustics_photons": "Photonen",
|
||||||
|
"render.caustics_radius": "Sammelradius",
|
||||||
|
"render.check_glass_cpu": "Die CPU unterstützt geprüfte ebene Glasscheiben und achsenparallele Boxen. Allgemeine geschlossene Netze, Verschachtelung und innere Streuung erfordern indirekte Beleuchtung. Durch geschlossenes Glas übertragene Schatten bleiben nicht unterstützt.",
|
||||||
|
"render.check_glass_gpu": "Die GPU unterstützt geprüfte getrennte Glasmedien oder ausdrücklich aktivierte geprüfte Verschachtelung. Dafür müssen die äußeren Körper konvex und die inneren streng enthalten sein; Grenzen dürfen sich nicht berühren.",
|
||||||
|
"render.check_media_remaining": "Dichtegitter, Displacement, vollständige spektrale Integration und fortgeführte Polarisation bleiben nicht verfügbar.",
|
||||||
|
"render.check_media_scope": "Aktive innere Streuung benötigt ein geschlossenes Glasmedium ohne gerade Schattenübertragung und ohne Rauschreduzierung. Die GPU benötigt außerdem eine Fresnel-Schicht. Aktive äußere Atmosphäre kann nicht mit Glas oder Oberflächen-Rauschreduzierung kombiniert werden.",
|
||||||
"render.check_opaque": "Transparenz: nur undurchsichtige Oberflächen; Hintergrund-Alpha wird unterstützt.",
|
"render.check_opaque": "Transparenz: nur undurchsichtige Oberflächen; Hintergrund-Alpha wird unterstützt.",
|
||||||
"render.check_sorted_alpha": "Transparenz: Näherung mit sortiertem Alpha.",
|
"render.check_sorted_alpha": "Transparenz: Näherung mit sortiertem Alpha.",
|
||||||
"render.check_transmission": "Transparenz: physikalische Transmission.",
|
"render.check_transmission": "Transparenz: physikalische Transmission.",
|
||||||
@@ -2910,7 +2983,7 @@
|
|||||||
"render.children.copy_request": "Anfrage-JSON kopieren",
|
"render.children.copy_request": "Anfrage-JSON kopieren",
|
||||||
"render.children.coverage_error": "Abdeckungsfehler",
|
"render.children.coverage_error": "Abdeckungsfehler",
|
||||||
"render.children.cpu": "CPU-Raytracing",
|
"render.children.cpu": "CPU-Raytracing",
|
||||||
"render.children.denoise": "Rauschen im fertigen Bild reduzieren",
|
"render.children.denoise": "Bildrauschen reduzieren",
|
||||||
"render.children.depth": "Pfadtiefe",
|
"render.children.depth": "Pfadtiefe",
|
||||||
"render.children.distortion": "Verzeichnung",
|
"render.children.distortion": "Verzeichnung",
|
||||||
"render.children.edge_samples": "Kanten-Samples",
|
"render.children.edge_samples": "Kanten-Samples",
|
||||||
@@ -2973,6 +3046,14 @@
|
|||||||
"render.choose_look": "Zuerst einen Look in der Bibliothek wählen",
|
"render.choose_look": "Zuerst einen Look in der Bibliothek wählen",
|
||||||
"render.chromatic_offset": "Chromatischer Kantenversatz",
|
"render.chromatic_offset": "Chromatischer Kantenversatz",
|
||||||
"render.clear_stage": "Remove saved stage",
|
"render.clear_stage": "Remove saved stage",
|
||||||
|
"render.coat_discard": "Klarlackänderung verwerfen",
|
||||||
|
"render.coat_pending_edit": "Diese Klarlackänderung ist ungespeichert. Mit der Aufnahme speichern oder verwerfen.",
|
||||||
|
"render.coat_remove": "Gespeicherten Klarlack entfernen",
|
||||||
|
"render.coat_remove_note": "Entfernt den Klarlack dieses Ziels aus der Aufnahme. Geometrie, Aussehen und physikalische Materialien bleiben unverändert.",
|
||||||
|
"render.coat_remove_title": "Gespeicherten Klarlack entfernen?",
|
||||||
|
"render.coat_revalidate_saved": "Der gespeicherte Klarlack wurde geändert. Diese Änderung verwerfen und das Ziel vor der nächsten Bearbeitung erneut öffnen.",
|
||||||
|
"render.coat_save": "Klarlack in Aufnahme speichern",
|
||||||
|
"render.coat_saved_help": "Klarlack fügt undurchsichtigen Flächen eine glänzende Schicht hinzu. Mit der Aufnahme speichern. Glas kann nicht dasselbe Ziel verwenden. Auf der CPU dürfen auch benutzerdefinierte Reflexions- und Anisotropieüberschreibungen dieses Ziel nicht verwenden.",
|
||||||
"render.compare_divider": "Vergleichstrenner",
|
"render.compare_divider": "Vergleichstrenner",
|
||||||
"render.compare_live": "Live",
|
"render.compare_live": "Live",
|
||||||
"render.compare_live_preview": "Live-Vorschau",
|
"render.compare_live_preview": "Live-Vorschau",
|
||||||
@@ -3006,10 +3087,18 @@
|
|||||||
"render.cpu_budget_triangles": "Dreiecksobergrenze",
|
"render.cpu_budget_triangles": "Dreiecksobergrenze",
|
||||||
"render.cpu_budget_workstation": "Workstation (komplexe Szenen)",
|
"render.cpu_budget_workstation": "Workstation (komplexe Szenen)",
|
||||||
"render.cpu_captured_hdr": "Für dieses Bild erfasstes HDR",
|
"render.cpu_captured_hdr": "Für dieses Bild erfasstes HDR",
|
||||||
|
"render.cpu_celestial_adopt": "CPU-Raytracing mit indirekter Beleuchtung · Pfadtiefe {depth} · {spp} SPP · verfolgte Schatten · Seed {seed} · 1 Primärsample",
|
||||||
|
"render.cpu_celestial_adopt_label": "CPU-Raytracing mit indirekter Beleuchtung",
|
||||||
|
"render.cpu_celestial_path_required": "Endliche Sonne und Verlaufshimmel verwenden indirekte CPU-Beleuchtung.",
|
||||||
|
"render.cpu_celestial_switch": "Wähle CPU-Raytracing mit indirekter Beleuchtung, um diese Sonne oder diesen Himmel beizubehalten.",
|
||||||
|
"render.cpu_celestial_unportable": "Behalte GPU-Pathtracing für die aktuellen Transmissions-Sample-Einstellungen bei.",
|
||||||
"render.cpu_check_capture_help": "Prüfen und Starten verifizieren dieselbe Datei-Prüfsumme oder verwenden das eingebettete HDR. Jedes Bild behält seine erfasste Quelle.",
|
"render.cpu_check_capture_help": "Prüfen und Starten verifizieren dieselbe Datei-Prüfsumme oder verwenden das eingebettete HDR. Jedes Bild behält seine erfasste Quelle.",
|
||||||
"render.cpu_check_changed": "Die Einstellungen oder das Modell haben sich nach dieser Prüfung geändert. Prüfen Sie erneut, bevor Sie rendern.",
|
"render.cpu_check_changed": "Die Einstellungen oder das Modell haben sich nach dieser Prüfung geändert. Prüfen Sie erneut, bevor Sie rendern.",
|
||||||
|
"render.cpu_constant_direct_help": "Ohne indirekte Beleuchtung kann diese konstante Quelle in Reflexionen und durch Glas sichtbar sein. Sie beleuchtet keine undurchsichtigen Flächen. Schalte indirekte Beleuchtung ein, um sie als Szenenlicht zu nutzen.",
|
||||||
|
"render.cpu_constant_enable_indirect_plan": "Pfadtiefe {depth}; progressives Sampling mit {spp} Samples/Pixel (Seed {seed}); ein Primärstrahl pro Sample; Raytracing-Schatten. Bildgröße, CPU-Budget und andere Einstellungen bleiben erhalten.",
|
||||||
"render.cpu_constant_help": "Konstante Strahldichte ist für reflektierte oder durchgelassene Strahlen sichtbar. Sie liefert keine direkte diffuse Beleuchtung.",
|
"render.cpu_constant_help": "Konstante Strahldichte ist für reflektierte oder durchgelassene Strahlen sichtbar. Sie liefert keine direkte diffuse Beleuchtung.",
|
||||||
"render.cpu_custom_directions": "Vorgabe durch eigene Richtungslichter ersetzen",
|
"render.cpu_custom_directions": "Vorgabe durch eigene Richtungslichter ersetzen",
|
||||||
|
"render.cpu_denoise_help": "Verringert das Rauschen bei jeder Bildaktualisierung. Feine Details und Übergänge der Tiefenschärfe können weicher werden. Die EXR-Rohdaten bleiben unverändert. Nachträgliche Anpassungen eines fertigen Bildes verwenden dessen Rohbild und wenden die Rauschreduzierung nicht erneut an.",
|
||||||
"render.cpu_direction": "Richtung zum Licht (X, Y, Z)",
|
"render.cpu_direction": "Richtung zum Licht (X, Y, Z)",
|
||||||
"render.cpu_directional": "Richtungslicht",
|
"render.cpu_directional": "Richtungslicht",
|
||||||
"render.cpu_directional_add": "Richtungslicht hinzufügen",
|
"render.cpu_directional_add": "Richtungslicht hinzufügen",
|
||||||
@@ -3021,8 +3110,11 @@
|
|||||||
"render.cpu_environment_samples": "HDR-Beleuchtungs-Samples",
|
"render.cpu_environment_samples": "HDR-Beleuchtungs-Samples",
|
||||||
"render.cpu_exposure_help": "Die CPU-Belichtung muss zwischen −16 und 16 EV liegen. Eine GPU-Belichtung außerhalb dieses Bereichs bleibt erhalten, bis Sie sie ändern.",
|
"render.cpu_exposure_help": "Die CPU-Belichtung muss zwischen −16 und 16 EV liegen. Eine GPU-Belichtung außerhalb dieses Bereichs bleibt erhalten, bis Sie sie ändern.",
|
||||||
"render.cpu_fixed": "Einzelbild",
|
"render.cpu_fixed": "Einzelbild",
|
||||||
|
"render.cpu_fixed_retained": "Feste Abtastung beibehalten",
|
||||||
|
"render.cpu_glass_enable_path": "Indirekte Beleuchtung einschalten",
|
||||||
"render.cpu_hdr_help": "Radiance-HDR-Datei (.hdr), Panorama im Format 2:1, bis zu 2048 × 1024 Pixel und 16 MiB. Für die CPU müssen die sRGB/D65-Primärfarben ausdrücklich in der Datei angegeben sein. Dateien ohne Angabe der Primärfarben sowie EXR-Dateien werden abgelehnt.",
|
"render.cpu_hdr_help": "Radiance-HDR-Datei (.hdr), Panorama im Format 2:1, bis zu 2048 × 1024 Pixel und 16 MiB. Für die CPU müssen die sRGB/D65-Primärfarben ausdrücklich in der Datei angegeben sein. Dateien ohne Angabe der Primärfarben sowie EXR-Dateien werden abgelehnt.",
|
||||||
"render.cpu_hdr_reflection_help": "Raue HDR-Reflexionen erfordern vollständig metallische Körper- und Flächenoberflächen. Die Prüfung meldet nicht unterstützte Kombinationen.",
|
"render.cpu_hdr_reflection_help": "Raue HDR-Reflexionen erfordern vollständig metallische Körper- und Flächenoberflächen. Die Prüfung meldet nicht unterstützte Kombinationen.",
|
||||||
|
"render.cpu_indirect_method_required": "Wähle CPU-Raytracing, um diesen Modus für indirekte Beleuchtung einzuschalten.",
|
||||||
"render.cpu_key_rim": "Haupt- und Konturlicht",
|
"render.cpu_key_rim": "Haupt- und Konturlicht",
|
||||||
"render.cpu_last_check": "Letzte CPU-Prüfung",
|
"render.cpu_last_check": "Letzte CPU-Prüfung",
|
||||||
"render.cpu_light_help": "Bis zu acht Richtungslichter. Eine leere eigene Liste schaltet sie aus; die Wahl einer Vorgabe stellt deren Lichter wieder her. Farben sind lineares RGB, und die Intensität ist ein relativer Wert, keine Leistungsangabe in Watt.",
|
"render.cpu_light_help": "Bis zu acht Richtungslichter. Eine leere eigene Liste schaltet sie aus; die Wahl einer Vorgabe stellt deren Lichter wieder her. Farben sind lineares RGB, und die Intensität ist ein relativer Wert, keine Leistungsangabe in Watt.",
|
||||||
@@ -3034,18 +3126,32 @@
|
|||||||
"render.cpu_lighting": "Beleuchtungsvorgabe",
|
"render.cpu_lighting": "Beleuchtungsvorgabe",
|
||||||
"render.cpu_lights": "CPU-Lichter",
|
"render.cpu_lights": "CPU-Lichter",
|
||||||
"render.cpu_linear_color": "Lineare Farbe (R, G, B)",
|
"render.cpu_linear_color": "Lineare Farbe (R, G, B)",
|
||||||
|
"render.cpu_maps_saved_help": "CPU-Renderings können gespeicherte Karten undurchsichtiger Flächen verwenden: Oberfläche wählen und die Karten in der Aufnahme speichern. Mit indirekter Beleuchtung darf ein anderer Körper Glas verwenden; der Körper mit Karten darf kein Glas sein. Ausgewählte Reflexionen, Anisotropie und prozedurale Effekte ausschalten. Temporäre Kartendateipfade verwenden die GPU.",
|
||||||
|
"render.cpu_material_certification": "Die CPU-Prüfung validiert unterstützte Glasgeometrie vor dem Rendern. Geschlossenes Glas verwendet geometrische Abstände.",
|
||||||
"render.cpu_neutral": "Neutral",
|
"render.cpu_neutral": "Neutral",
|
||||||
|
"render.cpu_opaque_hint": "CPU-Raytracing verwendet einen undurchsichtigen Hintergrund.",
|
||||||
"render.cpu_opaque_required": "CPU-Raytracing benötigt einen undurchsichtigen Hintergrund. Entfernen Sie das Häkchen bei „Transparenter Hintergrund“, um fortzufahren.",
|
"render.cpu_opaque_required": "CPU-Raytracing benötigt einen undurchsichtigen Hintergrund. Entfernen Sie das Häkchen bei „Transparenter Hintergrund“, um fortzufahren.",
|
||||||
"render.cpu_options_pending": "Dieses CPU-Bedienfeld kann Emission, Oberflächeneffekte, Glas oder Anisotropie noch nicht bearbeiten. Entfernen Sie diese API-Optionen, bevor Sie es verwenden.",
|
"render.cpu_options_pending": "Dieses CPU-Bedienfeld kann Emission, Oberflächeneffekte, Glas oder Anisotropie noch nicht bearbeiten. Entfernen Sie diese API-Optionen, bevor Sie es verwenden.",
|
||||||
|
"render.cpu_path_depth": "Pfadtiefe",
|
||||||
|
"render.cpu_path_help": "Berechnet Licht, das zwischen undurchsichtigen Flächen und durch unterstützte Glasformen läuft. Erfordert progressives Rendern und Raytracing-Schatten. Die Pfadtiefe zählt beleuchtete Flächen, Glasgrenzen und Streuereignisse ab dem ersten Treffer (1–16). Tiefe 1 liefert direkte Beleuchtung auf undurchsichtigen Flächen; höhere Werte erlauben mehr Reflexionen oder Glasdurchgänge und können länger dauern. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
|
||||||
|
"render.cpu_path_lighting": "Indirekte Beleuchtung",
|
||||||
|
"render.cpu_path_optics_reason": "Die Einstellungen bleiben erhalten. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
|
||||||
"render.cpu_point": "Punktlicht",
|
"render.cpu_point": "Punktlicht",
|
||||||
"render.cpu_point_add": "Punktlicht hinzufügen",
|
"render.cpu_point_add": "Punktlicht hinzufügen",
|
||||||
"render.cpu_point_help": "Bis zu acht Punktlichter, zusätzlich zur gewählten Richtungsbeleuchtung. Abstände in mm; der Mindestabstand darf den Referenzabstand nicht überschreiten.",
|
"render.cpu_point_help": "Bis zu acht Punktlichter, zusätzlich zur gewählten Richtungsbeleuchtung. Abstände in mm; der Mindestabstand darf den Referenzabstand nicht überschreiten.",
|
||||||
"render.cpu_primary_samples": "Kanten-Samples pro Pixel",
|
"render.cpu_primary_samples": "Kanten-Samples pro Pixel",
|
||||||
"render.cpu_progressive": "CPU progressiv",
|
"render.cpu_progressive": "CPU progressiv",
|
||||||
"render.cpu_progressive_help": "Den vorhandenen CPU-Integrator in Durchgängen mit je einem Primär-Sample akkumulieren. Ausgewählte Reflexionen erfordern ein Sample; Flächenlichter, Dielektrika und Anisotropie erfordern „Einzelbild“.",
|
"render.cpu_progressive_adopt": "Progressive Abtastung verwenden",
|
||||||
|
"render.cpu_progressive_adopt_help": "Die progressive Abtastung verwendet ein Kamerasample pro Durchlauf. Bildgröße, Lichtquellen und Reflexionseinstellungen bleiben unverändert.",
|
||||||
|
"render.cpu_progressive_always": "Progressive Abtastung ist immer aktiv",
|
||||||
|
"render.cpu_progressive_help": "Neue CPU-Kamerasamples sammeln und jeden vollständigen Durchgang zeigen. Ausgewählte Reflexionen verwenden einen Versuch pro Kamerasample; zertifiziertes Glas und planare Anisotropie behalten ihre bisherigen Grenzen. Adaptive Pilot- und Produktionssamples teilen sich das CPU-Profilbudget.",
|
||||||
"render.cpu_progressive_limits": "Höchstens 1.048.576 Pixel und 16.777.216 Auswertungen von Pixel × Sample. Das Ändern der Samples passt die Bildgröße nie automatisch an.",
|
"render.cpu_progressive_limits": "Höchstens 1.048.576 Pixel und 16.777.216 Auswertungen von Pixel × Sample. Das Ändern der Samples passt die Bildgröße nie automatisch an.",
|
||||||
|
"render.cpu_progressive_reflection_adopt": "Setzen Sie die Reflexionsversuche ausgewählter Körper auf 1, bevor Sie die progressive Abtastung übernehmen.",
|
||||||
"render.cpu_progressive_seed": "Startwert für das CPU-Sampling",
|
"render.cpu_progressive_seed": "Startwert für das CPU-Sampling",
|
||||||
"render.cpu_progressive_spp": "Samples pro Pixel",
|
"render.cpu_progressive_spp": "Samples pro Pixel",
|
||||||
|
"render.cpu_quality_budget_help": "Ändert Größe, Obergrenze der Produktionssamples, Startwert und CPU-Ressourcenprofil. Kamera, Objektiv, Beleuchtung, GI-Tiefe und Effekte bleiben erhalten.",
|
||||||
|
"render.cpu_quality_progressive_required": "Aktiviere unten progressive Samples, bevor du ein CPU-Budget auswählst.",
|
||||||
|
"render.cpu_quality_summary": "{width} × {height} · Sample-Grenze {samples} · Startwert {seed} · {profile}",
|
||||||
"render.cpu_reflection_add": "Raue Reflexion hinzufügen",
|
"render.cpu_reflection_add": "Raue Reflexion hinzufügen",
|
||||||
"render.cpu_reflection_depth": "Reflexionstiefe (1–8)",
|
"render.cpu_reflection_depth": "Reflexionstiefe (1–8)",
|
||||||
"render.cpu_reflection_empty": "Keine Körper ausgewählt: Szenenreflexionen sind aus.",
|
"render.cpu_reflection_empty": "Keine Körper ausgewählt: Szenenreflexionen sind aus.",
|
||||||
@@ -3059,7 +3165,21 @@
|
|||||||
"render.cpu_runtime_help": "Numerische Licht- und Umgebungseinstellungen werden mit dem Setup gespeichert. Körperreflexionen und HDR-Dateien bleiben vorübergehend. Vorhandene Werte für Flächenlicht und Spotkegel bleiben erhalten.",
|
"render.cpu_runtime_help": "Numerische Licht- und Umgebungseinstellungen werden mit dem Setup gespeichert. Körperreflexionen und HDR-Dateien bleiben vorübergehend. Vorhandene Werte für Flächenlicht und Spotkegel bleiben erhalten.",
|
||||||
"render.cpu_sampling_mode": "CPU-Sampling-Modus",
|
"render.cpu_sampling_mode": "CPU-Sampling-Modus",
|
||||||
"render.cpu_shadows": "Schatten werfen",
|
"render.cpu_shadows": "Schatten werfen",
|
||||||
|
"render.cpu_sky_bounds": "Die Himmelsrotation muss endlich innerhalb ±1.000.000 Radiant liegen; die Intensität muss 0–100 sein.",
|
||||||
|
"render.cpu_studio_enable": "Indirekte Beleuchtung für dieses Lichtset aktivieren",
|
||||||
|
"render.cpu_studio_enable_apply": "Indirekte Beleuchtung aktivieren und anwenden",
|
||||||
|
"render.cpu_studio_indirect_help": "Dieses Sonnen-Lichtset benötigt indirekte Beleuchtung mit Tiefe 8, verfolgte Schatten und eine Kamerastichprobe pro Durchlauf. Ist progressives Rendern ausgeschaltet, beginnt es mit 16 Stichproben pro Pixel und Startwert 0. Vorhandene progressive Einstellungen und das Ressourcenprofil bleiben erhalten. Unvereinbare Einstellungen verhindern die Anwendung, ohne sie zu ändern.",
|
||||||
|
"render.cpu_studio_radiance_limit": "Dieses Lichtset hat eine Flächenstrahldichte von {radiance}, über der CPU-Grenze von 64. Die vorgegebene Beleuchtung bleibt erhalten; wählen Sie ein anderes Lichtset oder GPU.",
|
||||||
|
"render.cpu_subsurface_denoise_reason": "Streuung im Glas auf der CPU benötigt ausgeschaltete Rauschreduzierung. Die Einstellungen bleiben erhalten.",
|
||||||
|
"render.cpu_subsurface_help": "Streuung lenkt Licht im Inneren geschlossenen Glases um. Positive Raten benötigen indirekte Beleuchtung ohne Rauschreduzierung. Die Rate zählt Kollisionen pro mm; 0 fügt keine Streuung hinzu. Positive Richtungswerte bevorzugen vorwärts, negative rückwärts.",
|
||||||
"render.cpu_summary": "CPU-Raytracing · Reflexionen · HDR · Richtungs- und Punktlichter",
|
"render.cpu_summary": "CPU-Raytracing · Reflexionen · HDR · Richtungs- und Punktlichter",
|
||||||
|
"render.cpu_transparent_help": "Transparentes PNG entfernt den Hintergrund und erhält die Kantenabdeckung. Körper- und Flächendeckkraft müssen 100 % bleiben.",
|
||||||
|
"render.cpu_volume_enable_path": "Indirekte Beleuchtung einschalten",
|
||||||
|
"render.cpu_volume_enable_path_plan": "CPU-Raytracing · Pfadtiefe {depth} · {spp} Samples/Pixel · Seed {seed} · Raytracing-Schatten. Atmosphäre und Bildbudget bleiben erhalten.",
|
||||||
|
"render.cpu_volume_help": "CPU-Atmosphäre nutzt indirekte Beleuchtung. Bei Extinktion null bleibt die gespeicherte Box ohne Nebel erhalten.",
|
||||||
|
"render.cpu_volume_method_adopt": "CPU-Raytracing mit indirekter Beleuchtung",
|
||||||
|
"render.cpu_volume_method_incompatible": "Diese Licht-, Sampling- oder Atmosphäreinstellungen lassen sich nicht auf diese Methode übertragen. Die Einstellungen bleiben erhalten.",
|
||||||
|
"render.cpu_volume_path_required": "Indirekte Beleuchtung einschalten, um CPU-Atmosphäre zu nutzen.",
|
||||||
"render.cpu_warm_cool": "Warm und kühl",
|
"render.cpu_warm_cool": "Warm und kühl",
|
||||||
"render.diag_bodies": "Sichtbare Körper",
|
"render.diag_bodies": "Sichtbare Körper",
|
||||||
"render.diag_job_details": "Erfasste Auftragsdetails",
|
"render.diag_job_details": "Erfasste Auftragsdetails",
|
||||||
@@ -3069,6 +3189,7 @@
|
|||||||
"render.diag_preflight_details": "Details der Vorabprüfung",
|
"render.diag_preflight_details": "Details der Vorabprüfung",
|
||||||
"render.diag_request": "Aktuelle Anfrage im Wortlaut",
|
"render.diag_request": "Aktuelle Anfrage im Wortlaut",
|
||||||
"render.diag_triangles": "Dreiecke",
|
"render.diag_triangles": "Dreiecke",
|
||||||
|
"render.directional_sun_limit": "Die GPU unterstützt insgesamt acht gerichtete Lichtquellen, darunter höchstens eine weiche Sonne. Dupliziere ein hartes gerichtetes Licht für eine weitere entfernte Quelle.",
|
||||||
"render.discard_continue": "Entwurf verwerfen und fortfahren",
|
"render.discard_continue": "Entwurf verwerfen und fortfahren",
|
||||||
"render.draft_confirm_help": "Der aktuelle Render-Entwurf enthält nicht angewendete Änderungen. Speichern Sie sie vor dem Wechsel, verwerfen Sie sie ausdrücklich oder bearbeiten Sie weiter.",
|
"render.draft_confirm_help": "Der aktuelle Render-Entwurf enthält nicht angewendete Änderungen. Speichern Sie sie vor dem Wechsel, verwerfen Sie sie ausdrücklich oder bearbeiten Sie weiter.",
|
||||||
"render.draft_confirm_title": "Bearbeitete Rendereinstellungen beibehalten",
|
"render.draft_confirm_title": "Bearbeitete Rendereinstellungen beibehalten",
|
||||||
@@ -3218,7 +3339,7 @@
|
|||||||
"render.export_display": "Export: Anzeige-PNG",
|
"render.export_display": "Export: Anzeige-PNG",
|
||||||
"render.export_image_label": "Bild exportieren",
|
"render.export_image_label": "Bild exportieren",
|
||||||
"render.export_linear": "Export: lineares Roh-EXR",
|
"render.export_linear": "Export: lineares Roh-EXR",
|
||||||
"render.exr_pending": "Die GPU kann rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
|
"render.exr_pending": "CPU-Raytracing und GPU-Pathtracing können rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
|
||||||
"render.film_grain": "Fotografisches Korn",
|
"render.film_grain": "Fotografisches Korn",
|
||||||
"render.fit": "Produkt einpassen",
|
"render.fit": "Produkt einpassen",
|
||||||
"render.fixed_camera": "Kamera",
|
"render.fixed_camera": "Kamera",
|
||||||
@@ -3256,11 +3377,21 @@
|
|||||||
"render.frame_tall": "9:16 · Vertikal",
|
"render.frame_tall": "9:16 · Vertikal",
|
||||||
"render.frame_thirds": "Drittellinien",
|
"render.frame_thirds": "Drittellinien",
|
||||||
"render.frame_wide": "16:9 · Breitbild",
|
"render.frame_wide": "16:9 · Breitbild",
|
||||||
|
"render.glass_abbe": "Abbe-Zahl",
|
||||||
|
"render.glass_dispersion": "Chromatische Dispersion",
|
||||||
|
"render.glass_film": "Dünnschichtinterferenz",
|
||||||
|
"render.glass_film_ior": "Brechungsindex der Schicht",
|
||||||
|
"render.glass_film_thickness": "Schichtdicke",
|
||||||
|
"render.glass_model_enable": "Erweitertes Glasmodell",
|
||||||
|
"render.glass_model_help": "Glasrauheit streut durchgelassene Strahlen. Dispersion und Schichtfarbe verwenden drei Wellenlängenbänder. Raue Scheiben benötigen ausgeschaltete gerade Schattenübertragung.",
|
||||||
|
"render.glass_roughness": "Glasrauheit",
|
||||||
"render.gpu_depth": "Maximale Pfadtiefe",
|
"render.gpu_depth": "Maximale Pfadtiefe",
|
||||||
|
"render.gpu_job_shader_wait": "Shader-Vorbereitung kann Minuten dauern",
|
||||||
"render.gpu_options_help": "Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
|
"render.gpu_options_help": "Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
|
||||||
"render.gpu_path_crashed": "SolidCraft wurde beim letzten Einrichten auf diesem Grafikadapter beendet, wahrscheinlich im Grafiktreiber, und richtet ihn deshalb nicht von selbst erneut ein. Verwenden Sie stattdessen Raytracing auf der CPU oder versuchen Sie es erneut (nach einem Treiberupdate wird es ebenfalls erneut versucht).",
|
"render.gpu_path_crashed": "SolidCraft wurde beim letzten Einrichten auf diesem Grafikadapter beendet, wahrscheinlich im Grafiktreiber, und richtet ihn deshalb nicht von selbst erneut ein. Verwenden Sie stattdessen Raytracing auf der CPU oder versuchen Sie es erneut (nach einem Treiberupdate wird es ebenfalls erneut versucht).",
|
||||||
"render.gpu_path_failed": "GPU-Pathtracer nicht verfügbar",
|
"render.gpu_path_failed": "GPU-Pathtracer nicht verfügbar",
|
||||||
"render.gpu_path_first_setup": "Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
|
"render.gpu_path_first_setup": "Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
|
||||||
|
"render.gpu_path_not_started": "Die Einrichtung des GPU-Pfadtracers wurde noch nicht gestartet.",
|
||||||
"render.gpu_path_preparing": "GPU-Pathtracer: Shader werden kompiliert…",
|
"render.gpu_path_preparing": "GPU-Pathtracer: Shader werden kompiliert…",
|
||||||
"render.gpu_path_retry": "Erneut versuchen",
|
"render.gpu_path_retry": "Erneut versuchen",
|
||||||
"render.gpu_seed": "Zufallsstartwert",
|
"render.gpu_seed": "Zufallsstartwert",
|
||||||
@@ -3456,6 +3587,8 @@
|
|||||||
"render.light_product_centre": "Produktmitte",
|
"render.light_product_centre": "Produktmitte",
|
||||||
"render.light_size": "Größe",
|
"render.light_size": "Größe",
|
||||||
"render.light_softness": "Weichheit",
|
"render.light_softness": "Weichheit",
|
||||||
|
"render.light_space_world": "Lichter in Weltkoordinaten",
|
||||||
|
"render.light_space_world_help": "Lichtpositionen und -richtungen verwenden Weltkoordinaten. Beim Umkreisen mit der Kamera bleiben die Lichter an ihrer Position.",
|
||||||
"render.light_temperature": "Farbtemperatur",
|
"render.light_temperature": "Farbtemperatur",
|
||||||
"render.light_temperature_help": "Angenäherte Schwarzkörperfarbe; als lineares RGB gespeichert.",
|
"render.light_temperature_help": "Angenäherte Schwarzkörperfarbe; als lineares RGB gespeichert.",
|
||||||
"render.light_type": "Typ",
|
"render.light_type": "Typ",
|
||||||
@@ -3512,6 +3645,25 @@
|
|||||||
"render.linear_help": "Behält ein lineares 32-Bit-Gleitkomma-RGBA-Rohbild vor Entrauschen, Belichtung, Tone-Mapping, Filtern und Hintergrund. Lineares sRGB/D65; Abdeckung vormultipliziert. Zusätzlicher Speicher wird ausdrücklich zugelassen. Keine Komprimierung und keine zusätzlichen Durchgänge.",
|
"render.linear_help": "Behält ein lineares 32-Bit-Gleitkomma-RGBA-Rohbild vor Entrauschen, Belichtung, Tone-Mapping, Filtern und Hintergrund. Lineares sRGB/D65; Abdeckung vormultipliziert. Zusätzlicher Speicher wird ausdrücklich zugelassen. Keine Komprimierung und keine zusätzlichen Durchgänge.",
|
||||||
"render.linear_retain": "Lineares Roh-EXR behalten",
|
"render.linear_retain": "Lineares Roh-EXR behalten",
|
||||||
"render.linear_unavailable": "Für das gewählte fertige Bild wurde kein EXR behalten. Aktivieren Sie das Behalten vor dem Rendern; ein älteres PNG kann die rohe Strahldichte nicht wiederherstellen.",
|
"render.linear_unavailable": "Für das gewählte fertige Bild wurde kein EXR behalten. Aktivieren Sie das Behalten vor dem Rendern; ein älteres PNG kann die rohe Strahldichte nicht wiederherstellen.",
|
||||||
|
"render.live.adaptive": "Live · {width} × {height} · {progress}",
|
||||||
|
"render.live.final_changed": "Die Render-Anfrage hat sich beim Anhalten des Live-Renderings geändert. Starten Sie das finale Bild erneut.",
|
||||||
|
"render.live.final_waiting": "Finales Bild vorbereiten · Live-Render-Ressourcen freigeben",
|
||||||
|
"render.live.invalid_size": "Live-Rendering benötigt eine positive Ausgabebreite und -höhe.",
|
||||||
|
"render.live.pause": "Live pausieren",
|
||||||
|
"render.live.paused": "Live · Pausiert",
|
||||||
|
"render.live.paused_outdated": "Live · Pausiert · Veraltet",
|
||||||
|
"render.live.quality_help": "Interaktive Qualität: höchstens 512 Pixel an der längsten Kante. Verwendet den gewählten Renderer und seine unterstützten Effekte. Endauflösung, Einstellungen und Export bleiben getrennt.",
|
||||||
|
"render.live.refused": "Live nicht verfügbar: {reason}",
|
||||||
|
"render.live.resume": "Live fortsetzen",
|
||||||
|
"render.live.samples": "Live · {width} × {height} · {current}/{target} Samples",
|
||||||
|
"render.live.source_current": "{method} · Live",
|
||||||
|
"render.live.source_outdated": "{method} · Vorheriges Bild",
|
||||||
|
"render.live.source_outdated_help": "Zeigt das zuletzt aufgenommene Bild, während das Live-Rendering aktualisiert wird oder nicht verfügbar ist. Es kann von der aktuellen Szene oder den Bildeinstellungen abweichen.",
|
||||||
|
"render.live.source_settings_help": "Ausgabe öffnen, um Renderer und Bildeinstellungen zu bearbeiten.",
|
||||||
|
"render.live.stopped": "Live-Rendering angehalten",
|
||||||
|
"render.live.updating": "Live · Interaktive Qualität (bis 512 px) · Aktualisierung",
|
||||||
|
"render.live.waiting_canvas": "Warten auf die Render-Ansicht",
|
||||||
|
"render.live.worker_stopped": "Der Aufnahmeprozess für das Live-Rendering wurde vor der Rückgabe eines Ergebnisses beendet",
|
||||||
"render.look_add": "Farblook hinzufügen",
|
"render.look_add": "Farblook hinzufügen",
|
||||||
"render.look_applied": "{name} angewendet",
|
"render.look_applied": "{name} angewendet",
|
||||||
"render.look_bleach": "Von Bleach-Bypass inspiriert",
|
"render.look_bleach": "Von Bleach-Bypass inspiriert",
|
||||||
@@ -3541,6 +3693,9 @@
|
|||||||
"render.material_coat_help": "Illustrative dielektrische Beschichtung über dem vorhandenen undurchsichtigen Erscheinungsbild. Gewicht null lässt das Grundmaterial unverändert.",
|
"render.material_coat_help": "Illustrative dielektrische Beschichtung über dem vorhandenen undurchsichtigen Erscheinungsbild. Gewicht null lässt das Grundmaterial unverändert.",
|
||||||
"render.material_color_map": "Grundfarbentextur (sRGB)",
|
"render.material_color_map": "Grundfarbentextur (sRGB)",
|
||||||
"render.material_color_replace": "Basismap ersetzt Körperfarbe",
|
"render.material_color_replace": "Basismap ersetzt Körperfarbe",
|
||||||
|
"render.material_cpu_glass_only": "Temporäre Bilddatei-Karten benötigen die GPU. Die CPU kann gespeicherte integrierte Oberflächenkarten verwenden.",
|
||||||
|
"render.material_cpu_optics_limits": "Indirekte Beleuchtung für unterstützte geschlossene Glasformen, geprüfte Verschachtelung und Streuung im Inneren einschalten. Dünne Scheiben können Licht für Schatten durchlassen. Sonst eine ebene Scheibe oder achsenparallele Box verwenden. Berührende Formen und durch geschlossenes Glas übertragene Schatten werden weiterhin nicht unterstützt. Die Werte bleiben erhalten.",
|
||||||
|
"render.material_cpu_saved_runtime_conflict": "Gespeicherte CPU-Optik kann nicht mit temporären optischen Bindungen kombiniert werden. Geprüfte Optik speichern oder die temporäre Bindung entfernen.",
|
||||||
"render.material_custom": "Eigene illustrative Optik",
|
"render.material_custom": "Eigene illustrative Optik",
|
||||||
"render.material_enable": "Diese GPU-Materialüberschreibungen verwenden",
|
"render.material_enable": "Diese GPU-Materialüberschreibungen verwenden",
|
||||||
"render.material_filter": "Szenenkörper filtern…",
|
"render.material_filter": "Szenenkörper filtern…",
|
||||||
@@ -3561,6 +3716,7 @@
|
|||||||
"render.material_normal_map": "Normalenbild (OpenGL, +Y)",
|
"render.material_normal_map": "Normalenbild (OpenGL, +Y)",
|
||||||
"render.material_normal_strength": "Normalenstärke (0–1)",
|
"render.material_normal_strength": "Normalenstärke (0–1)",
|
||||||
"render.material_origin": "Texturursprung X · Y · Z (mm)",
|
"render.material_origin": "Texturursprung X · Y · Z (mm)",
|
||||||
|
"render.material_raytrace_required": "Physikalisches Glas benötigt CPU-Raytracing oder GPU-Pathtracing.",
|
||||||
"render.material_refresh": "Szenenkörper aktualisieren",
|
"render.material_refresh": "Szenenkörper aktualisieren",
|
||||||
"render.material_refresh_needed": "Die Szene hat sich geändert. Aktualisieren Sie die Körperliste, bevor Sie ein Ziel hinzufügen.",
|
"render.material_refresh_needed": "Die Szene hat sich geändert. Aktualisieren Sie die Körperliste, bevor Sie ein Ziel hinzufügen.",
|
||||||
"render.material_remove": "Diese Zuweisung entfernen",
|
"render.material_remove": "Diese Zuweisung entfernen",
|
||||||
@@ -3570,6 +3726,7 @@
|
|||||||
"render.material_revalidate_needed": "Diese Zuweisung gehört zu einer früheren Szenenrevision. Prüfen Sie die genaue Körperidentität erneut, bevor Sie rendern.",
|
"render.material_revalidate_needed": "Diese Zuweisung gehört zu einer früheren Szenenrevision. Prüfen Sie die genaue Körperidentität erneut, bevor Sie rendern.",
|
||||||
"render.material_roughness": "Rauheit des Klarlacks (0,045–1)",
|
"render.material_roughness": "Rauheit des Klarlacks (0,045–1)",
|
||||||
"render.material_roughness_map": "Rauheitsmap (linear)",
|
"render.material_roughness_map": "Rauheitsmap (linear)",
|
||||||
|
"render.material_saved_edit_pending": "Diese Optikänderungen sind ungespeichert. Geprüfte Werte speichern, die gespeicherte Zuordnung entfernen oder Änderungen verwerfen. Temporäre Bindungen und nicht unterstützte Effekte können das gespeicherte Ziel nicht überschreiben.",
|
||||||
"render.material_shadow": "Gerade Schattenstrahlen durchlassen",
|
"render.material_shadow": "Gerade Schattenstrahlen durchlassen",
|
||||||
"render.material_target_missing": "Ordnungsnummer und Name des Körpers stimmen nicht mehr überein. Entfernen Sie diese Zuweisung und wählen Sie den gewünschten Körper ausdrücklich erneut.",
|
"render.material_target_missing": "Ordnungsnummer und Name des Körpers stimmen nicht mehr überein. Entfernen Sie diese Zuweisung und wählen Sie den gewünschten Körper ausdrücklich erneut.",
|
||||||
"render.material_thickness": "Dicke der dünnen Schicht (mm)",
|
"render.material_thickness": "Dicke der dünnen Schicht (mm)",
|
||||||
@@ -3581,7 +3738,7 @@
|
|||||||
"render.method_cpu": "CPU-Raytracing",
|
"render.method_cpu": "CPU-Raytracing",
|
||||||
"render.method_gpu": "GPU-Pathtracing",
|
"render.method_gpu": "GPU-Pathtracing",
|
||||||
"render.method_limit_raster": "Die Rasterung verwendet die feste undurchsichtige Studiovorschau. Wählen Sie ausdrücklich eine Raytracing-Methode für die unterstützten optischen Einstellungen.",
|
"render.method_limit_raster": "Die Rasterung verwendet die feste undurchsichtige Studiovorschau. Wählen Sie ausdrücklich eine Raytracing-Methode für die unterstützten optischen Einstellungen.",
|
||||||
"render.method_raster": "CPU-Rasterung",
|
"render.method_raster": "CPU-Rasterung (schematisch)",
|
||||||
"render.navigation_help": "Die Kamera bewegt sich wie im Konstruieren (Einstellungen › Navigation). Setup speichern übernimmt die aktuelle Kamera.",
|
"render.navigation_help": "Die Kamera bewegt sich wie im Konstruieren (Einstellungen › Navigation). Setup speichern übernimmt die aktuelle Kamera.",
|
||||||
"render.nested_enable": "Zertifizierte verschachtelte Dielektrika",
|
"render.nested_enable": "Zertifizierte verschachtelte Dielektrika",
|
||||||
"render.nested_help": "Bis zu 16 umschließende Schichten; jedes übergeordnete Element muss konvex und jedes untergeordnete strikt darin enthalten sein. Überschneidungen, sich berührende Flächen, dünne Schichten und die Transmission gerader Schattenstrahlen werden abgelehnt. Der Zustand „Kamera im Inneren“ folgt der zertifizierten Hierarchie. Erzeugt keine Flüssigkeitsoberfläche und verschmilzt keine sich berührenden CAD-Grenzflächen.",
|
"render.nested_help": "Bis zu 16 umschließende Schichten; jedes übergeordnete Element muss konvex und jedes untergeordnete strikt darin enthalten sein. Überschneidungen, sich berührende Flächen, dünne Schichten und die Transmission gerader Schattenstrahlen werden abgelehnt. Der Zustand „Kamera im Inneren“ folgt der zertifizierten Hierarchie. Erzeugt keine Flüssigkeitsoberfläche und verschmilzt keine sich berührenden CAD-Grenzflächen.",
|
||||||
@@ -3609,16 +3766,19 @@
|
|||||||
"render.output_exr_unavailable": "EXR-Ausgabeeinstellungen sind hier nicht verfügbar. Linearer EXR-Export erfordert ein fertiges GPU-Bild mit gespeichertem linearem Original.",
|
"render.output_exr_unavailable": "EXR-Ausgabeeinstellungen sind hier nicht verfügbar. Linearer EXR-Export erfordert ein fertiges GPU-Bild mit gespeichertem linearem Original.",
|
||||||
"render.output_fit": "Einpassen",
|
"render.output_fit": "Einpassen",
|
||||||
"render.output_format": "Format",
|
"render.output_format": "Format",
|
||||||
"render.output_format_status": "PNG enthält das exakte fertige Anzeigebild. Optionales GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Filtern. Export ersetzt nie durch Vorschaucache oder anderen Auftrag.",
|
"render.output_format_status": "PNG enthält das exakte fertige Anzeigebild. Optionales CPU/GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Anzeigefiltern. Der Export verwendet nie den Vorschaucache oder einen anderen Auftrag.",
|
||||||
"render.output_grading": "Farbkorrektur",
|
"render.output_grading": "Farbkorrektur",
|
||||||
"render.output_image": "Bild",
|
"render.output_image": "Bild",
|
||||||
"render.output_largest": "{width} × {height} max.",
|
"render.output_largest": "{width} × {height} max.",
|
||||||
"render.output_lock_ratio": "Seitenverhältnis sperren",
|
"render.output_lock_ratio": "Seitenverhältnis sperren",
|
||||||
|
"render.output_master_unavailable": "Zum EXR-Export ein fertiges Bild mit EXR-Rohdaten auswählen. EXR-Rohdaten vor dem Rendern neuer Bilder aktivieren.",
|
||||||
"render.output_over_budget": "Das angeforderte Bild überschreitet den gewählten Ressourcenrahmen. Wählen Sie ausdrücklich eine passende Größe oder ein passendes Profil; es erfolgt keine automatische Herabstufung.",
|
"render.output_over_budget": "Das angeforderte Bild überschreitet den gewählten Ressourcenrahmen. Wählen Sie ausdrücklich eine passende Größe oder ein passendes Profil; es erfolgt keine automatische Herabstufung.",
|
||||||
|
"render.output_presentation_limits": "CPU-Bildeffekte und EXR-Ausgabe unterstützen Bilder mit bis zu 8192 Pixeln Breite oder Höhe und insgesamt höchstens 16.777.216 Pixeln.",
|
||||||
"render.output_presets_help": "Format, Qualität und Prüfliste befinden sich im Ausgabeeditor.",
|
"render.output_presets_help": "Format, Qualität und Prüfliste befinden sich im Ausgabeeditor.",
|
||||||
"render.output_quality": "Qualität",
|
"render.output_quality": "Qualität",
|
||||||
"render.output_quality_waiting": "Qualitätsdefinition steht noch aus",
|
"render.output_quality_waiting": "Qualitätsdefinition steht noch aus",
|
||||||
"render.output_ratio_refused": "Die verknüpfte Abmessung überschreitet das Größenlimit dieser Methode. Die vorherige Größe bleibt erhalten; entsperren Sie das Verhältnis oder wählen Sie einen kleineren Wert.",
|
"render.output_ratio_refused": "Die verknüpfte Abmessung überschreitet das Größenlimit dieser Methode. Die vorherige Größe bleibt erhalten; entsperren Sie das Verhältnis oder wählen Sie einen kleineren Wert.",
|
||||||
|
"render.output_raytrace_only": "Bildeffekte erfordern CPU-Raytracing oder GPU-Pathtracing.",
|
||||||
"render.output_ready": "Bereit zum Rendern",
|
"render.output_ready": "Bereit zum Rendern",
|
||||||
"render.output_run_check": "Prüfung starten",
|
"render.output_run_check": "Prüfung starten",
|
||||||
"render.output_size_limit": "{method}: bis zu {dimension} px pro Seite und {pixels} Pixel. Größte passende Größe: {width} × {height}.",
|
"render.output_size_limit": "{method}: bis zu {dimension} px pro Seite und {pixels} Pixel. Größte passende Größe: {width} × {height}.",
|
||||||
@@ -3673,6 +3833,12 @@
|
|||||||
"render.post_saturation": "Sättigung",
|
"render.post_saturation": "Sättigung",
|
||||||
"render.post_title": "Darstellungsfilter",
|
"render.post_title": "Darstellungsfilter",
|
||||||
"render.post_vignette": "Vignettierung",
|
"render.post_vignette": "Vignettierung",
|
||||||
|
"render.preparation_cancelled": "Rendering-Vorbereitung abgebrochen",
|
||||||
|
"render.preparation_checking": "Rendering wird geprüft",
|
||||||
|
"render.preparation_retirement_failed": "Die Bereinigung der Rendering-Vorbereitung ist fehlgeschlagen. Dieses Fenster kann kein weiteres Bild vorbereiten.",
|
||||||
|
"render.preparation_retiring": "Rendering-Vorbereitung wird abgeschlossen",
|
||||||
|
"render.preparation_unavailable": "Rendering-Vorbereitung nicht verfügbar",
|
||||||
|
"render.presentation_banner": "Bildeffekte funktionieren mit CPU- und GPU-Raytracing. Das Öffnen dieser Seite ändert die Methode nicht.",
|
||||||
"render.preview": "Integrierte Vorschau",
|
"render.preview": "Integrierte Vorschau",
|
||||||
"render.preview_help": "Rasterdarstellung mit gemischter Transparenz. Brechung, HDRI und finales Rendering sind noch nicht verfügbar.",
|
"render.preview_help": "Rasterdarstellung mit gemischter Transparenz. Brechung, HDRI und finales Rendering sind noch nicht verfügbar.",
|
||||||
"render.preview_image_settings_help": "Die Live-Vorschau behält eine feste Studiobeleuchtung. Belichtung und Bildbeleuchtung gelten für den Bildauftrag, wenn Sie „Prüfen“ oder „Start“ verwenden.",
|
"render.preview_image_settings_help": "Die Live-Vorschau behält eine feste Studiobeleuchtung. Belichtung und Bildbeleuchtung gelten für den Bildauftrag, wenn Sie „Prüfen“ oder „Start“ verwenden.",
|
||||||
@@ -3691,11 +3857,15 @@
|
|||||||
"render.quality_ack": "Die Projektion mit festem Budget und ihre Auslassungen bestätigen",
|
"render.quality_ack": "Die Projektion mit festem Budget und ihre Auslassungen bestätigen",
|
||||||
"render.quality_apply": "Dieses feste Sampling-Budget verwenden",
|
"render.quality_apply": "Dieses feste Sampling-Budget verwenden",
|
||||||
"render.quality_balanced": "Ausgewogen",
|
"render.quality_balanced": "Ausgewogen",
|
||||||
|
"render.quality_budget_help": "Legt Größe, Samples, Tiefe, Startwert und Rauschreduzierung fest. Andere Bildeinstellungen bleiben erhalten.",
|
||||||
"render.quality_custom": "Benutzerdefiniert",
|
"render.quality_custom": "Benutzerdefiniert",
|
||||||
|
"render.quality_denoise_off": "Rauschreduzierung: Aus",
|
||||||
|
"render.quality_denoise_on": "Rauschreduzierung: Ein",
|
||||||
"render.quality_draft": "Entwurf",
|
"render.quality_draft": "Entwurf",
|
||||||
"render.quality_high": "Hoch",
|
"render.quality_high": "Hoch",
|
||||||
"render.quality_projection_help": "Q-Vorgaben enthalten künftige Anforderungen. Diese Aktion projiziert nur ein unterstütztes PNG-Budget mit festen Samples und lässt adaptives Stoppen, rohe lineare Ausgabe und unabhängige Transmissionslimits ausdrücklich aus.",
|
"render.quality_projection_help": "Diese Prüfung ändert nur Auflösung, Abtastbudget, Pfadtiefe, Startwert und Rauschreduzierung. Andere Einstellungen bleiben erhalten; das Budget garantiert keine Konvergenz.",
|
||||||
"render.quality_review": "Sampling-Budget prüfen",
|
"render.quality_review": "Sampling-Budget prüfen",
|
||||||
|
"render.quality_summary": "{width} × {height} · {samples} Samples · Tiefe {depth} · Startwert {seed} · {denoise}",
|
||||||
"render.queue_pending": "In dieser Oberfläche gibt es keine automatische Produktionswarteschlange und keinen Animations-Renderer. Fertige Einzelbilder bleiben separat exportierbar.",
|
"render.queue_pending": "In dieser Oberfläche gibt es keine automatische Produktionswarteschlange und keinen Animations-Renderer. Fertige Einzelbilder bleiben separat exportierbar.",
|
||||||
"render.radial_assign": "Erscheinungsbild zuweisen",
|
"render.radial_assign": "Erscheinungsbild zuweisen",
|
||||||
"render.radial_compare": "Vergleichen",
|
"render.radial_compare": "Vergleichen",
|
||||||
@@ -3725,7 +3895,7 @@
|
|||||||
"render.result_navigation": "Verschieben und Zoomen folgen den Navigationseinstellungen. Leertaste + Ziehen verschiebt.",
|
"render.result_navigation": "Verschieben und Zoomen folgen den Navigationseinstellungen. Leertaste + Ziehen verschiebt.",
|
||||||
"render.result_open": "Das fertige Bild in der Hauptansicht öffnen.",
|
"render.result_open": "Das fertige Bild in der Hauptansicht öffnen.",
|
||||||
"render.result_preview": "Live-Vorschau",
|
"render.result_preview": "Live-Vorschau",
|
||||||
"render.result_provisional": "Vorläufiges CPU-Bild",
|
"render.result_provisional": "Vorläufiges Bild",
|
||||||
"render.result_provisional_help": "Dieser Zwischenstand ist nicht exportierbar. Der Export behält das letzte fertige Ergebnis; ein Abbruch kehrt zu diesem Ergebnis zurück.",
|
"render.result_provisional_help": "Dieser Zwischenstand ist nicht exportierbar. Der Export behält das letzte fertige Ergebnis; ein Abbruch kehrt zu diesem Ergebnis zurück.",
|
||||||
"render.result_reduced_display": "Anzeigevorschau auf die Geräte-/Cachegrenze reduziert. PNG und Export behalten die volle Auflösung; native 100%-Ansicht ist hier nicht verfügbar.",
|
"render.result_reduced_display": "Anzeigevorschau auf die Geräte-/Cachegrenze reduziert. PNG und Export behalten die volle Auflösung; native 100%-Ansicht ist hier nicht verfügbar.",
|
||||||
"render.result_retained": "Das zuletzt fertiggestellte Bild wird angezeigt; ein neueres Rendering hat es noch nicht ersetzt.",
|
"render.result_retained": "Das zuletzt fertiggestellte Bild wird angezeigt; ein neueres Rendering hat es noch nicht ersetzt.",
|
||||||
@@ -3814,9 +3984,11 @@
|
|||||||
"render.shot_frame_subject": "Motiv einpassen",
|
"render.shot_frame_subject": "Motiv einpassen",
|
||||||
"render.shot_invalid_camera": "Kamerawerte müssen endlich und im gültigen Bereich sein.",
|
"render.shot_invalid_camera": "Kamerawerte müssen endlich und im gültigen Bereich sein.",
|
||||||
"render.shot_lens": "Objektiv",
|
"render.shot_lens": "Objektiv",
|
||||||
|
"render.shot_lens_cpu_progressive_reason": "Vor der CPU-Schärfentiefe unter Sampling die progressive Abtastung übernehmen.",
|
||||||
"render.shot_lens_enable_reason": "Schärfentiefe aktivieren, um Blende und Fokusabstand zu bearbeiten.",
|
"render.shot_lens_enable_reason": "Schärfentiefe aktivieren, um Blende und Fokusabstand zu bearbeiten.",
|
||||||
"render.shot_lens_gpu_reason": "Schärfentiefe benötigt GPU-Pfadverfolgung.",
|
"render.shot_lens_gpu_reason": "Schärfentiefe benötigt GPU-Pfadverfolgung.",
|
||||||
"render.shot_lens_perspective_reason": "Schärfentiefe benötigt perspektivische Projektion.",
|
"render.shot_lens_perspective_reason": "Schärfentiefe benötigt perspektivische Projektion.",
|
||||||
|
"render.shot_lens_raytrace_reason": "Schärfentiefe ist für CPU-Raytracing und GPU-Pathtracing verfügbar.",
|
||||||
"render.shot_look_selection": "Auswahl anvisieren",
|
"render.shot_look_selection": "Auswahl anvisieren",
|
||||||
"render.shot_manager": "Gespeicherte Aufnahmen",
|
"render.shot_manager": "Gespeicherte Aufnahmen",
|
||||||
"render.shot_new_name": "Neuer Aufnahmename",
|
"render.shot_new_name": "Neuer Aufnahmename",
|
||||||
@@ -3850,7 +4022,12 @@
|
|||||||
"render.sky_horizon": "Horizont · lineares RGB",
|
"render.sky_horizon": "Horizont · lineares RGB",
|
||||||
"render.sky_zenith": "Zenit · lineares RGB",
|
"render.sky_zenith": "Zenit · lineares RGB",
|
||||||
"render.soft_focus": "Weichzeichnung",
|
"render.soft_focus": "Weichzeichnung",
|
||||||
|
"render.spot_axis": "Abstrahlrichtung",
|
||||||
"render.spot_cone": "Spotkegel",
|
"render.spot_cone": "Spotkegel",
|
||||||
|
"render.spot_gpu_help": "Die Winkel werden von der Abstrahlrichtung aus gemessen. Zwischen innerem und äußerem Winkel wird das Licht schwächer. Sehr schmale Übergänge erfordern CPU-Rendering.",
|
||||||
|
"render.spot_help": "Die Winkel werden von der Abstrahlrichtung aus gemessen. Zwischen innerem und äußerem Winkel wird das Licht schwächer.",
|
||||||
|
"render.spot_inner": "Innerer Halbwinkel",
|
||||||
|
"render.spot_outer": "Äußerer Halbwinkel",
|
||||||
"render.stage_frame": "Bildausschnitt",
|
"render.stage_frame": "Bildausschnitt",
|
||||||
"render.stage_layout.appearance_note": "Zum Anwenden auf die Auswahl klicken.",
|
"render.stage_layout.appearance_note": "Zum Anwenden auf die Auswahl klicken.",
|
||||||
"render.stage_layout.studio_note": "Vor dem Anwenden zur Prüfung klicken.",
|
"render.stage_layout.studio_note": "Vor dem Anwenden zur Prüfung klicken.",
|
||||||
@@ -3876,6 +4053,7 @@
|
|||||||
"render.stage_panels.extent": "Ausdehnung",
|
"render.stage_panels.extent": "Ausdehnung",
|
||||||
"render.stage_panels.fit_save": "Einpassen und speichern",
|
"render.stage_panels.fit_save": "Einpassen und speichern",
|
||||||
"render.stage_panels.gpu_required": "Benötigt GPU-Pfadverfolgung",
|
"render.stage_panels.gpu_required": "Benötigt GPU-Pfadverfolgung",
|
||||||
|
"render.stage_panels.grain_angle": "Maserungswinkel",
|
||||||
"render.stage_panels.keep": "Behalten",
|
"render.stage_panels.keep": "Behalten",
|
||||||
"render.stage_panels.kept": "Beibehalten",
|
"render.stage_panels.kept": "Beibehalten",
|
||||||
"render.stage_panels.kept_fields": "Kamera, Abtastung, Abmessungen, optische Bindungen, CAD-Geometrie und technische Eigenschaften.",
|
"render.stage_panels.kept_fields": "Kamera, Abtastung, Abmessungen, optische Bindungen, CAD-Geometrie und technische Eigenschaften.",
|
||||||
@@ -3889,7 +4067,7 @@
|
|||||||
"render.stage_panels.prepare_plan": "Erstellungsplan vorbereiten",
|
"render.stage_panels.prepare_plan": "Erstellungsplan vorbereiten",
|
||||||
"render.stage_panels.projects": "Projiziert",
|
"render.stage_panels.projects": "Projiziert",
|
||||||
"render.stage_panels.quality_ack": "Ich bestätige das feste Budget und seine Auslassungen",
|
"render.stage_panels.quality_ack": "Ich bestätige das feste Budget und seine Auslassungen",
|
||||||
"render.stage_panels.quality_omissions": "Adaptives Stoppen und Fehlerziele, separate Transmissionstiefe, Russian-Roulette-Regeln, lineare Rohausgabe, EXR-Master und Konvergenznachweis.",
|
"render.stage_panels.quality_omissions": "Dieses Budget legt keine adaptiven Fehlerziele, eigenen Transmissionsgrenzen, Russian-Roulette-Regeln oder Speicherung des linearen Masters fest und garantiert keine Konvergenz.",
|
||||||
"render.stage_panels.reference": "Referenz",
|
"render.stage_panels.reference": "Referenz",
|
||||||
"render.stage_panels.references": "Referenzen",
|
"render.stage_panels.references": "Referenzen",
|
||||||
"render.stage_panels.remove_optics": "Optik entfernen",
|
"render.stage_panels.remove_optics": "Optik entfernen",
|
||||||
@@ -3933,7 +4111,7 @@
|
|||||||
"render.stage_panels.subtitle_environment": "Originale HDR-Umgebung · gesamte Szene",
|
"render.stage_panels.subtitle_environment": "Originale HDR-Umgebung · gesamte Szene",
|
||||||
"render.stage_panels.subtitle_maps": "Erstellte räumliche Texturen · temporäre Vorschau",
|
"render.stage_panels.subtitle_maps": "Erstellte räumliche Texturen · temporäre Vorschau",
|
||||||
"render.stage_panels.subtitle_physical": "Schreibgeschützter Plan · kein Material erstellt oder zugewiesen",
|
"render.stage_panels.subtitle_physical": "Schreibgeschützter Plan · kein Material erstellt oder zugewiesen",
|
||||||
"render.stage_panels.subtitle_quality": "Festes GPU-Budget · zukünftige Anforderungen ausgelassen",
|
"render.stage_panels.subtitle_quality": "GPU-Abtastbudget · nur aufgeführte Einstellungen",
|
||||||
"render.stage_panels.subtitle_rig": "Erstellter Lichtaufbau · Lichtquellen und passive Flächen",
|
"render.stage_panels.subtitle_rig": "Erstellter Lichtaufbau · Lichtquellen und passive Flächen",
|
||||||
"render.stage_panels.subtitle_saved_maps": "Erstellte Texturen · mit dieser Aufnahme gespeichert",
|
"render.stage_panels.subtitle_saved_maps": "Erstellte Texturen · mit dieser Aufnahme gespeichert",
|
||||||
"render.stage_panels.subtitle_stage": "Physischer Boden und Bühne · an das Produkt angepasst",
|
"render.stage_panels.subtitle_stage": "Physischer Boden und Bühne · an das Produkt angepasst",
|
||||||
@@ -3958,7 +4136,7 @@
|
|||||||
"render.studio_area_help": "Bis zu vier einseitige Rechtecke. Orthogonale Halbkanten U und V bestimmen die Größe; U × V zeigt zur beleuchteten Seite. Die Koordinaten sind Weltmillimeter.",
|
"render.studio_area_help": "Bis zu vier einseitige Rechtecke. Orthogonale Halbkanten U und V bestimmen die Größe; U × V zeigt zur beleuchteten Seite. Die Koordinaten sind Weltmillimeter.",
|
||||||
"render.studio_center": "Mittelpunkt X, Y, Z (mm)",
|
"render.studio_center": "Mittelpunkt X, Y, Z (mm)",
|
||||||
"render.studio_denoise": "Rauschen im fertigen Bild reduzieren",
|
"render.studio_denoise": "Rauschen im fertigen Bild reduzieren",
|
||||||
"render.studio_denoise_help": "Abschließender kantenerhaltender Filter; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
|
"render.studio_denoise_help": "Kantenerhaltender Filter für progressive und fertige Bilder; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
|
||||||
"render.studio_direction": "Richtung zur Sonne (X, Y, Z)",
|
"render.studio_direction": "Richtung zur Sonne (X, Y, Z)",
|
||||||
"render.studio_environment": "Umgebung",
|
"render.studio_environment": "Umgebung",
|
||||||
"render.studio_environment_bounds": "Die Umgebungsdrehung muss endlich und innerhalb von ±1.000.000 Radiant liegen, die Intensität zwischen 0 und 100.",
|
"render.studio_environment_bounds": "Die Umgebungsdrehung muss endlich und innerhalb von ±1.000.000 Radiant liegen, die Intensität zwischen 0 und 100.",
|
||||||
@@ -3994,6 +4172,19 @@
|
|||||||
"render.subsurface_enable": "Random Walk für innere Streuung",
|
"render.subsurface_enable": "Random Walk für innere Streuung",
|
||||||
"render.subsurface_help": "Nur für geschlossene Dielektrika. Streuung und RGB-Absorption sind getrennte Koeffizienten pro mm. Pfade streuen innerhalb der tatsächlichen Geometrie und werden dann an der Grenzfläche gebrochen oder reflektiert. Deaktivieren Sie die Transmission gerader Schattenstrahlen, das Entrauschen von Oberflächen und das äußere Volumen; verwenden Sie eine Fresnel-Schicht. Kein Diffusions-Shader, kein Dichtegitter und kein gemessenes Material. Dichte Medien können mehr Samples und Pfadtiefe benötigen.",
|
"render.subsurface_help": "Nur für geschlossene Dielektrika. Streuung und RGB-Absorption sind getrennte Koeffizienten pro mm. Pfade streuen innerhalb der tatsächlichen Geometrie und werden dann an der Grenzfläche gebrochen oder reflektiert. Deaktivieren Sie die Transmission gerader Schattenstrahlen, das Entrauschen von Oberflächen und das äußere Volumen; verwenden Sie eine Fresnel-Schicht. Kein Diffusions-Shader, kein Dichtegitter und kein gemessenes Material. Dichte Medien können mehr Samples und Pfadtiefe benötigen.",
|
||||||
"render.subsurface_rate": "Streukollisionsrate",
|
"render.subsurface_rate": "Streukollisionsrate",
|
||||||
|
"render.surface_import.author": "Autor",
|
||||||
|
"render.surface_import.busy": "Bild wird importiert…",
|
||||||
|
"render.surface_import.complete": "Wählen Sie ein Bild und geben Sie Autor, Quelle und Lizenz an.",
|
||||||
|
"render.surface_import.embedded": "Importiertes Bild",
|
||||||
|
"render.surface_import.failed": "Der Bildimport wurde nicht abgeschlossen.",
|
||||||
|
"render.surface_import.license": "Lizenz / Eigentum",
|
||||||
|
"render.surface_import.raytrace_required": "Bilder erfordern CPU- oder GPU-Raytracing. Wählen Sie es unter Ausgabe.",
|
||||||
|
"render.surface_import.repeat_u": "Wiederholung U",
|
||||||
|
"render.surface_import.repeat_v": "Wiederholung V",
|
||||||
|
"render.surface_import.save": "Bild am Körper speichern",
|
||||||
|
"render.surface_import.saved_cpu": "Speichern Sie Texturen für das CPU-Rendering.",
|
||||||
|
"render.surface_import.source": "Quelle",
|
||||||
|
"render.surface_import.title": "Bild importieren",
|
||||||
"render.tone_aces_fitted": "ACES angepasst",
|
"render.tone_aces_fitted": "ACES angepasst",
|
||||||
"render.tone_reinhard": "Reinhard",
|
"render.tone_reinhard": "Reinhard",
|
||||||
"render.transmission_scope": "GPU-Glas unterstützt zertifizierte getrennte Medien. Unbeschränkt verschachtelte Medien und ein rauschfreier Transmissionsschätzer werden nicht bereitgestellt.",
|
"render.transmission_scope": "GPU-Glas unterstützt zertifizierte getrennte Medien. Unbeschränkt verschachtelte Medien und ein rauschfreier Transmissionsschätzer werden nicht bereitgestellt.",
|
||||||
@@ -4038,11 +4229,15 @@
|
|||||||
"render.use_preview": "Integrierte Vorschau verwenden",
|
"render.use_preview": "Integrierte Vorschau verwenden",
|
||||||
"render.volume_albedo": "Streualbedo · lineares RGB",
|
"render.volume_albedo": "Streualbedo · lineares RGB",
|
||||||
"render.volume_anisotropy": "Mittlerer Phasenkosinus",
|
"render.volume_anisotropy": "Mittlerer Phasenkosinus",
|
||||||
|
"render.volume_denoise_required": "Für Atmosphäre die Rauschreduzierung ausschalten.",
|
||||||
"render.volume_enable": "Teilnehmendes Medium aktivieren",
|
"render.volume_enable": "Teilnehmendes Medium aktivieren",
|
||||||
"render.volume_extinction": "Extinktionskoeffizient",
|
"render.volume_extinction": "Extinktionskoeffizient",
|
||||||
|
"render.volume_glass_required": "Atmosphäre und Glas können in dieser Einstellung nicht kombiniert werden.",
|
||||||
"render.volume_help": "GPU-Transport innerhalb einer endlichen Box, mit Beer-Lambert-Abschwächung und HG-Streuung auf Kamera- und indirekten Pfaden. Verwendet ausdrückliche Studio-, HDR- und Himmelsbeleuchtung einschließlich Schattenabschwächung. Ein positiver Phasenkosinus streut nach vorn. Glasgrenzflächen und Oberflächenentrauschen müssen ausdrücklich deaktiviert werden. Extinktion null lässt den Transport unverändert. Keine physikalische Mondatmosphäre, kein Dichtegitter, keine Volumenemission, kein spektraler Transport und keine zertifizierte Materialeigenschaft.",
|
"render.volume_help": "GPU-Transport innerhalb einer endlichen Box, mit Beer-Lambert-Abschwächung und HG-Streuung auf Kamera- und indirekten Pfaden. Verwendet ausdrückliche Studio-, HDR- und Himmelsbeleuchtung einschließlich Schattenabschwächung. Ein positiver Phasenkosinus streut nach vorn. Glasgrenzflächen und Oberflächenentrauschen müssen ausdrücklich deaktiviert werden. Extinktion null lässt den Transport unverändert. Keine physikalische Mondatmosphäre, kein Dichtegitter, keine Volumenemission, kein spektraler Transport und keine zertifizierte Materialeigenschaft.",
|
||||||
|
"render.volume_lighting_required": "Für Atmosphäre eine Umgebung oder ein eigenes Lichtrig wählen.",
|
||||||
"render.volume_max": "Obere Grenzen · Weltkoordinaten (mm)",
|
"render.volume_max": "Obere Grenzen · Weltkoordinaten (mm)",
|
||||||
"render.volume_min": "Untere Grenzen · Weltkoordinaten (mm)",
|
"render.volume_min": "Untere Grenzen · Weltkoordinaten (mm)",
|
||||||
|
"render.volume_single_stratum_required": "Für Atmosphäre ein Transmissionssample verwenden.",
|
||||||
"render.volume_title": "Begrenztes homogenes Volumen",
|
"render.volume_title": "Begrenztes homogenes Volumen",
|
||||||
"render.wait_add_light": "Noch nicht unterstützt: benannte Lichtquellen erstellen",
|
"render.wait_add_light": "Noch nicht unterstützt: benannte Lichtquellen erstellen",
|
||||||
"render.wait_batch": "Noch nicht unterstützt: Renderwarteschlange gespeicherter Aufnahmen",
|
"render.wait_batch": "Noch nicht unterstützt: Renderwarteschlange gespeicherter Aufnahmen",
|
||||||
@@ -4070,6 +4265,8 @@
|
|||||||
"sample.plate.name": "Beispielplatte",
|
"sample.plate.name": "Beispielplatte",
|
||||||
"sample.vents.about": "Langlöcher in einem Rechteckmuster: Zeilen, Spalten und Abstand sind Parameter",
|
"sample.vents.about": "Langlöcher in einem Rechteckmuster: Zeilen, Spalten und Abstand sind Parameter",
|
||||||
"sample.vents.name": "Lüftungsplatte",
|
"sample.vents.name": "Lüftungsplatte",
|
||||||
|
"scale.per_axis": "Je Achse",
|
||||||
|
"scale.uniform": "Gleichmäßig",
|
||||||
"search.command_keys": "Eingabe ausführen Umschalt+Eingabe ausführen und offen lassen Esc schließen",
|
"search.command_keys": "Eingabe ausführen Umschalt+Eingabe ausführen und offen lassen Esc schließen",
|
||||||
"search.commands": "Befehle",
|
"search.commands": "Befehle",
|
||||||
"search.design": "In diesem Design",
|
"search.design": "In diesem Design",
|
||||||
@@ -4157,6 +4354,7 @@
|
|||||||
"sketch.dimension_applied": "Maß angewendet",
|
"sketch.dimension_applied": "Maß angewendet",
|
||||||
"sketch.dimension_applied_defined": "Maß angewendet; Parameter `{name}` definiert",
|
"sketch.dimension_applied_defined": "Maß angewendet; Parameter `{name}` definiert",
|
||||||
"sketch.dimension_applied_renamed": "Maß angewendet; der Parameter heißt jetzt `{name}`",
|
"sketch.dimension_applied_renamed": "Maß angewendet; der Parameter heißt jetzt `{name}`",
|
||||||
|
"sketch.divide_hint": "Punkte teilen jede Kurve in gleich lange Abschnitte und behalten sie bei, wenn sie sich ändert.",
|
||||||
"sketch.dof_left": "{count} Freiheitsgrade übrig",
|
"sketch.dof_left": "{count} Freiheitsgrade übrig",
|
||||||
"sketch.dof_left_one": "{count} Freiheitsgrad übrig",
|
"sketch.dof_left_one": "{count} Freiheitsgrad übrig",
|
||||||
"sketch.draw_curves": "Als {linetype} zeichnen",
|
"sketch.draw_curves": "Als {linetype} zeichnen",
|
||||||
@@ -4712,6 +4910,7 @@
|
|||||||
"ui.click_selects_whole_bodies": "Klick wählt ganze Körper aus",
|
"ui.click_selects_whole_bodies": "Klick wählt ganze Körper aus",
|
||||||
"ui.click_then_press_key": "Klicken, dann die neue Taste drücken",
|
"ui.click_then_press_key": "Klicken, dann die neue Taste drücken",
|
||||||
"ui.close": "Schließen",
|
"ui.close": "Schließen",
|
||||||
|
"ui.close_tab": "Tab schließen",
|
||||||
"ui.coil": "Schraube",
|
"ui.coil": "Schraube",
|
||||||
"ui.coincident": "Koinzident",
|
"ui.coincident": "Koinzident",
|
||||||
"ui.collapse": "Einklappen",
|
"ui.collapse": "Einklappen",
|
||||||
@@ -4927,6 +5126,11 @@
|
|||||||
"ui.factor": "Faktor",
|
"ui.factor": "Faktor",
|
||||||
"ui.fade": "Auslauf",
|
"ui.fade": "Auslauf",
|
||||||
"ui.fade_length": "Auslauflänge",
|
"ui.fade_length": "Auslauflänge",
|
||||||
|
"ui.fair_iterations": "Iterationen",
|
||||||
|
"ui.fair_keep_edges": "Kanten behalten",
|
||||||
|
"ui.fair_strength": "Stärke",
|
||||||
|
"ui.fair_surface_title": "FLÄCHENBEGRADIGUNG",
|
||||||
|
"ui.fair_tolerance_default": "0,001 der Körpergröße",
|
||||||
"ui.feature_history": "Feature-Verlauf",
|
"ui.feature_history": "Feature-Verlauf",
|
||||||
"ui.feature_options": "Funktionsoptionen",
|
"ui.feature_options": "Funktionsoptionen",
|
||||||
"ui.features": "Funktionen",
|
"ui.features": "Funktionen",
|
||||||
@@ -5007,6 +5211,7 @@
|
|||||||
"ui.heading_custom_view_navigator": "EIGENER ANSICHTSNAVIGATOR",
|
"ui.heading_custom_view_navigator": "EIGENER ANSICHTSNAVIGATOR",
|
||||||
"ui.heading_cylinder": "ZYLINDER",
|
"ui.heading_cylinder": "ZYLINDER",
|
||||||
"ui.heading_design": "DESIGN",
|
"ui.heading_design": "DESIGN",
|
||||||
|
"ui.heading_divide_curve": "KURVE TEILEN",
|
||||||
"ui.heading_draft": "ABZIEHEN",
|
"ui.heading_draft": "ABZIEHEN",
|
||||||
"ui.heading_edit": "BEARBEITEN",
|
"ui.heading_edit": "BEARBEITEN",
|
||||||
"ui.heading_equation_curve": "GLEICHUNGSKURVE",
|
"ui.heading_equation_curve": "GLEICHUNGSKURVE",
|
||||||
@@ -5087,6 +5292,11 @@
|
|||||||
"ui.history_play_button_show": "Wiedergabe-Schaltfläche einblenden",
|
"ui.history_play_button_show": "Wiedergabe-Schaltfläche einblenden",
|
||||||
"ui.history_play_finish_action": "Die aktuelle Aktion abschließen, bevor der Verlauf abgespielt wird",
|
"ui.history_play_finish_action": "Die aktuelle Aktion abschließen, bevor der Verlauf abgespielt wird",
|
||||||
"ui.history_play_roll_back": "Die Verlaufsmarkierung vor dem Abspielen zurücksetzen",
|
"ui.history_play_roll_back": "Die Verlaufsmarkierung vor dem Abspielen zurücksetzen",
|
||||||
|
"ui.history_scope_all": "Features des ganzen Designs zeigen",
|
||||||
|
"ui.history_scope_all_shown": "Die Features des ganzen Designs",
|
||||||
|
"ui.history_scope_pick": "Eine Komponente bearbeiten oder im Browser wählen, um nur ihre Features zu zeigen",
|
||||||
|
"ui.history_scope_show": "Nur die Features von {name} und der Komponenten darin zeigen",
|
||||||
|
"ui.history_scope_shown": "Die Features von {name} und der Komponenten darin",
|
||||||
"ui.history_show_names": "Namen anzeigen",
|
"ui.history_show_names": "Namen anzeigen",
|
||||||
"ui.history_sketch_listed_under": "Aufgeführt unter {feature}",
|
"ui.history_sketch_listed_under": "Aufgeführt unter {feature}",
|
||||||
"ui.history_title": "Verlauf",
|
"ui.history_title": "Verlauf",
|
||||||
@@ -5109,6 +5319,7 @@
|
|||||||
"ui.inspect": "Prüfen",
|
"ui.inspect": "Prüfen",
|
||||||
"ui.instances": "Instanzen",
|
"ui.instances": "Instanzen",
|
||||||
"ui.intersect": "Schnitt",
|
"ui.intersect": "Schnitt",
|
||||||
|
"ui.intersection_curve_second_pick": "Schnittkurve: zweite Fläche oder Kurve anklicken (Rechtsklick zum Neubeginn)",
|
||||||
"ui.isolate": "Isolieren",
|
"ui.isolate": "Isolieren",
|
||||||
"ui.join": "Verbinden",
|
"ui.join": "Verbinden",
|
||||||
"ui.joint_play": "▶ Abspielen",
|
"ui.joint_play": "▶ Abspielen",
|
||||||
@@ -5130,12 +5341,13 @@
|
|||||||
"ui.link_copies_text": "Diese Komponenten erzeugen dieselben Körper wie {name}. Sollen sie zu Platzierungen seiner Verknüpfung werden?",
|
"ui.link_copies_text": "Diese Komponenten erzeugen dieselben Körper wie {name}. Sollen sie zu Platzierungen seiner Verknüpfung werden?",
|
||||||
"ui.lock": "Sperren",
|
"ui.lock": "Sperren",
|
||||||
"ui.loft": "Loft",
|
"ui.loft": "Loft",
|
||||||
"ui.look_at": "Ansehen",
|
"ui.look_at": "Senkrecht auf",
|
||||||
"ui.look_at_anim_desc": "Achsenansicht, Ansehen, Home und Einpassen",
|
"ui.look_at_anim_desc": "Achsenansicht, Senkrecht auf, Home und Einpassen",
|
||||||
"ui.major_radius": "Großer Radius",
|
"ui.major_radius": "Großer Radius",
|
||||||
"ui.manipulator_free_alt": "Freies Ziehen des Manipulators auch mit Alt/Option",
|
"ui.manipulator_free_alt": "Freies Ziehen des Manipulators auch mit Alt/Option",
|
||||||
"ui.manipulator_free_alt_tip": "An: Alt/Option oder Strg/Cmd auf Extrusions-/Gizmo-Pfeilen deaktiviert das Einrasten von Werten. Aus: nur Strg/Cmd.",
|
"ui.manipulator_free_alt_tip": "An: Alt/Option oder Strg/Cmd auf Extrusions-/Gizmo-Pfeilen deaktiviert das Einrasten von Werten. Aus: nur Strg/Cmd.",
|
||||||
"ui.manufacture": "Fertigung",
|
"ui.manufacture": "Fertigung",
|
||||||
|
"ui.match_surface_title": "FLÄCHENANPASSUNG",
|
||||||
"ui.material_review.axes": "Materiallokale Achsen; keine Körperausrichtung zugewiesen.",
|
"ui.material_review.axes": "Materiallokale Achsen; keine Körperausrichtung zugewiesen.",
|
||||||
"ui.material_review.claims": "Quellen und Lizenzen sind ungeprüfte Angaben. Fehlende Werte bleiben unbekannt.",
|
"ui.material_review.claims": "Quellen und Lizenzen sind ungeprüfte Angaben. Fehlende Werte bleiben unbekannt.",
|
||||||
"ui.material_review.compressive_strength": "Druckfestigkeit",
|
"ui.material_review.compressive_strength": "Druckfestigkeit",
|
||||||
@@ -5158,10 +5370,15 @@
|
|||||||
"ui.material_review.title": "Physikalische Materialdaten prüfen",
|
"ui.material_review.title": "Physikalische Materialdaten prüfen",
|
||||||
"ui.material_review.unknown": "Unbekannt",
|
"ui.material_review.unknown": "Unbekannt",
|
||||||
"ui.material_review.yield_strength": "Streckgrenze",
|
"ui.material_review.yield_strength": "Streckgrenze",
|
||||||
|
"ui.mcp_agents": "Agenten über MCP",
|
||||||
|
"ui.mcp_compact": "Kompakte Ausgabe",
|
||||||
|
"ui.mcp_compact_note": "Für Agenten, die über den MCP-Server (solidcraft-cli mcp) verbunden sind: inspect_design fasst Skizzen zusammen und Werkzeugergebnisse werden nicht formatiert, sodass sie weniger Tokens verbrauchen. Gilt für Agentensitzungen, die nach der Änderung beginnen; --compact oder SOLIDCRAFT_MCP_COMPACT=1 schalten es ebenfalls ein.",
|
||||||
"ui.measure": "Messen",
|
"ui.measure": "Messen",
|
||||||
"ui.mesh_format_3mf": "3MF",
|
"ui.mesh_format_3mf": "3MF",
|
||||||
"ui.mesh_format_3mf_tagged": "3MF mit Flächenmarken",
|
"ui.mesh_format_3mf_tagged": "3MF mit Flächenmarken",
|
||||||
"ui.mesh_format_stl": "STL",
|
"ui.mesh_format_stl": "STL",
|
||||||
|
"ui.mesh_import_origin": "In den Ursprung verschieben",
|
||||||
|
"ui.mesh_import_origin_tip": "Das Netz in X und Y zentrieren und seine Unterseite auf Z = 0 legen. Wenn aus, behält das Netz die Koordinaten aus der Datei.",
|
||||||
"ui.minor_radius": "Kleiner Radius",
|
"ui.minor_radius": "Kleiner Radius",
|
||||||
"ui.mirror": "Spiegeln",
|
"ui.mirror": "Spiegeln",
|
||||||
"ui.mirror_line": "Spiegelachse",
|
"ui.mirror_line": "Spiegelachse",
|
||||||
@@ -5199,6 +5416,7 @@
|
|||||||
"ui.options": "Optionen",
|
"ui.options": "Optionen",
|
||||||
"ui.orbit": "Drehen",
|
"ui.orbit": "Drehen",
|
||||||
"ui.orbit_around": "Umkreisen um",
|
"ui.orbit_around": "Umkreisen um",
|
||||||
|
"ui.organize": "Organisieren",
|
||||||
"ui.orientation": "Ausrichtung",
|
"ui.orientation": "Ausrichtung",
|
||||||
"ui.origin": "Ursprung",
|
"ui.origin": "Ursprung",
|
||||||
"ui.palette_construction_mode": "Konstruktionsmodus (X)",
|
"ui.palette_construction_mode": "Konstruktionsmodus (X)",
|
||||||
@@ -5206,6 +5424,101 @@
|
|||||||
"ui.palette_sketch_options": "Skizzenoptionen",
|
"ui.palette_sketch_options": "Skizzenoptionen",
|
||||||
"ui.pan": "Verschieben",
|
"ui.pan": "Verschieben",
|
||||||
"ui.pan_zoom_orbit": "Verschieben, zoomen, umkreisen",
|
"ui.pan_zoom_orbit": "Verschieben, zoomen, umkreisen",
|
||||||
|
"ui.panel_fixture.apply": "Anwenden",
|
||||||
|
"ui.panel_fixture.cancel": "Abbrechen",
|
||||||
|
"ui.panel_fixture.caption": "Synthetisches Panelmuster",
|
||||||
|
"ui.panel_fixture.draft": "Beibehaltener Entwurf",
|
||||||
|
"ui.panel_fixture.editor": "Mustereditor",
|
||||||
|
"ui.panel_fixture.explanation": "Dieser synthetische Inhalt behält seinen Entwurf beim Ausblenden oder Verschieben.",
|
||||||
|
"ui.panel_fixture.fill": "Füllendes Muster",
|
||||||
|
"ui.panel_fixture.finish": "Abschließen",
|
||||||
|
"ui.panel_fixture.finished": "Synthetische Transaktion beendet",
|
||||||
|
"ui.panel_fixture.footer": "Musteraktionen lassen den Entwurf unverändert.",
|
||||||
|
"ui.panel_fixture.guard": "Abbruch bei laufender Transaktion verweigern",
|
||||||
|
"ui.panel_fixture.inline": "Einzeiliges Muster",
|
||||||
|
"ui.panel_fixture.live": "Synthetische Transaktion läuft",
|
||||||
|
"ui.panel_fixture.log": "Musterprotokoll",
|
||||||
|
"ui.panel_fixture.pin": "Angeheftetes Muster",
|
||||||
|
"ui.panel_fixture.title": "{title} · {id}",
|
||||||
|
"ui.panel_fixture.updates": "Dienstaktualisierungen: {count}",
|
||||||
|
"ui.panel_host.attention": "Aufmerksamkeit erforderlich",
|
||||||
|
"ui.panel_host.bottom": "Unten",
|
||||||
|
"ui.panel_host.close": "Panel schließen",
|
||||||
|
"ui.panel_host.collapse": "Panel einklappen",
|
||||||
|
"ui.panel_host.expand": "Panel ausklappen",
|
||||||
|
"ui.panel_host.floating": "Schwebend",
|
||||||
|
"ui.panel_host.focus": "Panel fokussieren",
|
||||||
|
"ui.panel_host.focus_target": "{title} fokussieren",
|
||||||
|
"ui.panel_host.header": "Panelkopf",
|
||||||
|
"ui.panel_host.join": "Tabs verbinden",
|
||||||
|
"ui.panel_host.join_target": "Tabs mit {title} verbinden",
|
||||||
|
"ui.panel_host.left": "Links",
|
||||||
|
"ui.panel_host.menu": "Panelaktionen",
|
||||||
|
"ui.panel_host.move": "Verschieben nach",
|
||||||
|
"ui.panel_host.overflow": "Weitere Panels",
|
||||||
|
"ui.panel_host.popout": "In einem Fenster öffnen",
|
||||||
|
"ui.panel_host.resize": "Panelgröße ändern",
|
||||||
|
"ui.panel_host.right": "Rechts",
|
||||||
|
"ui.panel_host.rule.availability": "Dieses Panel ist nicht verfügbar.",
|
||||||
|
"ui.panel_host.rule.bounds": "Die Panels passen nicht in ihre Größenbegrenzungen.",
|
||||||
|
"ui.panel_host.rule.capability": "Dieser Host unterstützt diese Aktion nicht.",
|
||||||
|
"ui.panel_host.rule.collection_limit": "Das Panellayout hat seine Grenze erreicht.",
|
||||||
|
"ui.panel_host.rule.duplicate_id": "Diese Panelkennung wird bereits verwendet.",
|
||||||
|
"ui.panel_host.rule.exactly_once": "Jedes Panel muss einmal erscheinen.",
|
||||||
|
"ui.panel_host.rule.feature_disabled": "Diese Aktion ist für dieses Panel deaktiviert.",
|
||||||
|
"ui.panel_host.rule.id": "Die Panelkennung ist ungültig.",
|
||||||
|
"ui.panel_host.rule.incompatible_tabs": "Diese Panels können keine Tabgruppe teilen.",
|
||||||
|
"ui.panel_host.rule.lifecycle": "Das Panel konnte diese Aktion nicht abschließen.",
|
||||||
|
"ui.panel_host.rule.missing_target": "Dieses Panelziel ist nicht mehr verfügbar.",
|
||||||
|
"ui.panel_host.rule.no_space": "Für dieses Panellayout ist kein Platz.",
|
||||||
|
"ui.panel_host.rule.pin_conflict": "An dieser Kante ist bereits ein anderes Panel angeheftet.",
|
||||||
|
"ui.panel_host.rule.pin_order": "Ein angeheftetes Panel muss an seiner Kante zuerst bleiben.",
|
||||||
|
"ui.panel_host.rule.placement": "Dieses Panel kann nicht an diese Position verschoben werden.",
|
||||||
|
"ui.panel_host.rule.revision": "Das Layout wurde geändert. Versuchen Sie die Aktion erneut.",
|
||||||
|
"ui.panel_host.rule.schema": "Die Panelanfrage ist ungültig.",
|
||||||
|
"ui.panel_host.rule.version": "Diese Panellayoutversion wird nicht unterstützt.",
|
||||||
|
"ui.panel_host.sash": "Benachbarte Panels skalieren",
|
||||||
|
"ui.panel_host.scroll_horizontal": "Panelspalten scrollen",
|
||||||
|
"ui.panel_host.scroll_vertical": "Panelstapel scrollen",
|
||||||
|
"ui.panel_host.surface": "Panel",
|
||||||
|
"ui.panel_host.tab": "Paneltab",
|
||||||
|
"ui.panel_host.tabs": "Alle Tabs",
|
||||||
|
"ui.panel_layout.applied": "{name} auf {workspace} angewendet",
|
||||||
|
"ui.panel_layout.cancel": "Abbrechen",
|
||||||
|
"ui.panel_layout.confirm_replace": "„{name}“ ersetzen?",
|
||||||
|
"ui.panel_layout.copy": "Kopieren",
|
||||||
|
"ui.panel_layout.current": "Aktuelles Layout",
|
||||||
|
"ui.panel_layout.custom": "Benutzerdefiniert",
|
||||||
|
"ui.panel_layout.custom_hint": "Panels wurden seit {name} verschoben.",
|
||||||
|
"ui.panel_layout.details": "Details",
|
||||||
|
"ui.panel_layout.layout_workspace": "Layout-Arbeitsbereich",
|
||||||
|
"ui.panel_layout.page_defaults_hint": "Setzt die Einstellungen dieser Seite zurück. Panel-Anordnungen und gespeicherte Layouts bleiben erhalten.",
|
||||||
|
"ui.panel_layout.panel_layout": "Panel-Layout",
|
||||||
|
"ui.panel_layout.panel_style": "Panel-Stil",
|
||||||
|
"ui.panel_layout.replace": "Layout ersetzen",
|
||||||
|
"ui.panel_layout.replace_current": "Durch aktuelle Anordnung ersetzen",
|
||||||
|
"ui.panel_layout.replace_short": "Ersetzen",
|
||||||
|
"ui.panel_layout.reset": "Arbeitsbereich zurücksetzen",
|
||||||
|
"ui.panel_layout.reset_hint": "{workspace} auf die Standardanordnung zurücksetzen. Gespeicherte Layouts bleiben erhalten.",
|
||||||
|
"ui.panel_layout.reset_short": "Zurücksetzen",
|
||||||
|
"ui.panel_layout.restore_failed": "Das Layout konnte nicht wiederhergestellt werden",
|
||||||
|
"ui.panel_layout.save": "Layout speichern",
|
||||||
|
"ui.panel_layout.save_as": "Speichern unter",
|
||||||
|
"ui.panel_layout.save_as_tile": "Speichern unter…",
|
||||||
|
"ui.panel_layout.selected": "ausgewählt",
|
||||||
|
"ui.panel_layout.suggested_name": "Layout {n}",
|
||||||
|
"ui.panel_layout.try_again": "Erneut versuchen",
|
||||||
|
"ui.panel_layout.validation.builtin": "Integrierte Layouts können nicht geändert werden",
|
||||||
|
"ui.panel_layout.validation.duplicate": "Dieser Name ist bereits vorhanden",
|
||||||
|
"ui.panel_layout.validation.empty": "Namen eingeben",
|
||||||
|
"ui.panel_layout.validation.full": "64 von 64 gespeichert. Ein Layout zum Ersetzen wählen.",
|
||||||
|
"ui.panel_layout.validation.invalid": "1–64 Zeichen ohne Steuerzeichen verwenden",
|
||||||
|
"ui.panel_layout.workspace.classic": "Klassisch",
|
||||||
|
"ui.panel_layout.workspace.modelling": "Modellierung",
|
||||||
|
"ui.panel_layout.workspace.render": "Rendern",
|
||||||
|
"ui.panel_layout.workspace.review": "Prüfung",
|
||||||
|
"ui.panel_layout.workspace.sketch": "Skizze",
|
||||||
|
"ui.panel_layout.your_layout": "Eigenes Layout",
|
||||||
"ui.parallel": "Parallel",
|
"ui.parallel": "Parallel",
|
||||||
"ui.parameters": "Parameter",
|
"ui.parameters": "Parameter",
|
||||||
"ui.part_size_needs_a_part": "Wählen Sie ein eingefügtes Standardbauteil (Schraube, Mutter, Scheibe oder Stift) aus, um seine Größe zu ändern. Einen Körper vergrößern oder verkleinern Sie mit Skalieren.",
|
"ui.part_size_needs_a_part": "Wählen Sie ein eingefügtes Standardbauteil (Schraube, Mutter, Scheibe oder Stift) aus, um seine Größe zu ändern. Einen Körper vergrößern oder verkleinern Sie mit Skalieren.",
|
||||||
@@ -5243,6 +5556,7 @@
|
|||||||
"ui.point_cloud_add_filter": "Filter hinzufügen…",
|
"ui.point_cloud_add_filter": "Filter hinzufügen…",
|
||||||
"ui.point_cloud_auto_range": "Bereich automatisch",
|
"ui.point_cloud_auto_range": "Bereich automatisch",
|
||||||
"ui.point_cloud_color_by": "Färben nach",
|
"ui.point_cloud_color_by": "Färben nach",
|
||||||
|
"ui.point_cloud_dots": "Einzelpunkte",
|
||||||
"ui.point_cloud_file_colors": "Farben der Datei",
|
"ui.point_cloud_file_colors": "Farben der Datei",
|
||||||
"ui.point_cloud_filter_by": "Filtern nach",
|
"ui.point_cloud_filter_by": "Filtern nach",
|
||||||
"ui.point_cloud_height_high": "Oberste Höhe",
|
"ui.point_cloud_height_high": "Oberste Höhe",
|
||||||
@@ -5251,6 +5565,7 @@
|
|||||||
"ui.point_cloud_no_file_colors": "Die Datei hat keine Farben",
|
"ui.point_cloud_no_file_colors": "Die Datei hat keine Farben",
|
||||||
"ui.point_cloud_pause": "Pause",
|
"ui.point_cloud_pause": "Pause",
|
||||||
"ui.point_cloud_play": "Abspielen",
|
"ui.point_cloud_play": "Abspielen",
|
||||||
|
"ui.point_cloud_reload": "Aus Datei neu laden",
|
||||||
"ui.point_cloud_single_color": "Einheitliche Farbe",
|
"ui.point_cloud_single_color": "Einheitliche Farbe",
|
||||||
"ui.point_cloud_size": "Punktgröße",
|
"ui.point_cloud_size": "Punktgröße",
|
||||||
"ui.point_cloud_units": "Einheit der Datei",
|
"ui.point_cloud_units": "Einheit der Datei",
|
||||||
@@ -5310,6 +5625,8 @@
|
|||||||
"ui.renderer": "Renderer",
|
"ui.renderer": "Renderer",
|
||||||
"ui.repeat": "Wiederholen",
|
"ui.repeat": "Wiederholen",
|
||||||
"ui.repeat_nothing": "Noch nichts zu wiederholen: Starten Sie zuerst ein Werkzeug oder einen Dialog.",
|
"ui.repeat_nothing": "Noch nichts zu wiederholen: Starten Sie zuerst ein Werkzeug oder einen Dialog.",
|
||||||
|
"ui.replace_sketch.from_file": "Aus einer Konstruktionsdatei…",
|
||||||
|
"ui.replace_sketch.no_sketches": "Diese Konstruktion enthält keine Skizze",
|
||||||
"ui.replace_with_link_done": "Die Kopien sind jetzt Platzierungen des verknüpften Designs.",
|
"ui.replace_with_link_done": "Die Kopien sind jetzt Platzierungen des verknüpften Designs.",
|
||||||
"ui.report_a_bug": "Fehler melden",
|
"ui.report_a_bug": "Fehler melden",
|
||||||
"ui.report_characters_left_one": "{count} Zeichen übrig",
|
"ui.report_characters_left_one": "{count} Zeichen übrig",
|
||||||
@@ -5345,6 +5662,7 @@
|
|||||||
"ui.second_rail": "Zweite Leitkurve",
|
"ui.second_rail": "Zweite Leitkurve",
|
||||||
"ui.section_analysis": "Querschnittsanalyse",
|
"ui.section_analysis": "Querschnittsanalyse",
|
||||||
"ui.section_size": "Querschnittsgröße",
|
"ui.section_size": "Querschnittsgröße",
|
||||||
|
"ui.segments": "Segmente",
|
||||||
"ui.select": "Auswählen",
|
"ui.select": "Auswählen",
|
||||||
"ui.selectOther": "Andere auswählen…",
|
"ui.selectOther": "Andere auswählen…",
|
||||||
"ui.selectTangentChain": "Tangentialkette auswählen",
|
"ui.selectTangentChain": "Tangentialkette auswählen",
|
||||||
@@ -5484,7 +5802,7 @@
|
|||||||
"ui.unsuppress": "Unterdrückung aufheben",
|
"ui.unsuppress": "Unterdrückung aufheben",
|
||||||
"ui.untied_ends_tip": "Berührt nur nach Position",
|
"ui.untied_ends_tip": "Berührt nur nach Position",
|
||||||
"ui.untitled": "Ohne Titel",
|
"ui.untitled": "Ohne Titel",
|
||||||
"ui.up_axis_note": "Welche Achse in den Ansichten nach oben zeigt (Achsenansicht, Home, Ansehen). Designs bleiben unverändert.",
|
"ui.up_axis_note": "Welche Achse in den Ansichten nach oben zeigt (Achsenansicht, Home, Senkrecht auf). Designs bleiben unverändert.",
|
||||||
"ui.updateToCurrentView": "Auf aktuelle Ansicht aktualisieren",
|
"ui.updateToCurrentView": "Auf aktuelle Ansicht aktualisieren",
|
||||||
"ui.upload": "Hochladen",
|
"ui.upload": "Hochladen",
|
||||||
"ui.vertical_constraint": "Vertikale Bedingung",
|
"ui.vertical_constraint": "Vertikale Bedingung",
|
||||||
|
|||||||
@@ -134,6 +134,7 @@
|
|||||||
"render.aging_metallic": "Layer metallic fraction",
|
"render.aging_metallic": "Layer metallic fraction",
|
||||||
"render.aging_origin": "Field origin · world mm",
|
"render.aging_origin": "Field origin · world mm",
|
||||||
"render.aging_patina": "Copper-green patina",
|
"render.aging_patina": "Copper-green patina",
|
||||||
|
"render.aging_raytrace_help": "Ageing is applied by CPU ray tracing and GPU path tracing. Raster keeps schematic rendering.",
|
||||||
"render.aging_refresh_targets": "Inspect current bodies",
|
"render.aging_refresh_targets": "Inspect current bodies",
|
||||||
"render.aging_roughness": "Layer roughness",
|
"render.aging_roughness": "Layer roughness",
|
||||||
"render.aging_rust": "Oxidized iron / rust",
|
"render.aging_rust": "Oxidized iron / rust",
|
||||||
@@ -159,6 +160,7 @@
|
|||||||
"render.background_workflow": "Choose the background you intend: a camera-only colour/gradient, a physical stage that receives light, or HDR illumination. They are independent. Live Preview is a modelling view; render a new image or regrade a retained master to see camera finishing.",
|
"render.background_workflow": "Choose the background you intend: a camera-only colour/gradient, a physical stage that receives light, or HDR illumination. They are independent. Live Preview is a modelling view; render a new image or regrade a retained master to see camera finishing.",
|
||||||
"render.base_color": "Base colour · sRGB",
|
"render.base_color": "Base colour · sRGB",
|
||||||
"render.bloom_help": "Creative output-only glow from positive linear highlights, spread with three normalised box passes. Radius is in output pixels. Added in linear display light before sRGB; not a lens, volumetric effect or convergence improvement. The raw EXR is unchanged.",
|
"render.bloom_help": "Creative output-only glow from positive linear highlights, spread with three normalised box passes. Radius is in output pixels. Added in linear display light before sRGB; not a lens, volumetric effect or convergence improvement. The raw EXR is unchanged.",
|
||||||
|
"render.check_media_remaining": "Density grids, displacement, full spectral integration and propagated polarisation remain unavailable.",
|
||||||
"render.chromatic_offset": "Chromatic edge offset",
|
"render.chromatic_offset": "Chromatic edge offset",
|
||||||
"render.cpu_area_editor_help": "Full side vectors in millimeters; U cross V points towards the emitting side. This differs from GPU half-edge vectors.",
|
"render.cpu_area_editor_help": "Full side vectors in millimeters; U cross V points towards the emitting side. This differs from GPU half-edge vectors.",
|
||||||
"render.cpu_light_help": "Up to eight directional lights. An empty custom list turns them off; selecting a preset restores its lights. Colours are linear RGB and intensity is relative, not watts.",
|
"render.cpu_light_help": "Up to eight directional lights. An empty custom list turns them off; selecting a preset restores its lights. Colours are linear RGB and intensity is relative, not watts.",
|
||||||
@@ -178,6 +180,7 @@
|
|||||||
"render.flare_strength": "Highlight flare strength",
|
"render.flare_strength": "Highlight flare strength",
|
||||||
"render.flare_threshold": "Scene-linear flare threshold",
|
"render.flare_threshold": "Scene-linear flare threshold",
|
||||||
"render.frame_centre_mark": "Centre mark",
|
"render.frame_centre_mark": "Centre mark",
|
||||||
|
"render.glass_model_help": "Glass roughness scatters transmitted rays. Dispersion and film colour use three wavelength bands. Rough sheets require straight transmitted shadows off.",
|
||||||
"render.hdr_primaries_declared": "The source declares its colour primaries.",
|
"render.hdr_primaries_declared": "The source declares its colour primaries.",
|
||||||
"render.hdr_primaries_srgb": "Colour primaries: sRGB/D65",
|
"render.hdr_primaries_srgb": "Colour primaries: sRGB/D65",
|
||||||
"render.hdr_primaries_undeclared": "The source does not declare primaries; review the renderer colour requirements.",
|
"render.hdr_primaries_undeclared": "The source does not declare primaries; review the renderer colour requirements.",
|
||||||
@@ -235,6 +238,8 @@
|
|||||||
"render.studio_area_help": "Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
|
"render.studio_area_help": "Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
|
||||||
"render.studio_center": "Centre X, Y, Z (mm)",
|
"render.studio_center": "Centre X, Y, Z (mm)",
|
||||||
"render.studio_hdr_help": "Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
|
"render.studio_hdr_help": "Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
|
||||||
|
"render.surface_import.complete": "Choose an image and enter its author, source and licence.",
|
||||||
|
"render.surface_import.license": "Licence / ownership",
|
||||||
"sketch.hint.arc.center_point": "Arc: click centre and start, go round either way and click the end, or type a signed sweep",
|
"sketch.hint.arc.center_point": "Arc: click centre and start, go round either way and click the end, or type a signed sweep",
|
||||||
"sketch.hint.circle.center": "Circle: click the centre, then a point on the circle",
|
"sketch.hint.circle.center": "Circle: click the centre, then a point on the circle",
|
||||||
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to centre the second on the first",
|
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to centre the second on the first",
|
||||||
@@ -266,6 +271,9 @@
|
|||||||
"ui.layout_compact": "Compact",
|
"ui.layout_compact": "Compact",
|
||||||
"ui.layout_floating": "Floating",
|
"ui.layout_floating": "Floating",
|
||||||
"ui.material_review.claims": "Sources and licences are unverified claims. Missing values remain unknown.",
|
"ui.material_review.claims": "Sources and licences are unverified claims. Missing values remain unknown.",
|
||||||
|
"ui.mesh_import_origin_tip": "Centre the mesh on X and Y with its base on Z = 0. When off, the mesh keeps the coordinates stored in the file.",
|
||||||
|
"ui.organize": "Organise",
|
||||||
|
"ui.panel_layout.workspace.modelling": "Modelling",
|
||||||
"ui.point_cloud_color_by": "Colour By",
|
"ui.point_cloud_color_by": "Colour By",
|
||||||
"ui.point_cloud_file_colors": "File Colours",
|
"ui.point_cloud_file_colors": "File Colours",
|
||||||
"ui.point_cloud_no_file_colors": "The file has no colours",
|
"ui.point_cloud_no_file_colors": "The file has no colours",
|
||||||
|
|||||||
+543
-15
File diff suppressed because it is too large
Load Diff
+539
-15
File diff suppressed because it is too large
Load Diff
@@ -4077,6 +4077,8 @@
|
|||||||
"sample.plate.name": "Esimerkkilevy",
|
"sample.plate.name": "Esimerkkilevy",
|
||||||
"sample.vents.about": "Urat suorakulmaisessa kuviossa: rivit, sarakkeet ja jako ovat parametreja",
|
"sample.vents.about": "Urat suorakulmaisessa kuviossa: rivit, sarakkeet ja jako ovat parametreja",
|
||||||
"sample.vents.name": "Tuuletuslevy",
|
"sample.vents.name": "Tuuletuslevy",
|
||||||
|
"scale.per_axis": "Akseleittain",
|
||||||
|
"scale.uniform": "Tasainen",
|
||||||
"search.command_keys": "Enter suorita Shift+Enter suorita ja pidä auki Esc sulje",
|
"search.command_keys": "Enter suorita Shift+Enter suorita ja pidä auki Esc sulje",
|
||||||
"search.commands": "Komennot",
|
"search.commands": "Komennot",
|
||||||
"search.design": "Tässä mallissa",
|
"search.design": "Tässä mallissa",
|
||||||
|
|||||||
+27
-1
@@ -566,6 +566,7 @@
|
|||||||
"Select edges": "Sélectionner des arêtes",
|
"Select edges": "Sélectionner des arêtes",
|
||||||
"Select points of the sketch being edited": "Sélectionner des points de l'esquisse en cours de modification",
|
"Select points of the sketch being edited": "Sélectionner des points de l'esquisse en cours de modification",
|
||||||
"Select the centre the pattern turns around first": "Sélectionner d'abord le centre autour duquel la répétition pivote",
|
"Select the centre the pattern turns around first": "Sélectionner d'abord le centre autour duquel la répétition pivote",
|
||||||
|
"Select the curves to divide first": "Sélectionner d'abord les courbes à diviser",
|
||||||
"Select the curves to offset first": "Sélectionner d'abord les courbes à décaler",
|
"Select the curves to offset first": "Sélectionner d'abord les courbes à décaler",
|
||||||
"Select the mirror line": "Sélectionner la ligne de symétrie",
|
"Select the mirror line": "Sélectionner la ligne de symétrie",
|
||||||
"Select the sketch curves or points to mirror first": "Sélectionner d'abord les courbes ou points d'esquisse à symétriser",
|
"Select the sketch curves or points to mirror first": "Sélectionner d'abord les courbes ou points d'esquisse à symétriser",
|
||||||
@@ -1048,6 +1049,7 @@
|
|||||||
"cmd.pointcloud_edit": "Modifier le nuage de points",
|
"cmd.pointcloud_edit": "Modifier le nuage de points",
|
||||||
"cmd.pointcloud_insert": "Insérer un nuage de points",
|
"cmd.pointcloud_insert": "Insérer un nuage de points",
|
||||||
"cmd.pointcloud_list": "Nuages de points",
|
"cmd.pointcloud_list": "Nuages de points",
|
||||||
|
"cmd.pointcloud_reload": "Recharger le nuage de points",
|
||||||
"cmd.render.compare.divider": "Déplacer le séparateur de comparaison des rendus",
|
"cmd.render.compare.divider": "Déplacer le séparateur de comparaison des rendus",
|
||||||
"cmd.render.result.discard": "Écarter le résultat de rendu",
|
"cmd.render.result.discard": "Écarter le résultat de rendu",
|
||||||
"cmd.render.result.select": "Sélectionner le résultat de rendu",
|
"cmd.render.result.select": "Sélectionner le résultat de rendu",
|
||||||
@@ -1137,6 +1139,7 @@
|
|||||||
"cmd.sketch.construction_line.tangent_external": "Tangente passant par un point extérieur",
|
"cmd.sketch.construction_line.tangent_external": "Tangente passant par un point extérieur",
|
||||||
"cmd.sketch.construction_line.tangent_two": "Tangente commune",
|
"cmd.sketch.construction_line.tangent_two": "Tangente commune",
|
||||||
"cmd.sketch.construction_line.vertical": "Droite de construction verticale",
|
"cmd.sketch.construction_line.vertical": "Droite de construction verticale",
|
||||||
|
"cmd.sketch.replace": "Remplacer l’esquisse",
|
||||||
"cmd.sketch_arc3d": "Arc 3D",
|
"cmd.sketch_arc3d": "Arc 3D",
|
||||||
"cmd.sketch_arc_center_point": "Arc par centre",
|
"cmd.sketch_arc_center_point": "Arc par centre",
|
||||||
"cmd.sketch_arc_tangent": "Arc tangent",
|
"cmd.sketch_arc_tangent": "Arc tangent",
|
||||||
@@ -1193,6 +1196,7 @@
|
|||||||
"cmd.sketch_options": "Options de la palette d'esquisse",
|
"cmd.sketch_options": "Options de la palette d'esquisse",
|
||||||
"cmd.sketch_paste": "Coller les entités d'esquisse",
|
"cmd.sketch_paste": "Coller les entités d'esquisse",
|
||||||
"cmd.sketch_point3d": "Point 3D",
|
"cmd.sketch_point3d": "Point 3D",
|
||||||
|
"cmd.sketch_point_divide": "Diviser la courbe",
|
||||||
"cmd.sketch_polygon_circumscribed": "Polygone circonscrit",
|
"cmd.sketch_polygon_circumscribed": "Polygone circonscrit",
|
||||||
"cmd.sketch_polygon_edge": "Polygone par côté",
|
"cmd.sketch_polygon_edge": "Polygone par côté",
|
||||||
"cmd.sketch_polygon_inscribed": "Polygone inscrit",
|
"cmd.sketch_polygon_inscribed": "Polygone inscrit",
|
||||||
@@ -1425,6 +1429,7 @@
|
|||||||
"file_load.reading_percent": "Lecture du fichier... {percent} %",
|
"file_load.reading_percent": "Lecture du fichier... {percent} %",
|
||||||
"file_load.rebuilding": "Reconstruction de l’entité {done} sur {total}",
|
"file_load.rebuilding": "Reconstruction de l’entité {done} sur {total}",
|
||||||
"file_load.recovering": "Récupération de la conception",
|
"file_load.recovering": "Récupération de la conception",
|
||||||
|
"file_load.reloading_cloud": "Rechargement du nuage de points",
|
||||||
"file_load.stopping": "Attente de l’arrêt de l’opération précédente...",
|
"file_load.stopping": "Attente de l’arrêt de l’opération précédente...",
|
||||||
"file_load.updating_links": "Mise à jour des liens",
|
"file_load.updating_links": "Mise à jour des liens",
|
||||||
"file_load.waiting": "En attente d’une opération sur fichier",
|
"file_load.waiting": "En attente d’une opération sur fichier",
|
||||||
@@ -1624,7 +1629,7 @@
|
|||||||
"help.sketch.insert_svg": "Lit les tracés d'un dessin SVG dans une esquisse, mis à l'échelle, tournés et placés comme vous le définissez. À utiliser pour transformer un logo ou un contour vectoriel en courbes que vous pouvez extruder.",
|
"help.sketch.insert_svg": "Lit les tracés d'un dessin SVG dans une esquisse, mis à l'échelle, tournés et placés comme vous le définissez. À utiliser pour transformer un logo ou un contour vectoriel en courbes que vous pouvez extruder.",
|
||||||
"help.sketch.inspect": "Ouvre un rapport sur l'esquisse ouverte : combien de degrés de liberté il reste et si elle est entièrement contrainte. À utiliser pour savoir ce qui bouge encore avant de terminer l'esquisse.",
|
"help.sketch.inspect": "Ouvre un rapport sur l'esquisse ouverte : combien de degrés de liberté il reste et si elle est entièrement contrainte. À utiliser pour savoir ce qui bouge encore avant de terminer l'esquisse.",
|
||||||
"help.sketch.intersect": "Ajoute les courbes ou points où des corps, faces, arêtes ou d'autres esquisses traversent le plan d'esquisse. Ils restent liés et suivent le modèle lorsqu'il change.",
|
"help.sketch.intersect": "Ajoute les courbes ou points où des corps, faces, arêtes ou d'autres esquisses traversent le plan d'esquisse. Ils restent liés et suivent le modèle lorsqu'il change.",
|
||||||
"help.sketch.intersection_curve": "Crée des courbes 3D là où deux corps ou faces se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
|
"help.sketch.intersection_curve": "Crée des courbes 3D là où deux corps, faces ou courbes d'autres esquisses se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
|
||||||
"help.sketch.isoparametric_curve": "Crée une courbe 3D le long de la direction U ou V d'une face, par un point que vous choisissez, liée à la face. À utiliser comme trajectoire ou ligne de partage qui suit la surface.",
|
"help.sketch.isoparametric_curve": "Crée une courbe 3D le long de la direction U ou V d'une face, par un point que vous choisissez, liée à la face. À utiliser comme trajectoire ou ligne de partage qui suit la surface.",
|
||||||
"help.sketch.line": "Dessine des lignes connectées point par point, en chaîne ouverte ou en contour fermé. C'est l'outil de base de la plupart des profils.",
|
"help.sketch.line": "Dessine des lignes connectées point par point, en chaîne ouverte ou en contour fermé. C'est l'outil de base de la plupart des profils.",
|
||||||
"help.sketch.line.midpoint": "Dessine une ligne de son milieu vers une extrémité, l'autre extrémité étant son symétrique. À utiliser pour des lignes centrées sur un point, comme une arête symétrique.",
|
"help.sketch.line.midpoint": "Dessine une ligne de son milieu vers une extrémité, l'autre extrémité étant son symétrique. À utiliser pour des lignes centrées sur un point, comme une arête symétrique.",
|
||||||
@@ -1636,6 +1641,7 @@
|
|||||||
"help.sketch.pattern.circular": "Copie la géométrie d'esquisse autour d'un centre, sur un cercle complet ou un angle donné. À utiliser pour des cercles de perçages, des rayons et des dents.",
|
"help.sketch.pattern.circular": "Copie la géométrie d'esquisse autour d'un centre, sur un cercle complet ou un angle donné. À utiliser pour des cercles de perçages, des rayons et des dents.",
|
||||||
"help.sketch.pattern.rectangular": "Copie la géométrie d'esquisse en lignes et colonnes à des espacements donnés. À utiliser pour des grilles de trous, de rainures ou d'aérations.",
|
"help.sketch.pattern.rectangular": "Copie la géométrie d'esquisse en lignes et colonnes à des espacements donnés. À utiliser pour des grilles de trous, de rainures ou d'aérations.",
|
||||||
"help.sketch.point": "Place un point d'esquisse. À utiliser comme référence pour des cotes, des contraintes, des centres de perçage ou des fonctions ultérieures.",
|
"help.sketch.point": "Place un point d'esquisse. À utiliser comme référence pour des cotes, des contraintes, des centres de perçage ou des fonctions ultérieures.",
|
||||||
|
"help.sketch.point.divide": "Place des points qui divisent les courbes choisies en longueurs égales. Les points restent sur chaque courbe et gardent leur espacement quand elle change.",
|
||||||
"help.sketch.point3d": "Place un point dans l'espace, avec Esquisse 3D activée. À utiliser comme référence pour des courbes et trajectoires 3D.",
|
"help.sketch.point3d": "Place un point dans l'espace, avec Esquisse 3D activée. À utiliser comme référence pour des courbes et trajectoires 3D.",
|
||||||
"help.sketch.polygon.circumscribed": "Dessine un polygone régulier autour d'un cercle, dimensionné du centre au milieu d'un côté. À utiliser lorsque la largeur sur plats est connue, comme pour un écrou hexagonal.",
|
"help.sketch.polygon.circumscribed": "Dessine un polygone régulier autour d'un cercle, dimensionné du centre au milieu d'un côté. À utiliser lorsque la largeur sur plats est connue, comme pour un écrou hexagonal.",
|
||||||
"help.sketch.polygon.edge": "Dessine un polygone régulier à partir des deux extrémités d'un côté. À utiliser lorsque la longueur et la position d'un côté sont connues.",
|
"help.sketch.polygon.edge": "Dessine un polygone régulier à partir des deux extrémités d'un côté. À utiliser lorsque la longueur et la position d'un côté sont connues.",
|
||||||
@@ -2201,6 +2207,8 @@
|
|||||||
"prefs.edges": "Arêtes et ombrage",
|
"prefs.edges": "Arêtes et ombrage",
|
||||||
"prefs.extend": "Étendre",
|
"prefs.extend": "Étendre",
|
||||||
"prefs.extra_navigation": "Ctrl+glisser gauche dans le vide fait orbiter ; maintenir Espace pour un panoramique.",
|
"prefs.extra_navigation": "Ctrl+glisser gauche dans le vide fait orbiter ; maintenir Espace pour un panoramique.",
|
||||||
|
"prefs.extrude_arrow_face_stops": "La flèche d'extrusion s'arrête aux faces",
|
||||||
|
"prefs.extrude_arrow_face_stops_note": "En faisant glisser la flèche de distance d'Extrusion, elle s'arrête aux faces planes parallèles au profil, comme l'autre côté d'un bloc qu'on découpe, et souligne la face. Désactivé : la flèche avance seulement par pas ronds.",
|
||||||
"prefs.extrude_view_isometric": "Passer la vue en isométrique à l'ouverture de l'extrusion",
|
"prefs.extrude_view_isometric": "Passer la vue en isométrique à l'ouverture de l'extrusion",
|
||||||
"prefs.extrude_view_return": "Revenir à la vue précédente à la fermeture de l'extrusion",
|
"prefs.extrude_view_return": "Revenir à la vue précédente à la fermeture de l'extrusion",
|
||||||
"prefs.follows_os": "Suit le système",
|
"prefs.follows_os": "Suit le système",
|
||||||
@@ -2280,6 +2288,8 @@
|
|||||||
"prefs.mouse": "Souris et pavé tactile",
|
"prefs.mouse": "Souris et pavé tactile",
|
||||||
"prefs.mouse_heading": "Souris",
|
"prefs.mouse_heading": "Souris",
|
||||||
"prefs.mouse_mapping": "Affectation des boutons de la souris",
|
"prefs.mouse_mapping": "Affectation des boutons de la souris",
|
||||||
|
"prefs.move_drag_highlights": "Mettre en évidence ce qui est sous le pointeur pendant que les flèches de Déplacer font glisser un composant",
|
||||||
|
"prefs.move_drag_highlights_tip": "Désactivé : pendant que les flèches de Déplacer font glisser un composant, rien n’est mis en évidence sous le pointeur, ce qui garde les grands assemblages fluides. Avec une coupe ou une analyse affichée, la mise en évidence reste, et une pièce glissée le long de ses liaisons n’en montre jamais.",
|
||||||
"prefs.navigation": "Navigation",
|
"prefs.navigation": "Navigation",
|
||||||
"prefs.navigation_note": "Comment la souris, le pavé tactile et la souris 3D déplacent la vue.",
|
"prefs.navigation_note": "Comment la souris, le pavé tactile et la souris 3D déplacent la vue.",
|
||||||
"prefs.navigator_fit": "Cadrer le modèle après un changement de vue du repère de navigation",
|
"prefs.navigator_fit": "Cadrer le modèle après un changement de vue du repère de navigation",
|
||||||
@@ -2355,6 +2365,8 @@
|
|||||||
"prefs.shortcuts": "Raccourcis",
|
"prefs.shortcuts": "Raccourcis",
|
||||||
"prefs.single_surface_analysis": "Utiliser une seule analyse de surface",
|
"prefs.single_surface_analysis": "Utiliser une seule analyse de surface",
|
||||||
"prefs.single_surface_analysis_help": "Désactivé, l'ombrage de surface et Continuité peuvent rester actifs ensemble. Activé, seule la dernière analyse lancée ou modifiée est conservée.",
|
"prefs.single_surface_analysis_help": "Désactivé, l'ombrage de surface et Continuité peuvent rester actifs ensemble. Activé, seule la dernière analyse lancée ou modifiée est conservée.",
|
||||||
|
"prefs.sketch.escape_twice_finishes": "Un double Échap termine l'esquisse",
|
||||||
|
"prefs.sketch.escape_twice_finishes_note": "Deux pressions rapides sur Échap terminent l'esquisse. Désactivé : Échap annule seulement l'outil ou la sélection, et Terminer l'esquisse ferme l'esquisse.",
|
||||||
"prefs.sketch_align_points": "S'aligner sur d'autres points d'esquisse pendant le dessin",
|
"prefs.sketch_align_points": "S'aligner sur d'autres points d'esquisse pendant le dessin",
|
||||||
"prefs.sketch_align_points_note": "Pendant le dessin, un point s'aligne à la fois horizontalement sur un point d'esquisse et verticalement sur un autre, avec un guide en pointillés, et le clic conserve les deux alignements. Désactivé : uniquement sur le point précédent, un axe à la fois.",
|
"prefs.sketch_align_points_note": "Pendant le dessin, un point s'aligne à la fois horizontalement sur un point d'esquisse et verticalement sur un autre, avec un guide en pointillés, et le clic conserve les deux alignements. Désactivé : uniquement sur le point précédent, un axe à la fois.",
|
||||||
"prefs.sketch_conflict_colour": "Géométrie en conflit dans la couleur de conflit",
|
"prefs.sketch_conflict_colour": "Géométrie en conflit dans la couleur de conflit",
|
||||||
@@ -2778,6 +2790,8 @@
|
|||||||
"render.cpu_lighting": "Préréglage d'éclairage",
|
"render.cpu_lighting": "Préréglage d'éclairage",
|
||||||
"render.cpu_lights": "Lumières CPU",
|
"render.cpu_lights": "Lumières CPU",
|
||||||
"render.cpu_linear_color": "Couleur linéaire (R, V, B)",
|
"render.cpu_linear_color": "Couleur linéaire (R, V, B)",
|
||||||
|
"render.cpu_maps_saved_help": "Le rendu CPU accepte les cartes opaques enregistrées : choisissez une surface, puis enregistrez ses cartes dans la vue. Avec l’éclairage indirect, un autre corps peut utiliser Verre ; le corps avec les cartes ne le peut pas. Désactivez les réflexions sélectionnées, l’anisotropie et les effets procéduraux. Les fichiers temporaires de cartes utilisent le GPU.",
|
||||||
|
"render.cpu_material_certification": "La vérification CPU valide la géométrie de verre prise en charge avant le rendu. Le verre fermé utilise la distance géométrique.",
|
||||||
"render.cpu_neutral": "Neutre",
|
"render.cpu_neutral": "Neutre",
|
||||||
"render.cpu_opaque_required": "Le lancer de rayons CPU nécessite un arrière-plan opaque. Décochez Arrière-plan transparent pour continuer.",
|
"render.cpu_opaque_required": "Le lancer de rayons CPU nécessite un arrière-plan opaque. Décochez Arrière-plan transparent pour continuer.",
|
||||||
"render.cpu_options_pending": "Ce panneau CPU ne peut pas encore modifier l'émission, les effets de surface, le verre ni l'anisotropie. Effacez ces options d'API avant de l'utiliser.",
|
"render.cpu_options_pending": "Ce panneau CPU ne peut pas encore modifier l'émission, les effets de surface, le verre ni l'anisotropie. Effacez ces options d'API avant de l'utiliser.",
|
||||||
@@ -3814,6 +3828,8 @@
|
|||||||
"sample.plate.name": "Plaque d'exemple",
|
"sample.plate.name": "Plaque d'exemple",
|
||||||
"sample.vents.about": "Rainures en répétition rectangulaire : lignes, colonnes et pas sont des paramètres",
|
"sample.vents.about": "Rainures en répétition rectangulaire : lignes, colonnes et pas sont des paramètres",
|
||||||
"sample.vents.name": "Plaque d'aération",
|
"sample.vents.name": "Plaque d'aération",
|
||||||
|
"scale.per_axis": "Par axe",
|
||||||
|
"scale.uniform": "Uniforme",
|
||||||
"search.command_keys": "Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
|
"search.command_keys": "Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
|
||||||
"search.commands": "Commandes",
|
"search.commands": "Commandes",
|
||||||
"search.design": "Dans cette conception",
|
"search.design": "Dans cette conception",
|
||||||
@@ -3901,6 +3917,7 @@
|
|||||||
"sketch.dimension_applied": "Cote appliquée",
|
"sketch.dimension_applied": "Cote appliquée",
|
||||||
"sketch.dimension_applied_defined": "Cote appliquée ; paramètre `{name}` défini",
|
"sketch.dimension_applied_defined": "Cote appliquée ; paramètre `{name}` défini",
|
||||||
"sketch.dimension_applied_renamed": "Cote appliquée ; paramètre nommé `{name}`",
|
"sketch.dimension_applied_renamed": "Cote appliquée ; paramètre nommé `{name}`",
|
||||||
|
"sketch.divide_hint": "Les points divisent chaque courbe en longueurs égales et les gardent quand elle change.",
|
||||||
"sketch.dof_left": "{count} degrés de liberté restants",
|
"sketch.dof_left": "{count} degrés de liberté restants",
|
||||||
"sketch.dof_left_one": "{count} degré de liberté restant",
|
"sketch.dof_left_one": "{count} degré de liberté restant",
|
||||||
"sketch.draw_curves": "Dessiner en {linetype}",
|
"sketch.draw_curves": "Dessiner en {linetype}",
|
||||||
@@ -4443,6 +4460,7 @@
|
|||||||
"ui.click_selects_whole_bodies": "Un clic sélectionne des corps entiers",
|
"ui.click_selects_whole_bodies": "Un clic sélectionne des corps entiers",
|
||||||
"ui.click_then_press_key": "Cliquer, puis appuyer sur la nouvelle touche",
|
"ui.click_then_press_key": "Cliquer, puis appuyer sur la nouvelle touche",
|
||||||
"ui.close": "Fermer",
|
"ui.close": "Fermer",
|
||||||
|
"ui.close_tab": "Fermer l’onglet",
|
||||||
"ui.coil": "Hélice",
|
"ui.coil": "Hélice",
|
||||||
"ui.coincident": "Coïncidence",
|
"ui.coincident": "Coïncidence",
|
||||||
"ui.collapse": "Réduire",
|
"ui.collapse": "Réduire",
|
||||||
@@ -4689,6 +4707,7 @@
|
|||||||
"ui.heading_create_sketch": "CRÉER UNE ESQUISSE",
|
"ui.heading_create_sketch": "CRÉER UNE ESQUISSE",
|
||||||
"ui.heading_cylinder": "CYLINDRE",
|
"ui.heading_cylinder": "CYLINDRE",
|
||||||
"ui.heading_design": "DESIGN",
|
"ui.heading_design": "DESIGN",
|
||||||
|
"ui.heading_divide_curve": "DIVISER LA COURBE",
|
||||||
"ui.heading_draft": "DÉPOUILLE",
|
"ui.heading_draft": "DÉPOUILLE",
|
||||||
"ui.heading_edit": "ÉDITION",
|
"ui.heading_edit": "ÉDITION",
|
||||||
"ui.heading_equation_curve": "COURBE PAR ÉQUATION",
|
"ui.heading_equation_curve": "COURBE PAR ÉQUATION",
|
||||||
@@ -4846,6 +4865,8 @@
|
|||||||
"ui.mesh_format_3mf": "3MF",
|
"ui.mesh_format_3mf": "3MF",
|
||||||
"ui.mesh_format_3mf_tagged": "3MF avec étiquettes de faces",
|
"ui.mesh_format_3mf_tagged": "3MF avec étiquettes de faces",
|
||||||
"ui.mesh_format_stl": "STL",
|
"ui.mesh_format_stl": "STL",
|
||||||
|
"ui.mesh_import_origin": "Déplacer à l'origine",
|
||||||
|
"ui.mesh_import_origin_tip": "Centre le maillage en X et Y et pose sa base sur Z = 0. Désactivé, le maillage garde les coordonnées enregistrées dans le fichier.",
|
||||||
"ui.minor_radius": "Petit rayon",
|
"ui.minor_radius": "Petit rayon",
|
||||||
"ui.mirror": "Symétrie",
|
"ui.mirror": "Symétrie",
|
||||||
"ui.mirror_line": "Ligne de symétrie",
|
"ui.mirror_line": "Ligne de symétrie",
|
||||||
@@ -4926,6 +4947,7 @@
|
|||||||
"ui.point_cloud_add_filter": "Ajouter un filtre…",
|
"ui.point_cloud_add_filter": "Ajouter un filtre…",
|
||||||
"ui.point_cloud_auto_range": "Plage automatique",
|
"ui.point_cloud_auto_range": "Plage automatique",
|
||||||
"ui.point_cloud_color_by": "Colorer par",
|
"ui.point_cloud_color_by": "Colorer par",
|
||||||
|
"ui.point_cloud_dots": "Points isolés",
|
||||||
"ui.point_cloud_file_colors": "Couleurs du fichier",
|
"ui.point_cloud_file_colors": "Couleurs du fichier",
|
||||||
"ui.point_cloud_filter_by": "Filtrer par",
|
"ui.point_cloud_filter_by": "Filtrer par",
|
||||||
"ui.point_cloud_height_high": "Plus haut",
|
"ui.point_cloud_height_high": "Plus haut",
|
||||||
@@ -4934,6 +4956,7 @@
|
|||||||
"ui.point_cloud_no_file_colors": "Le fichier n'a pas de couleurs",
|
"ui.point_cloud_no_file_colors": "Le fichier n'a pas de couleurs",
|
||||||
"ui.point_cloud_pause": "Pause",
|
"ui.point_cloud_pause": "Pause",
|
||||||
"ui.point_cloud_play": "Lire",
|
"ui.point_cloud_play": "Lire",
|
||||||
|
"ui.point_cloud_reload": "Recharger le fichier",
|
||||||
"ui.point_cloud_single_color": "Couleur unique",
|
"ui.point_cloud_single_color": "Couleur unique",
|
||||||
"ui.point_cloud_size": "Taille des points",
|
"ui.point_cloud_size": "Taille des points",
|
||||||
"ui.point_cloud_units": "Unités du fichier",
|
"ui.point_cloud_units": "Unités du fichier",
|
||||||
@@ -4993,6 +5016,8 @@
|
|||||||
"ui.renderer": "Moteur de rendu",
|
"ui.renderer": "Moteur de rendu",
|
||||||
"ui.repeat": "Répéter",
|
"ui.repeat": "Répéter",
|
||||||
"ui.repeat_nothing": "Rien à répéter pour l'instant : lancer d'abord un outil ou une boîte de dialogue.",
|
"ui.repeat_nothing": "Rien à répéter pour l'instant : lancer d'abord un outil ou une boîte de dialogue.",
|
||||||
|
"ui.replace_sketch.from_file": "Depuis un fichier de conception…",
|
||||||
|
"ui.replace_sketch.no_sketches": "Cette conception ne contient aucune esquisse",
|
||||||
"ui.replace_with_link_done": "Les copies sont maintenant des placements de la conception liée.",
|
"ui.replace_with_link_done": "Les copies sont maintenant des placements de la conception liée.",
|
||||||
"ui.report_a_bug": "Signaler un bug",
|
"ui.report_a_bug": "Signaler un bug",
|
||||||
"ui.reset": "Réinitialiser",
|
"ui.reset": "Réinitialiser",
|
||||||
@@ -5026,6 +5051,7 @@
|
|||||||
"ui.second_rail": "Second rail",
|
"ui.second_rail": "Second rail",
|
||||||
"ui.section_analysis": "Analyse de coupe",
|
"ui.section_analysis": "Analyse de coupe",
|
||||||
"ui.section_size": "Taille de la section",
|
"ui.section_size": "Taille de la section",
|
||||||
|
"ui.segments": "Segments",
|
||||||
"ui.select": "Sélectionner",
|
"ui.select": "Sélectionner",
|
||||||
"ui.selectOther": "Sélectionner un autre…",
|
"ui.selectOther": "Sélectionner un autre…",
|
||||||
"ui.selectTangentChain": "Sélectionner la chaîne tangente",
|
"ui.selectTangentChain": "Sélectionner la chaîne tangente",
|
||||||
|
|||||||
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Le os trazados dun debuxo SVG nun bosquexo, escalados, xirados e colocados onde indiques. Úsao para converter un logotipo ou un contorno vectorial en curvas que podes extruír.",
|
"help.sketch.insert_svg": "Le os trazados dun debuxo SVG nun bosquexo, escalados, xirados e colocados onde indiques. Úsao para converter un logotipo ou un contorno vectorial en curvas que podes extruír.",
|
||||||
"help.sketch.inspect": "Abre un informe do bosquexo aberto: cantos graos de liberdade quedan e se está totalmente restrinxido. Úsao para saber o que aínda se move antes de rematar o bosquexo.",
|
"help.sketch.inspect": "Abre un informe do bosquexo aberto: cantos graos de liberdade quedan e se está totalmente restrinxido. Úsao para saber o que aínda se move antes de rematar o bosquexo.",
|
||||||
"help.sketch.intersect": "Engade as curvas ou os puntos onde corpos, caras, arestas ou outros bosquexos cruzan o plano do bosquexo. Quedan ligados e seguen o modelo cando cambia.",
|
"help.sketch.intersect": "Engade as curvas ou os puntos onde corpos, caras, arestas ou outros bosquexos cruzan o plano do bosquexo. Quedan ligados e seguen o modelo cando cambia.",
|
||||||
"help.sketch.intersection_curve": "Crea curvas 3D onde se atopan dous corpos ou caras, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
|
"help.sketch.intersection_curve": "Crea curvas 3D onde se atopan dous corpos, caras ou curvas doutros bosquexos, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
|
||||||
"help.sketch.isoparametric_curve": "Crea unha curva 3D na dirección U ou V dunha cara por un punto que escollas, ligada á cara. Úsaa como traxectoria ou liña de división que segue a superficie.",
|
"help.sketch.isoparametric_curve": "Crea unha curva 3D na dirección U ou V dunha cara por un punto que escollas, ligada á cara. Úsaa como traxectoria ou liña de división que segue a superficie.",
|
||||||
"help.sketch.line": "Debuxa liñas conectadas punto a punto, como cadea aberta ou como contorno pechado. É a ferramenta básica para a maioría dos perfís.",
|
"help.sketch.line": "Debuxa liñas conectadas punto a punto, como cadea aberta ou como contorno pechado. É a ferramenta básica para a maioría dos perfís.",
|
||||||
"help.sketch.line.midpoint": "Debuxa unha liña desde o seu punto medio ata un extremo, co outro extremo reflectido. Úsao para liñas centradas nun punto, como unha aresta simétrica.",
|
"help.sketch.line.midpoint": "Debuxa unha liña desde o seu punto medio ata un extremo, co outro extremo reflectido. Úsao para liñas centradas nun punto, como unha aresta simétrica.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Placa de exemplo",
|
"sample.plate.name": "Placa de exemplo",
|
||||||
"sample.vents.about": "Ranuras nun patrón rectangular: filas, columnas e paso son parámetros",
|
"sample.vents.about": "Ranuras nun patrón rectangular: filas, columnas e paso son parámetros",
|
||||||
"sample.vents.name": "Placa de ventilación",
|
"sample.vents.name": "Placa de ventilación",
|
||||||
|
"scale.per_axis": "Por eixo",
|
||||||
|
"scale.uniform": "Uniforme",
|
||||||
"search.command_keys": "Intro executar Maiús+Intro executar e manter aberto Esc pechar",
|
"search.command_keys": "Intro executar Maiús+Intro executar e manter aberto Esc pechar",
|
||||||
"search.commands": "Ordes",
|
"search.commands": "Ordes",
|
||||||
"search.design": "Neste deseño",
|
"search.design": "Neste deseño",
|
||||||
|
|||||||
+542
-14
File diff suppressed because it is too large
Load Diff
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Čita putanje SVG crteža u skicu, skalirane, zakrenute i postavljene kako zadate. Upotrijebite ga za pretvaranje logotipa ili vektorskog obrisa u krivulje koje možete ekstrudirati.",
|
"help.sketch.insert_svg": "Čita putanje SVG crteža u skicu, skalirane, zakrenute i postavljene kako zadate. Upotrijebite ga za pretvaranje logotipa ili vektorskog obrisa u krivulje koje možete ekstrudirati.",
|
||||||
"help.sketch.inspect": "Otvara izvješće o otvorenoj skici: koliko je stupnjeva slobode preostalo i je li potpuno ograničena. Upotrijebite ga kako biste saznali što se još pomiče prije dovršetka skice.",
|
"help.sketch.inspect": "Otvara izvješće o otvorenoj skici: koliko je stupnjeva slobode preostalo i je li potpuno ograničena. Upotrijebite ga kako biste saznali što se još pomiče prije dovršetka skice.",
|
||||||
"help.sketch.intersect": "Dodaje krivulje ili točke u kojima tijela, lica, bridovi ili druge skice presijecaju ravninu skice. Ostaju povezane i prate model kad se promijeni.",
|
"help.sketch.intersect": "Dodaje krivulje ili točke u kojima tijela, lica, bridovi ili druge skice presijecaju ravninu skice. Ostaju povezane i prate model kad se promijeni.",
|
||||||
"help.sketch.intersection_curve": "Izrađuje 3D krivulje na mjestu gdje se dva tijela ili lica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
|
"help.sketch.intersection_curve": "Izrađuje 3D krivulje na mjestu gdje se dva tijela, lica ili krivulje drugih skica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
|
||||||
"help.sketch.isoparametric_curve": "Izrađuje 3D krivulju u smjeru U ili V lica kroz točku koju odaberete, povezanu s licem. Upotrijebite je kao putanju ili liniju razdvajanja koja prati plohu.",
|
"help.sketch.isoparametric_curve": "Izrađuje 3D krivulju u smjeru U ili V lica kroz točku koju odaberete, povezanu s licem. Upotrijebite je kao putanju ili liniju razdvajanja koja prati plohu.",
|
||||||
"help.sketch.line": "Crta povezane linije točku po točku, kao otvoreni lanac ili zatvoreni obris. To je osnovni alat za većinu profila.",
|
"help.sketch.line": "Crta povezane linije točku po točku, kao otvoreni lanac ili zatvoreni obris. To je osnovni alat za većinu profila.",
|
||||||
"help.sketch.line.midpoint": "Crta liniju od njezine središnje točke do jednog kraja, a drugi kraj zrcali. Upotrijebite ga za linije centrirane na točku, primjerice simetričan brid.",
|
"help.sketch.line.midpoint": "Crta liniju od njezine središnje točke do jednog kraja, a drugi kraj zrcali. Upotrijebite ga za linije centrirane na točku, primjerice simetričan brid.",
|
||||||
|
|||||||
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Beolvassa egy SVG-rajz útvonalait egy vázlatba, az Ön által megadott léptékkel, elforgatással és helyen. Használja egy logó vagy vektoros körvonal kihúzható görbékké alakítására.",
|
"help.sketch.insert_svg": "Beolvassa egy SVG-rajz útvonalait egy vázlatba, az Ön által megadott léptékkel, elforgatással és helyen. Használja egy logó vagy vektoros körvonal kihúzható görbékké alakítására.",
|
||||||
"help.sketch.inspect": "Jelentést nyit a megnyitott vázlatról: hány szabadságfok maradt, és teljesen kényszerezett-e. Használja annak kiderítésére, mi mozdul még, mielőtt befejezi a vázlatot.",
|
"help.sketch.inspect": "Jelentést nyit a megnyitott vázlatról: hány szabadságfok maradt, és teljesen kényszerezett-e. Használja annak kiderítésére, mi mozdul még, mielőtt befejezi a vázlatot.",
|
||||||
"help.sketch.intersect": "Hozzáadja azokat a görbéket vagy pontokat, ahol testek, lapok, élek vagy más vázlatok metszik a vázlatsíkot. Kapcsoltak maradnak, és követik a modellt, ha az megváltozik.",
|
"help.sketch.intersect": "Hozzáadja azokat a görbéket vagy pontokat, ahol testek, lapok, élek vagy más vázlatok metszik a vázlatsíkot. Kapcsoltak maradnak, és követik a modellt, ha az megváltozik.",
|
||||||
"help.sketch.intersection_curve": "3D görbéket hoz létre ott, ahol két test vagy lap találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
|
"help.sketch.intersection_curve": "3D görbéket hoz létre ott, ahol két test, lap vagy más vázlatok görbéi találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
|
||||||
"help.sketch.isoparametric_curve": "3D görbét hoz létre egy lap U vagy V iránya mentén egy kiválasztott ponton át, a laphoz kapcsolva. Használja útvonalként vagy osztóvonalként, amely követi a felületet.",
|
"help.sketch.isoparametric_curve": "3D görbét hoz létre egy lap U vagy V iránya mentén egy kiválasztott ponton át, a laphoz kapcsolva. Használja útvonalként vagy osztóvonalként, amely követi a felületet.",
|
||||||
"help.sketch.line": "Összefüggő vonalakat rajzol pontról pontra, nyitott láncként vagy zárt körvonalként. Ez a legtöbb profil alapeszköze.",
|
"help.sketch.line": "Összefüggő vonalakat rajzol pontról pontra, nyitott láncként vagy zárt körvonalként. Ez a legtöbb profil alapeszköze.",
|
||||||
"help.sketch.line.midpoint": "Vonalat rajzol a felezőpontjától az egyik végéig, a másik véget tükrözve. Használja egy pontra középen álló vonalakhoz, például szimmetrikus élhez.",
|
"help.sketch.line.midpoint": "Vonalat rajzol a felezőpontjától az egyik végéig, a másik véget tükrözve. Használja egy pontra középen álló vonalakhoz, például szimmetrikus élhez.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Mintalemez",
|
"sample.plate.name": "Mintalemez",
|
||||||
"sample.vents.about": "Hornyok téglalap mintában: a sorok, oszlopok és osztás paraméterek",
|
"sample.vents.about": "Hornyok téglalap mintában: a sorok, oszlopok és osztás paraméterek",
|
||||||
"sample.vents.name": "Szellőzőlemez",
|
"sample.vents.name": "Szellőzőlemez",
|
||||||
|
"scale.per_axis": "Tengelyenként",
|
||||||
|
"scale.uniform": "Egyenletes",
|
||||||
"search.command_keys": "Enter futtatás Shift+Enter futtatás és nyitva tartás Esc bezárás",
|
"search.command_keys": "Enter futtatás Shift+Enter futtatás és nyitva tartás Esc bezárás",
|
||||||
"search.commands": "Parancsok",
|
"search.commands": "Parancsok",
|
||||||
"search.design": "Ebben a tervben",
|
"search.design": "Ebben a tervben",
|
||||||
|
|||||||
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Membaca path gambar SVG ke dalam sketsa, diskalakan, diputar, dan ditempatkan sesuai pengaturan Anda. Gunakan untuk mengubah logo atau kontur vektor menjadi kurva yang dapat diekstrusi.",
|
"help.sketch.insert_svg": "Membaca path gambar SVG ke dalam sketsa, diskalakan, diputar, dan ditempatkan sesuai pengaturan Anda. Gunakan untuk mengubah logo atau kontur vektor menjadi kurva yang dapat diekstrusi.",
|
||||||
"help.sketch.inspect": "Membuka laporan tentang sketsa yang terbuka: berapa derajat kebebasan yang tersisa dan apakah sketsa terbatasi penuh. Gunakan untuk mengetahui apa yang masih bergerak sebelum Anda menyelesaikan sketsa.",
|
"help.sketch.inspect": "Membuka laporan tentang sketsa yang terbuka: berapa derajat kebebasan yang tersisa dan apakah sketsa terbatasi penuh. Gunakan untuk mengetahui apa yang masih bergerak sebelum Anda menyelesaikan sketsa.",
|
||||||
"help.sketch.intersect": "Menambahkan kurva atau titik tempat bodi, muka, tepi, atau sketsa lain memotong bidang sketsa. Semuanya tetap tertaut dan mengikuti model saat model berubah.",
|
"help.sketch.intersect": "Menambahkan kurva atau titik tempat bodi, muka, tepi, atau sketsa lain memotong bidang sketsa. Semuanya tetap tertaut dan mengikuti model saat model berubah.",
|
||||||
"help.sketch.intersection_curve": "Membuat kurva 3D di tempat dua bodi atau muka bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
|
"help.sketch.intersection_curve": "Membuat kurva 3D di tempat dua bodi, muka, atau kurva sketsa lain bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
|
||||||
"help.sketch.isoparametric_curve": "Membuat kurva 3D sepanjang arah U atau V sebuah muka melalui titik yang Anda pilih, tertaut ke muka itu. Gunakan sebagai jalur atau garis pisah yang mengikuti permukaan.",
|
"help.sketch.isoparametric_curve": "Membuat kurva 3D sepanjang arah U atau V sebuah muka melalui titik yang Anda pilih, tertaut ke muka itu. Gunakan sebagai jalur atau garis pisah yang mengikuti permukaan.",
|
||||||
"help.sketch.line": "Menggambar garis-garis tersambung titik demi titik, sebagai rantai terbuka atau kontur tertutup. Ini alat dasar untuk sebagian besar profil.",
|
"help.sketch.line": "Menggambar garis-garis tersambung titik demi titik, sebagai rantai terbuka atau kontur tertutup. Ini alat dasar untuk sebagian besar profil.",
|
||||||
"help.sketch.line.midpoint": "Menggambar garis dari titik tengahnya ke satu ujung, dengan ujung lainnya dicerminkan. Gunakan untuk garis yang dipusatkan pada sebuah titik, seperti tepi simetris.",
|
"help.sketch.line.midpoint": "Menggambar garis dari titik tengahnya ke satu ujung, dengan ujung lainnya dicerminkan. Gunakan untuk garis yang dipusatkan pada sebuah titik, seperti tepi simetris.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Pelat Contoh",
|
"sample.plate.name": "Pelat Contoh",
|
||||||
"sample.vents.about": "Slot dalam pola persegi panjang: baris, kolom, dan pitch adalah parameter",
|
"sample.vents.about": "Slot dalam pola persegi panjang: baris, kolom, dan pitch adalah parameter",
|
||||||
"sample.vents.name": "Pelat Ventilasi",
|
"sample.vents.name": "Pelat Ventilasi",
|
||||||
|
"scale.per_axis": "Per sumbu",
|
||||||
|
"scale.uniform": "Seragam",
|
||||||
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan tetap buka Esc tutup",
|
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan tetap buka Esc tutup",
|
||||||
"search.commands": "Perintah",
|
"search.commands": "Perintah",
|
||||||
"search.design": "Di desain ini",
|
"search.design": "Di desain ini",
|
||||||
|
|||||||
+11
-1
@@ -567,6 +567,7 @@
|
|||||||
"Select edges": "Seleziona spigoli",
|
"Select edges": "Seleziona spigoli",
|
||||||
"Select points of the sketch being edited": "Seleziona punti dello schizzo in modifica",
|
"Select points of the sketch being edited": "Seleziona punti dello schizzo in modifica",
|
||||||
"Select the centre the pattern turns around first": "Seleziona prima il centro attorno a cui ruota la serie",
|
"Select the centre the pattern turns around first": "Seleziona prima il centro attorno a cui ruota la serie",
|
||||||
|
"Select the curves to divide first": "Selezionare prima le curve da dividere",
|
||||||
"Select the curves to offset first": "Seleziona prima le curve per l'offset",
|
"Select the curves to offset first": "Seleziona prima le curve per l'offset",
|
||||||
"Select the mirror line": "Seleziona la linea di simmetria",
|
"Select the mirror line": "Seleziona la linea di simmetria",
|
||||||
"Select the sketch curves or points to mirror first": "Seleziona prima le curve o i punti dello schizzo da specchiare",
|
"Select the sketch curves or points to mirror first": "Seleziona prima le curve o i punti dello schizzo da specchiare",
|
||||||
@@ -1194,6 +1195,7 @@
|
|||||||
"cmd.sketch_options": "Opzioni della tavolozza schizzo",
|
"cmd.sketch_options": "Opzioni della tavolozza schizzo",
|
||||||
"cmd.sketch_paste": "Incolla entità dello schizzo",
|
"cmd.sketch_paste": "Incolla entità dello schizzo",
|
||||||
"cmd.sketch_point3d": "Punto 3D",
|
"cmd.sketch_point3d": "Punto 3D",
|
||||||
|
"cmd.sketch_point_divide": "Dividi curva",
|
||||||
"cmd.sketch_polygon_circumscribed": "Poligono circoscritto",
|
"cmd.sketch_polygon_circumscribed": "Poligono circoscritto",
|
||||||
"cmd.sketch_polygon_edge": "Poligono per lato",
|
"cmd.sketch_polygon_edge": "Poligono per lato",
|
||||||
"cmd.sketch_polygon_inscribed": "Poligono inscritto",
|
"cmd.sketch_polygon_inscribed": "Poligono inscritto",
|
||||||
@@ -1625,7 +1627,7 @@
|
|||||||
"help.sketch.insert_svg": "Legge i tracciati di un disegno SVG in uno schizzo, scalati, ruotati e posizionati come imposti. Usalo per trasformare un logo o un contorno vettoriale in curve che puoi estrudere.",
|
"help.sketch.insert_svg": "Legge i tracciati di un disegno SVG in uno schizzo, scalati, ruotati e posizionati come imposti. Usalo per trasformare un logo o un contorno vettoriale in curve che puoi estrudere.",
|
||||||
"help.sketch.inspect": "Apre un rapporto sullo schizzo aperto: quanti gradi di libertà restano e se è completamente vincolato. Usalo per scoprire cosa si muove ancora prima di terminare lo schizzo.",
|
"help.sketch.inspect": "Apre un rapporto sullo schizzo aperto: quanti gradi di libertà restano e se è completamente vincolato. Usalo per scoprire cosa si muove ancora prima di terminare lo schizzo.",
|
||||||
"help.sketch.intersect": "Aggiunge le curve o i punti in cui corpi, facce, spigoli o altri schizzi attraversano il piano dello schizzo. Restano collegati e seguono il modello quando cambia.",
|
"help.sketch.intersect": "Aggiunge le curve o i punti in cui corpi, facce, spigoli o altri schizzi attraversano il piano dello schizzo. Restano collegati e seguono il modello quando cambia.",
|
||||||
"help.sketch.intersection_curve": "Crea curve 3D dove si incontrano due corpi o facce, collegate a essi. Usale come percorsi o guide che seguono il modello.",
|
"help.sketch.intersection_curve": "Crea curve 3D dove si incontrano due corpi, facce o curve di altri schizzi, collegate a essi. Usale come percorsi o guide che seguono il modello.",
|
||||||
"help.sketch.isoparametric_curve": "Crea una curva 3D lungo la direzione U o V di una faccia passante per un punto che scegli, collegata alla faccia. Usala come percorso o linea di divisione che segue la superficie.",
|
"help.sketch.isoparametric_curve": "Crea una curva 3D lungo la direzione U o V di una faccia passante per un punto che scegli, collegata alla faccia. Usala come percorso o linea di divisione che segue la superficie.",
|
||||||
"help.sketch.line": "Disegna linee collegate punto per punto, come catena aperta o contorno chiuso. È lo strumento di base per la maggior parte dei profili.",
|
"help.sketch.line": "Disegna linee collegate punto per punto, come catena aperta o contorno chiuso. È lo strumento di base per la maggior parte dei profili.",
|
||||||
"help.sketch.line.midpoint": "Disegna una linea dal suo punto medio fino a un'estremità, con l'altra estremità speculare. Usalo per linee centrate su un punto, come un bordo simmetrico.",
|
"help.sketch.line.midpoint": "Disegna una linea dal suo punto medio fino a un'estremità, con l'altra estremità speculare. Usalo per linee centrate su un punto, come un bordo simmetrico.",
|
||||||
@@ -1637,6 +1639,7 @@
|
|||||||
"help.sketch.pattern.circular": "Copia la geometria dello schizzo attorno a un punto centrale, su un cerchio completo o su un angolo stabilito. Usalo per cerchi di bulloni, raggi e denti.",
|
"help.sketch.pattern.circular": "Copia la geometria dello schizzo attorno a un punto centrale, su un cerchio completo o su un angolo stabilito. Usalo per cerchi di bulloni, raggi e denti.",
|
||||||
"help.sketch.pattern.rectangular": "Copia la geometria dello schizzo in righe e colonne con spaziature stabilite. Usalo per griglie di fori, asole o prese d'aria.",
|
"help.sketch.pattern.rectangular": "Copia la geometria dello schizzo in righe e colonne con spaziature stabilite. Usalo per griglie di fori, asole o prese d'aria.",
|
||||||
"help.sketch.point": "Colloca un punto dello schizzo. Usalo come riferimento per quote, vincoli, centri di fori o lavorazioni successive.",
|
"help.sketch.point": "Colloca un punto dello schizzo. Usalo come riferimento per quote, vincoli, centri di fori o lavorazioni successive.",
|
||||||
|
"help.sketch.point.divide": "Posiziona punti che dividono le curve scelte in lunghezze uguali. I punti restano su ogni curva e ne mantengono la spaziatura quando cambia.",
|
||||||
"help.sketch.point3d": "Colloca un punto nello spazio, con Schizzo 3D attivo. Usalo come riferimento per curve e percorsi 3D.",
|
"help.sketch.point3d": "Colloca un punto nello spazio, con Schizzo 3D attivo. Usalo come riferimento per curve e percorsi 3D.",
|
||||||
"help.sketch.polygon.circumscribed": "Disegna un poligono regolare attorno a un cerchio, dimensionato dal centro al punto medio di un lato. Usalo quando è nota l'apertura chiave, come per un dado esagonale.",
|
"help.sketch.polygon.circumscribed": "Disegna un poligono regolare attorno a un cerchio, dimensionato dal centro al punto medio di un lato. Usalo quando è nota l'apertura chiave, come per un dado esagonale.",
|
||||||
"help.sketch.polygon.edge": "Disegna un poligono regolare a partire dai due estremi di un lato. Usalo quando sono note la lunghezza e la posizione di un lato.",
|
"help.sketch.polygon.edge": "Disegna un poligono regolare a partire dai due estremi di un lato. Usalo quando sono note la lunghezza e la posizione di un lato.",
|
||||||
@@ -3813,6 +3816,8 @@
|
|||||||
"sample.plate.name": "Piastra di esempio",
|
"sample.plate.name": "Piastra di esempio",
|
||||||
"sample.vents.about": "Asole in ripetizione rettangolare: righe, colonne e passo sono parametri",
|
"sample.vents.about": "Asole in ripetizione rettangolare: righe, colonne e passo sono parametri",
|
||||||
"sample.vents.name": "Piastra di ventilazione",
|
"sample.vents.name": "Piastra di ventilazione",
|
||||||
|
"scale.per_axis": "Per asse",
|
||||||
|
"scale.uniform": "Uniforme",
|
||||||
"search.command_keys": "Invio esegui Maiusc+Invio esegui e tieni aperto Esc chiudi",
|
"search.command_keys": "Invio esegui Maiusc+Invio esegui e tieni aperto Esc chiudi",
|
||||||
"search.commands": "Comandi",
|
"search.commands": "Comandi",
|
||||||
"search.design": "In questo progetto",
|
"search.design": "In questo progetto",
|
||||||
@@ -3900,6 +3905,7 @@
|
|||||||
"sketch.dimension_applied": "Quota applicata",
|
"sketch.dimension_applied": "Quota applicata",
|
||||||
"sketch.dimension_applied_defined": "Quota applicata; parametro `{name}` definito",
|
"sketch.dimension_applied_defined": "Quota applicata; parametro `{name}` definito",
|
||||||
"sketch.dimension_applied_renamed": "Quota applicata; parametro denominato `{name}`",
|
"sketch.dimension_applied_renamed": "Quota applicata; parametro denominato `{name}`",
|
||||||
|
"sketch.divide_hint": "I punti dividono ogni curva in lunghezze uguali e le mantengono quando cambia.",
|
||||||
"sketch.dof_left": "{count} gradi di libertà rimanenti",
|
"sketch.dof_left": "{count} gradi di libertà rimanenti",
|
||||||
"sketch.dof_left_one": "{count} grado di libertà rimanente",
|
"sketch.dof_left_one": "{count} grado di libertà rimanente",
|
||||||
"sketch.draw_curves": "Disegna come {linetype}",
|
"sketch.draw_curves": "Disegna come {linetype}",
|
||||||
@@ -4689,6 +4695,7 @@
|
|||||||
"ui.heading_create_sketch": "CREA SCHIZZO",
|
"ui.heading_create_sketch": "CREA SCHIZZO",
|
||||||
"ui.heading_cylinder": "CILINDRO",
|
"ui.heading_cylinder": "CILINDRO",
|
||||||
"ui.heading_design": "PROGETTAZIONE",
|
"ui.heading_design": "PROGETTAZIONE",
|
||||||
|
"ui.heading_divide_curve": "DIVIDI CURVA",
|
||||||
"ui.heading_draft": "SFORMO",
|
"ui.heading_draft": "SFORMO",
|
||||||
"ui.heading_edit": "MODIFICA",
|
"ui.heading_edit": "MODIFICA",
|
||||||
"ui.heading_equation_curve": "CURVA DA EQUAZIONE",
|
"ui.heading_equation_curve": "CURVA DA EQUAZIONE",
|
||||||
@@ -4847,6 +4854,8 @@
|
|||||||
"ui.mesh_format_3mf": "3MF",
|
"ui.mesh_format_3mf": "3MF",
|
||||||
"ui.mesh_format_3mf_tagged": "3MF con etichette delle facce",
|
"ui.mesh_format_3mf_tagged": "3MF con etichette delle facce",
|
||||||
"ui.mesh_format_stl": "STL",
|
"ui.mesh_format_stl": "STL",
|
||||||
|
"ui.mesh_import_origin": "Sposta all'origine",
|
||||||
|
"ui.mesh_import_origin_tip": "Centra la mesh in X e Y con la base su Z = 0. Se disattivato, la mesh mantiene le coordinate salvate nel file.",
|
||||||
"ui.minor_radius": "Raggio minore",
|
"ui.minor_radius": "Raggio minore",
|
||||||
"ui.mirror": "Specchia",
|
"ui.mirror": "Specchia",
|
||||||
"ui.mirror_line": "Asse di simmetria",
|
"ui.mirror_line": "Asse di simmetria",
|
||||||
@@ -5027,6 +5036,7 @@
|
|||||||
"ui.second_rail": "Seconda guida",
|
"ui.second_rail": "Seconda guida",
|
||||||
"ui.section_analysis": "Analisi in sezione",
|
"ui.section_analysis": "Analisi in sezione",
|
||||||
"ui.section_size": "Dimensione della sezione",
|
"ui.section_size": "Dimensione della sezione",
|
||||||
|
"ui.segments": "Segmenti",
|
||||||
"ui.select": "Seleziona",
|
"ui.select": "Seleziona",
|
||||||
"ui.selectOther": "Seleziona altro…",
|
"ui.selectOther": "Seleziona altro…",
|
||||||
"ui.selectTangentChain": "Seleziona catena tangente",
|
"ui.selectTangentChain": "Seleziona catena tangente",
|
||||||
|
|||||||
+542
-14
File diff suppressed because it is too large
Load Diff
+1072
-52
File diff suppressed because it is too large
Load Diff
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Membaca laluan lukisan SVG ke dalam lakaran, diskalakan, diputar dan diletakkan mengikut tetapan anda. Gunakan untuk menukar logo atau garis luar vektor kepada lengkung yang boleh disemperit.",
|
"help.sketch.insert_svg": "Membaca laluan lukisan SVG ke dalam lakaran, diskalakan, diputar dan diletakkan mengikut tetapan anda. Gunakan untuk menukar logo atau garis luar vektor kepada lengkung yang boleh disemperit.",
|
||||||
"help.sketch.inspect": "Membuka laporan tentang lakaran yang terbuka: berapa banyak darjah kebebasan yang tinggal dan sama ada ia terkekang sepenuhnya. Gunakan untuk mengetahui apa yang masih bergerak sebelum anda menamatkan lakaran.",
|
"help.sketch.inspect": "Membuka laporan tentang lakaran yang terbuka: berapa banyak darjah kebebasan yang tinggal dan sama ada ia terkekang sepenuhnya. Gunakan untuk mengetahui apa yang masih bergerak sebelum anda menamatkan lakaran.",
|
||||||
"help.sketch.intersect": "Menambah lengkung atau titik tempat jasad, muka, tepi atau lakaran lain melintasi satah lakaran. Ia kekal terpaut dan mengikut model apabila model berubah.",
|
"help.sketch.intersect": "Menambah lengkung atau titik tempat jasad, muka, tepi atau lakaran lain melintasi satah lakaran. Ia kekal terpaut dan mengikut model apabila model berubah.",
|
||||||
"help.sketch.intersection_curve": "Mencipta lengkung 3D tempat dua jasad atau muka bertemu, terpaut kepadanya. Gunakan sebagai laluan atau rel pemandu yang mengikut model.",
|
"help.sketch.intersection_curve": "Mencipta lengkung 3D tempat dua jasad, muka atau lengkung lakaran lain bertemu, terpaut kepadanya. Gunakan sebagai laluan atau rel pemandu yang mengikut model.",
|
||||||
"help.sketch.isoparametric_curve": "Mencipta lengkung 3D di sepanjang arah U atau V muka melalui titik yang anda pilih, terpaut kepada muka itu. Gunakan sebagai laluan atau garis belahan yang mengikut permukaan.",
|
"help.sketch.isoparametric_curve": "Mencipta lengkung 3D di sepanjang arah U atau V muka melalui titik yang anda pilih, terpaut kepada muka itu. Gunakan sebagai laluan atau garis belahan yang mengikut permukaan.",
|
||||||
"help.sketch.line": "Melukis garis bersambung titik demi titik, sebagai rantaian terbuka atau garis luar tertutup. Ia ialah alat asas bagi kebanyakan profil.",
|
"help.sketch.line": "Melukis garis bersambung titik demi titik, sebagai rantaian terbuka atau garis luar tertutup. Ia ialah alat asas bagi kebanyakan profil.",
|
||||||
"help.sketch.line.midpoint": "Melukis garis daripada titik tengahnya ke satu hujung, dengan hujung satu lagi dicerminkan. Gunakan untuk garis yang berpusat pada satu titik, seperti tepi simetri.",
|
"help.sketch.line.midpoint": "Melukis garis daripada titik tengahnya ke satu hujung, dengan hujung satu lagi dicerminkan. Gunakan untuk garis yang berpusat pada satu titik, seperti tepi simetri.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Plat Contoh",
|
"sample.plate.name": "Plat Contoh",
|
||||||
"sample.vents.about": "Corak slot segi empat tepat: baris, lajur dan pic sebagai parameter",
|
"sample.vents.about": "Corak slot segi empat tepat: baris, lajur dan pic sebagai parameter",
|
||||||
"sample.vents.name": "Plat Pengudaraan",
|
"sample.vents.name": "Plat Pengudaraan",
|
||||||
|
"scale.per_axis": "Mengikut paksi",
|
||||||
|
"scale.uniform": "Seragam",
|
||||||
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan kekalkan terbuka Esc tutup",
|
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan kekalkan terbuka Esc tutup",
|
||||||
"search.commands": "Arahan",
|
"search.commands": "Arahan",
|
||||||
"search.design": "Dalam reka bentuk ini",
|
"search.design": "Dalam reka bentuk ini",
|
||||||
|
|||||||
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Leser banene fra en SVG-tegning inn i en skisse, skalert, dreid og plassert slik du angir. Bruk det til å gjøre en logo eller vektorkontur om til kurver du kan ekstrudere.",
|
"help.sketch.insert_svg": "Leser banene fra en SVG-tegning inn i en skisse, skalert, dreid og plassert slik du angir. Bruk det til å gjøre en logo eller vektorkontur om til kurver du kan ekstrudere.",
|
||||||
"help.sketch.inspect": "Åpner en rapport om den åpne skissen: hvor mange frihetsgrader som gjenstår og om den er fullstendig bundet. Bruk det til å finne ut hva som fortsatt beveger seg før du fullfører skissen.",
|
"help.sketch.inspect": "Åpner en rapport om den åpne skissen: hvor mange frihetsgrader som gjenstår og om den er fullstendig bundet. Bruk det til å finne ut hva som fortsatt beveger seg før du fullfører skissen.",
|
||||||
"help.sketch.intersect": "Legger til kurvene eller punktene der kropper, flater, kanter eller andre skisser krysser skisseplanet. De forblir koblet og følger modellen når den endres.",
|
"help.sketch.intersect": "Legger til kurvene eller punktene der kropper, flater, kanter eller andre skisser krysser skisseplanet. De forblir koblet og følger modellen når den endres.",
|
||||||
"help.sketch.intersection_curve": "Oppretter 3D-kurver der to kropper eller flater møtes, koblet til dem. Bruk dem som baner eller føringskurver som følger modellen.",
|
"help.sketch.intersection_curve": "Oppretter 3D-kurver der to kropper, flater eller kurver fra andre skisser møtes, koblet til dem. Bruk dem som baner eller føringskurver som følger modellen.",
|
||||||
"help.sketch.isoparametric_curve": "Oppretter en 3D-kurve langs U- eller V-retningen til en flate gjennom et punkt du velger, koblet til flaten. Bruk den som en bane eller delelinje som følger flaten.",
|
"help.sketch.isoparametric_curve": "Oppretter en 3D-kurve langs U- eller V-retningen til en flate gjennom et punkt du velger, koblet til flaten. Bruk den som en bane eller delelinje som følger flaten.",
|
||||||
"help.sketch.line": "Tegner sammenhengende linjer punkt for punkt, som en åpen kjede eller en lukket kontur. Det er det grunnleggende verktøyet for de fleste profiler.",
|
"help.sketch.line": "Tegner sammenhengende linjer punkt for punkt, som en åpen kjede eller en lukket kontur. Det er det grunnleggende verktøyet for de fleste profiler.",
|
||||||
"help.sketch.line.midpoint": "Tegner en linje fra midtpunktet og ut til den ene enden, med den andre enden speilet. Bruk det til linjer sentrert på et punkt, for eksempel en symmetrisk kant.",
|
"help.sketch.line.midpoint": "Tegner en linje fra midtpunktet og ut til den ene enden, med den andre enden speilet. Bruk det til linjer sentrert på et punkt, for eksempel en symmetrisk kant.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Eksempelplate",
|
"sample.plate.name": "Eksempelplate",
|
||||||
"sample.vents.about": "Spor i et rektangulært mønster: rader, kolonner og stigning er parametere",
|
"sample.vents.about": "Spor i et rektangulært mønster: rader, kolonner og stigning er parametere",
|
||||||
"sample.vents.name": "Ventilasjonsplate",
|
"sample.vents.name": "Ventilasjonsplate",
|
||||||
|
"scale.per_axis": "Per akse",
|
||||||
|
"scale.uniform": "Jevn",
|
||||||
"search.command_keys": "Enter kjør Shift+Enter kjør og behold åpen Esc lukk",
|
"search.command_keys": "Enter kjør Shift+Enter kjør og behold åpen Esc lukk",
|
||||||
"search.commands": "Kommandoer",
|
"search.commands": "Kommandoer",
|
||||||
"search.design": "I dette designet",
|
"search.design": "I dette designet",
|
||||||
|
|||||||
@@ -1625,7 +1625,7 @@
|
|||||||
"help.sketch.insert_svg": "Leest de paden van een SVG-tekening in een schets, geschaald, gedraaid en geplaatst waar je instelt. Gebruik dit om een logo of vectorcontour om te zetten in curven die je kunt extruderen.",
|
"help.sketch.insert_svg": "Leest de paden van een SVG-tekening in een schets, geschaald, gedraaid en geplaatst waar je instelt. Gebruik dit om een logo of vectorcontour om te zetten in curven die je kunt extruderen.",
|
||||||
"help.sketch.inspect": "Opent een rapport over de geopende schets: hoeveel vrijheidsgraden er nog over zijn en of hij volledig beperkt is. Gebruik dit om te zien wat nog beweegt voordat je de schets voltooit.",
|
"help.sketch.inspect": "Opent een rapport over de geopende schets: hoeveel vrijheidsgraden er nog over zijn en of hij volledig beperkt is. Gebruik dit om te zien wat nog beweegt voordat je de schets voltooit.",
|
||||||
"help.sketch.intersect": "Voegt de curven of punten toe waar lichamen, vlakken, randen of andere schetsen het schetsvlak kruisen. Ze blijven gekoppeld en volgen het model wanneer dat verandert.",
|
"help.sketch.intersect": "Voegt de curven of punten toe waar lichamen, vlakken, randen of andere schetsen het schetsvlak kruisen. Ze blijven gekoppeld en volgen het model wanneer dat verandert.",
|
||||||
"help.sketch.intersection_curve": "Maakt 3D-curven waar twee lichamen of vlakken samenkomen, eraan gekoppeld. Gebruik ze als paden of geleiders die het model volgen.",
|
"help.sketch.intersection_curve": "Maakt 3D-curven waar twee lichamen, vlakken of curven van andere schetsen samenkomen, eraan gekoppeld. Gebruik ze als paden of geleiders die het model volgen.",
|
||||||
"help.sketch.isoparametric_curve": "Maakt een 3D-curve langs de U- of V-richting van een vlak door een punt dat je kiest, gekoppeld aan het vlak. Gebruik dit als pad of splitslijn die het oppervlak volgt.",
|
"help.sketch.isoparametric_curve": "Maakt een 3D-curve langs de U- of V-richting van een vlak door een punt dat je kiest, gekoppeld aan het vlak. Gebruik dit als pad of splitslijn die het oppervlak volgt.",
|
||||||
"help.sketch.line": "Tekent verbonden lijnen punt voor punt, als open keten of gesloten contour. Het is het basisgereedschap voor de meeste profielen.",
|
"help.sketch.line": "Tekent verbonden lijnen punt voor punt, als open keten of gesloten contour. Het is het basisgereedschap voor de meeste profielen.",
|
||||||
"help.sketch.line.midpoint": "Tekent een lijn vanuit zijn middelpunt naar één uiteinde, waarbij het andere uiteinde wordt gespiegeld. Gebruik dit voor lijnen die op een punt zijn gecentreerd, zoals een symmetrische rand.",
|
"help.sketch.line.midpoint": "Tekent een lijn vanuit zijn middelpunt naar één uiteinde, waarbij het andere uiteinde wordt gespiegeld. Gebruik dit voor lijnen die op een punt zijn gecentreerd, zoals een symmetrische rand.",
|
||||||
@@ -3818,6 +3818,8 @@
|
|||||||
"sample.plate.name": "Voorbeeldplaat",
|
"sample.plate.name": "Voorbeeldplaat",
|
||||||
"sample.vents.about": "Sleuven in een rechthoekig patroon: rijen, kolommen en spoed zijn parameters",
|
"sample.vents.about": "Sleuven in een rechthoekig patroon: rijen, kolommen en spoed zijn parameters",
|
||||||
"sample.vents.name": "Ventilatieplaat",
|
"sample.vents.name": "Ventilatieplaat",
|
||||||
|
"scale.per_axis": "Per as",
|
||||||
|
"scale.uniform": "Uniform",
|
||||||
"search.command_keys": "Enter uitvoeren Shift+Enter uitvoeren en open houden Esc sluiten",
|
"search.command_keys": "Enter uitvoeren Shift+Enter uitvoeren en open houden Esc sluiten",
|
||||||
"search.commands": "Opdrachten",
|
"search.commands": "Opdrachten",
|
||||||
"search.design": "In dit ontwerp",
|
"search.design": "In dit ontwerp",
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"en": {
|
|
||||||
"ai.prefs.instructions": "Custom instructions",
|
|
||||||
"ai.prefs.instructions_note": "Sent to the agent at the start of every chat, after SolidCraft's own instructions: how you like to work, rules to keep to. A change applies from the next chat (Connect or New chat)."
|
|
||||||
},
|
|
||||||
"ar": {
|
|
||||||
"ai.prefs.instructions": "تعليمات مخصصة",
|
|
||||||
"ai.prefs.instructions_note": "تُرسَل إلى الوكيل في بداية كل محادثة، بعد تعليمات SolidCraft الخاصة: طريقتك المفضلة في العمل والقواعد التي يجب الالتزام بها. يسري أي تغيير من المحادثة التالية (اتصال أو محادثة جديدة)."
|
|
||||||
},
|
|
||||||
"he": {
|
|
||||||
"ai.prefs.instructions": "הוראות מותאמות אישית",
|
|
||||||
"ai.prefs.instructions_note": "נשלחות לסוכן בתחילת כל צ'אט, אחרי ההוראות של SolidCraft עצמה: דרך העבודה המועדפת עליך וכללים שיש להקפיד עליהם. שינוי חל מהצ'אט הבא (התחברות או צ'אט חדש)."
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"ai.prefs.instructions": "カスタム指示",
|
|
||||||
"ai.prefs.instructions_note": "各チャットの開始時に、SolidCraft 自身の指示の後に続けてエージェントへ送られます。作業の進め方や守ってほしいルールを書いてください。変更は次のチャット(接続または新しいチャット)から適用されます。"
|
|
||||||
},
|
|
||||||
"vi": {
|
|
||||||
"ai.prefs.instructions": "Hướng dẫn tùy chỉnh",
|
|
||||||
"ai.prefs.instructions_note": "Được gửi cho agent khi bắt đầu mỗi cuộc trò chuyện, sau hướng dẫn riêng của SolidCraft: cách bạn muốn làm việc, các quy tắc cần tuân theo. Thay đổi có hiệu lực từ cuộc trò chuyện tiếp theo (Kết nối hoặc Cuộc trò chuyện mới)."
|
|
||||||
},
|
|
||||||
"zh": {
|
|
||||||
"ai.prefs.instructions": "自定义指令",
|
|
||||||
"ai.prefs.instructions_note": "在每次对话开始时发送给代理,位于 SolidCraft 自身的指令之后:你喜欢的工作方式、需要遵守的规则。更改从下一次对话(连接或新对话)起生效。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"ar": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "تبديل قوائم اللوحات عند مرور المؤشر",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "انقر على اسم لوحة في الشريط لفتح قائمتها. مرّر المؤشر فوق اسم لوحة أخرى للتبديل. حرّك المؤشر خارج الشريط والقائمة المفتوحة لإغلاقها."
|
|
||||||
},
|
|
||||||
"de": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "Bereichsmenüs beim Darüberfahren wechseln",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "Klicken Sie auf einen Bereichsnamen, um das Menü zu öffnen. Bewegen Sie den Zeiger auf einen anderen Bereichsnamen, um das Menü zu wechseln. Außerhalb des Menübands und des geöffneten Menüs schließt es sich."
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "Switch panel menus on hover",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "Click a ribbon panel name to open its menu. Hover another panel name to switch. Move outside the ribbon and open menu to close it."
|
|
||||||
},
|
|
||||||
"he": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "מעבר בין תפריטי לוחות בריחוף",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "לחצו על שם לוח ברצועת הכלים כדי לפתוח את התפריט שלו. העבירו את הסמן מעל שם של לוח אחר כדי לעבור לתפריט שלו. הזיזו את הסמן אל מחוץ לרצועת הכלים ולתפריט הפתוח כדי לסגור אותו."
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "ポイントしてパネルメニューを切り替える",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "リボンのパネル名をクリックしてメニューを開きます。別のパネル名にポインターを合わせると切り替わります。リボンと開いているメニューの外に移動すると閉じます。"
|
|
||||||
},
|
|
||||||
"ko": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "포인터를 올려 패널 메뉴 전환",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "리본의 패널 이름을 클릭하여 메뉴를 엽니다. 다른 패널 이름에 포인터를 올리면 메뉴가 전환됩니다. 리본과 열린 메뉴 밖으로 포인터를 이동하면 메뉴가 닫힙니다."
|
|
||||||
},
|
|
||||||
"vi": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "Chuyển menu nhóm khi di chuột",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "Nhấp vào tên nhóm trên dải công cụ để mở menu. Di chuột lên tên nhóm khác để chuyển menu. Di chuyển con trỏ ra ngoài dải công cụ và menu đang mở để đóng menu."
|
|
||||||
},
|
|
||||||
"zh": {
|
|
||||||
"prefs.layout_page.hover_panel_menus": "悬停切换面板菜单",
|
|
||||||
"prefs.layout_page.hover_panel_menus_help": "点击功能区的面板名称打开菜单。将指针悬停在另一面板名称上即可切换菜单。将指针移到功能区和已打开菜单之外即可关闭菜单。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"en": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "The origin axis is perpendicular to the sketch plane, so it has no line in the plane"
|
|
||||||
},
|
|
||||||
"ar": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "محور الأصل عمودي على مستوى الرسم، لذا ليس له خط في المستوى"
|
|
||||||
},
|
|
||||||
"de": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Die Ursprungsachse steht senkrecht auf der Skizzenebene und hat daher keine Linie in ihr"
|
|
||||||
},
|
|
||||||
"he": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "ציר הראשית ניצב למישור הסקיצה, ולכן אין לו קו במישור"
|
|
||||||
},
|
|
||||||
"ja": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "原点軸はスケッチ平面に垂直なため、平面内に線がありません"
|
|
||||||
},
|
|
||||||
"ru": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Ось начала координат перпендикулярна плоскости эскиза, поэтому линии в этой плоскости нет"
|
|
||||||
},
|
|
||||||
"vi": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Trục gốc vuông góc với mặt phẳng phác thảo nên không có đường thẳng trong mặt phẳng này"
|
|
||||||
},
|
|
||||||
"zh": {
|
|
||||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "原点轴垂直于草图平面,因此在该平面内没有直线"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2703
-1
File diff suppressed because it is too large
Load Diff
+3731
-1
File diff suppressed because it is too large
Load Diff
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Citește traseele unui desen SVG într-o schiță, scalate, rotite și plasate așa cum stabiliți. Folosiți-o pentru a transforma un logo sau un contur vectorial în curbe pe care le puteți extruda.",
|
"help.sketch.insert_svg": "Citește traseele unui desen SVG într-o schiță, scalate, rotite și plasate așa cum stabiliți. Folosiți-o pentru a transforma un logo sau un contur vectorial în curbe pe care le puteți extruda.",
|
||||||
"help.sketch.inspect": "Deschide un raport despre schița deschisă: câte grade de libertate au rămas și dacă este complet constrânsă. Folosiți-l pentru a afla ce se mai mișcă înainte de a finaliza schița.",
|
"help.sketch.inspect": "Deschide un raport despre schița deschisă: câte grade de libertate au rămas și dacă este complet constrânsă. Folosiți-l pentru a afla ce se mai mișcă înainte de a finaliza schița.",
|
||||||
"help.sketch.intersect": "Adaugă curbele sau punctele în care corpuri, fețe, muchii sau alte schițe taie planul schiței. Rămân legate și urmează modelul când acesta se schimbă.",
|
"help.sketch.intersect": "Adaugă curbele sau punctele în care corpuri, fețe, muchii sau alte schițe taie planul schiței. Rămân legate și urmează modelul când acesta se schimbă.",
|
||||||
"help.sketch.intersection_curve": "Creează curbe 3D acolo unde se întâlnesc două corpuri sau fețe, legate de ele. Folosiți-le ca trasee sau ghidaje care urmează modelul.",
|
"help.sketch.intersection_curve": "Creează curbe 3D acolo unde se întâlnesc două corpuri, fețe sau curbe ale altor schițe, legate de ele. Folosiți-le ca trasee sau ghidaje care urmează modelul.",
|
||||||
"help.sketch.isoparametric_curve": "Creează o curbă 3D pe direcția U sau V a unei fețe, printr-un punct ales de dumneavoastră, legată de față. Folosiți-o ca traseu sau linie de separare care urmează suprafața.",
|
"help.sketch.isoparametric_curve": "Creează o curbă 3D pe direcția U sau V a unei fețe, printr-un punct ales de dumneavoastră, legată de față. Folosiți-o ca traseu sau linie de separare care urmează suprafața.",
|
||||||
"help.sketch.line": "Desenează linii conectate punct cu punct, ca lanț deschis sau contur închis. Este instrumentul de bază pentru majoritatea profilurilor.",
|
"help.sketch.line": "Desenează linii conectate punct cu punct, ca lanț deschis sau contur închis. Este instrumentul de bază pentru majoritatea profilurilor.",
|
||||||
"help.sketch.line.midpoint": "Desenează o linie de la mijlocul ei spre un capăt, celălalt capăt fiind în oglindă. Folosiți-o pentru linii centrate pe un punct, de exemplu o muchie simetrică.",
|
"help.sketch.line.midpoint": "Desenează o linie de la mijlocul ei spre un capăt, celălalt capăt fiind în oglindă. Folosiți-o pentru linii centrate pe un punct, de exemplu o muchie simetrică.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Placă exemplu",
|
"sample.plate.name": "Placă exemplu",
|
||||||
"sample.vents.about": "Canale într-o matrice dreptunghiulară: rândurile, coloanele și pasul sunt parametri",
|
"sample.vents.about": "Canale într-o matrice dreptunghiulară: rândurile, coloanele și pasul sunt parametri",
|
||||||
"sample.vents.name": "Placă de ventilație",
|
"sample.vents.name": "Placă de ventilație",
|
||||||
|
"scale.per_axis": "Pe axe",
|
||||||
|
"scale.uniform": "Uniform",
|
||||||
"search.command_keys": "Enter execută Shift+Enter execută și păstrează deschis Esc închide",
|
"search.command_keys": "Enter execută Shift+Enter execută și păstrează deschis Esc închide",
|
||||||
"search.commands": "Comenzi",
|
"search.commands": "Comenzi",
|
||||||
"search.design": "În acest proiect",
|
"search.design": "În acest proiect",
|
||||||
|
|||||||
+10
-1
@@ -567,6 +567,7 @@
|
|||||||
"Select edges": "Выберите рёбра",
|
"Select edges": "Выберите рёбра",
|
||||||
"Select points of the sketch being edited": "Выберите точки редактируемого эскиза",
|
"Select points of the sketch being edited": "Выберите точки редактируемого эскиза",
|
||||||
"Select the centre the pattern turns around first": "Сначала выберите центр, вокруг которого поворачивается массив",
|
"Select the centre the pattern turns around first": "Сначала выберите центр, вокруг которого поворачивается массив",
|
||||||
|
"Select the curves to divide first": "Сначала выберите кривые для деления",
|
||||||
"Select the curves to offset first": "Сначала выберите кривые для смещения",
|
"Select the curves to offset first": "Сначала выберите кривые для смещения",
|
||||||
"Select the mirror line": "Выберите линию зеркала",
|
"Select the mirror line": "Выберите линию зеркала",
|
||||||
"Select the sketch curves or points to mirror first": "Сначала выберите кривые или точки эскиза для зеркального отражения",
|
"Select the sketch curves or points to mirror first": "Сначала выберите кривые или точки эскиза для зеркального отражения",
|
||||||
@@ -647,6 +648,7 @@
|
|||||||
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Направление должно быть осью или прямолинейной кривой эскиза, лежащей в плоскости эскиза",
|
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Направление должно быть осью или прямолинейной кривой эскиза, лежащей в плоскости эскиза",
|
||||||
"The mirror line cannot also be mirrored": "Линию зеркала нельзя одновременно отражать",
|
"The mirror line cannot also be mirrored": "Линию зеркала нельзя одновременно отражать",
|
||||||
"The mirror line must be a straight curve of this sketch, not an origin axis": "Линия зеркала должна быть прямолинейной кривой этого эскиза, а не осью начала координат",
|
"The mirror line must be a straight curve of this sketch, not an origin axis": "Линия зеркала должна быть прямолинейной кривой этого эскиза, а не осью начала координат",
|
||||||
|
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Ось начала координат перпендикулярна плоскости эскиза, поэтому линии в этой плоскости нет",
|
||||||
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Сохранённое исходное вхождение отсутствует. Выберите корректное исходное положение перед перепривязкой.",
|
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Сохранённое исходное вхождение отсутствует. Выберите корректное исходное положение перед перепривязкой.",
|
||||||
"Then": "Затем",
|
"Then": "Затем",
|
||||||
"These features will be deleted:": "Эти операции будут удалены:",
|
"These features will be deleted:": "Эти операции будут удалены:",
|
||||||
@@ -1184,6 +1186,7 @@
|
|||||||
"cmd.sketch_options": "Параметры палитры эскиза",
|
"cmd.sketch_options": "Параметры палитры эскиза",
|
||||||
"cmd.sketch_paste": "Вставить элементы эскиза",
|
"cmd.sketch_paste": "Вставить элементы эскиза",
|
||||||
"cmd.sketch_point3d": "3D-точка",
|
"cmd.sketch_point3d": "3D-точка",
|
||||||
|
"cmd.sketch_point_divide": "Разделить кривую",
|
||||||
"cmd.sketch_polygon_circumscribed": "Описанный многоугольник",
|
"cmd.sketch_polygon_circumscribed": "Описанный многоугольник",
|
||||||
"cmd.sketch_polygon_edge": "Многоугольник по ребру",
|
"cmd.sketch_polygon_edge": "Многоугольник по ребру",
|
||||||
"cmd.sketch_polygon_inscribed": "Вписанный многоугольник",
|
"cmd.sketch_polygon_inscribed": "Вписанный многоугольник",
|
||||||
@@ -1610,7 +1613,7 @@
|
|||||||
"help.sketch.insert_svg": "Читает контуры рисунка SVG в эскиз, масштабируя, поворачивая и размещая их так, как вы зададите. Используйте, чтобы превратить логотип или векторный контур в кривые, которые можно выдавить.",
|
"help.sketch.insert_svg": "Читает контуры рисунка SVG в эскиз, масштабируя, поворачивая и размещая их так, как вы зададите. Используйте, чтобы превратить логотип или векторный контур в кривые, которые можно выдавить.",
|
||||||
"help.sketch.inspect": "Открывает отчёт об открытом эскизе: сколько осталось степеней свободы и определён ли он полностью. Используйте, чтобы выяснить, что ещё двигается, прежде чем завершить эскиз.",
|
"help.sketch.inspect": "Открывает отчёт об открытом эскизе: сколько осталось степеней свободы и определён ли он полностью. Используйте, чтобы выяснить, что ещё двигается, прежде чем завершить эскиз.",
|
||||||
"help.sketch.intersect": "Добавляет кривые или точки, где тела, грани, рёбра или другие эскизы пересекают плоскость эскиза. Они остаются связанными и следуют за моделью при её изменении.",
|
"help.sketch.intersect": "Добавляет кривые или точки, где тела, грани, рёбра или другие эскизы пересекают плоскость эскиза. Они остаются связанными и следуют за моделью при её изменении.",
|
||||||
"help.sketch.intersection_curve": "Создаёт 3D-кривые там, где встречаются два тела или две грани, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
|
"help.sketch.intersection_curve": "Создаёт 3D-кривые там, где встречаются два тела, две грани или кривые других эскизов, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
|
||||||
"help.sketch.isoparametric_curve": "Создаёт 3D-кривую вдоль направления U или V грани через выбранную точку, связанную с гранью. Используйте как траекторию или линию разделения, следующую за поверхностью.",
|
"help.sketch.isoparametric_curve": "Создаёт 3D-кривую вдоль направления U или V грани через выбранную точку, связанную с гранью. Используйте как траекторию или линию разделения, следующую за поверхностью.",
|
||||||
"help.sketch.line": "Рисует соединённые линии точка за точкой, как открытую цепочку или замкнутый контур. Это основной инструмент для большинства профилей.",
|
"help.sketch.line": "Рисует соединённые линии точка за точкой, как открытую цепочку или замкнутый контур. Это основной инструмент для большинства профилей.",
|
||||||
"help.sketch.line.midpoint": "Рисует линию от её середины до одного конца, второй конец отражается зеркально. Используйте для линий с центром в точке, например симметричного ребра.",
|
"help.sketch.line.midpoint": "Рисует линию от её середины до одного конца, второй конец отражается зеркально. Используйте для линий с центром в точке, например симметричного ребра.",
|
||||||
@@ -1622,6 +1625,7 @@
|
|||||||
"help.sketch.pattern.circular": "Копирует геометрию эскиза вокруг центральной точки на полный круг или заданный угол. Используйте для окружностей болтов, спиц и зубьев.",
|
"help.sketch.pattern.circular": "Копирует геометрию эскиза вокруг центральной точки на полный круг или заданный угол. Используйте для окружностей болтов, спиц и зубьев.",
|
||||||
"help.sketch.pattern.rectangular": "Копирует геометрию эскиза рядами и столбцами с заданным шагом. Используйте для сеток отверстий, пазов или вентиляционных прорезей.",
|
"help.sketch.pattern.rectangular": "Копирует геометрию эскиза рядами и столбцами с заданным шагом. Используйте для сеток отверстий, пазов или вентиляционных прорезей.",
|
||||||
"help.sketch.point": "Ставит точку эскиза. Используйте как ориентир для размеров, ограничений, центров отверстий или последующих операций.",
|
"help.sketch.point": "Ставит точку эскиза. Используйте как ориентир для размеров, ограничений, центров отверстий или последующих операций.",
|
||||||
|
"help.sketch.point.divide": "Ставит точки, делящие выбранные кривые на равные по длине части. Точки остаются на каждой кривой и сохраняют шаг при её изменении.",
|
||||||
"help.sketch.point3d": "Ставит точку в пространстве при включённом 3D-эскизе. Используйте как ориентир для 3D-кривых и траекторий.",
|
"help.sketch.point3d": "Ставит точку в пространстве при включённом 3D-эскизе. Используйте как ориентир для 3D-кривых и траекторий.",
|
||||||
"help.sketch.polygon.circumscribed": "Рисует правильный многоугольник вокруг окружности; размер задаётся от центра до середины стороны. Используйте, когда известен размер под ключ, например у шестигранной гайки.",
|
"help.sketch.polygon.circumscribed": "Рисует правильный многоугольник вокруг окружности; размер задаётся от центра до середины стороны. Используйте, когда известен размер под ключ, например у шестигранной гайки.",
|
||||||
"help.sketch.polygon.edge": "Рисует правильный многоугольник по двум концам одной стороны. Используйте, когда известны длина и положение стороны.",
|
"help.sketch.polygon.edge": "Рисует правильный многоугольник по двум концам одной стороны. Используйте, когда известны длина и положение стороны.",
|
||||||
@@ -3750,6 +3754,8 @@
|
|||||||
"sample.plate.name": "Образец пластины",
|
"sample.plate.name": "Образец пластины",
|
||||||
"sample.vents.about": "Пазы в прямоугольном массиве: число рядов, столбцов и шаг заданы параметрами",
|
"sample.vents.about": "Пазы в прямоугольном массиве: число рядов, столбцов и шаг заданы параметрами",
|
||||||
"sample.vents.name": "Вентиляционная пластина",
|
"sample.vents.name": "Вентиляционная пластина",
|
||||||
|
"scale.per_axis": "По осям",
|
||||||
|
"scale.uniform": "Равномерно",
|
||||||
"search.command_keys": "Enter выполнить Shift+Enter выполнить и оставить открытым Esc закрыть",
|
"search.command_keys": "Enter выполнить Shift+Enter выполнить и оставить открытым Esc закрыть",
|
||||||
"search.commands": "Команды",
|
"search.commands": "Команды",
|
||||||
"search.design": "В этом дизайне",
|
"search.design": "В этом дизайне",
|
||||||
@@ -3837,6 +3843,7 @@
|
|||||||
"sketch.dimension_applied": "Размер применён",
|
"sketch.dimension_applied": "Размер применён",
|
||||||
"sketch.dimension_applied_defined": "Размер применён; параметр `{name}` определён",
|
"sketch.dimension_applied_defined": "Размер применён; параметр `{name}` определён",
|
||||||
"sketch.dimension_applied_renamed": "Размер применён; параметру присвоено имя `{name}`",
|
"sketch.dimension_applied_renamed": "Размер применён; параметру присвоено имя `{name}`",
|
||||||
|
"sketch.divide_hint": "Точки делят каждую кривую на равные по длине части и сохраняют их при её изменении.",
|
||||||
"sketch.dof_left": "Осталось степеней свободы: {count}",
|
"sketch.dof_left": "Осталось степеней свободы: {count}",
|
||||||
"sketch.dof_left_one": "Осталась {count} степень свободы",
|
"sketch.dof_left_one": "Осталась {count} степень свободы",
|
||||||
"sketch.draw_curves": "Рисовать как {linetype}",
|
"sketch.draw_curves": "Рисовать как {linetype}",
|
||||||
@@ -4626,6 +4633,7 @@
|
|||||||
"ui.heading_create_sketch": "СОЗДАТЬ ЭСКИЗ",
|
"ui.heading_create_sketch": "СОЗДАТЬ ЭСКИЗ",
|
||||||
"ui.heading_cylinder": "ЦИЛИНДР",
|
"ui.heading_cylinder": "ЦИЛИНДР",
|
||||||
"ui.heading_design": "ДИЗАЙН",
|
"ui.heading_design": "ДИЗАЙН",
|
||||||
|
"ui.heading_divide_curve": "РАЗДЕЛИТЬ КРИВУЮ",
|
||||||
"ui.heading_draft": "УКЛОН",
|
"ui.heading_draft": "УКЛОН",
|
||||||
"ui.heading_edit": "ИЗМЕНИТЬ",
|
"ui.heading_edit": "ИЗМЕНИТЬ",
|
||||||
"ui.heading_equation_curve": "КРИВАЯ ПО УРАВНЕНИЮ",
|
"ui.heading_equation_curve": "КРИВАЯ ПО УРАВНЕНИЮ",
|
||||||
@@ -4953,6 +4961,7 @@
|
|||||||
"ui.second_rail": "Вторая направляющая",
|
"ui.second_rail": "Вторая направляющая",
|
||||||
"ui.section_analysis": "Анализ сечения",
|
"ui.section_analysis": "Анализ сечения",
|
||||||
"ui.section_size": "Размер сечения",
|
"ui.section_size": "Размер сечения",
|
||||||
|
"ui.segments": "Сегменты",
|
||||||
"ui.select": "Выбрать",
|
"ui.select": "Выбрать",
|
||||||
"ui.selectOther": "Выбрать другое…",
|
"ui.selectOther": "Выбрать другое…",
|
||||||
"ui.selectTangentChain": "Выбрать касательную цепочку",
|
"ui.selectTangentChain": "Выбрать касательную цепочку",
|
||||||
|
|||||||
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Учитава путање SVG цртежа у скицу, скалиране, окренуте и постављене како зададете. Користите га да логотип или векторски обрис претворите у криве које можете екструдирати.",
|
"help.sketch.insert_svg": "Учитава путање SVG цртежа у скицу, скалиране, окренуте и постављене како зададете. Користите га да логотип или векторски обрис претворите у криве које можете екструдирати.",
|
||||||
"help.sketch.inspect": "Отвара извештај о отвореној скици: колико је степени слободе остало и да ли је потпуно ограничена. Користите га да сазнате шта се још помера пре него што завршите скицу.",
|
"help.sketch.inspect": "Отвара извештај о отвореној скици: колико је степени слободе остало и да ли је потпуно ограничена. Користите га да сазнате шта се још помера пре него што завршите скицу.",
|
||||||
"help.sketch.intersect": "Додаје криве или тачке у којима тела, лица, ивице или друге скице секу раван скице. Оне остају повезане и прате модел када се он промени.",
|
"help.sketch.intersect": "Додаје криве или тачке у којима тела, лица, ивице или друге скице секу раван скице. Оне остају повезане и прате модел када се он промени.",
|
||||||
"help.sketch.intersection_curve": "Прави 3D криве тамо где се два тела или лица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
|
"help.sketch.intersection_curve": "Прави 3D криве тамо где се два тела, лица или криве других скица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
|
||||||
"help.sketch.isoparametric_curve": "Прави 3D криву дуж U или V смера лица кроз тачку коју изаберете, повезану са лицем. Користите је као путању или линију поделе која прати површ.",
|
"help.sketch.isoparametric_curve": "Прави 3D криву дуж U или V смера лица кроз тачку коју изаберете, повезану са лицем. Користите је као путању или линију поделе која прати површ.",
|
||||||
"help.sketch.line": "Црта повезане линије тачку по тачку, као отворени ланац или затворени обрис. То је основни алат за већину профила.",
|
"help.sketch.line": "Црта повезане линије тачку по тачку, као отворени ланац или затворени обрис. То је основни алат за већину профила.",
|
||||||
"help.sketch.line.midpoint": "Црта линију од њене средишње тачке до једног краја, а други крај се пресликава. Користите је за линије центриране на тачки, на пример за симетричну ивицу.",
|
"help.sketch.line.midpoint": "Црта линију од њене средишње тачке до једног краја, а други крај се пресликава. Користите је за линије центриране на тачки, на пример за симетричну ивицу.",
|
||||||
|
|||||||
@@ -1622,7 +1622,7 @@
|
|||||||
"help.sketch.insert_svg": "Läser in banorna från en SVG-ritning i en skiss, skalade, vridna och placerade där du anger. Använd det för att göra en logotyp eller en vektorkontur till kurvor som du kan extrudera.",
|
"help.sketch.insert_svg": "Läser in banorna från en SVG-ritning i en skiss, skalade, vridna och placerade där du anger. Använd det för att göra en logotyp eller en vektorkontur till kurvor som du kan extrudera.",
|
||||||
"help.sketch.inspect": "Öppnar en rapport om den öppna skissen: hur många frihetsgrader som återstår och om den är fullt definierad. Använd det för att ta reda på vad som fortfarande rör sig innan du avslutar skissen.",
|
"help.sketch.inspect": "Öppnar en rapport om den öppna skissen: hur många frihetsgrader som återstår och om den är fullt definierad. Använd det för att ta reda på vad som fortfarande rör sig innan du avslutar skissen.",
|
||||||
"help.sketch.intersect": "Lägger till de kurvor eller punkter där kroppar, ytor, kanter eller andra skisser korsar skissplanet. De förblir länkade och följer modellen när den ändras.",
|
"help.sketch.intersect": "Lägger till de kurvor eller punkter där kroppar, ytor, kanter eller andra skisser korsar skissplanet. De förblir länkade och följer modellen när den ändras.",
|
||||||
"help.sketch.intersection_curve": "Skapar 3D-kurvor där två kroppar eller ytor möts, länkade till dem. Använd dem som banor eller styrkurvor som följer modellen.",
|
"help.sketch.intersection_curve": "Skapar 3D-kurvor där två kroppar, ytor eller kurvor från andra skisser möts, länkade till dem. Använd dem som banor eller styrkurvor som följer modellen.",
|
||||||
"help.sketch.isoparametric_curve": "Skapar en 3D-kurva längs en ytas U- eller V-riktning genom en punkt som du väljer, länkad till ytan. Använd den som en bana eller delningslinje som följer ytan.",
|
"help.sketch.isoparametric_curve": "Skapar en 3D-kurva längs en ytas U- eller V-riktning genom en punkt som du väljer, länkad till ytan. Använd den som en bana eller delningslinje som följer ytan.",
|
||||||
"help.sketch.line": "Ritar sammanhängande linjer punkt för punkt, som en öppen kedja eller en sluten kontur. Det är det grundläggande verktyget för de flesta profiler.",
|
"help.sketch.line": "Ritar sammanhängande linjer punkt för punkt, som en öppen kedja eller en sluten kontur. Det är det grundläggande verktyget för de flesta profiler.",
|
||||||
"help.sketch.line.midpoint": "Ritar en linje från dess mittpunkt ut till ena änden, med den andra änden speglad. Använd det för linjer centrerade på en punkt, till exempel en symmetrisk kant.",
|
"help.sketch.line.midpoint": "Ritar en linje från dess mittpunkt ut till ena änden, med den andra änden speglad. Använd det för linjer centrerade på en punkt, till exempel en symmetrisk kant.",
|
||||||
@@ -3808,6 +3808,8 @@
|
|||||||
"sample.plate.name": "Exempelplatta",
|
"sample.plate.name": "Exempelplatta",
|
||||||
"sample.vents.about": "Spår i ett rektangulärt mönster: rader, kolumner och delning är parametrar",
|
"sample.vents.about": "Spår i ett rektangulärt mönster: rader, kolumner och delning är parametrar",
|
||||||
"sample.vents.name": "Ventilationsplatta",
|
"sample.vents.name": "Ventilationsplatta",
|
||||||
|
"scale.per_axis": "Per axel",
|
||||||
|
"scale.uniform": "Likformig",
|
||||||
"search.command_keys": "Enter kör Shift+Enter kör och behåll öppen Esc stäng",
|
"search.command_keys": "Enter kör Shift+Enter kör och behåll öppen Esc stäng",
|
||||||
"search.commands": "Kommandon",
|
"search.commands": "Kommandon",
|
||||||
"search.design": "I denna konstruktion",
|
"search.design": "I denna konstruktion",
|
||||||
|
|||||||
@@ -1621,7 +1621,7 @@
|
|||||||
"help.sketch.insert_svg": "Bir SVG çiziminin yollarını, belirlediğiniz ölçekte, açıda ve konumda bir eskize okur. Bir logoyu veya vektör dış hattını ekstrüzyon yapabileceğiniz eğrilere dönüştürmek için kullanın.",
|
"help.sketch.insert_svg": "Bir SVG çiziminin yollarını, belirlediğiniz ölçekte, açıda ve konumda bir eskize okur. Bir logoyu veya vektör dış hattını ekstrüzyon yapabileceğiniz eğrilere dönüştürmek için kullanın.",
|
||||||
"help.sketch.inspect": "Açık eskiz için bir rapor açar: kaç serbestlik derecesi kaldığını ve tam kısıtlı olup olmadığını. Eskizi bitirmeden önce nelerin hâlâ hareket ettiğini öğrenmek için kullanın.",
|
"help.sketch.inspect": "Açık eskiz için bir rapor açar: kaç serbestlik derecesi kaldığını ve tam kısıtlı olup olmadığını. Eskizi bitirmeden önce nelerin hâlâ hareket ettiğini öğrenmek için kullanın.",
|
||||||
"help.sketch.intersect": "Gövdelerin, yüzlerin, kenarların veya diğer eskizlerin eskiz düzlemini kestiği eğrileri ya da noktaları ekler. Bağlı kalır ve model değiştiğinde onu izler.",
|
"help.sketch.intersect": "Gövdelerin, yüzlerin, kenarların veya diğer eskizlerin eskiz düzlemini kestiği eğrileri ya da noktaları ekler. Bağlı kalır ve model değiştiğinde onu izler.",
|
||||||
"help.sketch.intersection_curve": "İki gövdenin veya yüzün buluştuğu yerde, onlara bağlı 3B eğriler oluşturur. Bunları modeli izleyen yollar veya kılavuz raylar olarak kullanın.",
|
"help.sketch.intersection_curve": "İki gövdenin, yüzün veya diğer eskizlerin eğrilerinin buluştuğu yerde, onlara bağlı 3B eğriler oluşturur. Bunları modeli izleyen yollar veya kılavuz raylar olarak kullanın.",
|
||||||
"help.sketch.isoparametric_curve": "Bir yüzün U veya V yönü boyunca, seçtiğiniz bir noktadan geçen ve yüze bağlı bir 3B eğri oluşturur. Yüzeyi izleyen bir yol veya bölme çizgisi olarak kullanın.",
|
"help.sketch.isoparametric_curve": "Bir yüzün U veya V yönü boyunca, seçtiğiniz bir noktadan geçen ve yüze bağlı bir 3B eğri oluşturur. Yüzeyi izleyen bir yol veya bölme çizgisi olarak kullanın.",
|
||||||
"help.sketch.line": "Bağlantılı çizgileri nokta nokta, açık bir zincir veya kapalı bir dış hat olarak çizer. Çoğu profil için temel araçtır.",
|
"help.sketch.line": "Bağlantılı çizgileri nokta nokta, açık bir zincir veya kapalı bir dış hat olarak çizer. Çoğu profil için temel araçtır.",
|
||||||
"help.sketch.line.midpoint": "Bir çizgiyi orta noktasından bir uca doğru çizer; diğer uç yansıtılır. Bir nokta etrafında ortalanmış çizgiler, örneğin simetrik bir kenar için kullanın.",
|
"help.sketch.line.midpoint": "Bir çizgiyi orta noktasından bir uca doğru çizer; diğer uç yansıtılır. Bir nokta etrafında ortalanmış çizgiler, örneğin simetrik bir kenar için kullanın.",
|
||||||
@@ -3807,6 +3807,8 @@
|
|||||||
"sample.plate.name": "Örnek plaka",
|
"sample.plate.name": "Örnek plaka",
|
||||||
"sample.vents.about": "Dikdörtgen desende yuvalar: satırlar, sütunlar ve aralık parametredir",
|
"sample.vents.about": "Dikdörtgen desende yuvalar: satırlar, sütunlar ve aralık parametredir",
|
||||||
"sample.vents.name": "Havalandırma plakası",
|
"sample.vents.name": "Havalandırma plakası",
|
||||||
|
"scale.per_axis": "Eksen başına",
|
||||||
|
"scale.uniform": "Tekdüze",
|
||||||
"search.command_keys": "Enter çalıştır Shift+Enter çalıştır ve açık tut Esc kapat",
|
"search.command_keys": "Enter çalıştır Shift+Enter çalıştır ve açık tut Esc kapat",
|
||||||
"search.commands": "Komutlar",
|
"search.commands": "Komutlar",
|
||||||
"search.design": "Bu tasarımda",
|
"search.design": "Bu tasarımda",
|
||||||
|
|||||||
+530
-5
File diff suppressed because it is too large
Load Diff
+542
-14
File diff suppressed because it is too large
Load Diff
+2924
-2915
File diff suppressed because it is too large
Load Diff
+542
-14
File diff suppressed because it is too large
Load Diff
+192
-31
@@ -16,6 +16,9 @@ struct U {
|
|||||||
ana_c: array<vec4<f32>, 8>,
|
ana_c: array<vec4<f32>, 8>,
|
||||||
ana_f: vec4<f32>, // x = 1: stripe the negative draft and blocked faces
|
ana_f: vec4<f32>, // x = 1: stripe the negative draft and blocked faces
|
||||||
depth: vec4<f32>, // x = 1: reversed depth (1 near, 0 far); y, z: the faces' push (`FACE_BIAS`)
|
depth: vec4<f32>, // x = 1: reversed depth (1 near, 0 far); y, z: the faces' push (`FACE_BIAS`)
|
||||||
|
// Where the drawn geometry is moved in the world: identity, or a dragged component's move
|
||||||
|
// from where its scene was built (`MovingLayer`)
|
||||||
|
model: mat4x4<f32>,
|
||||||
tint: vec4<f32>, // xyz added to a whole hovered body's colour before lighting
|
tint: vec4<f32>, // xyz added to a whole hovered body's colour before lighting
|
||||||
sel: vec4<f32>, // xyz a whole selected body's colour before lighting
|
sel: vec4<f32>, // xyz a whole selected body's colour before lighting
|
||||||
// the section hatch colours by part (`solidcraft_render::hatch`), the first the cap's own
|
// the section hatch colours by part (`solidcraft_render::hatch`), the first the cap's own
|
||||||
@@ -25,6 +28,16 @@ struct U {
|
|||||||
sky: vec4<f32>, // xyz = world up; w = 1: shade in linear light with a sky fill (#7974)
|
sky: vec4<f32>, // xyz = world up; w = 1: shade in linear light with a sky fill (#7974)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A scene's point in the world (see `U.model`).
|
||||||
|
fn placed(p: vec3<f32>) -> vec3<f32> {
|
||||||
|
return (u.model * vec4<f32>(p, 1.0)).xyz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A scene's direction in the world (see `placed`).
|
||||||
|
fn turned(n: vec3<f32>) -> vec3<f32> {
|
||||||
|
return (u.model * vec4<f32>(n, 0.0)).xyz;
|
||||||
|
}
|
||||||
|
|
||||||
fn reversed() -> bool {
|
fn reversed() -> bool {
|
||||||
return u.depth.x > 0.5;
|
return u.depth.x > 0.5;
|
||||||
}
|
}
|
||||||
@@ -83,10 +96,11 @@ struct TOut {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_tri(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: u32) -> TOut {
|
fn vs_tri(@location(0) local: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: u32) -> TOut {
|
||||||
var o: TOut;
|
var o: TOut;
|
||||||
|
let p = placed(local);
|
||||||
o.pos = u.vp * vec4<f32>(p, 1.0);
|
o.pos = u.vp * vec4<f32>(p, 1.0);
|
||||||
o.n = n;
|
o.n = turned(n);
|
||||||
let e = palette_entry(c);
|
let e = palette_entry(c);
|
||||||
o.c = unpack_color(e.x);
|
o.c = unpack_color(e.x);
|
||||||
o.h = e.y;
|
o.h = e.y;
|
||||||
@@ -95,7 +109,8 @@ fn vs_tri(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shared triangles (in their part's own frame) drawn at one placement: the rows of its rigid
|
/// Shared triangles (in their part's own frame) drawn at one placement: the rows of its rigid
|
||||||
/// move (`r0`..`r2`, a point's coordinates are their dot products with (p, 1)) and its colour.
|
/// move (`r0`..`r2`, a point's coordinates are their dot products with (p, 1)) and its colour,
|
||||||
|
/// then moved like the rest of its scene (`placed`: a dragged component's layer).
|
||||||
/// The vertex colour (location 2) is not read.
|
/// The vertex colour (location 2) is not read.
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_tri_inst(
|
fn vs_tri_inst(
|
||||||
@@ -107,10 +122,11 @@ fn vs_tri_inst(
|
|||||||
@location(6) c: u32,
|
@location(6) c: u32,
|
||||||
) -> TOut {
|
) -> TOut {
|
||||||
let q = vec4<f32>(p, 1.0);
|
let q = vec4<f32>(p, 1.0);
|
||||||
let wp = vec3<f32>(dot(r0, q), dot(r1, q), dot(r2, q));
|
let wp = placed(vec3<f32>(dot(r0, q), dot(r1, q), dot(r2, q)));
|
||||||
|
let pn = vec3<f32>(dot(r0.xyz, n), dot(r1.xyz, n), dot(r2.xyz, n));
|
||||||
var o: TOut;
|
var o: TOut;
|
||||||
o.pos = u.vp * vec4<f32>(wp, 1.0);
|
o.pos = u.vp * vec4<f32>(wp, 1.0);
|
||||||
o.n = vec3<f32>(dot(r0.xyz, n), dot(r1.xyz, n), dot(r2.xyz, n));
|
o.n = turned(pn);
|
||||||
let e = palette_entry(c);
|
let e = palette_entry(c);
|
||||||
o.c = unpack_color(e.x);
|
o.c = unpack_color(e.x);
|
||||||
o.h = e.y;
|
o.h = e.y;
|
||||||
@@ -140,7 +156,7 @@ fn vs_tri_inst_flat(
|
|||||||
@location(6) c: u32,
|
@location(6) c: u32,
|
||||||
) -> FOut {
|
) -> FOut {
|
||||||
let q = vec4<f32>(p, 1.0);
|
let q = vec4<f32>(p, 1.0);
|
||||||
let wp = vec3<f32>(dot(r0, q), dot(r1, q), dot(r2, q));
|
let wp = placed(vec3<f32>(dot(r0, q), dot(r1, q), dot(r2, q)));
|
||||||
var o: FOut;
|
var o: FOut;
|
||||||
o.pos = u.vp * vec4<f32>(wp, 1.0);
|
o.pos = u.vp * vec4<f32>(wp, 1.0);
|
||||||
o.rp = wp - u.eye.xyz;
|
o.rp = wp - u.eye.xyz;
|
||||||
@@ -785,7 +801,9 @@ fn lifted_z(p: vec3<f32>, c: vec4<f32>, px: f32, half: vec2<f32>) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3<f32>, @location(1) b: vec3<f32>, @location(2) c: u32, @location(3) wbits: u32) -> LOut {
|
fn vs_line(@builtin(vertex_index) vi: u32, @location(0) local_a: vec3<f32>, @location(1) local_b: vec3<f32>, @location(2) c: u32, @location(3) wbits: u32) -> LOut {
|
||||||
|
let a = placed(local_a);
|
||||||
|
let b = placed(local_b);
|
||||||
var pa = a;
|
var pa = a;
|
||||||
var pb = b;
|
var pb = b;
|
||||||
var ca = u.vp * vec4<f32>(a, 1.0);
|
var ca = u.vp * vec4<f32>(a, 1.0);
|
||||||
@@ -832,7 +850,7 @@ fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3<f32>, @location(
|
|||||||
var z = base.z;
|
var z = base.z;
|
||||||
if (lifted && base.w > 1e-6) {
|
if (lifted && base.w > 1e-6) {
|
||||||
let p = select(pa, pb, t > 0.5);
|
let p = select(pa, pb, t > 0.5);
|
||||||
let g = grazing(p, octahedral(wbits & 4095u), octahedral((wbits >> 12u) & 4095u));
|
let g = grazing(p, turned(octahedral(wbits & 4095u)), turned(octahedral((wbits >> 12u) & 4095u)));
|
||||||
z = lifted_z(p, base, wd * 0.5 * g + 1.0, half);
|
z = lifted_z(p, base, wd * 0.5 * g + 1.0, half);
|
||||||
}
|
}
|
||||||
var o: LOut;
|
var o: LOut;
|
||||||
@@ -883,10 +901,10 @@ fn fs_line_all(i: LOut) -> @location(0) vec4<f32> {
|
|||||||
return out_color(i.c);
|
return out_color(i.c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point clouds (`view/clouds.rs`): each point is a round dot a few pixels across, facing the
|
// Point clouds and their cells (`view/clouds.rs`): dots, segments as beads (or lines when they
|
||||||
// screen, depth-tested and written like the model's faces, so the model hides the points behind
|
// have no size) and triangles, from one record per element. The cloud's style colours them by
|
||||||
// it and they hide it. The cloud's style colours them by their own colour, a ramp or a palette,
|
// their own colour, a ramp or a palette, and shows only those whose filtered values lie in its
|
||||||
// and shows only those whose filtered values lie in its ranges or sets.
|
// ranges or sets; values vary across an element, so a range cuts it where its value crosses an end.
|
||||||
|
|
||||||
struct Cloud {
|
struct Cloud {
|
||||||
style: vec4<f32>, // x: colour (0 own, 1 ramp, 2 palette), y: dot size (px), z, w: ramp range
|
style: vec4<f32>, // x: colour (0 own, 1 ramp, 2 palette), y: dot size (px), z, w: ramp range
|
||||||
@@ -895,43 +913,88 @@ struct Cloud {
|
|||||||
@group(1) @binding(3) var<uniform> cloud: Cloud;
|
@group(1) @binding(3) var<uniform> cloud: Cloud;
|
||||||
|
|
||||||
struct CIn {
|
struct CIn {
|
||||||
@location(0) p: vec3<f32>,
|
@location(0) a: vec3<f32>,
|
||||||
@location(1) v: f32,
|
@location(1) b: vec3<f32>,
|
||||||
@location(2) f: vec4<f32>,
|
@location(2) c: vec3<f32>,
|
||||||
@location(3) rgb: vec4<f32>,
|
@location(8) before: vec3<f32>,
|
||||||
|
@location(9) after: vec3<f32>,
|
||||||
|
@location(3) v: vec3<f32>,
|
||||||
|
@location(4) f0: vec3<f32>,
|
||||||
|
@location(5) f1: vec3<f32>,
|
||||||
|
@location(10) f2: vec3<f32>,
|
||||||
|
@location(11) f3: vec3<f32>,
|
||||||
|
@location(6) size: vec2<f32>,
|
||||||
|
@location(7) rgb: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct COut {
|
struct COut {
|
||||||
@builtin(position) pos: vec4<f32>,
|
@builtin(position) pos: vec4<f32>,
|
||||||
@location(0) wp: vec3<f32>,
|
@location(0) wp: vec3<f32>,
|
||||||
@location(1) v: f32,
|
@location(1) n: vec3<f32>, // zero: not lit
|
||||||
@location(2) f: vec4<f32>,
|
@location(2) v: f32,
|
||||||
@location(3) rgb: vec3<f32>,
|
@location(3) f: vec4<f32>,
|
||||||
@location(4) at: vec2<f32>, // the place on its disc
|
@location(4) rgb: vec3<f32>,
|
||||||
@location(5) @interpolate(flat) has: u32,
|
@location(5) at: vec2<f32>, // a dot's place on its disc
|
||||||
|
@location(6) @interpolate(flat) has: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn cloud_out(i: CIn, w: vec3<f32>, wp: vec3<f32>, pos: vec4<f32>, n: vec3<f32>) -> COut {
|
||||||
|
var o: COut;
|
||||||
|
o.pos = pos;
|
||||||
|
o.wp = wp;
|
||||||
|
o.n = n;
|
||||||
|
o.v = dot(w, i.v);
|
||||||
|
o.f = vec4<f32>(dot(w, i.f0), dot(w, i.f1), dot(w, i.f2), dot(w, i.f3));
|
||||||
|
o.rgb = i.rgb.rgb;
|
||||||
|
o.at = vec2<f32>(0.0);
|
||||||
|
o.has = u32(round(i.rgb.a * 255.0));
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn behind(c: vec4<f32>) -> bool {
|
||||||
|
return near_z(c) < 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_cloud_dot(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
|
fn vs_cloud_dot(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
|
||||||
var xs = array<f32, 6>(-1.0, 1.0, 1.0, -1.0, 1.0, -1.0);
|
var xs = array<f32, 6>(-1.0, 1.0, 1.0, -1.0, 1.0, -1.0);
|
||||||
var ys = array<f32, 6>(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0);
|
var ys = array<f32, 6>(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0);
|
||||||
let at = vec2<f32>(xs[vi], ys[vi]);
|
let at = vec2<f32>(xs[vi], ys[vi]);
|
||||||
let size = max(cloud.style.y, 1.0);
|
let size = max(cloud.style.y, 1.0);
|
||||||
let centre = u.vp * vec4<f32>(i.p, 1.0);
|
let centre = u.vp * vec4<f32>(i.a, 1.0);
|
||||||
var o: COut;
|
var pos = vec4<f32>(centre.xy + at * size / u.screen.xy * centre.w, centre.z, centre.w);
|
||||||
o.pos = vec4<f32>(centre.xy + at * size / u.screen.xy * centre.w, centre.z, centre.w);
|
if (behind(centre)) { pos = vec4<f32>(0.0, 0.0, 0.0, -1.0); }
|
||||||
// Behind the eye: nothing.
|
var o = cloud_out(i, vec3<f32>(1.0, 0.0, 0.0), i.a, pos, vec3<f32>(0.0));
|
||||||
if (near_z(centre) < 0.0) { o.pos = vec4<f32>(0.0, 0.0, 0.0, -1.0); }
|
|
||||||
o.wp = i.p;
|
|
||||||
o.v = i.v;
|
|
||||||
o.f = i.f;
|
|
||||||
o.rgb = i.rgb.rgb;
|
|
||||||
o.has = u32(round(i.rgb.a * 255.0));
|
|
||||||
// Dots under two pixels are squares: a disc that small loses its centre pixel.
|
// Dots under two pixels are squares: a disc that small loses its centre pixel.
|
||||||
o.at = at * select(1.0, 0.0, size < 2.0);
|
o.at = at * select(1.0, 0.0, size < 2.0);
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The least half-width of a segment on screen, in pixels.
|
||||||
|
const MIN_HALF_PX: f32 = 0.75;
|
||||||
|
|
||||||
|
/// The frame of a bead along `d`: along it, across it level, and up, its height axis.
|
||||||
|
fn bead_frame(d: vec3<f32>) -> mat3x3<f32> {
|
||||||
|
var t = vec3<f32>(1.0, 0.0, 0.0);
|
||||||
|
if (length(d) > 1e-9) { t = d / length(d); }
|
||||||
|
var side = cross(vec3<f32>(0.0, 0.0, 1.0), t);
|
||||||
|
if (length(side) < 1e-6) { side = vec3<f32>(1.0, 0.0, 0.0); }
|
||||||
|
side = normalize(side);
|
||||||
|
return mat3x3<f32>(t, side, cross(t, side));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Corner `j` (0…9) of a bead's cross-section, a stadium: its place across and up, and its
|
||||||
|
/// normal. Corners 0…4 go up the right half-disc, 5…9 down the left one, so 4–5 is the flat top
|
||||||
|
/// and 9–0 the flat bottom.
|
||||||
|
fn stadium(j: u32, size: vec2<f32>) -> vec4<f32> {
|
||||||
|
let k = j % 10u;
|
||||||
|
let right = k < 5u;
|
||||||
|
let a = -1.5707963 + 0.7853982 * f32(k % 5u) + select(3.1415927, 0.0, right);
|
||||||
|
let r = 0.5 * size.y;
|
||||||
|
let flat_half = 0.5 * max(size.x - size.y, 0.0);
|
||||||
|
return vec4<f32>(select(-flat_half, flat_half, right) + r * cos(a), r * sin(a), cos(a), sin(a));
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether filter `f` keeps the value `v`.
|
/// Whether filter `f` keeps the value `v`.
|
||||||
fn cloud_keeps(f: vec4<f32>, v: f32) -> bool {
|
fn cloud_keeps(f: vec4<f32>, v: f32) -> bool {
|
||||||
if (f.x < 0.5) { return true; }
|
if (f.x < 0.5) { return true; }
|
||||||
@@ -940,6 +1003,101 @@ fn cloud_keeps(f: vec4<f32>, v: f32) -> bool {
|
|||||||
return k >= 0 && k < 32 && ((bitcast<u32>(f.w) >> u32(k)) & 1u) != 0u;
|
return k >= 0 && k < 32 && ((bitcast<u32>(f.w) >> u32(k)) & 1u) != 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether filter `f` leaves out a whole element with values `v` at its corners: all on one
|
||||||
|
/// side of a range, or one value at every corner, not kept. A set of values with corners that
|
||||||
|
/// differ may keep part of the element: its fragments decide.
|
||||||
|
fn cloud_drops(f: vec4<f32>, v: vec3<f32>) -> bool {
|
||||||
|
if (f.x > 1.5) { return all(v == vec3<f32>(v.x)) && !cloud_keeps(f, v.x); }
|
||||||
|
return f.x > 0.5 && (all(v < vec3<f32>(f.y)) || all(v > vec3<f32>(f.z)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a filter leaves out the whole element.
|
||||||
|
fn filtered_out(i: CIn) -> bool {
|
||||||
|
let has = u32(round(i.rgb.a * 255.0));
|
||||||
|
let values = array<vec3<f32>, 4>(i.f0, i.f1, i.f2, i.f3);
|
||||||
|
for (var k = 0u; k < 4u; k++) {
|
||||||
|
if (((has >> k) & 1u) != 0u && cloud_drops(cloud.filters[k], values[k])) { return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The part in front of the near plane of the segment between clip-space `a` and `b`, as
|
||||||
|
/// fractions of it from `a`; empty (first above second) when all of it is behind. Clip-space
|
||||||
|
/// coordinates are affine along a line, so the crossing is exact.
|
||||||
|
fn in_front(a: vec4<f32>, b: vec4<f32>) -> vec2<f32> {
|
||||||
|
let za = near_z(a);
|
||||||
|
let zb = near_z(b);
|
||||||
|
if (za < 0.0 && zb < 0.0) { return vec2<f32>(1.0, 0.0); }
|
||||||
|
let t = za / (za - zb);
|
||||||
|
return vec2<f32>(select(0.0, t, za < 0.0), select(1.0, t, zb < 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_cloud_segment(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
|
||||||
|
// Corner `vi % 10` of the cross-section at the start (`vi < 10`) or the end; the index
|
||||||
|
// buffer joins them into the bead's sides.
|
||||||
|
let pa = u.vp * vec4<f32>(i.a, 1.0);
|
||||||
|
let pb = u.vp * vec4<f32>(i.b, 1.0);
|
||||||
|
let part = in_front(pa, pb);
|
||||||
|
if (filtered_out(i) || part.x > part.y) {
|
||||||
|
return cloud_out(i, vec3<f32>(1.0, 0.0, 0.0), i.a, vec4<f32>(0.0, 0.0, 0.0, -1.0), vec3<f32>(0.0));
|
||||||
|
}
|
||||||
|
// Where the near plane cuts it, the bead ends there, square; its numbers are exact at `e`.
|
||||||
|
let at_end = vi >= 10u;
|
||||||
|
let e = select(part.x, part.y, at_end);
|
||||||
|
let cut = e != select(0.0, 1.0, at_end);
|
||||||
|
let f = bead_frame(i.b - i.a);
|
||||||
|
let c = stadium(vi % 10u, i.size);
|
||||||
|
let axis = mix(i.a, i.b, e);
|
||||||
|
// Where it meets the next or the previous bead, the cross-section lies in the plane halfway
|
||||||
|
// between their directions, stretched across to keep its width: both beads end on the same
|
||||||
|
// section. A turn sharper than 120° ends square instead.
|
||||||
|
let other = select(i.a - i.before, i.after - i.b, at_end);
|
||||||
|
var across_dir = f[1];
|
||||||
|
var stretch = 1.0;
|
||||||
|
if (!cut && length(other) > 1e-9) {
|
||||||
|
let g = bead_frame(other);
|
||||||
|
let mid = f[1] + g[1];
|
||||||
|
if (length(mid) > 1e-6 && dot(normalize(mid), f[1]) > 0.5) {
|
||||||
|
across_dir = normalize(mid);
|
||||||
|
stretch = 1.0 / dot(across_dir, f[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let wp = axis + across_dir * (c.x * stretch) + f[2] * c.y;
|
||||||
|
let ca = u.vp * vec4<f32>(axis, 1.0);
|
||||||
|
let cw = u.vp * vec4<f32>(wp, 1.0);
|
||||||
|
let half = u.screen.xy * 0.5;
|
||||||
|
var pos = cw;
|
||||||
|
// At least a pixel and a half across: a bead seen from afar, or a line without size. A corner
|
||||||
|
// behind the eye has no place on the screen and needs no widening: the rasteriser clips it.
|
||||||
|
if (cw.w > 0.0) {
|
||||||
|
let c0 = mix(pa, pb, part.x);
|
||||||
|
let c1 = mix(pa, pb, part.y);
|
||||||
|
let along = (c1.xy / c1.w - c0.xy / c0.w) * half;
|
||||||
|
var across = vec2<f32>(-along.y, along.x);
|
||||||
|
if (length(across) < 1e-6) { across = vec2<f32>(1.0, 0.0); }
|
||||||
|
across = normalize(across);
|
||||||
|
var px = (cw.xy / cw.w - ca.xy / ca.w) * half;
|
||||||
|
if (length(px) < MIN_HALF_PX * abs(c.z)) {
|
||||||
|
px = across * c.z * MIN_HALF_PX;
|
||||||
|
pos = vec4<f32>((ca.xy / ca.w + px / half) * cw.w, cw.z, cw.w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let n = select(f[1] * c.z + f[2] * c.w, vec3<f32>(0.0), i.size.y <= 0.0);
|
||||||
|
return cloud_out(i, vec3<f32>(1.0 - e, e, 0.0), wp, pos, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_cloud_triangle(@builtin(vertex_index) vi: u32, i: CIn) -> COut {
|
||||||
|
// Not `w[vi] = 1.0`: the Windows D3D12 shader compiler cannot write through a dynamic vector index.
|
||||||
|
let w = vec3<f32>(f32(vi == 0u), f32(vi == 1u), f32(vi == 2u));
|
||||||
|
let wp = i.a * w.x + i.b * w.y + i.c * w.z;
|
||||||
|
var n = cross(i.b - i.a, i.c - i.a);
|
||||||
|
if (length(n) > 0.0) { n = normalize(n); }
|
||||||
|
// The true clip-space corner, even behind the eye: the rasteriser clips a triangle exactly.
|
||||||
|
return cloud_out(i, w, wp, u.vp * vec4<f32>(wp, 1.0), n);
|
||||||
|
}
|
||||||
|
|
||||||
fn cloud_ramp(t: f32) -> vec3<f32> {
|
fn cloud_ramp(t: f32) -> vec3<f32> {
|
||||||
var stops = array<vec3<f32>, 5>(
|
var stops = array<vec3<f32>, 5>(
|
||||||
vec3<f32>(40.0, 70.0, 200.0), vec3<f32>(30.0, 170.0, 230.0), vec3<f32>(60.0, 200.0, 90.0),
|
vec3<f32>(40.0, 70.0, 200.0), vec3<f32>(30.0, 170.0, 230.0), vec3<f32>(60.0, 200.0, 90.0),
|
||||||
@@ -974,5 +1132,8 @@ fn fs_cloud(o: COut) -> @location(0) vec4<f32> {
|
|||||||
} else if ((o.has & 16u) != 0u && mode > 0.5) {
|
} else if ((o.has & 16u) != 0u && mode > 0.5) {
|
||||||
c = cloud_ramp((o.v - cloud.style.z) / max(cloud.style.w - cloud.style.z, 1e-12));
|
c = cloud_ramp((o.v - cloud.style.z) / max(cloud.style.w - cloud.style.z, 1e-12));
|
||||||
}
|
}
|
||||||
|
if (length(o.n) > 0.5) {
|
||||||
|
c = c * (0.45 + 0.55 * abs(dot(normalize(o.n), normalize(u.back.xyz))));
|
||||||
|
}
|
||||||
return out_color(vec4<f32>(c, 1.0));
|
return out_color(vec4<f32>(c, 1.0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"order": 155,
|
||||||
|
"provenance": "Original SolidCraft mapping of Solarized (MIT palette)",
|
||||||
|
"base": "dark",
|
||||||
|
"borders": {},
|
||||||
|
"colors": {
|
||||||
|
"accent": "#268bd2",
|
||||||
|
"accent_soft": "#073642",
|
||||||
|
"agent_busy": "#268bd2",
|
||||||
|
"agent_ok": "#859900",
|
||||||
|
"app_bar": "#002b36",
|
||||||
|
"app_bar_hover": "#073642",
|
||||||
|
"app_bar_hover_button": "#073642",
|
||||||
|
"app_bar_hover_strong": "#586e75",
|
||||||
|
"app_bar_hover_subtle": "#073642",
|
||||||
|
"app_bar_separator": "#586e75",
|
||||||
|
"app_bar_tab_active": "#073642",
|
||||||
|
"app_bar_text": "#93a1a1",
|
||||||
|
"axis_x": "#dc322f",
|
||||||
|
"axis_y": "#859900",
|
||||||
|
"axis_z": "#346e88",
|
||||||
|
"bend_down": "#b58900",
|
||||||
|
"bend_up": "#859900",
|
||||||
|
"body": "#586e75",
|
||||||
|
"body_edge": "#346e88",
|
||||||
|
"body_selected": "#073642",
|
||||||
|
"border": "#586e75",
|
||||||
|
"box_crossing": "#859900",
|
||||||
|
"box_window": "#268bd2",
|
||||||
|
"chip": "#268bd2",
|
||||||
|
"construction_geom": "#cb4b16",
|
||||||
|
"construction_plane": "#b4870022",
|
||||||
|
"continuity": [
|
||||||
|
"#859900",
|
||||||
|
"#346e88",
|
||||||
|
"#b58900",
|
||||||
|
"#6c71c4"
|
||||||
|
],
|
||||||
|
"control_border": "#586e75",
|
||||||
|
"control_fill": "#073642",
|
||||||
|
"cube_border": "#586e75",
|
||||||
|
"cube_face": "#073642",
|
||||||
|
"cube_hover": "#586e75",
|
||||||
|
"cube_text": "#93a1a1",
|
||||||
|
"dialog_bg": "#073642",
|
||||||
|
"dialog_body": "#073642",
|
||||||
|
"dialog_border": "#586e75",
|
||||||
|
"dialog_footer": "#073642",
|
||||||
|
"dialog_label": "#93a1a1",
|
||||||
|
"dialog_rule": "#586e75",
|
||||||
|
"dimension": "#2aa198",
|
||||||
|
"dimension_driven": "#1f7f78",
|
||||||
|
"error": "#dc322f",
|
||||||
|
"field": "#002b36",
|
||||||
|
"file_background": "#073642",
|
||||||
|
"file_border": "#586e75",
|
||||||
|
"file_dim": "#93a1a1",
|
||||||
|
"file_image_tint": "#002b36",
|
||||||
|
"file_primary": "#586e75",
|
||||||
|
"file_primary_border": "#268bd2",
|
||||||
|
"file_primary_ink": "#ffffff",
|
||||||
|
"file_rail": "#1b6397",
|
||||||
|
"file_rail_hover": "#2670a6",
|
||||||
|
"file_rail_ink": "#ffffff",
|
||||||
|
"file_recovery": "#073642",
|
||||||
|
"file_recovery_border": "#b58900",
|
||||||
|
"file_recovery_ink": "#93a1a1",
|
||||||
|
"file_recovery_well": "#002b36",
|
||||||
|
"file_surface": "#073642",
|
||||||
|
"form_accent": "#6b70c3",
|
||||||
|
"form_dim": "#839496",
|
||||||
|
"form_error": "#dc322f",
|
||||||
|
"form_error_fill": "#586e75",
|
||||||
|
"form_fill": "#a2a9f8",
|
||||||
|
"form_ink": "#93a1a1",
|
||||||
|
"form_primary": "#268bd2",
|
||||||
|
"form_soft": "#073642",
|
||||||
|
"form_success": "#859900",
|
||||||
|
"form_success_fill": "#586e75",
|
||||||
|
"form_tab_ink": "#565a9d",
|
||||||
|
"glyph_bg": "#073642",
|
||||||
|
"glyph_edge": "#da2f2d",
|
||||||
|
"glyph_text": "#da2f2d",
|
||||||
|
"grid": "#073642",
|
||||||
|
"grid_major": "#586e75",
|
||||||
|
"hover": "#586e75",
|
||||||
|
"hover_edge": "#346e88",
|
||||||
|
"hover_edge_halo": "#002b36",
|
||||||
|
"hover_profile": "#268cd228",
|
||||||
|
"hover_profile_edge": "#346e88",
|
||||||
|
"icon": "#93a1a1",
|
||||||
|
"icons.agent": "#6c71c4",
|
||||||
|
"icons.highlight": "#93a1a1",
|
||||||
|
"icons.marker_halo": "#002b36cc",
|
||||||
|
"interference": "#dc322f",
|
||||||
|
"logo": [
|
||||||
|
"#268bd2",
|
||||||
|
"#346e88",
|
||||||
|
"#586e75"
|
||||||
|
],
|
||||||
|
"manipulator": "#268bd2",
|
||||||
|
"mesh_accent": "#97751d",
|
||||||
|
"mesh_fill": "#d7aa3c",
|
||||||
|
"mesh_ink": "#7b5d00",
|
||||||
|
"mesh_soft": "#073642",
|
||||||
|
"on_accent": "#ffffff",
|
||||||
|
"origin_plane": "#b3860028",
|
||||||
|
"origin_plane_edge": "#b6890088",
|
||||||
|
"origin_plane_hover": "#288bd340",
|
||||||
|
"origin_point": "#839496",
|
||||||
|
"overlay": "#073642",
|
||||||
|
"panel": "#073642",
|
||||||
|
"panel_header": "#002b36",
|
||||||
|
"preview_add": "#346e88",
|
||||||
|
"preview_cut": "#dc323066",
|
||||||
|
"preview_new": "#b58900",
|
||||||
|
"preview_sketch": "#859900",
|
||||||
|
"profile_fill": "#35718722",
|
||||||
|
"render_accent": "#cf317e",
|
||||||
|
"render_fill": "#f986b4",
|
||||||
|
"render_ink": "#ad2c6b",
|
||||||
|
"render_soft": "#073642",
|
||||||
|
"rubber_band": "#268bd2",
|
||||||
|
"section_cap": "#b58900",
|
||||||
|
"sel_edge": "#346e88",
|
||||||
|
"sel_edge_rim": "#346e88",
|
||||||
|
"sel_face": "#073642",
|
||||||
|
"sel_profile": "#073642",
|
||||||
|
"sel_profile_edge": "#346e88",
|
||||||
|
"sel_vertex": "#346e88",
|
||||||
|
"selected_bg": "#0a3e50",
|
||||||
|
"selected_ink": "#93a1a1",
|
||||||
|
"sheet_fill": "#58c6bc",
|
||||||
|
"sheet_ink": "#1d6d67",
|
||||||
|
"sheet_soft": "#073642",
|
||||||
|
"sketch": "#346e88",
|
||||||
|
"sketch_accent": "#738400",
|
||||||
|
"sketch_centerline": "#b58900",
|
||||||
|
"sketch_construction": "#b58900",
|
||||||
|
"sketch_fixed": "#93a1a1",
|
||||||
|
"sketch_halo": "#002b36",
|
||||||
|
"sketch_ink": "#5a6800",
|
||||||
|
"sketch_locked": "#859900",
|
||||||
|
"sketch_lost": "#dc322f",
|
||||||
|
"sketch_point": "#93a1a1",
|
||||||
|
"sketch_point_free": "#346e88",
|
||||||
|
"sketch_projected": "#6c71c4",
|
||||||
|
"sketch_soft": "#073642",
|
||||||
|
"surface_accent": "#cb4b16",
|
||||||
|
"surface_fill": "#fb906b",
|
||||||
|
"surface_ink": "#a83e12",
|
||||||
|
"surface_soft": "#073642",
|
||||||
|
"tab_on_fill_dark": "#002b36",
|
||||||
|
"text": "#93a1a1",
|
||||||
|
"text_dim": "#839496",
|
||||||
|
"timeline": "#073642",
|
||||||
|
"timeline_item": "#073642",
|
||||||
|
"timeline_item_error": "#eabcad",
|
||||||
|
"timeline_marker": "#839496",
|
||||||
|
"toolbar": "#073642",
|
||||||
|
"viewport_bottom": "#002b36",
|
||||||
|
"viewport_top": "#002b36",
|
||||||
|
"warning": "#b58900"
|
||||||
|
},
|
||||||
|
"format": "solidcraft-theme/1",
|
||||||
|
"icon_palette": {
|
||||||
|
"constraint": [
|
||||||
|
"#fee0dc",
|
||||||
|
"#fb8d81",
|
||||||
|
"#da2f2d"
|
||||||
|
],
|
||||||
|
"construction": [
|
||||||
|
"#fee1d7",
|
||||||
|
"#fb906b",
|
||||||
|
"#cb4b16"
|
||||||
|
],
|
||||||
|
"form": [
|
||||||
|
"#e3e6fe",
|
||||||
|
"#a2a9f8",
|
||||||
|
"#6b70c3"
|
||||||
|
],
|
||||||
|
"guide": "#839496",
|
||||||
|
"ink": "#fdf6e3",
|
||||||
|
"line": "#93a1a1",
|
||||||
|
"mesh": [
|
||||||
|
"#fde5b2",
|
||||||
|
"#d7aa3c",
|
||||||
|
"#97751d"
|
||||||
|
],
|
||||||
|
"passive-1": "#586e75",
|
||||||
|
"passive-2": "#073642",
|
||||||
|
"passive-3": "#002b36",
|
||||||
|
"sketch": [
|
||||||
|
"#def67b",
|
||||||
|
"#a8be40",
|
||||||
|
"#738400"
|
||||||
|
],
|
||||||
|
"solid": [
|
||||||
|
"#d5ebff",
|
||||||
|
"#67b9fb",
|
||||||
|
"#1480c6"
|
||||||
|
],
|
||||||
|
"surface": [
|
||||||
|
"#fee1d7",
|
||||||
|
"#fb906b",
|
||||||
|
"#cb4b16"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "Solarized Dark",
|
||||||
|
"radii": {
|
||||||
|
"control_radius": 6,
|
||||||
|
"radius_lg": 8,
|
||||||
|
"radius_md": 6,
|
||||||
|
"radius_sm": 4,
|
||||||
|
"radius_xl": 10,
|
||||||
|
"sketch_mode_radius": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
| assets/locales/pending/ribbon-panel-hover.json | SolidCraft contributors | Original ribbon panel menu preference labels and translations | MIT OR Apache-2.0 |
|
|
||||||
@@ -210,13 +210,16 @@ fn map_plane_ref(m: &Mat, p: &mut PlaneRef) {
|
|||||||
map_plane_ref(m, base);
|
map_plane_ref(m, base);
|
||||||
map_geo_ref(m, point);
|
map_geo_ref(m, point);
|
||||||
}
|
}
|
||||||
PlaneRef::AtAngle { base, axis_origin, axis_dir, line, .. } => {
|
PlaneRef::AtAngle { base, axis_origin, axis_dir, line, pivot, .. } => {
|
||||||
map_plane_ref(m, base);
|
map_plane_ref(m, base);
|
||||||
*axis_origin = apply_point(m, *axis_origin);
|
*axis_origin = apply_point(m, *axis_origin);
|
||||||
*axis_dir = apply_vector(m, *axis_dir);
|
*axis_dir = apply_vector(m, *axis_dir);
|
||||||
if let Some(g) = line {
|
if let Some(g) = line {
|
||||||
map_geo_ref(m, g);
|
map_geo_ref(m, g);
|
||||||
}
|
}
|
||||||
|
if let Some(g) = pivot {
|
||||||
|
map_geo_ref(m, g);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Named origin planes and construction planes are the component's own.
|
// Named origin planes and construction planes are the component's own.
|
||||||
PlaneRef::Origin { .. } | PlaneRef::Construction { .. } => {}
|
PlaneRef::Origin { .. } | PlaneRef::Construction { .. } => {}
|
||||||
@@ -353,25 +356,7 @@ impl Document {
|
|||||||
/// Every placement of each component: (component, occurrence path ids, world transform).
|
/// Every placement of each component: (component, occurrence path ids, world transform).
|
||||||
/// A branch stops past [`MAX_PLACEMENT_DEPTH`] levels, the walk past [`MAX_PLACEMENTS`] placements.
|
/// A branch stops past [`MAX_PLACEMENT_DEPTH`] levels, the walk past [`MAX_PLACEMENTS`] placements.
|
||||||
pub fn placements(&self) -> Vec<(u64, Vec<u64>, Mat)> {
|
pub fn placements(&self) -> Vec<(u64, Vec<u64>, Mat)> {
|
||||||
let mut out = vec![(0, Vec::new(), IDENTITY)];
|
crate::occurrence_tree::OccurrenceTree::new(self).placements_with(|o| o.transform)
|
||||||
let mut stack: Vec<(u64, Vec<u64>, Mat)> = vec![(0, Vec::new(), IDENTITY)];
|
|
||||||
while let Some((comp, path, m)) = stack.pop() {
|
|
||||||
if out.len() > MAX_PLACEMENTS {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Only this branch stops past the depth limit; the others are still walked.
|
|
||||||
if path.len() > MAX_PLACEMENT_DEPTH {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for o in self.occurrences.iter().filter(|o| o.parent == comp) {
|
|
||||||
let mut p = path.clone();
|
|
||||||
p.push(o.id);
|
|
||||||
let w = mat_mul(&m, &o.transform);
|
|
||||||
out.push((o.component, p.clone(), w));
|
|
||||||
stack.push((o.component, p, w));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every body as the world shows it: its world name ("Body1", or "Body1 (Occ:2)" for a
|
/// Every body as the world shows it: its world name ("Body1", or "Body1 (Occ:2)" for a
|
||||||
@@ -461,13 +446,14 @@ impl Document {
|
|||||||
if self.component_within(parent, component) {
|
if self.component_within(parent, component) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
let tree = crate::occurrence_tree::OccurrenceTree::new(self);
|
||||||
let mut stack = vec![component];
|
let mut stack = vec![component];
|
||||||
let mut seen = std::collections::BTreeSet::new();
|
let mut seen = std::collections::BTreeSet::new();
|
||||||
while let Some(c) = stack.pop() {
|
while let Some(c) = stack.pop() {
|
||||||
if !seen.insert(c) || seen.len() > MAX_PLACEMENTS {
|
if !seen.insert(c) || seen.len() > MAX_PLACEMENTS {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for o in self.occurrences.iter().filter(|o| o.parent == c) {
|
for o in tree.children(c) {
|
||||||
if o.component == parent {
|
if o.component == parent {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ pub struct Canvas {
|
|||||||
pub flip: bool,
|
pub flip: bool,
|
||||||
#[serde(default = "yes")]
|
#[serde(default = "yes")]
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
|
/// Locked (#12310): its place, size, angle and flip stay as they are, and it is not deleted,
|
||||||
|
/// until it is unlocked. Its opacity, visibility and name can still change.
|
||||||
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
|
pub locked: bool,
|
||||||
/// The component it belongs to (its plane is in that component's frame, so it moves with
|
/// The component it belongs to (its plane is in that component's frame, so it moves with
|
||||||
/// the component's occurrence); 0 = the root.
|
/// the component's occurrence); 0 = the root.
|
||||||
#[serde(default, skip_serializing_if = "is_root")]
|
#[serde(default, skip_serializing_if = "is_root")]
|
||||||
|
|||||||
@@ -119,11 +119,14 @@ fn remap_plane(p: &mut PlaneRef, ids: &BTreeMap<u64, u64>, bodies: &BTreeMap<Str
|
|||||||
remap_plane(base, ids, bodies, depth + 1)?;
|
remap_plane(base, ids, bodies, depth + 1)?;
|
||||||
remap_geo(point, ids, bodies, depth + 1)?;
|
remap_geo(point, ids, bodies, depth + 1)?;
|
||||||
}
|
}
|
||||||
PlaneRef::AtAngle { base, line, .. } => {
|
PlaneRef::AtAngle { base, line, pivot, .. } => {
|
||||||
remap_plane(base, ids, bodies, depth + 1)?;
|
remap_plane(base, ids, bodies, depth + 1)?;
|
||||||
if let Some(g) = line {
|
if let Some(g) = line {
|
||||||
remap_geo(g, ids, bodies, depth + 1)?;
|
remap_geo(g, ids, bodies, depth + 1)?;
|
||||||
}
|
}
|
||||||
|
if let Some(g) = pivot {
|
||||||
|
remap_geo(g, ids, bodies, depth + 1)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PlaneRef::Face { name, anchor, .. } => {
|
PlaneRef::Face { name, anchor, .. } => {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
@@ -323,11 +326,14 @@ fn checked_plane(p: &PlaneRef, depth: usize) -> crate::Result<()> {
|
|||||||
}
|
}
|
||||||
match p {
|
match p {
|
||||||
PlaneRef::Offset { base, .. } | PlaneRef::Flipped { base, .. } => checked_plane(base, depth + 1)?,
|
PlaneRef::Offset { base, .. } | PlaneRef::Flipped { base, .. } => checked_plane(base, depth + 1)?,
|
||||||
PlaneRef::AtAngle { base, line, .. } => {
|
PlaneRef::AtAngle { base, line, pivot, .. } => {
|
||||||
checked_plane(base, depth + 1)?;
|
checked_plane(base, depth + 1)?;
|
||||||
if let Some(g) = line {
|
if let Some(g) = line {
|
||||||
checked_geo(g, depth + 1)?;
|
checked_geo(g, depth + 1)?;
|
||||||
}
|
}
|
||||||
|
if let Some(g) = pivot {
|
||||||
|
checked_geo(g, depth + 1)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } | PlaneRef::AxisPoint { axis: a, point: b } => {
|
PlaneRef::Midplane { a, b } | PlaneRef::TwoEdges { a, b } | PlaneRef::AxisPoint { axis: a, point: b } => {
|
||||||
checked_geo(a, depth + 1)?;
|
checked_geo(a, depth + 1)?;
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ fn shell_planar_format13_keeps_v12_documents_and_refuses_future() {
|
|||||||
}
|
}
|
||||||
assert_eq!(legacy, current, "the migration preserves every prior field");
|
assert_eq!(legacy, current, "the migration preserves every prior field");
|
||||||
legacy["format"] = json!(crate::format::format_string(crate::format::FORMAT + 1));
|
legacy["format"] = json!(crate::format::format_string(crate::format::FORMAT + 1));
|
||||||
assert!(crate::format::read(&legacy.to_string()).is_err());
|
assert!(crate::format::read(&legacy.to_string()).unwrap().newer_file.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -196,6 +196,45 @@ fn edge_pick<'a>(st: &'a ModelState, at: Vec3, name: &Option<String>) -> Result<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The closest point on a polyline to `target`, projecting onto each segment rather than
|
||||||
|
/// choosing only its vertices. This keeps an edge pick on the edge between tessellation points.
|
||||||
|
fn closest_point_on_polyline(pts: &[Vec3], target: Vec3) -> Option<Vec3> {
|
||||||
|
let mut best: Option<(f64, Vec3)> = None;
|
||||||
|
for w in pts.windows(2) {
|
||||||
|
let (a, b) = (w[0], w[1]);
|
||||||
|
let ab = b - a;
|
||||||
|
let len_sq = ab.dot(ab);
|
||||||
|
let q = if len_sq < 1e-18 {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
let t = ((target - a).dot(ab) / len_sq).clamp(0.0, 1.0);
|
||||||
|
a + ab * t
|
||||||
|
};
|
||||||
|
let d = target.dist(q);
|
||||||
|
if best.as_ref().is_none_or(|(bd, _)| d < *bd) {
|
||||||
|
best = Some((d, q));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best.map(|(_, q)| q)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The point on an edge nearest a pick location, projecting onto exact edge geometry
|
||||||
|
/// (such as `kernel::edge_circle`) when available, else onto its drawn polyline segments.
|
||||||
|
fn edge_point_at_pick(st: &ModelState, pick: Vec3, name: &Option<String>) -> Option<Vec3> {
|
||||||
|
let (b, i, pts) = edge_pick(st, pick, name).ok()?;
|
||||||
|
if let Some(c) = kernel::edge_circle(&b.body, i) {
|
||||||
|
let v = pick - c.center;
|
||||||
|
let v_perp = v - *c.axis * v.dot(*c.axis);
|
||||||
|
if let Some(dir) = v_perp.normalized() {
|
||||||
|
return Some(c.center + dir * c.radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Until the kernel can project onto any edge geometry (circles are exact above via
|
||||||
|
// kernel::edge_circle), the fallback projects onto the drawn polyline. This is exact for
|
||||||
|
// straight edges and approximates splines within mesh display tolerance.
|
||||||
|
closest_point_on_polyline(&pts, pick)
|
||||||
|
}
|
||||||
|
|
||||||
/// The circle a picked edge lies on, from its exact curve (`kernel::edge_circle`) when the drawn
|
/// The circle a picked edge lies on, from its exact curve (`kernel::edge_circle`) when the drawn
|
||||||
/// edge lies on it, else fitted to the drawn points: (centre, unit normal).
|
/// edge lies on it, else fitted to the drawn points: (centre, unit normal).
|
||||||
fn edge_circle(b: &ModelBody, index: usize, pts: &[Vec3]) -> Option<(Vec3, Vec3)> {
|
fn edge_circle(b: &ModelBody, index: usize, pts: &[Vec3]) -> Option<(Vec3, Vec3)> {
|
||||||
@@ -743,7 +782,29 @@ pub(crate) fn resolve_geo_plane(ctx: &Ctx, p: &PlaneRef) -> Result<Plane> {
|
|||||||
PlaneRef::AxisPoint { axis, point: at } => {
|
PlaneRef::AxisPoint { axis, point: at } => {
|
||||||
let (origin, dir) = line(ctx, axis)?;
|
let (origin, dir) = line(ctx, axis)?;
|
||||||
let dir = dir.normalized().ok_or_else(|| bad("the axis has no direction"))?;
|
let dir = dir.normalized().ok_or_else(|| bad("the axis has no direction"))?;
|
||||||
let delta = point(ctx, at)? - origin;
|
let p = match point(ctx, at) {
|
||||||
|
Ok(p) => {
|
||||||
|
let delta = p - origin;
|
||||||
|
let across = delta - dir * delta.dot(dir);
|
||||||
|
if across.len().is_finite() && across.len() >= 1e-9 {
|
||||||
|
p
|
||||||
|
} else if let GeoRef::Edge { at: pick, name } = at.as_ref() {
|
||||||
|
// When the centre of a circular edge lies on the axis, resolve to the picked point on the edge.
|
||||||
|
edge_point_at_pick(ctx.st, *pick, name).unwrap_or(p)
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Non-circular edges are not converted to centres by `point`; resolve to the picked edge point.
|
||||||
|
if let GeoRef::Edge { at: pick, name } = at.as_ref() {
|
||||||
|
edge_point_at_pick(ctx.st, *pick, name).ok_or(e)?
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let delta = p - origin;
|
||||||
let across = delta - dir * delta.dot(dir);
|
let across = delta - dir * delta.dot(dir);
|
||||||
if !across.len().is_finite() || across.len() < 1e-9 {
|
if !across.len().is_finite() || across.len() < 1e-9 {
|
||||||
return Err(bad("the point is on the axis: pick a point off the axis to define a unique plane"));
|
return Err(bad("the point is on the axis: pick a point off the axis to define a unique plane"));
|
||||||
@@ -923,9 +984,10 @@ impl PlaneRef {
|
|||||||
refs.push(point.as_mut());
|
refs.push(point.as_mut());
|
||||||
refs
|
refs
|
||||||
}
|
}
|
||||||
PlaneRef::AtAngle { base, line, .. } => {
|
PlaneRef::AtAngle { base, line, pivot, .. } => {
|
||||||
let mut v = base.refs_mut();
|
let mut v = base.refs_mut();
|
||||||
v.extend(line.as_deref_mut());
|
v.extend(line.as_deref_mut());
|
||||||
|
v.extend(pivot.as_deref_mut());
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
PlaneRef::Offset { base, .. } | PlaneRef::Flipped { base, .. } => base.refs_mut(),
|
PlaneRef::Offset { base, .. } | PlaneRef::Flipped { base, .. } => base.refs_mut(),
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ pub enum PlaneRef {
|
|||||||
/// Another plane rotated about a line (in world coordinates) by an angle expression. With
|
/// Another plane rotated about a line (in world coordinates) by an angle expression. With
|
||||||
/// `line`, an edge or round face of the model ([`crate::construct::model_axis`]), the line is
|
/// `line`, an edge or round face of the model ([`crate::construct::model_axis`]), the line is
|
||||||
/// found there on each evaluation and `axis_origin`/`axis_dir` are where it was when picked.
|
/// found there on each evaluation and `axis_origin`/`axis_dir` are where it was when picked.
|
||||||
|
/// With `direction_only`, only the axis direction is used: it is projected onto the base
|
||||||
|
/// (nothing of that projection is kept as geometry) and the plane turns about the line
|
||||||
|
/// through `pivot`, or through the base origin when `pivot` is absent.
|
||||||
AtAngle {
|
AtAngle {
|
||||||
base: Box<PlaneRef>,
|
base: Box<PlaneRef>,
|
||||||
axis_origin: Vec3,
|
axis_origin: Vec3,
|
||||||
@@ -68,6 +71,12 @@ pub enum PlaneRef {
|
|||||||
angle: Expr,
|
angle: Expr,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
line: Option<Box<GeoRef>>,
|
line: Option<Box<GeoRef>>,
|
||||||
|
/// Use only the axis direction (projected onto the base), not the axis as a full line.
|
||||||
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
|
direction_only: bool,
|
||||||
|
/// Pivot for `direction_only`; absent means the base plane's origin.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pivot: Option<Box<GeoRef>>,
|
||||||
},
|
},
|
||||||
/// A construction plane feature (by id).
|
/// A construction plane feature (by id).
|
||||||
Construction { feature: FeatureId },
|
Construction { feature: FeatureId },
|
||||||
@@ -321,6 +330,12 @@ pub struct Extent {
|
|||||||
/// Through all bodies in the direction(s) instead of `distance`.
|
/// Through all bodies in the direction(s) instead of `distance`.
|
||||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
pub through_all: bool,
|
pub through_all: bool,
|
||||||
|
/// Two Sides as older builds read it: `distance2` goes the other way from wherever the
|
||||||
|
/// first side went, so a negative entered distance sends it back along the normal. Saved
|
||||||
|
/// designs from before entered distances kept their sides carry it, so they rebuild as
|
||||||
|
/// they did whatever their expressions evaluate to; new and edited extrudes leave it off.
|
||||||
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
|
pub second_follows_first: bool,
|
||||||
/// Taper angle (expression; positive grows the profile).
|
/// Taper angle (expression; positive grows the profile).
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
#[walk(angle = "Taper angle")]
|
#[walk(angle = "Taper angle")]
|
||||||
@@ -665,6 +680,12 @@ impl AlignFrame {
|
|||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, solidcraft_derive::Walk)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum FeatureKind {
|
pub enum FeatureKind {
|
||||||
|
/// A feature a newer SolidCraft saved that this build can't read ([`crate::newer_file`]): it
|
||||||
|
/// holds its place on the timeline, reports an error when evaluated, and goes back into the
|
||||||
|
/// file as it came.
|
||||||
|
NewerFileFeature {
|
||||||
|
raw: crate::newer_file::RawFeature,
|
||||||
|
},
|
||||||
/// A recorded timeline ownership boundary. Geometry is converted from its
|
/// A recorded timeline ownership boundary. Geometry is converted from its
|
||||||
/// current local frame into the destination component frame exactly once.
|
/// current local frame into the destination component frame exactly once.
|
||||||
BodyTransfer {
|
BodyTransfer {
|
||||||
@@ -1487,7 +1508,7 @@ pub enum FeatureKind {
|
|||||||
},
|
},
|
||||||
/// Plastic lip (raised band) or groove (recess) along the rim face picked at `face`.
|
/// Plastic lip (raised band) or groove (recess) along the rim face picked at `face`.
|
||||||
Lip {
|
Lip {
|
||||||
face: Position,
|
face: FacePick,
|
||||||
#[walk(length = "Width")]
|
#[walk(length = "Width")]
|
||||||
width: Expr,
|
width: Expr,
|
||||||
#[walk(length = "Height")]
|
#[walk(length = "Height")]
|
||||||
@@ -2608,6 +2629,7 @@ impl FeatureKind {
|
|||||||
/// Short type name (timeline tooltip, `inspect`).
|
/// Short type name (timeline tooltip, `inspect`).
|
||||||
pub fn type_name(&self) -> &'static str {
|
pub fn type_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
FeatureKind::NewerFileFeature { .. } => "NewerFileFeature",
|
||||||
FeatureKind::BodyTransfer { .. } => "BodyTransferFeature",
|
FeatureKind::BodyTransfer { .. } => "BodyTransferFeature",
|
||||||
FeatureKind::Sketch { .. } => "Sketch",
|
FeatureKind::Sketch { .. } => "Sketch",
|
||||||
FeatureKind::Extrude { .. } => "ExtrudeFeature",
|
FeatureKind::Extrude { .. } => "ExtrudeFeature",
|
||||||
@@ -2693,6 +2715,7 @@ impl FeatureKind {
|
|||||||
/// Design-file variant key (e.g. `"extrude"`, `"box"`, `"sheet_flange"`).
|
/// Design-file variant key (e.g. `"extrude"`, `"box"`, `"sheet_flange"`).
|
||||||
pub fn kind_key(&self) -> &'static str {
|
pub fn kind_key(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
FeatureKind::NewerFileFeature { .. } => "newer_file_feature",
|
||||||
FeatureKind::BodyTransfer { .. } => "body_transfer",
|
FeatureKind::BodyTransfer { .. } => "body_transfer",
|
||||||
FeatureKind::Sketch { .. } => "sketch",
|
FeatureKind::Sketch { .. } => "sketch",
|
||||||
FeatureKind::Extrude { .. } => "extrude",
|
FeatureKind::Extrude { .. } => "extrude",
|
||||||
@@ -2855,6 +2878,7 @@ impl FeatureKind {
|
|||||||
/// Default name prefix (`Extrude` → `Extrude1`).
|
/// Default name prefix (`Extrude` → `Extrude1`).
|
||||||
pub fn base_name(&self) -> &'static str {
|
pub fn base_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
FeatureKind::NewerFileFeature { .. } => "Unknown",
|
||||||
FeatureKind::BodyTransfer { .. } => "BodyTransfer",
|
FeatureKind::BodyTransfer { .. } => "BodyTransfer",
|
||||||
FeatureKind::Sketch { .. } => "Sketch",
|
FeatureKind::Sketch { .. } => "Sketch",
|
||||||
FeatureKind::Extrude { .. } => "Extrude",
|
FeatureKind::Extrude { .. } => "Extrude",
|
||||||
@@ -3127,6 +3151,10 @@ pub fn is_counted_body_name(name: &str) -> bool {
|
|||||||
/// The document.
|
/// The document.
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
|
/// Set when a newer SolidCraft saved the file this came from: what to tell the user, and what
|
||||||
|
/// this build couldn't read and keeps for the next save ([`crate::newer_file`]).
|
||||||
|
#[serde(skip)]
|
||||||
|
pub newer_file: Option<std::sync::Arc<crate::newer_file::NewerFile>>,
|
||||||
/// Named image intent and immutable embedded HDR sources. Active jobs and device state are not saved.
|
/// Named image intent and immutable embedded HDR sources. Active jobs and device state are not saved.
|
||||||
#[serde(default, skip_serializing_if = "crate::render_settings::RenderSettings::is_empty")]
|
#[serde(default, skip_serializing_if = "crate::render_settings::RenderSettings::is_empty")]
|
||||||
pub render: std::sync::Arc<crate::render_settings::RenderSettings>,
|
pub render: std::sync::Arc<crate::render_settings::RenderSettings>,
|
||||||
@@ -3488,6 +3516,7 @@ impl Document {
|
|||||||
|
|
||||||
pub fn new(name: &str) -> Self {
|
pub fn new(name: &str) -> Self {
|
||||||
Document {
|
Document {
|
||||||
|
newer_file: None,
|
||||||
render: Default::default(),
|
render: Default::default(),
|
||||||
format: default_format(),
|
format: default_format(),
|
||||||
format_pending: crate::format::pending_names(),
|
format_pending: crate::format::pending_names(),
|
||||||
@@ -3909,7 +3938,12 @@ impl Document {
|
|||||||
|
|
||||||
/// Read a design file's JSON (any format this build knows; older ones are upgraded).
|
/// Read a design file's JSON (any format this build knows; older ones are upgraded).
|
||||||
pub fn from_json(s: &str) -> Result<Document> {
|
pub fn from_json(s: &str) -> Result<Document> {
|
||||||
crate::render_resources::check_json_budget(s)?;
|
// A newer build's resources may not read here; the section stays out then (`newer_file`).
|
||||||
|
if let Err(why) = crate::render_resources::check_json_budget(s).and_then(|()| crate::render_texture_resources::check_json_budget(s))
|
||||||
|
&& !crate::format::says_newer(s)
|
||||||
|
{
|
||||||
|
return Err(why);
|
||||||
|
}
|
||||||
let mut d = crate::format::read(s)?;
|
let mut d = crate::format::read(s)?;
|
||||||
if d.features.len() > MAX_FEATURES || d.params.len() > MAX_PARAMS {
|
if d.features.len() > MAX_FEATURES || d.params.len() > MAX_PARAMS {
|
||||||
return Err(DocError::Invalid("document too large".into()));
|
return Err(DocError::Invalid("document too large".into()));
|
||||||
@@ -3921,6 +3955,11 @@ impl Document {
|
|||||||
Ok(d)
|
Ok(d)
|
||||||
}
|
}
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
|
if self.newer_file.is_some() {
|
||||||
|
let mut value = serde_json::to_value(self).unwrap_or_default();
|
||||||
|
crate::newer_file::restore(self, &mut value);
|
||||||
|
return serde_json::to_string_pretty(&value).unwrap_or_default();
|
||||||
|
}
|
||||||
serde_json::to_string_pretty(self).unwrap_or_default()
|
serde_json::to_string_pretty(self).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4404,7 +4443,7 @@ impl Document {
|
|||||||
let b = self.resolve_plane_in(vals, base, depth + 1, st)?;
|
let b = self.resolve_plane_in(vals, base, depth + 1, st)?;
|
||||||
Ok(b.offset(Self::eval_in(vals, distance, Kind::Length)?))
|
Ok(b.offset(Self::eval_in(vals, distance, Kind::Length)?))
|
||||||
}
|
}
|
||||||
PlaneRef::AtAngle { base, axis_origin, axis_dir, angle, line } => {
|
PlaneRef::AtAngle { base, axis_origin, axis_dir, angle, line, direction_only, pivot } => {
|
||||||
let b = self.resolve_plane_in(vals, base, depth + 1, st)?;
|
let b = self.resolve_plane_in(vals, base, depth + 1, st)?;
|
||||||
let a = Self::eval_in(vals, angle, Kind::Angle)?;
|
let a = Self::eval_in(vals, angle, Kind::Angle)?;
|
||||||
let (axis_origin, axis_dir) = match line {
|
let (axis_origin, axis_dir) = match line {
|
||||||
@@ -4414,7 +4453,26 @@ impl Document {
|
|||||||
}
|
}
|
||||||
None => (*axis_origin, *axis_dir),
|
None => (*axis_origin, *axis_dir),
|
||||||
};
|
};
|
||||||
let d = axis_dir.normalized().ok_or_else(|| DocError::Invalid("rotation axis".into()))?;
|
let (axis_origin, d) = if *direction_only {
|
||||||
|
let n = b.normal();
|
||||||
|
let in_plane = axis_dir - n * n.dot(axis_dir);
|
||||||
|
let d = in_plane
|
||||||
|
.normalized()
|
||||||
|
.ok_or_else(|| DocError::Invalid("the axis is square to the plane, so it has no direction in the plane".into()))?;
|
||||||
|
let pivot = match pivot {
|
||||||
|
Some(g) => {
|
||||||
|
let st = st.ok_or_else(|| DocError::Invalid("this plane is built from the model: evaluate first".into()))?;
|
||||||
|
crate::construct::point(&crate::construct::Ctx { doc: self, vals, st, depth }, g)?
|
||||||
|
}
|
||||||
|
None => b.origin,
|
||||||
|
};
|
||||||
|
// Keep the pivot in the base so a slightly off pick still turns about an in-plane line.
|
||||||
|
let pivot = pivot - n * n.dot(pivot - b.origin);
|
||||||
|
(pivot, d)
|
||||||
|
} else {
|
||||||
|
let d = axis_dir.normalized().ok_or_else(|| DocError::Invalid("rotation axis".into()))?;
|
||||||
|
(axis_origin, d)
|
||||||
|
};
|
||||||
let rot = |v: Vec3| v * a.cos() + d.cross(v) * a.sin() + d * (d.dot(v) * (1.0 - a.cos()));
|
let rot = |v: Vec3| v * a.cos() + d.cross(v) * a.sin() + d * (d.dot(v) * (1.0 - a.cos()));
|
||||||
let o = axis_origin + rot(b.origin - axis_origin);
|
let o = axis_origin + rot(b.origin - axis_origin);
|
||||||
Plane::new(o, rot(b.x), rot(b.y)).ok_or_else(|| DocError::Invalid("degenerate plane".into()))
|
Plane::new(o, rot(b.x), rot(b.y)).ok_or_else(|| DocError::Invalid("degenerate plane".into()))
|
||||||
|
|||||||
+125
-3
@@ -84,6 +84,65 @@ pub struct Drawing {
|
|||||||
pub language: String,
|
pub language: String,
|
||||||
/// In order, each behind its own `Arc`, so an undo step shares every sheet it didn't change.
|
/// In order, each behind its own `Arc`, so an undo step shares every sheet it didn't change.
|
||||||
pub sheets: Vec<Arc<Sheet>>,
|
pub sheets: Vec<Arc<Sheet>>,
|
||||||
|
/// Set when a newer SolidCraft saved the file this came from.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub newer_file: Option<Arc<NewerDrawing>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The fields a drawing's record has: anything else in a file is a newer build's.
|
||||||
|
const RECORD_FIELDS: [&str; 8] = ["format", "format_pending", "name", "sources", "properties", "units", "language", "sheets"];
|
||||||
|
|
||||||
|
/// Move the elements of the list `key` that don't read as `T` out of a newer build's drawing.
|
||||||
|
fn take_unread<T: serde::de::DeserializeOwned>(top: &mut serde_json::Map<String, Value>, key: &str, one: &str, file: &mut NewerDrawing) {
|
||||||
|
let Some(Value::Array(list)) = top.get_mut(key) else { return };
|
||||||
|
let mut readable = Vec::with_capacity(list.len());
|
||||||
|
for element in std::mem::take(list) {
|
||||||
|
match DRAWING.check_numbers(&element, "properties").and_then(|()| serde_json::from_value::<T>(element.clone()).map_err(damaged)) {
|
||||||
|
Ok(_) => readable.push(element),
|
||||||
|
Err(why) => {
|
||||||
|
let name = element.get("name").or(element.get("path")).and_then(Value::as_str).unwrap_or("unnamed");
|
||||||
|
file.not_understood.push(format!("{one} `{name}` ({why})"));
|
||||||
|
file.elements.push((key.to_string(), element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*list = readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a drawing file says and holds that this build doesn't read ([`Drawing::from_newer`]).
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct NewerDrawing {
|
||||||
|
/// The number the file says.
|
||||||
|
pub format: u32,
|
||||||
|
/// The number this build reads up to.
|
||||||
|
pub reads_up_to: u32,
|
||||||
|
/// The changes the file names that this build doesn't have.
|
||||||
|
pub unknown_changes: Vec<String>,
|
||||||
|
/// What this build couldn't read, by name.
|
||||||
|
pub not_understood: Vec<String>,
|
||||||
|
fields: Vec<(String, Value)>,
|
||||||
|
elements: Vec<(String, Value)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewerDrawing {
|
||||||
|
/// What to tell the user.
|
||||||
|
pub fn notice(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Saved by a newer SolidCraft (format {}, this version reads up to {}). If something's missing, update.",
|
||||||
|
self.format, self.reads_up_to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&self, value: &mut Value) {
|
||||||
|
for (key, field) in &self.fields {
|
||||||
|
value[key] = field.clone();
|
||||||
|
}
|
||||||
|
for (key, element) in &self.elements {
|
||||||
|
if let Some(list) = value.get_mut(key).and_then(Value::as_array_mut) {
|
||||||
|
list.push(element.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A design the drawing draws.
|
/// A design the drawing draws.
|
||||||
@@ -175,6 +234,7 @@ impl Drawing {
|
|||||||
units: Units::default(),
|
units: Units::default(),
|
||||||
language: language.into(),
|
language: language.into(),
|
||||||
sheets: vec![Arc::new(first)],
|
sheets: vec![Arc::new(first)],
|
||||||
|
newer_file: None,
|
||||||
};
|
};
|
||||||
drawing.validate()?;
|
drawing.validate()?;
|
||||||
Ok(drawing)
|
Ok(drawing)
|
||||||
@@ -188,11 +248,65 @@ impl Drawing {
|
|||||||
}
|
}
|
||||||
let mut v: Value = serde_json::from_str(s).map_err(|e| DocError::Invalid(format!("not a SolidCraft drawing: {e}")))?;
|
let mut v: Value = serde_json::from_str(s).map_err(|e| DocError::Invalid(format!("not a SolidCraft drawing: {e}")))?;
|
||||||
let (version, applied) = DRAWING.take_version(&mut v)?;
|
let (version, applied) = DRAWING.take_version(&mut v)?;
|
||||||
|
if let Some(newer) = DRAWING.newer(version, applied.as_deref()) {
|
||||||
|
return Self::from_newer(v, version, applied, newer);
|
||||||
|
}
|
||||||
|
let d = Self::decode(v, version, applied.as_deref())?;
|
||||||
|
Ok(Drawing { format: DRAWING.format_string(DRAWING.current()), format_pending: DRAWING.pending_names(), ..d })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(mut v: Value, version: u32, applied: Option<&[String]>) -> Result<Drawing> {
|
||||||
DRAWING.check_numbers(&v, "properties")?;
|
DRAWING.check_numbers(&v, "properties")?;
|
||||||
DRAWING.upgrade(&mut v, version, applied.as_deref())?;
|
DRAWING.upgrade(&mut v, version, applied)?;
|
||||||
let d: Drawing = serde_json::from_value(v).map_err(damaged)?;
|
let d: Drawing = serde_json::from_value(v).map_err(damaged)?;
|
||||||
d.validate().map_err(damaged)?;
|
d.validate().map_err(damaged)?;
|
||||||
Ok(Drawing { format: DRAWING.format_string(DRAWING.current()), format_pending: DRAWING.pending_names(), ..d })
|
Ok(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A drawing a newer build saved: it opens with what this build can read of it. Everything in a
|
||||||
|
/// drawing refuses a field it doesn't know, so what it can't read shows as a top-level field,
|
||||||
|
/// a source or a sheet that doesn't read, which stays out and is named in
|
||||||
|
/// [`NewerDrawing::not_understood`]; [`Drawing::to_json`] writes it all back, with the file's
|
||||||
|
/// format. It takes at least one sheet this build can read to open.
|
||||||
|
fn from_newer(mut v: Value, version: u32, applied: Option<Vec<String>>, newer: crate::format::Newer) -> Result<Drawing> {
|
||||||
|
let list = applied.as_deref();
|
||||||
|
let can_read = |v: Value| Self::decode(v, version, list).map_err(|e| e.to_string());
|
||||||
|
let mut file = NewerDrawing {
|
||||||
|
format: newer.format,
|
||||||
|
reads_up_to: newer.reads_up_to,
|
||||||
|
unknown_changes: newer.unknown_changes.clone(),
|
||||||
|
..NewerDrawing::default()
|
||||||
|
};
|
||||||
|
let mut read = can_read(v.clone());
|
||||||
|
if read.is_err() {
|
||||||
|
let top = v.as_object_mut().ok_or_else(|| damaged("expected a JSON object"))?;
|
||||||
|
let keys: Vec<String> = top.keys().cloned().collect();
|
||||||
|
for key in keys {
|
||||||
|
if !RECORD_FIELDS.contains(&key.as_str())
|
||||||
|
&& let Some(value) = top.remove(&key)
|
||||||
|
{
|
||||||
|
file.not_understood.push(format!("field `{key}`"));
|
||||||
|
file.fields.push((key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
take_unread::<Sheet>(top, "sheets", "sheet", &mut file);
|
||||||
|
take_unread::<Source>(top, "sources", "source", &mut file);
|
||||||
|
read = can_read(v);
|
||||||
|
}
|
||||||
|
let d = read.map_err(|why| {
|
||||||
|
DocError::Invalid(format!(
|
||||||
|
"this drawing was saved by a newer SolidCraft (format {}, this version reads up to {}) and this version can't read it: {why}. Update SolidCraft to open it",
|
||||||
|
newer.format, newer.reads_up_to
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let (format, format_pending) = if newer.format > newer.reads_up_to {
|
||||||
|
(DRAWING.format_string(newer.format), applied.unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
let mut pending = DRAWING.pending_names();
|
||||||
|
pending.extend(newer.unknown_changes.iter().cloned());
|
||||||
|
(DRAWING.format_string(DRAWING.current()), pending)
|
||||||
|
};
|
||||||
|
Ok(Drawing { format, format_pending, newer_file: Some(Arc::new(file)), ..d })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The drawing's JSON: the same bytes for the same drawing. JSON longer than a read takes
|
/// The drawing's JSON: the same bytes for the same drawing. JSON longer than a read takes
|
||||||
@@ -200,7 +314,15 @@ impl Drawing {
|
|||||||
/// properties are kept as they came, and indenting them can make a drawing that read in
|
/// properties are kept as they came, and indenting them can make a drawing that read in
|
||||||
/// under the limit save over it.
|
/// under the limit save over it.
|
||||||
pub fn to_json(&self) -> Result<String> {
|
pub fn to_json(&self) -> Result<String> {
|
||||||
let text = serde_json::to_string_pretty(self).map_err(|e| DocError::Invalid(format!("drawing not saved: {e}")))?;
|
let unsaved = |e: serde_json::Error| DocError::Invalid(format!("drawing not saved: {e}"));
|
||||||
|
let text = match &self.newer_file {
|
||||||
|
None => serde_json::to_string_pretty(self).map_err(unsaved)?,
|
||||||
|
Some(file) => {
|
||||||
|
let mut value = serde_json::to_value(self).map_err(unsaved)?;
|
||||||
|
file.restore(&mut value);
|
||||||
|
serde_json::to_string_pretty(&value).map_err(unsaved)?
|
||||||
|
}
|
||||||
|
};
|
||||||
require(text.len() <= MAX_DRAWING_BYTES, || format!("drawing too large to save (at most {} MiB)", MAX_DRAWING_BYTES >> 20))?;
|
require(text.len() <= MAX_DRAWING_BYTES, || format!("drawing too large to save (at most {} MiB)", MAX_DRAWING_BYTES >> 20))?;
|
||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ whole format bump: never edit the `DRAWING` family in `format.rs`, another chang
|
|||||||
|
|
||||||
- Name it after the change, lowercase with dashes: `sheet-views.rs`.
|
- Name it after the change, lowercase with dashes: `sheet-views.rs`.
|
||||||
- Start it with a `//!` note: what is saved, what an older drawing gets without it, and what
|
- Start it with a `//!` note: what is saved, what an older drawing gets without it, and what
|
||||||
the last release does with a drawing that has it (it refuses the newer format number).
|
the last release does with a drawing that has it (it warns about the newer format number, opens what it can read and keeps the rest).
|
||||||
- If older drawings must be rewritten as they open, add the step to the same file:
|
- If older drawings must be rewritten as they open, add the step to the same file:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
@@ -325,28 +325,28 @@ fn damaged_files_are_refused_and_never_patched() {
|
|||||||
let why = refused(&d, edit);
|
let why = refused(&d, edit);
|
||||||
assert!(why.starts_with("damaged drawing: ") && why.contains("unknown field"), "{i}: {why}");
|
assert!(why.starts_with("damaged drawing: ") && why.contains("unknown field"), "{i}: {why}");
|
||||||
}
|
}
|
||||||
// A newer build's file is refused as one before anything in it is read: a field this build
|
// A newer build's file isn't held to this build's rules: a field it doesn't know, or a sheet with
|
||||||
// doesn't know, or a number out of its range.
|
// a number out of range, stays out. With no sheet left to show, it says why.
|
||||||
let (current, newer) = (DRAWING.current(), DRAWING.current() + 1);
|
let (current, newer) = (DRAWING.current(), DRAWING.current() + 1);
|
||||||
let why = refused(&d, |v| {
|
let why = refused(&d, |v| {
|
||||||
v["format"] = json!(DRAWING.format_string(newer));
|
v["format"] = json!(DRAWING.format_string(newer));
|
||||||
v["views"] = json!([]);
|
v["views"] = json!([]);
|
||||||
v["sheets"][0]["dpi"] = json!(1e10);
|
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(why, "read");
|
||||||
why,
|
let why = refused(&d, |v| {
|
||||||
format!(
|
v["format"] = json!(DRAWING.format_string(newer));
|
||||||
"this drawing was saved by a newer SolidCraft (file format {newer}; this version reads formats up to {current}): update SolidCraft to open it"
|
v["sheets"] = json!([{"id": "future", "a_future_field": true}]);
|
||||||
)
|
});
|
||||||
|
assert!(
|
||||||
|
why.starts_with(&format!(
|
||||||
|
"this drawing was saved by a newer SolidCraft (format {newer}, this version reads up to {current}) and this version can't read it: "
|
||||||
|
)),
|
||||||
|
"{why}"
|
||||||
);
|
);
|
||||||
// A list of pending changes means nothing beside a format made of none; beside the pending
|
// A list of pending changes means nothing beside a format made of none; beside the pending
|
||||||
// number, a change this build doesn't know is a newer build's.
|
// number, a change this build doesn't know is a newer build's.
|
||||||
let why = refused(&d, |v| v["format_pending"] = json!(["a-later-change"]));
|
let why = refused(&d, |v| v["format_pending"] = json!(["a-later-change"]));
|
||||||
if DRAWING.pending_names().is_empty() {
|
assert_eq!(why, "read");
|
||||||
assert_eq!(why, "read");
|
|
||||||
} else {
|
|
||||||
assert!(why.contains("newer SolidCraft"), "{why}");
|
|
||||||
}
|
|
||||||
// A design is never read as a drawing, nor a drawing as a design.
|
// A design is never read as a drawing, nor a drawing as a design.
|
||||||
let design = crate::Document::new("Plate").to_json();
|
let design = crate::Document::new("Plate").to_json();
|
||||||
let design_format = crate::format::format_string(crate::format::FORMAT);
|
let design_format = crate::format::format_string(crate::format::FORMAT);
|
||||||
@@ -551,3 +551,32 @@ fn mutate(v: &mut Value, pick: &mut usize, lie: &Value, how: u64) -> bool {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A drawing a newer build saved opens, with what it can read, and saving keeps the rest.
|
||||||
|
#[test]
|
||||||
|
fn a_newer_builds_drawing_opens_and_keeps_what_it_cant_read() {
|
||||||
|
let d = plate();
|
||||||
|
let mut file: Value = file(&d);
|
||||||
|
assert!(file.as_object().unwrap().keys().all(|key| RECORD_FIELDS.contains(&key.as_str())), "a new field goes in RECORD_FIELDS");
|
||||||
|
let newer = DRAWING.format_string(DRAWING.current() + 1);
|
||||||
|
file["format"] = json!(newer);
|
||||||
|
// With nothing it can't read: the same drawing.
|
||||||
|
let opened = Drawing::from_json(&file.to_string()).unwrap();
|
||||||
|
assert_eq!(opened.sheets, d.sheets);
|
||||||
|
assert!(opened.newer_file.as_ref().unwrap().notice().starts_with("Saved by a newer SolidCraft"));
|
||||||
|
assert_eq!(serde_json::from_str::<Value>(&opened.to_json().unwrap()).unwrap()["format"], json!(newer));
|
||||||
|
// A field and a second sheet it can't read stay out, named, and go back.
|
||||||
|
file["views"] = json!([{"id": "a"}]);
|
||||||
|
let mut future_sheet = file["sheets"][0].clone();
|
||||||
|
future_sheet["name"] = json!("Future");
|
||||||
|
future_sheet["future_layers"] = json!([1]);
|
||||||
|
file["sheets"].as_array_mut().unwrap().push(future_sheet);
|
||||||
|
let opened = Drawing::from_json(&file.to_string()).unwrap();
|
||||||
|
assert_eq!(opened.sheets, d.sheets);
|
||||||
|
let said = &opened.newer_file.as_ref().unwrap().not_understood;
|
||||||
|
assert!(said.iter().any(|what| what.contains("views")) && said.iter().any(|what| what.contains("Future")), "{said:?}");
|
||||||
|
let again: Value = serde_json::from_str(&opened.to_json().unwrap()).unwrap();
|
||||||
|
assert_eq!(again["views"], file["views"]);
|
||||||
|
assert_eq!(again["sheets"].as_array().unwrap().len(), d.sheets.len() + 1);
|
||||||
|
assert_eq!(again["format"], json!(newer));
|
||||||
|
}
|
||||||
|
|||||||
+481
-131
@@ -143,7 +143,7 @@ impl ModelBody {
|
|||||||
|
|
||||||
/// Face measurements for joint origins, at their original chord tolerance. Pose changes
|
/// Face measurements for joint origins, at their original chord tolerance. Pose changes
|
||||||
/// and scratch copies share them; a geometry edit makes a new body and cache.
|
/// and scratch copies share them; a geometry edit makes a new body and cache.
|
||||||
pub(crate) fn joint_faces(&self) -> Result<Arc<[kernel::FaceInfo]>> {
|
pub fn joint_faces(&self) -> Result<Arc<[kernel::FaceInfo]>> {
|
||||||
let measure = || {
|
let measure = || {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
cost_tests::count(|c| c.joint_faces += 1);
|
cost_tests::count(|c| c.joint_faces += 1);
|
||||||
@@ -254,6 +254,35 @@ impl ModelBody {
|
|||||||
self.mesh.clone()
|
self.mesh.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identity of the display cells of the solid this body shows: its own, or for a rigidly
|
||||||
|
/// placed copy its definition's (shared by every placement of it).
|
||||||
|
fn solid_id(&self) -> usize {
|
||||||
|
self.mesh_placement.as_deref().map_or(Arc::as_ptr(&self.mesh) as usize, placed_mesh::PlacedMesh::definition_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How this body is drawn moved from `before`: [`crate::IDENTITY`] when it is `before` where
|
||||||
|
/// it stood (its meshes and plans the same), the rigid motion between them when it is the same
|
||||||
|
/// solid placed elsewhere (a dragged component's body: `before`'s meshes and plans drawn moved
|
||||||
|
/// show it; a definition shown where it is defined is its own placement), `None` when it is
|
||||||
|
/// another body or its geometry differs.
|
||||||
|
pub fn display_moved_from(&self, before: &ModelBody) -> Option<crate::Mat> {
|
||||||
|
if self.name != before.name || self.solid_id() != before.solid_id() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (now, was) = (self.mesh_placement.as_deref(), before.mesh_placement.as_deref());
|
||||||
|
if let (Some(now), Some(was)) = (now, was)
|
||||||
|
&& now.tolerance.to_bits() != was.tolerance.to_bits()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let at = |p: Option<&placed_mesh::PlacedMesh>| p.map_or(crate::IDENTITY, |p| *p.transform());
|
||||||
|
let (now, was) = (at(now), at(was));
|
||||||
|
if now == was {
|
||||||
|
return Some(crate::IDENTITY);
|
||||||
|
}
|
||||||
|
Some(crate::mat_mul(&now, &crate::mat_inverse(&was)?))
|
||||||
|
}
|
||||||
|
|
||||||
/// [`ModelBody::mesh`] as a shared mesh and the move that places it: for a rigid placement
|
/// [`ModelBody::mesh`] as a shared mesh and the move that places it: for a rigid placement
|
||||||
/// of a definition, the definition's own mesh (the same one for every placement of it) and
|
/// of a definition, the definition's own mesh (the same one for every placement of it) and
|
||||||
/// the placement; for any other body its own mesh, unmoved. The mesh moved by the matrix is
|
/// the placement; for any other body its own mesh, unmoved. The mesh moved by the matrix is
|
||||||
@@ -438,6 +467,21 @@ fn removed_note(gone: &[Vec3]) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// Current feature evaluation validation mode (`EachStep`, `EachFeature`, or `Off`).
|
||||||
|
static EVAL_VALIDATION: std::cell::Cell<kernel::Validation> = const { std::cell::Cell::new(kernel::Validation::EachFeature) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restores `EVAL_VALIDATION` to its previous value when dropped.
|
||||||
|
struct EvalValidationGuard(kernel::Validation);
|
||||||
|
|
||||||
|
impl Drop for EvalValidationGuard {
|
||||||
|
/// Restores the saved validation mode on scope exit.
|
||||||
|
fn drop(&mut self) {
|
||||||
|
EVAL_VALIDATION.set(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A blend's refusal as too large (`why`) with the largest `size` (radius, distance) that fits,
|
/// A blend's refusal as too large (`why`) with the largest `size` (radius, distance) that fits,
|
||||||
/// as `search` finds it by building candidates ([`kernel::fillet_max`]): the refusal as it is
|
/// as `search` finds it by building candidates ([`kernel::fillet_max`]): the refusal as it is
|
||||||
/// when none fits or the search itself fails (a candidate failing for another reason says
|
/// when none fits or the search itself fails (a candidate failing for another reason says
|
||||||
@@ -447,6 +491,9 @@ fn with_largest_fit(
|
|||||||
size: &str,
|
size: &str,
|
||||||
search: impl FnOnce() -> std::result::Result<Option<(f64, kernel::Body)>, kernel::KernelError>,
|
search: impl FnOnce() -> std::result::Result<Option<(f64, kernel::Body)>, kernel::KernelError>,
|
||||||
) -> kernel::KernelError {
|
) -> kernel::KernelError {
|
||||||
|
if EVAL_VALIDATION.get() == kernel::Validation::Off {
|
||||||
|
return kernel::KernelError::TooLarge(why);
|
||||||
|
}
|
||||||
// On solidcore the refusal names a size already, less a margin (34.99 where 35 builds), and
|
// On solidcore the refusal names a size already, less a margin (34.99 where 35 builds), and
|
||||||
// its edge: the edge stays, the size is the largest the search builds, solidcore's where the
|
// its edge: the edge stays, the size is the largest the search builds, solidcore's where the
|
||||||
// search finds none.
|
// search finds none.
|
||||||
@@ -784,7 +831,7 @@ impl ModelState {
|
|||||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||||
serde_json::to_string(&s.plane).unwrap_or_default().hash(&mut h);
|
serde_json::to_string(&s.plane).unwrap_or_default().hash(&mut h);
|
||||||
let mut sketch = s.sketch.clone();
|
let mut sketch = s.sketch.clone();
|
||||||
quantize(&mut sketch);
|
fingerprint_sketch(&mut sketch);
|
||||||
serde_json::to_string(&sketch).unwrap_or_default().hash(&mut h);
|
serde_json::to_string(&sketch).unwrap_or_default().hash(&mut h);
|
||||||
s.report.ok().hash(&mut h);
|
s.report.ok().hash(&mut h);
|
||||||
self.sketch_keys.insert(feature, h.finish());
|
self.sketch_keys.insert(feature, h.finish());
|
||||||
@@ -1114,6 +1161,18 @@ pub struct Model {
|
|||||||
placement_inputs: Option<placement_reuse::Inputs>,
|
placement_inputs: Option<placement_reuse::Inputs>,
|
||||||
/// All evaluated features use supported local inputs; computed with the existing owner index.
|
/// All evaluated features use supported local inputs; computed with the existing owner index.
|
||||||
placement_local: bool,
|
placement_local: bool,
|
||||||
|
/// A failed sketch shown while it is edited ([`Model::hold_failed_sketch`]).
|
||||||
|
held: Option<HeldSketch>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A failed sketch built for its editor (#12085): `shown` is the final state `base` with that
|
||||||
|
/// sketch in it. [`Model::state`] gives it only while the results still end in `base`, so the
|
||||||
|
/// next evaluation, or anything else that changes the results, lets it go.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct HeldSketch {
|
||||||
|
feature: u64,
|
||||||
|
base: Arc<ModelState>,
|
||||||
|
shown: Arc<ModelState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameters a feature uses directly (inputs, sketch dimensions and the expressions of a
|
/// Parameters a feature uses directly (inputs, sketch dimensions and the expressions of a
|
||||||
@@ -1253,6 +1312,13 @@ fn quantize(sk: &mut solidcraft_sketch::Sketch) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The stored solve as a cache key: on the [`QUANTUM`] grid, without the editor state
|
||||||
|
/// (see [`solidcraft_sketch::Sketch::clear_id_counters`]).
|
||||||
|
fn fingerprint_sketch(sk: &mut solidcraft_sketch::Sketch) {
|
||||||
|
quantize(sk);
|
||||||
|
sk.clear_id_counters();
|
||||||
|
}
|
||||||
|
|
||||||
/// A feature as fingerprinted: its definition, a sketch's stored solve quantized, an imported
|
/// A feature as fingerprinted: its definition, a sketch's stored solve quantized, an imported
|
||||||
/// file's text or mesh payload by its content digest.
|
/// file's text or mesh payload by its content digest.
|
||||||
/// Hash [`feature_json`] of `f` into `h`. Every evaluation fingerprints every feature, and in a
|
/// Hash [`feature_json`] of `f` into `h`. Every evaluation fingerprints every feature, and in a
|
||||||
@@ -1319,7 +1385,7 @@ fn feature_json(f: &Feature, steps: &ImportDigests) -> String {
|
|||||||
if let FeatureKind::Sketch { .. } = &f.kind {
|
if let FeatureKind::Sketch { .. } = &f.kind {
|
||||||
let mut g = f.clone();
|
let mut g = f.clone();
|
||||||
if let FeatureKind::Sketch { sketch, .. } = &mut g.kind {
|
if let FeatureKind::Sketch { sketch, .. } = &mut g.kind {
|
||||||
quantize(sketch);
|
fingerprint_sketch(sketch);
|
||||||
}
|
}
|
||||||
return serde_json::to_string(&g).unwrap_or_default();
|
return serde_json::to_string(&g).unwrap_or_default();
|
||||||
}
|
}
|
||||||
@@ -1570,6 +1636,9 @@ fn cache_key(f: &Feature, chain_fp: u64, local: u64, ctx: u64, state: &ModelStat
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct PlacementReads {
|
struct PlacementReads {
|
||||||
all: bool,
|
all: bool,
|
||||||
|
/// A body named that `st` lacks: a further placement's (`Block (Part:2)`), found through
|
||||||
|
/// its occurrence (#9957), or gone.
|
||||||
|
unlisted: bool,
|
||||||
components: std::collections::BTreeSet<u64>,
|
components: std::collections::BTreeSet<u64>,
|
||||||
paths: Vec<Vec<u64>>,
|
paths: Vec<Vec<u64>>,
|
||||||
bodies: std::collections::BTreeSet<String>,
|
bodies: std::collections::BTreeSet<String>,
|
||||||
@@ -1590,6 +1659,21 @@ impl PlacementReads {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The bodies To / From Object's stored face names find, each where it is placed: evaluation
|
||||||
|
/// finds a named face by its name ([`with_named_face_points`] fails a feature whose name finds
|
||||||
|
/// none). Everything when a name is missing or empty, finds no one body, or `st` is `None`:
|
||||||
|
/// the face is then found by its point, on whatever body lies there.
|
||||||
|
fn object_faces(&mut self, st: Option<&ModelState>, names: Option<&[String]>) {
|
||||||
|
let bodies: Option<Vec<&str>> = st.zip(names).and_then(|(st, names)| {
|
||||||
|
let first_of_its_name = |b: &&ModelBody| st.body(&b.name).is_some_and(|first| std::ptr::eq(first, *b));
|
||||||
|
names.iter().map(|n| crate::name_lookup::face_body(st, n).filter(first_of_its_name).map(|b| b.name.as_str())).collect()
|
||||||
|
});
|
||||||
|
match bodies {
|
||||||
|
Some(bodies) => bodies.into_iter().for_each(|b| self.placed_body(st, b)),
|
||||||
|
None => self.all = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The same target selection as `apply_traced`, expanded only over the timeline input.
|
/// The same target selection as `apply_traced`, expanded only over the timeline input.
|
||||||
fn drawn_targets(&mut self, st: Option<&ModelState>, f: &Feature) {
|
fn drawn_targets(&mut self, st: Option<&ModelState>, f: &Feature) {
|
||||||
let Some(st) = st else {
|
let Some(st) = st else {
|
||||||
@@ -1608,14 +1692,17 @@ impl PlacementReads {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The component of body `name` in `st`; every component when the body is not there (a
|
/// The component of body `name` in `st`; every component and every placement when the body
|
||||||
/// projection then takes the body nearest its point, wherever that is).
|
/// is not there (a further placement's body, or the body nearest its point, wherever that is).
|
||||||
fn body(&mut self, doc: &Document, st: Option<&ModelState>, name: &str) {
|
fn body(&mut self, doc: &Document, st: Option<&ModelState>, name: &str) {
|
||||||
match st.and_then(|st| st.body(name)) {
|
match st.and_then(|st| st.body(name)) {
|
||||||
Some(b) => {
|
Some(b) => {
|
||||||
self.components.insert(doc.model_body_component(b));
|
self.components.insert(doc.model_body_component(b));
|
||||||
}
|
}
|
||||||
None => self.all = true,
|
None => {
|
||||||
|
self.all = true;
|
||||||
|
self.unlisted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1766,11 +1853,22 @@ fn placement_digest(doc: &Document, f: &Feature, st: Option<&ModelState>) -> u64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The faces To / From Object names, in `face_points` order.
|
||||||
|
let object_names = match &f.kind {
|
||||||
|
FeatureKind::Extrude { extent, .. } => usize::from(extent.to.is_some()) + usize::from(extent.from.is_some()),
|
||||||
|
FeatureKind::Revolve { to, .. } => usize::from(to.is_some()),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
match &f.kind {
|
match &f.kind {
|
||||||
// To / From Object finds its target where that target's component is placed (#928),
|
// To / From Object finds its target where the target's body is placed (#928): only that
|
||||||
// over every body.
|
// body's placement, not every part's (#6325). Its tools meet their targets as other
|
||||||
FeatureKind::Extrude { extent, .. } if extent.to.is_some() || extent.from.is_some() => reads.all = true,
|
// joins and cuts do.
|
||||||
FeatureKind::Revolve { to: Some(_), .. } => reads.all = true,
|
kind if object_names > 0 => {
|
||||||
|
reads.object_faces(st, f.face_names.get(..object_names));
|
||||||
|
if drawn_tools_change_bodies(kind) {
|
||||||
|
reads.drawn_targets(st, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
// A mirror of placed bodies follows the placements it was picked at and its own component's
|
// A mirror of placed bodies follows the placements it was picked at and its own component's
|
||||||
// frame, not every occurrence: moving an unrelated one leaves it (and its cache) alone.
|
// frame, not every occurrence: moving an unrelated one leaves it (and its cache) alone.
|
||||||
FeatureKind::Mirror { placements, bodies, .. } if !placements.is_empty() => return mirror_placements_digest(doc, f, placements, bodies),
|
FeatureKind::Mirror { placements, bodies, .. } if !placements.is_empty() => return mirror_placements_digest(doc, f, placements, bodies),
|
||||||
@@ -1851,6 +1949,9 @@ fn placement_digest(doc: &Document, f: &Feature, st: Option<&ModelState>) -> u64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hash_placed_bodies(doc, st, &reads.bodies, &inverse, &mut h);
|
hash_placed_bodies(doc, st, &reads.bodies, &inverse, &mut h);
|
||||||
|
if reads.unlisted {
|
||||||
|
placements_hash(doc).hash(&mut h);
|
||||||
|
}
|
||||||
// Legacy and component-wide reads still need the conservative offset dependency. Body
|
// Legacy and component-wide reads still need the conservative offset dependency. Body
|
||||||
// reads above already include just their own offsets, in the frame that evaluates them.
|
// reads above already include just their own offsets, in the frame that evaluates them.
|
||||||
if reads.all || !reads.components.is_empty() || !reads.paths.is_empty() {
|
if reads.all || !reads.components.is_empty() || !reads.paths.is_empty() {
|
||||||
@@ -1915,9 +2016,56 @@ impl Model {
|
|||||||
|
|
||||||
/// The final model state.
|
/// The final model state.
|
||||||
pub fn state(&self) -> Arc<ModelState> {
|
pub fn state(&self) -> Arc<ModelState> {
|
||||||
|
let last = self.evaluated_state();
|
||||||
|
match &self.held {
|
||||||
|
Some(held) if Arc::ptr_eq(&held.base, &last) => held.shown.clone(),
|
||||||
|
_ => last,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state after the last feature evaluated, without a failed sketch held for its editor.
|
||||||
|
fn evaluated_state(&self) -> Arc<ModelState> {
|
||||||
self.results.iter().rev().find(|r| !r.skipped).map(|r| r.state.clone()).unwrap_or_else(|| self.empty.clone())
|
self.results.iter().rev().find(|r| !r.skipped).map(|r| r.state.clone()).unwrap_or_else(|| self.empty.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show the sketch `feature` while it is edited although it failed (#12085): a link whose
|
||||||
|
/// named face or edge is gone fails the sketch, so the evaluation keeps no sketch for it, and
|
||||||
|
/// its editor had nothing to show or repair. Here the sketch is built as it is evaluated, but
|
||||||
|
/// with such a link marked lost and kept where it was (no other face or edge is taken for
|
||||||
|
/// it), and added to the final state only: the sketch's result keeps its error and the
|
||||||
|
/// features after it still fail. `None`, a sketch that did not fail, or one that fails for
|
||||||
|
/// another reason too, shows nothing more. Returns whether a held sketch is shown.
|
||||||
|
pub fn hold_failed_sketch(&mut self, doc: &Document, feature: Option<u64>) -> bool {
|
||||||
|
let Some(id) = feature else {
|
||||||
|
self.held = None;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let base = self.evaluated_state();
|
||||||
|
if self.held.as_ref().is_some_and(|held| held.feature == id && Arc::ptr_eq(&held.base, &base)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.held = None;
|
||||||
|
if base.sketch(id).is_some() || !self.result(id).is_some_and(|r| r.error.is_some() && !r.skipped && !r.stale) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(f) = doc.feature(id) else { return false };
|
||||||
|
let FeatureKind::Sketch { plane, sketch } = &f.kind else { return false };
|
||||||
|
let mut next = (*self.state_before(id)).clone();
|
||||||
|
let vals = evaluation_values(doc).0;
|
||||||
|
let mut warning = None;
|
||||||
|
let built = kernel::guard(&f.name, || {
|
||||||
|
let _frames = ToolFrames::install(doc, f, &next);
|
||||||
|
eval_sketch(doc, &vals, f, plane, sketch, &mut next, &mut warning, crate::project::LostNames::Mark)
|
||||||
|
.map_err(|e| kernel::KernelError::Failed(e.to_string()))
|
||||||
|
});
|
||||||
|
let Some(solved) = built.ok().and_then(|()| next.sketches.iter().find(|s| s.feature == id).cloned()) else { return false };
|
||||||
|
let mut shown = (*base).clone();
|
||||||
|
shown.sketches.retain(|s| s.feature != id);
|
||||||
|
shown.sketches.push(solved);
|
||||||
|
self.held = Some(HeldSketch { feature: id, base, shown: Arc::new(shown) });
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// State just before the feature with this id (for editing a sketch in context).
|
/// State just before the feature with this id (for editing a sketch in context).
|
||||||
pub fn state_before(&self, id: u64) -> Arc<ModelState> {
|
pub fn state_before(&self, id: u64) -> Arc<ModelState> {
|
||||||
let Some(i) = self.results.iter().position(|r| r.id == id) else { return self.state() };
|
let Some(i) = self.results.iter().position(|r| r.id == id) else { return self.state() };
|
||||||
@@ -2081,6 +2229,10 @@ impl Model {
|
|||||||
|
|
||||||
/// Re-evaluate the document, reusing results up to the first changed feature.
|
/// Re-evaluate the document, reusing results up to the first changed feature.
|
||||||
pub fn evaluate(&mut self, doc: &Document) {
|
pub fn evaluate(&mut self, doc: &Document) {
|
||||||
|
// A failed sketch held for its editor is built again from the evaluated design: when the
|
||||||
|
// sketch and every feature after it fail, the results end in the state they ended in
|
||||||
|
// before, which would keep showing the sketch as it was before the edit.
|
||||||
|
self.held = None;
|
||||||
let _span = solidcraft_profile::span("doc", "evaluate");
|
let _span = solidcraft_profile::span("doc", "evaluate");
|
||||||
self.placement_inputs = None;
|
self.placement_inputs = None;
|
||||||
self.fingerprinted = None;
|
self.fingerprinted = None;
|
||||||
@@ -2305,6 +2457,7 @@ impl Model {
|
|||||||
let r = match frame_error.or(bad_param) {
|
let r = match frame_error.or(bad_param) {
|
||||||
Some(e) => Err(kernel::KernelError::Failed(e)),
|
Some(e) => Err(kernel::KernelError::Failed(e)),
|
||||||
None => kernel::guard(&f.name, || {
|
None => kernel::guard(&f.name, || {
|
||||||
|
let _val_guard = EvalValidationGuard(EVAL_VALIDATION.replace(validation));
|
||||||
let (r, deferred) =
|
let (r, deferred) =
|
||||||
kernel::validating(validation, || eval_feature(doc, &vals, f, &mut next, &mut warning, &mut import_report, &mut repair));
|
kernel::validating(validation, || eval_feature(doc, &vals, f, &mut next, &mut warning, &mut import_report, &mut repair));
|
||||||
r.map_err(|e| match e {
|
r.map_err(|e| match e {
|
||||||
@@ -3639,6 +3792,27 @@ fn body_at(st: &ModelState, body: &Option<crate::BodyName>, points: &[Vec3]) ->
|
|||||||
.ok_or_else(|| DocError::Invalid("there is no body".into()))
|
.ok_or_else(|| DocError::Invalid("there is no body".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the whole line through `p` along `dir` (both ways) meets the box (slab test). A zero
|
||||||
|
/// component of `dir` keeps the line in its slab only when `p` is already within it.
|
||||||
|
fn line_meets_box(p: Vec3, dir: Vec3, b: &solidcraft_geom::Aabb3) -> bool {
|
||||||
|
if b.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let (mut lo, mut hi) = (f64::NEG_INFINITY, f64::INFINITY);
|
||||||
|
for (o, d, min, max) in [(p.x, dir.x, b.min.x, b.max.x), (p.y, dir.y, b.min.y, b.max.y), (p.z, dir.z, b.min.z, b.max.z)] {
|
||||||
|
if d == 0.0 || !d.is_finite() {
|
||||||
|
if !(min..=max).contains(&o) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (t0, t1) = ((min - o) / d, (max - o) / d);
|
||||||
|
lo = lo.max(t0.min(t1));
|
||||||
|
hi = hi.min(t0.max(t1));
|
||||||
|
}
|
||||||
|
lo <= hi
|
||||||
|
}
|
||||||
|
|
||||||
/// A hole stays on its face when the model changes: a position now inside a body moves back
|
/// A hole stays on its face when the model changes: a position now inside a body moves back
|
||||||
/// against the drilling direction to the surface, one now above it moves down onto it. Only the
|
/// against the drilling direction to the surface, one now above it moves down onto it. Only the
|
||||||
/// bodies in `scope` count (every body when it is empty, see [`hole_scope`]). Edit Hole seeds from
|
/// bodies in `scope` count (every body when it is empty, see [`hole_scope`]). Edit Hole seeds from
|
||||||
@@ -3649,6 +3823,12 @@ pub fn hole_on_surface(st: &ModelState, p: Vec3, dir: Vec3, scope: &[String]) ->
|
|||||||
let mut best: Option<Vec3> = None;
|
let mut best: Option<Vec3> = None;
|
||||||
for b in st.bodies.iter().filter(|b| in_hole_scope(scope, b)) {
|
for b in st.bodies.iter().filter(|b| in_hole_scope(scope, b)) {
|
||||||
let m = b.mesh();
|
let m = b.mesh();
|
||||||
|
// Every test below casts along the line through `p`: a mesh whose box that line misses
|
||||||
|
// has no triangle to hit, so skip it before scanning its triangles (in an assembly of
|
||||||
|
// hundreds of parts, these scans were most of a hole's time).
|
||||||
|
if !line_meets_box(p, dir, &m.bounds().inflated(eps * 8.0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Already on the surface: the drill enters right here.
|
// Already on the surface: the drill enters right here.
|
||||||
if let Some((t, _)) = m.raycast(p - dir * eps, dir)
|
if let Some((t, _)) = m.raycast(p - dir * eps, dir)
|
||||||
&& t < eps * 4.0
|
&& t < eps * 4.0
|
||||||
@@ -3665,15 +3845,18 @@ pub fn hole_on_surface(st: &ModelState, p: Vec3, dir: Vec3, scope: &[String]) ->
|
|||||||
best.unwrap_or(p)
|
best.unwrap_or(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The faces of the model near `p`: for each, how far `p` is from it, the outward normal there
|
/// The faces of the model near `p`, nearest first, before their surfaces are looked at: for
|
||||||
/// (the surface's own normal for planes, cylinders and spheres, turned the way the face looks out
|
/// each, how far `p` is from it, its body, its index and the outward normal of its nearest mesh
|
||||||
/// of its body; the nearest mesh facet's otherwise) and, for a plane, how far `p` is from the
|
/// facet. Only faces within `reach` of `p`, of the bodies in `scope` (every body when it is
|
||||||
/// plane itself. Only faces within `reach` of `p`, nearest first, of the bodies in `scope`
|
/// empty). Ties keep body order, then face order.
|
||||||
/// (every body when it is empty).
|
fn faces_near(st: &ModelState, p: Vec3, reach: f64, scope: &[String]) -> Vec<NearFace> {
|
||||||
fn faces_through(st: &ModelState, p: Vec3, reach: f64, scope: &[String]) -> Vec<(f64, Vec3, Option<f64>)> {
|
let mut out: Vec<NearFace> = Vec::new();
|
||||||
let mut out: Vec<(f64, Vec3, Option<f64>)> = Vec::new();
|
for (i, b) in st.bodies.iter().enumerate().filter(|(_, b)| !b.body.is_mesh() && in_hole_scope(scope, b)) {
|
||||||
for b in st.bodies.iter().filter(|b| !b.body.is_mesh() && in_hole_scope(scope, b)) {
|
|
||||||
let m = b.mesh();
|
let m = b.mesh();
|
||||||
|
// No facet is nearer `p` than the mesh's box: one beyond `reach` has none to give.
|
||||||
|
if !m.bounds().inflated(reach).contains(p) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// The nearest facet of each face of this body.
|
// The nearest facet of each face of this body.
|
||||||
let mut near: BTreeMap<u32, (f64, Vec3)> = BTreeMap::new();
|
let mut near: BTreeMap<u32, (f64, Vec3)> = BTreeMap::new();
|
||||||
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
|
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
|
||||||
@@ -3684,53 +3867,67 @@ fn faces_through(st: &ModelState, p: Vec3, reach: f64, scope: &[String]) -> Vec<
|
|||||||
near.insert(*f, (d, n));
|
near.insert(*f, (d, n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (f, (d, facet)) in near {
|
out.extend(near.into_iter().map(|(f, (d, n))| (d, i, f, n)));
|
||||||
let (exact, off) = match kernel::face_surface(&b.body, f as usize) {
|
|
||||||
Some(kernel::FaceSurface::Plane { normal, point }) => (Some(*normal), Some((p - point).dot(normal).abs())),
|
|
||||||
Some(kernel::FaceSurface::Cylinder { origin, axis, .. }) => {
|
|
||||||
let v = p - origin;
|
|
||||||
((v - axis * v.dot(axis)).normalized(), None)
|
|
||||||
}
|
|
||||||
Some(kernel::FaceSurface::Sphere { center, .. }) => ((p - center).normalized(), None),
|
|
||||||
// Square to the line through the apex, not the nearest facet's normal (#10698).
|
|
||||||
Some(kernel::FaceSurface::Cone { apex, axis, half_angle }) => {
|
|
||||||
let v = p - apex;
|
|
||||||
let out = (v - axis * v.dot(axis)).normalized();
|
|
||||||
(out.and_then(|u| (u * half_angle.cos() - axis * half_angle.sin()).normalized()), None)
|
|
||||||
}
|
|
||||||
// Away from the nearest point of the ring the tube goes round.
|
|
||||||
Some(kernel::FaceSurface::Torus { center, axis, major, .. }) => {
|
|
||||||
let v = p - center;
|
|
||||||
let ring = (v - axis * v.dot(axis)).normalized().map(|u| center + u * major);
|
|
||||||
(ring.and_then(|q| (p - q).normalized()), None)
|
|
||||||
}
|
|
||||||
_ => (None, None),
|
|
||||||
};
|
|
||||||
let n = match exact {
|
|
||||||
Some(n) if n.dot(facet) < 0.0 => -n,
|
|
||||||
Some(n) => n,
|
|
||||||
None => facet,
|
|
||||||
};
|
|
||||||
out.push((d, n, off));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
out.sort_by(|a, b| a.0.total_cmp(&b.0));
|
out.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The faces near `p` a hole placed there goes by (as [`faces_through`]): those of the bodies in
|
/// A face of [`faces_near`]: its distance from the point, its body, its index and the outward
|
||||||
|
/// normal of its nearest mesh facet.
|
||||||
|
type NearFace = (f64, usize, u32, Vec3);
|
||||||
|
|
||||||
|
/// One face of [`faces_near`] as [`faces_through`] gives it: the distance, the outward normal
|
||||||
|
/// (the surface's own normal for planes, cylinders, spheres, cones and tori, turned the way the
|
||||||
|
/// face looks out of its body; the nearest mesh facet's otherwise) and, for a plane, how far `p`
|
||||||
|
/// is from the plane itself. Classifying the surface is the costly part, so callers that stop at
|
||||||
|
/// the first face that answers them ask for no more than that.
|
||||||
|
fn face_through(st: &ModelState, p: Vec3, (d, body, f, facet): NearFace) -> (f64, Vec3, Option<f64>) {
|
||||||
|
#[cfg(test)]
|
||||||
|
cost_tests::count(|c| c.surfaces += 1);
|
||||||
|
let exact = st.bodies.get(body).and_then(|b| kernel::face_surface(&b.body, f as usize));
|
||||||
|
let (exact, off) = match exact {
|
||||||
|
Some(kernel::FaceSurface::Plane { normal, point }) => (Some(*normal), Some((p - point).dot(normal).abs())),
|
||||||
|
Some(kernel::FaceSurface::Cylinder { origin, axis, .. }) => {
|
||||||
|
let v = p - origin;
|
||||||
|
((v - axis * v.dot(axis)).normalized(), None)
|
||||||
|
}
|
||||||
|
Some(kernel::FaceSurface::Sphere { center, .. }) => ((p - center).normalized(), None),
|
||||||
|
// Square to the line through the apex, not the nearest facet's normal (#10698).
|
||||||
|
Some(kernel::FaceSurface::Cone { apex, axis, half_angle }) => {
|
||||||
|
let v = p - apex;
|
||||||
|
let out = (v - axis * v.dot(axis)).normalized();
|
||||||
|
(out.and_then(|u| (u * half_angle.cos() - axis * half_angle.sin()).normalized()), None)
|
||||||
|
}
|
||||||
|
// Away from the nearest point of the ring the tube goes round.
|
||||||
|
Some(kernel::FaceSurface::Torus { center, axis, major, .. }) => {
|
||||||
|
let v = p - center;
|
||||||
|
let ring = (v - axis * v.dot(axis)).normalized().map(|u| center + u * major);
|
||||||
|
(ring.and_then(|q| (p - q).normalized()), None)
|
||||||
|
}
|
||||||
|
_ => (None, None),
|
||||||
|
};
|
||||||
|
let n = match exact {
|
||||||
|
Some(n) if n.dot(facet) < 0.0 => -n,
|
||||||
|
Some(n) => n,
|
||||||
|
None => facet,
|
||||||
|
};
|
||||||
|
(d, n, off)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The faces near `p` a hole placed there goes by (as [`faces_near`]): those of the bodies in
|
||||||
/// `scope`, the hole's own, when one of them passes within `on` of `p`, else those of every body.
|
/// `scope`, the hole's own, when one of them passes within `on` of `p`, else those of every body.
|
||||||
/// Where a plate's face touches another body both faces pass through `p`, and a hole that drills
|
/// Where a plate's face touches another body both faces pass through `p`, and a hole that drills
|
||||||
/// the plate goes by the plate's face, never the other body's (#12066). A hole placed on another
|
/// the plate goes by the plate's face, never the other body's (#12066). A hole placed on another
|
||||||
/// body's face, to drill through it into its own body behind, still goes by that face.
|
/// body's face, to drill through it into its own body behind, still goes by that face.
|
||||||
fn hole_faces_through(st: &ModelState, p: Vec3, reach: f64, on: f64, scope: &[String]) -> Vec<(f64, Vec3, Option<f64>)> {
|
fn hole_faces_near(st: &ModelState, p: Vec3, reach: f64, on: f64, scope: &[String]) -> Vec<NearFace> {
|
||||||
if !scope.is_empty() {
|
if !scope.is_empty() {
|
||||||
let own = faces_through(st, p, reach, scope);
|
let own = faces_near(st, p, reach, scope);
|
||||||
if own.first().is_some_and(|(d, _, _)| *d <= on) {
|
if own.first().is_some_and(|c| c.0 <= on) {
|
||||||
return own;
|
return own;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
faces_through(st, p, reach, &[])
|
faces_near(st, p, reach, &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The way a hole placed at `p` drills: into the material, against the outward normal of the
|
/// The way a hole placed at `p` drills: into the material, against the outward normal of the
|
||||||
@@ -3740,17 +3937,22 @@ fn hole_faces_through(st: &ModelState, p: Vec3, reach: f64, on: f64, scope: &[St
|
|||||||
/// of the bodies in `scope` (the bodies the hole drills; empty for every body) decide.
|
/// of the bodies in `scope` (the bodies the hole drills; empty for every body) decide.
|
||||||
pub fn hole_direction_at(st: &ModelState, p: Vec3, scope: &[String]) -> Option<Vec3> {
|
pub fn hole_direction_at(st: &ModelState, p: Vec3, scope: &[String]) -> Option<Vec3> {
|
||||||
let size = st.bodies.iter().map(|b| b.body.size()).fold(1.0, f64::max);
|
let size = st.bodies.iter().map(|b| b.body.size()).fold(1.0, f64::max);
|
||||||
let faces = hole_faces_through(st, p, f64::INFINITY, size * 1e-4, scope);
|
// Every face of the model, nearest first; each surface is classified only when the rules
|
||||||
if let Some((d, n, _)) = faces.first()
|
// below reach it. Classifying all of them first took most of a hole's time in an assembly
|
||||||
&& *d <= size * 1e-4
|
// of hundreds of parts, where the nearest face nearly always answers.
|
||||||
|
let near = hole_faces_near(st, p, f64::INFINITY, size * 1e-4, scope);
|
||||||
|
let mut faces = near.iter().map(|c| face_through(st, p, *c));
|
||||||
|
let first = faces.next();
|
||||||
|
if let Some((d, n, _)) = first
|
||||||
|
&& d <= size * 1e-4
|
||||||
{
|
{
|
||||||
return Some(-*n);
|
return Some(-n);
|
||||||
}
|
}
|
||||||
if let Some((_, n, _)) = faces.iter().find(|(_, _, off)| off.is_some_and(|o| o <= size * 1e-4)) {
|
if let Some((_, n, _)) = first.into_iter().chain(faces).find(|(_, _, off)| off.is_some_and(|o| o <= size * 1e-4)) {
|
||||||
return Some(-*n);
|
return Some(-n);
|
||||||
}
|
}
|
||||||
let reach = st.bodies.iter().map(crate::appearance::pick_tolerance).fold(0.0, f64::max);
|
let reach = st.bodies.iter().map(crate::appearance::pick_tolerance).fold(0.0, f64::max);
|
||||||
faces.first().filter(|(d, _, _)| *d <= reach).map(|(_, n, _)| -*n)
|
first.filter(|(d, _, _)| *d <= reach).map(|(_, n, _)| -n)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A hole placed on a face drills into the material: when its direction leaves every face it
|
/// A hole placed on a face drills into the material: when its direction leaves every face it
|
||||||
@@ -3763,7 +3965,7 @@ pub fn hole_direction_at(st: &ModelState, p: Vec3, scope: &[String]) -> Option<V
|
|||||||
/// the plate there, drills the plate (#12066).
|
/// the plate there, drills the plate (#12066).
|
||||||
fn hole_dir_into_face(st: &ModelState, p: Vec3, dir: Vec3, scope: &[String]) -> Vec3 {
|
fn hole_dir_into_face(st: &ModelState, p: Vec3, dir: Vec3, scope: &[String]) -> Vec3 {
|
||||||
let size = st.bodies.iter().map(|b| b.body.size()).fold(1.0, f64::max);
|
let size = st.bodies.iter().map(|b| b.body.size()).fold(1.0, f64::max);
|
||||||
let faces = hole_faces_through(st, p, size * 1e-5, size * 1e-5, scope);
|
let faces: Vec<_> = hole_faces_near(st, p, size * 1e-5, size * 1e-5, scope).into_iter().map(|c| face_through(st, p, c)).collect();
|
||||||
if faces.is_empty() || faces.iter().any(|(_, n, _)| n.dot(dir) < -1e-9) {
|
if faces.is_empty() || faces.iter().any(|(_, n, _)| n.dot(dir) < -1e-9) {
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
@@ -5700,13 +5902,13 @@ fn feature_tools_with_constructor(
|
|||||||
FeatureKind::Torus { center, major, minor, .. } => {
|
FeatureKind::Torus { center, major, minor, .. } => {
|
||||||
Ok(unnamed(vec![kernel::torus(point(vals, center)?, val(vals, major, Kind::Length)?, val(vals, minor, Kind::Length)?)?]))
|
Ok(unnamed(vec![kernel::torus(point(vals, center)?, val(vals, major, Kind::Length)?, val(vals, minor, Kind::Length)?)?]))
|
||||||
}
|
}
|
||||||
FeatureKind::Cone { base, axis, radius, top_radius, height, .. } => Ok(unnamed(vec![more::cone(
|
FeatureKind::Cone { base, axis, radius, top_radius, height, .. } => Ok(vec![more::cone(
|
||||||
point(vals, base)?,
|
point(vals, base)?,
|
||||||
axis.0,
|
axis.0,
|
||||||
val(vals, radius, Kind::Length)?,
|
val(vals, radius, Kind::Length)?,
|
||||||
val(vals, top_radius, Kind::Length)?,
|
val(vals, top_radius, Kind::Length)?,
|
||||||
val(vals, height, Kind::Length)?,
|
val(vals, height, Kind::Length)?,
|
||||||
)?])),
|
)?]),
|
||||||
FeatureKind::Hole { .. } => Ok(hole_feature_tools(vals, st, f)?.into_iter().map(|(tool, _)| tool).collect()),
|
FeatureKind::Hole { .. } => Ok(hole_feature_tools(vals, st, f)?.into_iter().map(|(tool, _)| tool).collect()),
|
||||||
_ => Err(DocError::Invalid(format!("{} cannot be patterned or mirrored yet", f.name))),
|
_ => Err(DocError::Invalid(format!("{} cannot be patterned or mirrored yet", f.name))),
|
||||||
}
|
}
|
||||||
@@ -6038,15 +6240,28 @@ fn from_profile_on_closed_path(segs: Vec<solidcore_record::Seg2>, path: &Plane,
|
|||||||
/// Where an extrude of distance `d` starts and ends along the sketch normal, before any start
|
/// Where an extrude of distance `d` starts and ends along the sketch normal, before any start
|
||||||
/// offset: one side, the other, both (symmetric), or a second distance on the other side.
|
/// offset: one side, the other, both (symmetric), or a second distance on the other side.
|
||||||
///
|
///
|
||||||
/// A second distance (`distance2`) goes the other way from the first side, wherever that went:
|
/// A second distance (`distance2`) goes the other way from the first side: against the normal,
|
||||||
/// against the normal for `direction: negative` (#2700), and back along it when a negative
|
/// or along it for `direction: negative` (#2700). Entered distances keep those sides whatever
|
||||||
/// distance or a To Object below the sketch sends the first side down.
|
/// their signs; a To Object below the sketch or a Through All that reaches down sends the first
|
||||||
|
/// side down and the second back along the normal.
|
||||||
pub(crate) fn extent_sides(vals: &BTreeMap<String, Value>, extent: &crate::Extent, d: f64, direction: crate::Direction) -> Result<(f64, f64)> {
|
pub(crate) fn extent_sides(vals: &BTreeMap<String, Value>, extent: &crate::Extent, d: f64, direction: crate::Direction) -> Result<(f64, f64)> {
|
||||||
Ok(match (&extent.distance2, direction) {
|
Ok(match (&extent.distance2, direction) {
|
||||||
(Some(d2), _) => {
|
(Some(d2), _) => {
|
||||||
let first = if direction == crate::Direction::Negative { -d } else { d };
|
let negative = direction == crate::Direction::Negative;
|
||||||
|
let first = if negative { -d } else { d };
|
||||||
let d2 = val(vals, d2, Kind::Length)?;
|
let d2 = val(vals, d2, Kind::Length)?;
|
||||||
(if first < 0.0 { d2 } else { -d2 }, first)
|
// An entered distance keeps its side whatever its sign: Direction fixes the sides,
|
||||||
|
// and a sign only turns its own side over. A reach computed from the model (To
|
||||||
|
// Object, Through All) has the sign of where the object is, so the other side
|
||||||
|
// follows it.
|
||||||
|
let second = if extent.second_follows_first || extent.to.is_some() || extent.through_all {
|
||||||
|
if first < 0.0 { d2 } else { -d2 }
|
||||||
|
} else if negative {
|
||||||
|
d2
|
||||||
|
} else {
|
||||||
|
-d2
|
||||||
|
};
|
||||||
|
(second, first)
|
||||||
}
|
}
|
||||||
(None, crate::Direction::Positive) => (0.0, d),
|
(None, crate::Direction::Positive) => (0.0, d),
|
||||||
(None, crate::Direction::Negative) => (-d, 0.0),
|
(None, crate::Direction::Negative) => (-d, 0.0),
|
||||||
@@ -6726,6 +6941,24 @@ fn replay(
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// A moved copy is copied again as a body: each instance is the copy the Move made,
|
||||||
|
// placed by the pattern (#5931).
|
||||||
|
if let FeatureKind::Move { copy: true, .. } = &src.kind {
|
||||||
|
let made: Vec<ModelBody> = st.bodies.iter().filter(|b| b.feature == src.id).cloned().collect();
|
||||||
|
if made.is_empty() {
|
||||||
|
return Err(DocError::Invalid(format!("{} made no copy to repeat", src.name)));
|
||||||
|
}
|
||||||
|
let mut output = 0;
|
||||||
|
for (k, m) in mats.iter().enumerate() {
|
||||||
|
for mb in &made {
|
||||||
|
let moved = moved_copy(mb, kernel::transform_matrix(&mb.body, *m)?, first + k + 1);
|
||||||
|
let name = copy_body_name(st, f, output, &mb.name);
|
||||||
|
st.bodies.push(ModelBody::traced_new(name, moved, f.id));
|
||||||
|
output += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let (op, targets) = feature_op(src);
|
let (op, targets) = feature_op(src);
|
||||||
// A primitive's or a hole's targets are its participants (#1108): the copies change
|
// A primitive's or a hole's targets are its participants (#1108): the copies change
|
||||||
// those bodies only, like the feature itself (#2755).
|
// those bodies only, like the feature itself (#2755).
|
||||||
@@ -7088,12 +7321,14 @@ pub(crate) fn sheet_face_plane_in_frame(doc: &Document, st: &ModelState, comp: u
|
|||||||
PlaneRef::Offset { base, distance } => {
|
PlaneRef::Offset { base, distance } => {
|
||||||
PlaneRef::Offset { base: Box::new(sheet_face_plane_in_frame(doc, st, comp, base)?), distance: distance.clone() }
|
PlaneRef::Offset { base: Box::new(sheet_face_plane_in_frame(doc, st, comp, base)?), distance: distance.clone() }
|
||||||
}
|
}
|
||||||
PlaneRef::AtAngle { base, axis_origin, axis_dir, angle, line } => PlaneRef::AtAngle {
|
PlaneRef::AtAngle { base, axis_origin, axis_dir, angle, line, direction_only, pivot } => PlaneRef::AtAngle {
|
||||||
base: Box::new(sheet_face_plane_in_frame(doc, st, comp, base)?),
|
base: Box::new(sheet_face_plane_in_frame(doc, st, comp, base)?),
|
||||||
axis_origin: *axis_origin,
|
axis_origin: *axis_origin,
|
||||||
axis_dir: *axis_dir,
|
axis_dir: *axis_dir,
|
||||||
angle: angle.clone(),
|
angle: angle.clone(),
|
||||||
line: line.clone(),
|
line: line.clone(),
|
||||||
|
direction_only: *direction_only,
|
||||||
|
pivot: pivot.clone(),
|
||||||
},
|
},
|
||||||
PlaneRef::Flipped { base, about_y } => {
|
PlaneRef::Flipped { base, about_y } => {
|
||||||
PlaneRef::Flipped { base: Box::new(sheet_face_plane_in_frame(doc, st, comp, base)?), about_y: *about_y }
|
PlaneRef::Flipped { base: Box::new(sheet_face_plane_in_frame(doc, st, comp, base)?), about_y: *about_y }
|
||||||
@@ -7194,6 +7429,7 @@ fn eval_feature(
|
|||||||
// Its tools meet bodies in other placements where those are shown (#3383).
|
// Its tools meet bodies in other placements where those are shown (#3383).
|
||||||
let _frames = ToolFrames::install(doc, f, st);
|
let _frames = ToolFrames::install(doc, f, st);
|
||||||
match &f.kind {
|
match &f.kind {
|
||||||
|
FeatureKind::NewerFileFeature { raw } => Err(DocError::Invalid(raw.why())),
|
||||||
FeatureKind::BodyTransfer { bodies, from, to, transform } => {
|
FeatureKind::BodyTransfer { bodies, from, to, transform } => {
|
||||||
crate::body_transfer::evaluate(doc, st, &crate::plain(bodies), *from, *to, *transform)
|
crate::body_transfer::evaluate(doc, st, &crate::plain(bodies), *from, *to, *transform)
|
||||||
}
|
}
|
||||||
@@ -7232,65 +7468,8 @@ fn eval_feature(
|
|||||||
| FeatureKind::Rib { .. }
|
| FeatureKind::Rib { .. }
|
||||||
| FeatureKind::ReplaceFace { .. }
|
| FeatureKind::ReplaceFace { .. }
|
||||||
| FeatureKind::Align { .. }
|
| FeatureKind::Align { .. }
|
||||||
| FeatureKind::Remove { .. } => more::eval(doc, vals, f, st),
|
| FeatureKind::Remove { .. } => more::eval(doc, vals, f, st, warning),
|
||||||
FeatureKind::Sketch { plane, sketch } => {
|
FeatureKind::Sketch { plane, sketch } => eval_sketch(doc, vals, f, plane, sketch, st, warning, crate::project::LostNames::Fail),
|
||||||
let anchor = crate::face_anchor::plane_face_anchor(plane);
|
|
||||||
let fst = match anchor {
|
|
||||||
Some(anchor) => crate::state_in_face_anchor_frame(doc, st, anchor, f.component)?,
|
|
||||||
// A sketch that reads no geometry (a fixed plane, no projections) needs no other
|
|
||||||
// component's bodies moved into its frame: placing every body of a large
|
|
||||||
// assembly again is what made each sketch edit as slow as the design is big.
|
|
||||||
None if !plane_reads_geometry(plane) && sketch.links.is_empty() => std::borrow::Cow::Borrowed(st),
|
|
||||||
None => state_in_frame(doc, st, f.component)?,
|
|
||||||
};
|
|
||||||
let (plane, face_warning) = match (anchor, plane) {
|
|
||||||
(Some(_), plane) => doc.resolve_attached_plane_noted(vals, plane, f.component, st, &fst)?,
|
|
||||||
// A flat face of a sheet: the panel's exact plane, from its flat region and fold.
|
|
||||||
(None, plane) if crate::sheet::plane_sheet_face(plane).is_some() => {
|
|
||||||
(doc.resolve_plane_in(vals, &sheet_face_plane_in_frame(doc, st, f.component, plane)?, 0, Some(&fst))?, None)
|
|
||||||
}
|
|
||||||
(None, PlaneRef::Face { plane: picked, at, name, anchor: None }) => face_plane(&fst, picked, *at, name.as_deref())?,
|
|
||||||
(None, other) => (doc.resolve_plane_in(vals, other, 0, Some(&fst))?, None),
|
|
||||||
};
|
|
||||||
let mut sk = sketch.clone();
|
|
||||||
// A text's outlines are rebuilt from its numbers when the links are refreshed: the
|
|
||||||
// ones its expressions give come first.
|
|
||||||
doc.apply_text_values(vals, &mut sk)?;
|
|
||||||
let mut warns = match anchor {
|
|
||||||
Some(anchor) => crate::project::refresh_face_anchor_links(doc, vals, &fst, &plane, &mut sk, anchor)?,
|
|
||||||
None => crate::project::refresh_links(doc, vals, &fst, &plane, &mut sk)?,
|
|
||||||
};
|
|
||||||
drop(fst);
|
|
||||||
sk.refresh_offsets()?;
|
|
||||||
if let Some(w) = face_warning {
|
|
||||||
warns.insert(0, w);
|
|
||||||
}
|
|
||||||
// The stored sketch is solved for its stored values, with its links where they
|
|
||||||
// were stored: a link that moved must not change the side a dimension from it is on.
|
|
||||||
let stored = sk.driving_values();
|
|
||||||
let report = doc.solve_sketch(vals, &mut sk, &stored, Some(sketch))?;
|
|
||||||
if !report.keeps_orientation() {
|
|
||||||
warns.insert(0, report.failure(|f| format!("the sketch constraints conflict ({f})")));
|
|
||||||
} else {
|
|
||||||
// The solve may have moved the curve a text runs along.
|
|
||||||
warns.extend(crate::project::relay_path_texts(&mut sk));
|
|
||||||
}
|
|
||||||
if !warns.is_empty() {
|
|
||||||
*warning = Some(warns.join("; "));
|
|
||||||
}
|
|
||||||
sk.validate_periodic_splines()?;
|
|
||||||
// A group of touching curves too large to search whole says so, rather than
|
|
||||||
// leaving profiles out silently (#6896).
|
|
||||||
let search = solidcraft_sketch::find_profiles_checked(&sk);
|
|
||||||
if search.incomplete {
|
|
||||||
append_warning(warning, profiles_cut_short_note());
|
|
||||||
}
|
|
||||||
let profiles = search.profiles;
|
|
||||||
let ends = solidcraft_sketch::loose_ends(&sk);
|
|
||||||
st.sketches.retain(|s| s.feature != f.id);
|
|
||||||
st.sketches.push(Arc::new(SolvedSketch { feature: f.id, name: f.name.clone(), plane, sketch: sk, report, profiles, ends }));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
FeatureKind::Extrude { operation, targets, .. }
|
FeatureKind::Extrude { operation, targets, .. }
|
||||||
| FeatureKind::Pipe { operation, targets, .. }
|
| FeatureKind::Pipe { operation, targets, .. }
|
||||||
| FeatureKind::Revolve { operation, targets, .. }
|
| FeatureKind::Revolve { operation, targets, .. }
|
||||||
@@ -7884,8 +8063,8 @@ fn eval_feature(
|
|||||||
FeatureKind::Cone { base, axis, radius, top_radius, height, operation } => {
|
FeatureKind::Cone { base, axis, radius, top_radius, height, operation } => {
|
||||||
let (radius, top_radius, height) =
|
let (radius, top_radius, height) =
|
||||||
(val(vals, radius, Kind::Length)?, val(vals, top_radius, Kind::Length)?, val(vals, height, Kind::Length)?);
|
(val(vals, radius, Kind::Length)?, val(vals, top_radius, Kind::Length)?, val(vals, height, Kind::Length)?);
|
||||||
let b = more::cone(point(vals, base)?, axis.0, radius, top_radius, height)?;
|
let tool = more::cone(point(vals, base)?, axis.0, radius, top_radius, height)?;
|
||||||
apply_op(doc, st, f, vec![b], *operation, &[])
|
apply_traced(doc, st, f, vec![tool], *operation, &[])
|
||||||
}
|
}
|
||||||
FeatureKind::Combine { target, tools, operation, keep_tools } => {
|
FeatureKind::Combine { target, tools, operation, keep_tools } => {
|
||||||
if tools.iter().any(|t| t == target) {
|
if tools.iter().any(|t| t == target) {
|
||||||
@@ -8109,6 +8288,88 @@ fn eval_feature(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A sketch feature: its links refreshed against the model before it, solved, its profiles
|
||||||
|
/// found. `lost_names` says what a link whose named face or edge is gone does: the evaluation
|
||||||
|
/// fails the sketch ([`crate::project::LostNames::Fail`]); a failed sketch shown for editing
|
||||||
|
/// marks the link lost instead ([`Model::hold_failed_sketch`]).
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn eval_sketch(
|
||||||
|
doc: &Document,
|
||||||
|
vals: &BTreeMap<String, Value>,
|
||||||
|
f: &Feature,
|
||||||
|
plane: &PlaneRef,
|
||||||
|
sketch: &solidcraft_sketch::Sketch,
|
||||||
|
st: &mut ModelState,
|
||||||
|
warning: &mut Option<String>,
|
||||||
|
lost_names: crate::project::LostNames,
|
||||||
|
) -> Result<()> {
|
||||||
|
let anchor = crate::face_anchor::plane_face_anchor(plane);
|
||||||
|
let fst = match anchor {
|
||||||
|
Some(anchor) => crate::state_in_face_anchor_frame(doc, st, anchor, f.component)?,
|
||||||
|
// A sketch that reads no geometry (a fixed plane, no projections) needs no other
|
||||||
|
// component's bodies moved into its frame: placing every body of a large
|
||||||
|
// assembly again is what made each sketch edit as slow as the design is big.
|
||||||
|
None if !plane_reads_geometry(plane) && sketch.links.is_empty() => std::borrow::Cow::Borrowed(st),
|
||||||
|
None => state_in_frame(doc, st, f.component)?,
|
||||||
|
};
|
||||||
|
// A further placement a link reads is placed in its frame too (#9957).
|
||||||
|
let named: Vec<&str> = sketch.links.iter().flat_map(|l| l.source.bodies()).collect();
|
||||||
|
let fst = match anchor.map(|anchor| anchor.author_frame(doc, f.component)) {
|
||||||
|
_ if named.is_empty() => fst,
|
||||||
|
Some(Ok(frame)) => with_placed_copies(doc, st, fst, &frame, &named),
|
||||||
|
Some(Err(_)) => fst,
|
||||||
|
None => with_placed_copies(doc, st, fst, &doc.component_transform(f.component), &named),
|
||||||
|
};
|
||||||
|
let (plane, face_warning) = match (anchor, plane) {
|
||||||
|
(Some(_), plane) => doc.resolve_attached_plane_noted(vals, plane, f.component, st, &fst)?,
|
||||||
|
// A flat face of a sheet: the panel's exact plane, from its flat region and fold.
|
||||||
|
(None, plane) if crate::sheet::plane_sheet_face(plane).is_some() => {
|
||||||
|
(doc.resolve_plane_in(vals, &sheet_face_plane_in_frame(doc, st, f.component, plane)?, 0, Some(&fst))?, None)
|
||||||
|
}
|
||||||
|
(None, PlaneRef::Face { plane: picked, at, name, anchor: None }) => face_plane(&fst, picked, *at, name.as_deref())?,
|
||||||
|
(None, other) => (doc.resolve_plane_in(vals, other, 0, Some(&fst))?, None),
|
||||||
|
};
|
||||||
|
let mut sk = sketch.clone();
|
||||||
|
// A text's outlines are rebuilt from its numbers when the links are refreshed: the
|
||||||
|
// ones its expressions give come first.
|
||||||
|
doc.apply_text_values(vals, &mut sk)?;
|
||||||
|
let mut warns = match (anchor, lost_names) {
|
||||||
|
(Some(anchor), crate::project::LostNames::Fail) => crate::project::refresh_face_anchor_links(doc, vals, &fst, &plane, &mut sk, anchor)?,
|
||||||
|
(None, crate::project::LostNames::Fail) => crate::project::refresh_links(doc, vals, &fst, &plane, &mut sk)?,
|
||||||
|
(anchor, crate::project::LostNames::Mark) => crate::project::refresh_links_marking_lost(doc, vals, &fst, &plane, &mut sk, anchor)?,
|
||||||
|
};
|
||||||
|
drop(fst);
|
||||||
|
sk.refresh_offsets()?;
|
||||||
|
if let Some(w) = face_warning {
|
||||||
|
warns.insert(0, w);
|
||||||
|
}
|
||||||
|
// The stored sketch is solved for its stored values, with its links where they
|
||||||
|
// were stored: a link that moved must not change the side a dimension from it is on.
|
||||||
|
let stored = sk.driving_values();
|
||||||
|
let report = doc.solve_sketch(vals, &mut sk, &stored, Some(sketch))?;
|
||||||
|
if !report.keeps_orientation() {
|
||||||
|
warns.insert(0, report.failure(|f| format!("the sketch constraints conflict ({f})")));
|
||||||
|
} else {
|
||||||
|
// The solve may have moved the curve a text runs along.
|
||||||
|
warns.extend(crate::project::relay_path_texts(&mut sk));
|
||||||
|
}
|
||||||
|
if !warns.is_empty() {
|
||||||
|
*warning = Some(warns.join("; "));
|
||||||
|
}
|
||||||
|
sk.validate_periodic_splines()?;
|
||||||
|
// A group of touching curves too large to search whole says so, rather than
|
||||||
|
// leaving profiles out silently (#6896).
|
||||||
|
let search = solidcraft_sketch::find_profiles_checked(&sk);
|
||||||
|
if search.incomplete {
|
||||||
|
append_warning(warning, profiles_cut_short_note());
|
||||||
|
}
|
||||||
|
let profiles = search.profiles;
|
||||||
|
let ends = solidcraft_sketch::loose_ends(&sk);
|
||||||
|
st.sketches.retain(|s| s.feature != f.id);
|
||||||
|
st.sketches.push(Arc::new(SolvedSketch { feature: f.id, name: f.name.clone(), plane, sketch: sk, report, profiles, ends }));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// `copy`, a moved copy of `source` (instance `instance` of a pattern or mirror): its faces are
|
/// `copy`, a moved copy of `source` (instance `instance` of a pattern or mirror): its faces are
|
||||||
/// named after the source's.
|
/// named after the source's.
|
||||||
fn moved_copy(source: &ModelBody, copy: Body, instance: usize) -> Traced {
|
fn moved_copy(source: &ModelBody, copy: Body, instance: usize) -> Traced {
|
||||||
@@ -8234,7 +8495,7 @@ fn move_constructions_with(doc: &Document, st: &mut ModelState, moved: &[crate::
|
|||||||
fn plane_is_fixed(plane: &PlaneRef) -> bool {
|
fn plane_is_fixed(plane: &PlaneRef) -> bool {
|
||||||
match plane {
|
match plane {
|
||||||
PlaneRef::Origin { .. } | PlaneRef::Custom { .. } | PlaneRef::Placed { .. } => true,
|
PlaneRef::Origin { .. } | PlaneRef::Custom { .. } | PlaneRef::Placed { .. } => true,
|
||||||
PlaneRef::AtAngle { line: Some(_), .. } => false,
|
PlaneRef::AtAngle { line: Some(_), .. } | PlaneRef::AtAngle { pivot: Some(_), .. } => false,
|
||||||
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } | PlaneRef::Flipped { base, .. } => plane_is_fixed(base),
|
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } | PlaneRef::Flipped { base, .. } => plane_is_fixed(base),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@@ -8245,7 +8506,7 @@ fn plane_is_fixed(plane: &PlaneRef) -> bool {
|
|||||||
fn plane_reads_geometry(plane: &PlaneRef) -> bool {
|
fn plane_reads_geometry(plane: &PlaneRef) -> bool {
|
||||||
match plane {
|
match plane {
|
||||||
PlaneRef::Origin { .. } | PlaneRef::Custom { .. } | PlaneRef::Placed { .. } | PlaneRef::Construction { .. } => false,
|
PlaneRef::Origin { .. } | PlaneRef::Custom { .. } | PlaneRef::Placed { .. } | PlaneRef::Construction { .. } => false,
|
||||||
PlaneRef::AtAngle { line: Some(_), .. } => true,
|
PlaneRef::AtAngle { line: Some(_), .. } | PlaneRef::AtAngle { pivot: Some(_), .. } => true,
|
||||||
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } | PlaneRef::Flipped { base, .. } => plane_reads_geometry(base),
|
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } | PlaneRef::Flipped { base, .. } => plane_reads_geometry(base),
|
||||||
PlaneRef::ThroughPoint { .. }
|
PlaneRef::ThroughPoint { .. }
|
||||||
| PlaneRef::Midplane { .. }
|
| PlaneRef::Midplane { .. }
|
||||||
@@ -8480,6 +8741,51 @@ pub fn state_in_frame_at<'a>(doc: &Document, st: &'a ModelState, comp: u64, fram
|
|||||||
Ok(Cow::Owned(out))
|
Ok(Cow::Owned(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `framed` (the model `st` in the frame that `frame` places in the world) with the further
|
||||||
|
/// placements `names` names (`Block (Part:2)`), where they are in the world. A component's model
|
||||||
|
/// holds its first placement only, so a sketch projecting a copy's face found no body of that
|
||||||
|
/// name and took the first placement's (#9957). Only the named ones are placed: placing every
|
||||||
|
/// body of an assembly for each sketch made sketch edits as slow as the design is big.
|
||||||
|
pub fn with_placed_copies<'a>(
|
||||||
|
doc: &Document,
|
||||||
|
st: &ModelState,
|
||||||
|
framed: std::borrow::Cow<'a, ModelState>,
|
||||||
|
frame: &Mat,
|
||||||
|
names: &[&str],
|
||||||
|
) -> std::borrow::Cow<'a, ModelState> {
|
||||||
|
let missing: Vec<&str> = names.iter().copied().filter(|n| !n.is_empty() && framed.body(n).is_none()).collect();
|
||||||
|
if missing.is_empty() || doc.occurrences.is_empty() {
|
||||||
|
return framed;
|
||||||
|
}
|
||||||
|
let Some(inv) = crate::mat_inverse(frame) else { return framed };
|
||||||
|
let mut by_component: std::collections::HashMap<u64, Vec<&ModelBody>> = std::collections::HashMap::new();
|
||||||
|
for (b, owner) in st.bodies.iter().zip(doc.model_body_components(st)) {
|
||||||
|
by_component.entry(owner).or_default().push(b);
|
||||||
|
}
|
||||||
|
let world = crate::WorldNames::new(doc, st);
|
||||||
|
let mut placed: Vec<ModelBody> = Vec::new();
|
||||||
|
for (comp, path, m) in doc.placements() {
|
||||||
|
for b in by_component.get(&comp).into_iter().flatten() {
|
||||||
|
let name = world.name(&b.name, comp, &path);
|
||||||
|
if !missing.contains(&name.as_str()) || placed.iter().any(|p| p.name == name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let bm = doc.model_body_offset(b).map(|o| crate::mat_mul(&m, o)).unwrap_or(m);
|
||||||
|
if let Ok(mut copy) = st.body_in_frame(b, &crate::mat_mul(&inv, &bm)) {
|
||||||
|
copy.name = name;
|
||||||
|
copy.source_name = Some(b.definition_name().to_string());
|
||||||
|
placed.push(copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if placed.is_empty() {
|
||||||
|
return framed;
|
||||||
|
}
|
||||||
|
let mut out = framed.into_owned();
|
||||||
|
out.bodies.extend(placed);
|
||||||
|
std::borrow::Cow::Owned(out)
|
||||||
|
}
|
||||||
|
|
||||||
/// The model placed in the world: each component's bodies, sketches, threads and construction
|
/// The model placed in the world: each component's bodies, sketches, threads and construction
|
||||||
/// geometry through every occurrence of it (bodies of the first placement keep their names;
|
/// geometry through every occurrence of it (bodies of the first placement keep their names;
|
||||||
/// further instances get the occurrence path in brackets). Without components this is the model
|
/// further instances get the occurrence path in brackets). Without components this is the model
|
||||||
@@ -8539,6 +8845,7 @@ impl PlacedBodies {
|
|||||||
let mut fresh = ModelBody::new(name.to_string(), placed.body.clone(), b.feature);
|
let mut fresh = ModelBody::new(name.to_string(), placed.body.clone(), b.feature);
|
||||||
fresh.mesh = placed.mesh.clone();
|
fresh.mesh = placed.mesh.clone();
|
||||||
fresh.measured = placed.measured.clone();
|
fresh.measured = placed.measured.clone();
|
||||||
|
fresh.joint_faces = placed.joint_faces.clone();
|
||||||
fresh.mesh_placement = placed.mesh_placement.clone();
|
fresh.mesh_placement = placed.mesh_placement.clone();
|
||||||
fresh.plan = placed.plan.clone();
|
fresh.plan = placed.plan.clone();
|
||||||
fresh.plan_mesh = placed.plan_mesh.clone();
|
fresh.plan_mesh = placed.plan_mesh.clone();
|
||||||
@@ -8907,6 +9214,49 @@ mod world_placement_cache_tests {
|
|||||||
assert!(Arc::ptr_eq(&original_mesh, &again.body("Block").unwrap().mesh()));
|
assert!(Arc::ptr_eq(&original_mesh, &again.body("Block").unwrap().mesh()));
|
||||||
assert!(Arc::ptr_eq(&names, &crate::naming::face_names(again.body(&second_name).unwrap())));
|
assert!(Arc::ptr_eq(&names, &crate::naming::face_names(again.body(&second_name).unwrap())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A dragged component's bodies are drawn from the meshes and plans made where the drag found
|
||||||
|
/// them, moved: each is the same solid moved rigidly, the others stand where they were, and a
|
||||||
|
/// body whose geometry changed is no such move.
|
||||||
|
#[test]
|
||||||
|
fn a_moved_placement_is_drawn_moved_from_where_it_stood() {
|
||||||
|
// A placement moved, and the part moved off where it is defined.
|
||||||
|
for from_definition in [false, true] {
|
||||||
|
let mut doc = Document::new("placements");
|
||||||
|
let component = doc.add_component(Some("Part"), 0).unwrap();
|
||||||
|
doc.body_components.insert("Block".into(), component);
|
||||||
|
let start = if from_definition { crate::IDENTITY } else { crate::rigid(Vec3::new(10., 20., 0.), Vec3::ZERO, Vec3::Z, 0.4) };
|
||||||
|
let second = doc.add_occurrence(component, 0, start).unwrap();
|
||||||
|
let mut state = ModelState::default();
|
||||||
|
state.bodies.push(ModelBody::new("Block".into(), kernel::box_solid(Vec3::ZERO, Vec3::new(2., 3., 4.)).unwrap(), 1));
|
||||||
|
let before = world_state(&doc, &state).unwrap();
|
||||||
|
let delta = crate::rigid(Vec3::new(4., -7., 1.5), Vec3::new(1., 1., 0.), Vec3::new(0., 1., 1.), 0.6);
|
||||||
|
let moved = doc.occurrences.iter_mut().find(|o| o.id == second).unwrap();
|
||||||
|
moved.transform = crate::mat_mul(&delta, &moved.transform);
|
||||||
|
let after = world_state(&doc, &state).unwrap();
|
||||||
|
assert_eq!(after.bodies.len(), 2);
|
||||||
|
let mut moves = 0;
|
||||||
|
for now in &after.bodies {
|
||||||
|
let was = before.body(&now.name).unwrap();
|
||||||
|
let d = now.display_moved_from(was).unwrap();
|
||||||
|
if d == crate::IDENTITY {
|
||||||
|
assert!(Arc::ptr_eq(&now.mesh(), &was.mesh()), "{}: unchanged, the same mesh", now.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
moves += 1;
|
||||||
|
assert!(d.iter().flatten().zip(delta.iter().flatten()).all(|(a, b)| (a - b).abs() < 1e-9), "{}: {d:?}", now.name);
|
||||||
|
// `was`'s mesh drawn moved is `now`'s.
|
||||||
|
for (p, q) in was.mesh().positions.iter().zip(&now.mesh().positions) {
|
||||||
|
assert!(crate::apply_point(&d, *p).dist(*q) < 1e-9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(moves, 1, "from its definition: {from_definition}");
|
||||||
|
// Another body, or other geometry: no move draws it.
|
||||||
|
assert!(after.bodies[0].display_moved_from(&before.bodies[1]).is_none());
|
||||||
|
let other = ModelBody::new(before.bodies[0].name.clone(), kernel::box_solid(Vec3::ZERO, Vec3::new(1., 1., 1.)).unwrap(), 1);
|
||||||
|
assert!(other.display_moved_from(&before.bodies[0]).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use crate::*;
|
|||||||
/// intersect) ran its booleans on, bodies searched for the cylinder a tap marking lies on,
|
/// intersect) ran its booleans on, bodies searched for the cylinder a tap marking lies on,
|
||||||
/// bodies whose faces a tapped hole read for its wall, intersects run to find a pattern's copies
|
/// bodies whose faces a tapped hole read for its wall, intersects run to find a pattern's copies
|
||||||
/// that cut nothing, and the tools a cut of many tools is made with (one when they are cut in one
|
/// that cut nothing, and the tools a cut of many tools is made with (one when they are cut in one
|
||||||
/// go).
|
/// go), and face surfaces classified for a hole's direction.
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
pub(super) struct Counts {
|
pub(super) struct Counts {
|
||||||
pub moved: usize,
|
pub moved: usize,
|
||||||
@@ -36,6 +36,7 @@ pub(super) struct Counts {
|
|||||||
pub tapped_wall: usize,
|
pub tapped_wall: usize,
|
||||||
pub miss_intersects: usize,
|
pub miss_intersects: usize,
|
||||||
pub cut_tools: usize,
|
pub cut_tools: usize,
|
||||||
|
pub surfaces: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
@@ -53,6 +54,7 @@ thread_local! {
|
|||||||
tapped_wall: 0,
|
tapped_wall: 0,
|
||||||
miss_intersects: 0,
|
miss_intersects: 0,
|
||||||
cut_tools: 0,
|
cut_tools: 0,
|
||||||
|
surfaces: 0,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -654,6 +656,126 @@ fn a_tool_cutting_another_part_cuts_again_when_that_part_moves() {
|
|||||||
assert!((volume(&model, &body) - (400.0 - 16.0)).abs() < 1e-6, "{}", volume(&model, &body));
|
assert!((volume(&model, &body) - (400.0 - 16.0)).abs() < 1e-6, "{}", volume(&model, &body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A root design with two 10 x 10 x 4 mm plates, "Plate" at the origin and "Other" 30 mm along Y,
|
||||||
|
/// and a sketch on origin plane `plane` of the rectangle `profile` spans (opposite corners, in mm):
|
||||||
|
/// (the plate part, the other part, the sketch).
|
||||||
|
fn two_plates_and_a_sketch(doc: &mut Document, plane: &str, profile: [Vec2; 2]) -> (u64, u64, u64) {
|
||||||
|
let plate = doc.add_component(Some("Plate"), 0).unwrap();
|
||||||
|
let other = doc.add_component(Some("Other"), 0).unwrap();
|
||||||
|
place(doc, other, rigid(Vec3::new(0.0, 30.0, 0.0), Vec3::ZERO, Vec3::Z, 0.0));
|
||||||
|
plate_with_holes(doc, other, other, 0);
|
||||||
|
plate_with_holes(doc, plate, plate, 0);
|
||||||
|
let mut sk = Sketch::new();
|
||||||
|
let [a, c] = profile;
|
||||||
|
let p = [a, Vec2::new(c.x, a.y), c, Vec2::new(a.x, c.y)].map(|q| sk.add_point(q, None).unwrap());
|
||||||
|
for k in 0..4 {
|
||||||
|
sk.add_line_pts(p[k], p[(k + 1) % 4], None).unwrap();
|
||||||
|
}
|
||||||
|
let sketch = doc.add_feature(FeatureKind::Sketch { plane: PlaneRef::Origin { name: plane.into() }, sketch: sk }, None).unwrap();
|
||||||
|
(plate, other, sketch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `kind` added to the root with its face points named after the faces under them, as the
|
||||||
|
/// command that makes it names them.
|
||||||
|
fn add_named(doc: &mut Document, kind: FeatureKind) -> u64 {
|
||||||
|
let mut model = Model::new();
|
||||||
|
model.evaluate(doc);
|
||||||
|
let names = face_point_names(doc, &model.state(), &kind, 0).unwrap();
|
||||||
|
assert!(names.iter().all(|n| !n.is_empty()), "{names:?}");
|
||||||
|
let id = doc.add_feature(kind, None).unwrap();
|
||||||
|
doc.feature_mut(id).unwrap().face_names = names;
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extrude in the root from the top of the plate (z 4) 6 mm up, over 2 to 8 mm on X and Y.
|
||||||
|
fn extrude_from_the_plate(doc: &mut Document) -> (u64, u64, u64) {
|
||||||
|
let (plate, other, sketch) = two_plates_and_a_sketch(doc, "XY", [Vec2::new(2.0, 2.0), Vec2::new(8.0, 8.0)]);
|
||||||
|
let kind = FeatureKind::Extrude {
|
||||||
|
thin: None,
|
||||||
|
sketch: SketchId(sketch),
|
||||||
|
profiles: ProfileSel::All,
|
||||||
|
extent: serde_json::from_value(serde_json::json!({"distance": "6", "from": [5.0, 5.0, 4.0]})).unwrap(),
|
||||||
|
operation: Operation::NewBody,
|
||||||
|
targets: vec![],
|
||||||
|
};
|
||||||
|
(plate, other, add_named(doc, kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where the body feature `id` made has its centre.
|
||||||
|
fn centroid_of(model: &Model, id: u64) -> Vec3 {
|
||||||
|
let st = model.state();
|
||||||
|
kernel::measure(&st.bodies.iter().find(|b| b.feature == id).unwrap().body).unwrap().centroid
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extrude from a face of the plate reads where the plate is, and nothing else (#6325): a
|
||||||
|
/// joint moving another part leaves its result, and every result after it, as it was.
|
||||||
|
#[test]
|
||||||
|
fn an_extrude_from_its_own_part_keeps_its_result_when_another_part_moves() {
|
||||||
|
let mut doc = Document::new("from object");
|
||||||
|
let (plate, other, extrude) = extrude_from_the_plate(&mut doc);
|
||||||
|
let mut model = Model::new();
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert!(model.results.iter().all(|r| r.error.is_none()), "{:?}", model.results.iter().find(|r| r.error.is_some()));
|
||||||
|
assert!((centroid_of(&model, extrude).z - 7.0).abs() < 1e-6, "{:?}", centroid_of(&model, extrude));
|
||||||
|
place(&mut doc, other, rigid(Vec3::new(0.0, 40.0, 3.0), Vec3::ZERO, Vec3::Z, 0.0));
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert_eq!(model.last_recomputed, 0);
|
||||||
|
// The plate lifted 5 mm: the extrude starts on its top where it is now (#928).
|
||||||
|
place(&mut doc, plate, rigid(Vec3::new(0.0, 0.0, 5.0), Vec3::ZERO, Vec3::Z, 0.0));
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert_eq!(model.last_recomputed, 1);
|
||||||
|
assert!((centroid_of(&model, extrude).z - 12.0).abs() < 1e-6, "{:?}", centroid_of(&model, extrude));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A revolve To a face of the plate, as the extrude from it: it turns again only when the plate
|
||||||
|
/// moves.
|
||||||
|
#[test]
|
||||||
|
fn a_revolve_to_its_own_part_keeps_its_result_when_another_part_moves() {
|
||||||
|
let mut doc = Document::new("revolve to object");
|
||||||
|
let (plate, other, sketch) = two_plates_and_a_sketch(&mut doc, "XZ", [Vec2::new(12.0, 0.0), Vec2::new(15.0, 2.0)]);
|
||||||
|
let kind = FeatureKind::Revolve {
|
||||||
|
sketch: SketchId(sketch),
|
||||||
|
profiles: ProfileSel::All,
|
||||||
|
axis: AxisRef::SketchAxis { axis: "y".into() },
|
||||||
|
angle: "90 deg".into(),
|
||||||
|
operation: Operation::NewBody,
|
||||||
|
targets: vec![],
|
||||||
|
angle2: None,
|
||||||
|
symmetric: false,
|
||||||
|
convention: Default::default(),
|
||||||
|
to: Some(serde_json::from_value(serde_json::json!([5.0, 5.0, 4.0])).unwrap()),
|
||||||
|
};
|
||||||
|
let revolve = add_named(&mut doc, kind);
|
||||||
|
let mut model = Model::new();
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert!(model.results.iter().all(|r| r.error.is_none()), "{:?}", model.results.iter().find(|r| r.error.is_some()));
|
||||||
|
let turned = volume(&model, &model.state().bodies.iter().find(|b| b.feature == revolve).unwrap().name.clone());
|
||||||
|
place(&mut doc, other, rigid(Vec3::new(0.0, 40.0, 3.0), Vec3::ZERO, Vec3::Z, 0.0));
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert_eq!(model.last_recomputed, 0);
|
||||||
|
// The plate turned a quarter about Z: the revolve turns to its top where it is now.
|
||||||
|
place(&mut doc, plate, rigid(Vec3::ZERO, Vec3::ZERO, Vec3::Z, std::f64::consts::FRAC_PI_2));
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert_eq!(model.last_recomputed, 1);
|
||||||
|
let now = volume(&model, &model.state().bodies.iter().find(|b| b.feature == revolve).unwrap().name.clone());
|
||||||
|
assert!((now - turned).abs() > 1.0, "{turned} then {now}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A From face without a name is found by its point, on whatever body lies there: moving any
|
||||||
|
/// part extrudes again.
|
||||||
|
#[test]
|
||||||
|
fn an_extrude_from_a_face_without_a_name_extrudes_again_when_any_part_moves() {
|
||||||
|
let mut doc = Document::new("from a point");
|
||||||
|
let (_, other, extrude) = extrude_from_the_plate(&mut doc);
|
||||||
|
doc.feature_mut(extrude).unwrap().face_names.clear();
|
||||||
|
let mut model = Model::new();
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert!((centroid_of(&model, extrude).z - 7.0).abs() < 1e-6, "{:?}", centroid_of(&model, extrude));
|
||||||
|
place(&mut doc, other, rigid(Vec3::new(0.0, 40.0, 3.0), Vec3::ZERO, Vec3::Z, 0.0));
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert_eq!(model.last_recomputed, 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_body_is_measured_once_while_it_stays_the_same() {
|
fn a_body_is_measured_once_while_it_stays_the_same() {
|
||||||
let mut doc = Document::new("measured once");
|
let mut doc = Document::new("measured once");
|
||||||
@@ -1759,3 +1881,78 @@ fn mesh_metadata_and_order_remain_part_of_the_fingerprint() {
|
|||||||
same_as_fresh(&doc, &model);
|
same_as_fresh(&doc, &model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_added_and_deleted_sketch_entity_keeps_its_fingerprint() {
|
||||||
|
let mut doc = Document::new("sketch fingerprint");
|
||||||
|
let id = doc.add_feature(line_sketch(PlaneRef::Origin { name: "XY".into() }, Vec2::new(10.0, 0.0)), None).unwrap();
|
||||||
|
let mut model = Model::new();
|
||||||
|
model.evaluate(&doc);
|
||||||
|
let key = model.state().sketch_keys.get(&id).copied();
|
||||||
|
let json = super::feature_json(doc.feature(id).unwrap(), &super::ImportDigests::default());
|
||||||
|
|
||||||
|
// Make and delete an id: the counters advance, but the geometry is the same.
|
||||||
|
{
|
||||||
|
let sk = doc.sketch_mut(id).unwrap();
|
||||||
|
let a = sk.add_point(Vec2::new(2.0, 2.0), None).unwrap();
|
||||||
|
let b = sk.add_point(Vec2::new(3.0, 2.0), None).unwrap();
|
||||||
|
let line = sk.add_line_pts(a, b, None).unwrap();
|
||||||
|
sk.remove_curves(&[line]);
|
||||||
|
sk.remove_points(&[b]);
|
||||||
|
sk.remove_points(&[a]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let json_after = super::feature_json(doc.feature(id).unwrap(), &super::ImportDigests::default());
|
||||||
|
assert_eq!(json, json_after, "deleted ids are not geometry");
|
||||||
|
model.evaluate(&doc);
|
||||||
|
assert_eq!(model.state().sketch_keys.get(&id).copied(), key);
|
||||||
|
assert_eq!((model.last_recomputed, model.last_restored), (0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_hole_skips_the_parts_its_line_cannot_reach() {
|
||||||
|
use solidcraft_geom::Aabb3;
|
||||||
|
let unit = Aabb3 { min: Vec3::ZERO, max: Vec3::new(1.0, 1.0, 1.0) };
|
||||||
|
// Down through the box, from above and from below (the line runs both ways).
|
||||||
|
assert!(super::line_meets_box(Vec3::new(0.5, 0.5, 5.0), Vec3::new(0.0, 0.0, -1.0), &unit));
|
||||||
|
assert!(super::line_meets_box(Vec3::new(0.5, 0.5, -5.0), Vec3::new(0.0, 0.0, -1.0), &unit));
|
||||||
|
// Beside it, parallel to a side: a zero direction component keeps the line out of the slab.
|
||||||
|
assert!(!super::line_meets_box(Vec3::new(2.0, 0.5, 5.0), Vec3::new(0.0, 0.0, -1.0), &unit));
|
||||||
|
// Slanted past a corner, and slanted through it.
|
||||||
|
assert!(!super::line_meets_box(Vec3::new(3.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 0.0), &unit));
|
||||||
|
assert!(super::line_meets_box(Vec3::new(-1.0, -1.0, 0.5), Vec3::new(1.0, 1.0, 0.0), &unit));
|
||||||
|
// Touching a face counts; an empty box never meets.
|
||||||
|
assert!(super::line_meets_box(Vec3::new(1.0, 0.5, 9.0), Vec3::new(0.0, 0.0, 1.0), &unit));
|
||||||
|
assert!(!super::line_meets_box(Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.0, 0.0, 1.0), &Aabb3::EMPTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_hole_classifies_the_faces_it_reads_not_every_face_of_the_design() {
|
||||||
|
// Forty separate cylinders, three faces each. A hole placed on the top of one drills down,
|
||||||
|
// and finding that out reads the nearest face only: it used to classify every face of every
|
||||||
|
// body first, which in an assembly of hundreds of imported parts took seconds per hole.
|
||||||
|
let mut doc = Document::new("hole direction work");
|
||||||
|
for k in 0..40 {
|
||||||
|
let kind = FeatureKind::Cylinder {
|
||||||
|
base: point_expr(Vec3::new(10.0 * f64::from(k), 0.0, 0.0)),
|
||||||
|
axis: Dir(Vec3::Z),
|
||||||
|
radius: "2".into(),
|
||||||
|
height: "10".into(),
|
||||||
|
operation: Operation::NewBody,
|
||||||
|
references: Vec::new(),
|
||||||
|
};
|
||||||
|
doc.add_feature(kind, None).unwrap();
|
||||||
|
}
|
||||||
|
let mut model = Model::new();
|
||||||
|
model.evaluate(&doc);
|
||||||
|
let st = model.state();
|
||||||
|
assert_eq!(st.bodies.len(), 40);
|
||||||
|
let mut dir = None;
|
||||||
|
let work = counted(|| dir = super::hole_direction_at(&st, Vec3::new(250.5, 0.5, 10.0), &[]));
|
||||||
|
let dir = dir.unwrap();
|
||||||
|
assert!((dir - Vec3::new(0.0, 0.0, -1.0)).len() < 1e-9, "drills down into the top: {dir:?}");
|
||||||
|
assert!(work.surfaces <= 2, "classified {} face surfaces for one hole", work.surfaces);
|
||||||
|
// A point on a side wall drills into that wall.
|
||||||
|
let side = super::hole_direction_at(&st, Vec3::new(252.0, 0.0, 5.0), &[]).unwrap();
|
||||||
|
assert!((side - Vec3::new(-1.0, 0.0, 0.0)).len() < 1e-6, "into the side wall: {side:?}");
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::sheet;
|
|||||||
use solidcore_record::{Loop2, Seg2};
|
use solidcore_record::{Loop2, Seg2};
|
||||||
use solidcraft_geom::{Aabb3, Ring};
|
use solidcraft_geom::{Aabb3, Ring};
|
||||||
|
|
||||||
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState) -> Result<()> {
|
pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut ModelState, warning: &mut Option<String>) -> Result<()> {
|
||||||
match &f.kind {
|
match &f.kind {
|
||||||
FeatureKind::Emboss { sketch, profiles, depth, deboss, targets, face } => {
|
FeatureKind::Emboss { sketch, profiles, depth, deboss, targets, face } => {
|
||||||
let ss = st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?;
|
let ss = st.sketch(sketch.0).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?;
|
||||||
@@ -59,7 +59,20 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
|
|||||||
if st.bodies.is_empty() {
|
if st.bodies.is_empty() {
|
||||||
return Err(DocError::Invalid("there is no body to emboss".into()));
|
return Err(DocError::Invalid("there is no body to emboss".into()));
|
||||||
}
|
}
|
||||||
apply_op(doc, st, f, tools, if *deboss { Operation::Cut } else { Operation::Join }, &crate::plain(targets))
|
// #10940: an emboss or deboss whose tool lies clear of the body (a sketch on the far
|
||||||
|
// side of the material, a depth that stops short) used to report success and change
|
||||||
|
// nothing. Say so: the body is compared before and after, as the command's reply does.
|
||||||
|
let before: Vec<Body> = st.bodies.iter().map(|b| b.body.clone()).collect();
|
||||||
|
apply_op(doc, st, f, tools, if *deboss { Operation::Cut } else { Operation::Join }, &crate::plain(targets))?;
|
||||||
|
let unchanged = st.bodies.len() == before.len() && st.bodies.iter().zip(&before).all(|(b, old)| old.same(&b.body));
|
||||||
|
if unchanged {
|
||||||
|
let what = if *deboss { "deboss" } else { "emboss" };
|
||||||
|
append_warning(
|
||||||
|
warning,
|
||||||
|
format!("the {what} changed nothing; check where its profile lies, which side of the face the sketch is on, and the depth"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
FeatureKind::Rib { .. } => {
|
FeatureKind::Rib { .. } => {
|
||||||
let tools = rib_tools(vals, st, f, None)?;
|
let tools = rib_tools(vals, st, f, None)?;
|
||||||
@@ -1428,7 +1441,8 @@ const MAX_CONE_SIZE: f64 = 1e7;
|
|||||||
/// of `top_radius` `height` along `axis` (a negative height goes the other way). A top radius of
|
/// of `top_radius` `height` along `axis` (a negative height goes the other way). A top radius of
|
||||||
/// 0 (or below a micrometre) makes a full cone ending in an apex. Built, as the torus is, by
|
/// 0 (or below a micrometre) makes a full cone ending in an apex. Built, as the torus is, by
|
||||||
/// revolving its half section, a trapezoid (a triangle for a full cone) with an edge on the axis.
|
/// revolving its half section, a trapezoid (a triangle for a full cone) with an edge on the axis.
|
||||||
pub(super) fn cone(base: Vec3, axis: Vec3, radius: f64, top_radius: f64, height: f64) -> Result<Body> {
|
/// Its faces are named `bottom`, `side` and `top` ([`crate::lineage::cone_roles`]).
|
||||||
|
pub(super) fn cone(base: Vec3, axis: Vec3, radius: f64, top_radius: f64, height: f64) -> Result<Traced> {
|
||||||
let in_range = |v: f64| v.is_finite() && v.abs() <= MAX_CONE_SIZE;
|
let in_range = |v: f64| v.is_finite() && v.abs() <= MAX_CONE_SIZE;
|
||||||
if !(in_range(base.x) && in_range(base.y) && in_range(base.z)) {
|
if !(in_range(base.x) && in_range(base.y) && in_range(base.z)) {
|
||||||
return Err(DocError::Invalid("the cone's base is out of range".into()));
|
return Err(DocError::Invalid("the cone's base is out of range".into()));
|
||||||
@@ -1462,14 +1476,16 @@ pub(super) fn cone(base: Vec3, axis: Vec3, radius: f64, top_radius: f64, height:
|
|||||||
(Some(b), true) => b,
|
(Some(b), true) => b,
|
||||||
_ => return Err(DocError::Invalid("the cone could not be built".into())),
|
_ => return Err(DocError::Invalid("the cone could not be built".into())),
|
||||||
};
|
};
|
||||||
|
// Read off the revolve's own history; moving the body keeps its faces' order.
|
||||||
|
let roles = crate::lineage::cone_roles(&body);
|
||||||
if (dir - Vec3::Z).len() < 1e-12 && base.len() == 0.0 {
|
if (dir - Vec3::Z).len() < 1e-12 && base.len() == 0.0 {
|
||||||
return Ok(body);
|
return Ok(Traced::made(body, roles));
|
||||||
}
|
}
|
||||||
// A rotation taking +Z onto the axis (x and y square to it, right-handed), then the base.
|
// A rotation taking +Z onto the axis (x and y square to it, right-handed), then the base.
|
||||||
let x = Plane::from_normal(base, dir).ok_or_else(|| DocError::Invalid("the cone's axis has no direction".into()))?.x;
|
let x = Plane::from_normal(base, dir).ok_or_else(|| DocError::Invalid("the cone's axis has no direction".into()))?.x;
|
||||||
let y = dir.cross(x);
|
let y = dir.cross(x);
|
||||||
let m = [[x.x, x.y, x.z, 0.0], [y.x, y.y, y.z, 0.0], [dir.x, dir.y, dir.z, 0.0], [base.x, base.y, base.z, 1.0]];
|
let m = [[x.x, x.y, x.z, 0.0], [y.x, y.y, y.z, 0.0], [dir.x, dir.y, dir.z, 0.0], [base.x, base.y, base.z, 1.0]];
|
||||||
Ok(kernel::transform_matrix(&body, m)?)
|
Ok(Traced::made(kernel::transform_matrix(&body, m)?, roles))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A primitive tool's exact geometry, for [`sheet_primitive_cut`].
|
/// A primitive tool's exact geometry, for [`sheet_primitive_cut`].
|
||||||
|
|||||||
@@ -63,13 +63,16 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature,
|
|||||||
let t = val(vals, thickness, Kind::Length)?;
|
let t = val(vals, thickness, Kind::Length)?;
|
||||||
let mut tools = Vec::new();
|
let mut tools = Vec::new();
|
||||||
for n in bodies {
|
for n in bodies {
|
||||||
let b = st.body(n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
|
let b = st.body(n).cloned().ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
|
||||||
tools.push(kernel::thicken(&b.body, t, *symmetric)?);
|
// Kept face, offset and edge walls. Carry that history so a sketch can attach
|
||||||
|
// to the solid's faces and follow the thickness (#7641).
|
||||||
|
let thickened = kernel::thicken(&b.body, t, *symmetric)?;
|
||||||
|
tools.push(Traced::step(thickened, &[&b.traced()]));
|
||||||
}
|
}
|
||||||
if tools.is_empty() {
|
if tools.is_empty() {
|
||||||
return Err(DocError::Invalid("pick the surfaces to thicken".into()));
|
return Err(DocError::Invalid("pick the surfaces to thicken".into()));
|
||||||
}
|
}
|
||||||
apply_op(doc, st, f, tools, *operation, &crate::plain(targets))
|
apply_traced(doc, st, f, tools, *operation, &crate::plain(targets))
|
||||||
}
|
}
|
||||||
FeatureKind::SurfaceTrim { body, plane, tool, keep, keep_more, curves, with, keep_with, stitch, extend } => {
|
FeatureKind::SurfaceTrim { body, plane, tool, keep, keep_more, curves, with, keep_with, stitch, extend } => {
|
||||||
let i = st.bodies.iter().position(|b| &b.name == body).ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
|
let i = st.bodies.iter().position(|b| &b.name == body).ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
|
||||||
|
|||||||
@@ -1122,4 +1122,31 @@ mod tests {
|
|||||||
// The square stop leaves about half a pitch's worth of groove uncut: well over a mm³.
|
// The square stop leaves about half a pitch's worth of groove uncut: well over a mm³.
|
||||||
assert!(square > trimmed + 1.0, "{square} vs {trimmed}");
|
assert!(square > trimmed + 1.0, "{square} vs {trimmed}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A modelled thread on one hole leaves the other hole's wall a cylinder to thread: the
|
||||||
|
/// second hole still takes its thread (#11367).
|
||||||
|
///
|
||||||
|
/// The modelled thread cuts the body, and the cut must not take the body's other cylinders
|
||||||
|
/// with it: the second hole's wall is still a cylinder, so it still takes a thread, modelled
|
||||||
|
/// or cosmetic.
|
||||||
|
#[test]
|
||||||
|
fn a_modelled_thread_leaves_the_next_hole_a_cylinder_to_thread() {
|
||||||
|
// An 80 x 80 x 20 mm block with two 5 mm holes.
|
||||||
|
let block = kernel::box_solid(Vec3::new(-40.0, -40.0, 0.0), Vec3::new(40.0, 40.0, 20.0)).unwrap();
|
||||||
|
let hole = |x: f64| kernel::cylinder(Vec3::new(x, 0.0, -1.0), Vec3::new(0.0, 0.0, 1.0), 2.5, 22.0).unwrap();
|
||||||
|
let mut body = kernel::boolean(&block, &hole(-20.0), BoolOp::Cut).unwrap().unwrap();
|
||||||
|
body = kernel::boolean(&body, &hole(20.0), BoolOp::Cut).unwrap().unwrap();
|
||||||
|
let mut st = ModelState::default();
|
||||||
|
st.bodies.push(ModelBody::new("Part".into(), body, 1));
|
||||||
|
let ask = ThreadAsk { designation: Some("M6"), ..ask() };
|
||||||
|
// The first hole takes a modelled M6 thread.
|
||||||
|
let on = thread_on(&st, 1, Vec3::new(-17.5, 0.0, 10.0), &ask).unwrap();
|
||||||
|
cut(&mut st, on, (ThreadTip::None, None, None)).unwrap();
|
||||||
|
// The second hole's wall is still a cylinder: it takes a thread too.
|
||||||
|
let on = thread_on(&st, 1, Vec3::new(22.5, 0.0, 10.0), &ask);
|
||||||
|
assert!(on.is_ok(), "the second hole still has a cylinder to thread: {:?}", on.err());
|
||||||
|
// And a modelled one cuts there as well.
|
||||||
|
let on = thread_on(&st, 1, Vec3::new(22.5, 0.0, 10.0), &ask).unwrap();
|
||||||
|
assert!(cut(&mut st, on, (ThreadTip::None, None, None)).is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -982,6 +982,12 @@ fn call(f: &str, args: &[Value], d: Defaults) -> Result<Value, DocError> {
|
|||||||
// Round in the value's own default unit (round(2.4 in) = 2 in for an inch parameter).
|
// Round in the value's own default unit (round(2.4 in) = 2 in for an inch parameter).
|
||||||
let scale = scale_bare(Value::num(1.0), a.len, a.ang, d);
|
let scale = scale_bare(Value::num(1.0), a.len, a.ang, d);
|
||||||
let x = if scale != 0.0 { a.v / scale } else { a.v };
|
let x = if scale != 0.0 { a.v / scale } else { a.v };
|
||||||
|
// Unit conversions leave a rounding error of an ulp or two (`3 in / 1 in` is
|
||||||
|
// 2.9999999999999996): a value that close to a whole number is that number, so `floor`
|
||||||
|
// does not drop to 2. The tolerance is relative to the whole number, so a tiny value
|
||||||
|
// never snaps to 0, and it stays under 1/8 so no real fraction is absorbed.
|
||||||
|
let nearest = x.round();
|
||||||
|
let x = if (x - nearest).abs() <= (4.0 * f64::EPSILON * nearest.abs()).min(0.125) { nearest } else { x };
|
||||||
let r = match f {
|
let r = match f {
|
||||||
"floor" => x.floor(),
|
"floor" => x.floor(),
|
||||||
"ceil" => x.ceil(),
|
"ceil" => x.ceil(),
|
||||||
@@ -1444,6 +1450,43 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `floor`, `ceil`, `trunc` and `round` work on the number a person typed, not on float noise:
|
||||||
|
/// `3 in / 1 in` is 2.9999999999999996 in floats, and `floor` of it must still be 3.
|
||||||
|
#[test]
|
||||||
|
fn rounding_functions_ignore_float_noise() {
|
||||||
|
for (e, want) in [("floor(3 in / 1 in)", 3.0), ("ceil(3 in / 1 in)", 3.0), ("trunc(3 in / 1 in)", 3.0), ("floor(-3 in / 1 in)", -3.0)] {
|
||||||
|
assert_eq!(ev(e, Kind::Unitless).unwrap(), want, "{e}");
|
||||||
|
}
|
||||||
|
let d = Defaults { len: 25.4, ang: std::f64::consts::PI / 180.0 };
|
||||||
|
let look = |_: &str| Err(DocError::Expr("no parameters".into()));
|
||||||
|
for e in ["floor(3 in)", "ceil(3 in)", "trunc(3 in)", "round(3 in)"] {
|
||||||
|
let got = eval_with_defaults(e, &look, d).unwrap().to_kind_in(Kind::Length, d).unwrap();
|
||||||
|
assert!((got - 76.2).abs() < 1e-9, "{e} gave {got}");
|
||||||
|
}
|
||||||
|
let got = eval_with_defaults("floor(30 deg)", &look, Defaults::default()).unwrap().to_kind(Kind::Angle).unwrap();
|
||||||
|
assert!((got - 30f64.to_radians()).abs() < 1e-9, "floor(30 deg) gave {got}");
|
||||||
|
// A real fraction still rounds the way its name says, however small, and large values keep working.
|
||||||
|
for (e, want) in [
|
||||||
|
("floor(2.9999)", 2.0),
|
||||||
|
("ceil(3.0001)", 4.0),
|
||||||
|
("floor(100.99999995)", 100.0),
|
||||||
|
("ceil(100.00000005)", 101.0),
|
||||||
|
("floor(1000000000.7)", 1_000_000_000.0),
|
||||||
|
("ceil(1000000000.7)", 1_000_000_001.0),
|
||||||
|
("trunc(1000000000.7)", 1_000_000_000.0),
|
||||||
|
("round(1000000000.7)", 1_000_000_001.0),
|
||||||
|
("ceil(1e-16)", 1.0),
|
||||||
|
("floor(-1e-16)", -1.0),
|
||||||
|
("trunc(1e-16)", 0.0),
|
||||||
|
("trunc(-1e-16)", 0.0),
|
||||||
|
("round(1e-16)", 0.0),
|
||||||
|
("round(-1e-16)", 0.0),
|
||||||
|
("floor(1000000000000000.5)", 1_000_000_000_000_000.0),
|
||||||
|
] {
|
||||||
|
assert_eq!(ev(e, Kind::Unitless).unwrap(), want, "{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A count from a length (`(L - 20) / 210`, the 210 read as unit-less) names the fix (#5901).
|
/// A count from a length (`(L - 20) / 210`, the 210 read as unit-less) names the fix (#5901).
|
||||||
#[test]
|
#[test]
|
||||||
fn a_length_where_a_count_is_expected_says_to_divide_by_a_length() {
|
fn a_length_where_a_count_is_expected_says_to_divide_by_a_length() {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ impl FeatureKind {
|
|||||||
| FeatureKind::ReplaceFace { body, .. }
|
| FeatureKind::ReplaceFace { body, .. }
|
||||||
| FeatureKind::SurfaceOffset { body, .. }
|
| FeatureKind::SurfaceOffset { body, .. }
|
||||||
| FeatureKind::Boss { body, .. }
|
| FeatureKind::Boss { body, .. }
|
||||||
|
| FeatureKind::Lip { body, .. }
|
||||||
| FeatureKind::SnapFit { body, .. }
|
| FeatureKind::SnapFit { body, .. }
|
||||||
| FeatureKind::Rest { body, .. } => body.as_deref(),
|
| FeatureKind::Rest { body, .. } => body.as_deref(),
|
||||||
FeatureKind::SheetConvert { body, .. } | FeatureKind::SurfaceRuled { body, .. } => Some(body.as_ref()),
|
FeatureKind::SheetConvert { body, .. } | FeatureKind::SurfaceRuled { body, .. } => Some(body.as_ref()),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
|
|||||||
let hole =
|
let hole =
|
||||||
|h: Value| json!({"type": "hole", "position": o, "direction": [0, 0, 1], "diameter": "hole.diameter", "depth": "hole.depth", "hole": h});
|
|h: Value| json!({"type": "hole", "position": o, "direction": [0, 0, 1], "diameter": "hole.diameter", "depth": "hole.depth", "hole": h});
|
||||||
vec![
|
vec![
|
||||||
|
("newer_file_feature", json!({"type": "newer_file_feature", "raw": {"type": "gyroid_lattice", "cell": 2}})),
|
||||||
(
|
(
|
||||||
"body_transfer",
|
"body_transfer",
|
||||||
json!({"type": "body_transfer", "bodies": ["B"], "from": 0, "to": 1, "transform": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}),
|
json!({"type": "body_transfer", "bodies": ["B"], "from": 0, "to": 1, "transform": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
newer_file_feature
|
||||||
body_transfer
|
body_transfer
|
||||||
sketch
|
sketch
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
newer_file_feature
|
||||||
body_transfer
|
body_transfer
|
||||||
sketch
|
sketch
|
||||||
moves: plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2]
|
moves: plane.axis_dir[0], plane.axis_dir[1], plane.axis_origin[0], plane.axis_origin[1], plane.axis_origin[2]
|
||||||
@@ -150,6 +151,7 @@ boss
|
|||||||
face points: [0, 0, 0]
|
face points: [0, 0, 0]
|
||||||
lip
|
lip
|
||||||
moves: face[0], face[1], face[2]
|
moves: face[0], face[1], face[2]
|
||||||
|
face points: [0, 0, 0]
|
||||||
snap_fit
|
snap_fit
|
||||||
moves: direction[0], direction[2], hook[0], hook[1], position[0], position[1], position[2]
|
moves: direction[0], direction[2], hook[0], hook[1], position[0], position[1], position[2]
|
||||||
face points: [0, 0, 0]
|
face points: [0, 0, 0]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
newer_file_feature
|
||||||
body_transfer
|
body_transfer
|
||||||
remap_bodies: bodies[0]
|
remap_bodies: bodies[0]
|
||||||
sketch
|
sketch
|
||||||
|
|||||||
+156
-129
@@ -2,8 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! A design file starts with `"format": "solidcraft/N"`. A file of an older format is read and
|
//! A design file starts with `"format": "solidcraft/N"`. A file of an older format is read and
|
||||||
//! upgraded step by step (N → N+1 → … → current): JSON-level steps first, for changes in the
|
//! upgraded step by step (N → N+1 → … → current): JSON-level steps first, for changes in the
|
||||||
//! file's shape, then document-level steps. A file of a newer format than this build knows is
|
//! file's shape, then document-level steps. A file a newer build saved opens too, with a warning:
|
||||||
//! refused with a message saying so, never guessed at.
|
//! what this build reads is read, what it doesn't is reported by name and kept for the next save
|
||||||
|
//! (`crate::newer_file`).
|
||||||
//!
|
//!
|
||||||
//! History:
|
//! History:
|
||||||
//! - 1: every design up to 2026-10-08 (format string since M0). Fields added over time all
|
//! - 1: every design up to 2026-10-08 (format string since M0). Fields added over time all
|
||||||
@@ -264,7 +265,7 @@ const FORMATS: Formats<'static> = Formats { hand: &JSON_STEPS, folded: FOLDED, p
|
|||||||
|
|
||||||
/// A kind of file whose format is versioned here: designs, and drawings (#3890). Each family has
|
/// A kind of file whose format is versioned here: designs, and drawings (#3890). Each family has
|
||||||
/// its own format tag, its own numbers and its own folder of change files (`build.rs` lists them);
|
/// its own format tag, its own numbers and its own folder of change files (`build.rs` lists them);
|
||||||
/// reading a file's format, refusing a file a newer build saved and the upgrade are the same code
|
/// reading a file's format, telling a file a newer build saved and the upgrade are the same code
|
||||||
/// for every family.
|
/// for every family.
|
||||||
pub struct Family<'a> {
|
pub struct Family<'a> {
|
||||||
/// What a file of the family is called in messages.
|
/// What a file of the family is called in messages.
|
||||||
@@ -317,8 +318,8 @@ impl Family<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read a file's format and take its `format_pending` out of its JSON, before anything else
|
/// Read a file's format and take its `format_pending` out of its JSON, before anything else
|
||||||
/// in it is read: a file that isn't this family's, or that a newer build saved, is refused
|
/// in it is read: a file that isn't this family's is refused here. One a newer build saved is
|
||||||
/// here, so nothing a newer build saved is judged by this build's rules.
|
/// not: [`Family::newer`] says so.
|
||||||
pub fn take_version(&self, v: &mut Value) -> Result<(u32, Option<Vec<String>>)> {
|
pub fn take_version(&self, v: &mut Value) -> Result<(u32, Option<Vec<String>>)> {
|
||||||
let noun = self.noun;
|
let noun = self.noun;
|
||||||
let not_ours = |what: String| DocError::Invalid(format!("not a SolidCraft {noun} ({what})"));
|
let not_ours = |what: String| DocError::Invalid(format!("not a SolidCraft {noun} ({what})"));
|
||||||
@@ -329,10 +330,19 @@ impl Family<'_> {
|
|||||||
Some(_) => return Err(not_ours("format is not a string".into())),
|
Some(_) => return Err(not_ours("format is not a string".into())),
|
||||||
};
|
};
|
||||||
let applied = take_names(v, "format_pending", noun)?;
|
let applied = take_names(v, "format_pending", noun)?;
|
||||||
self.formats.check(noun, version, applied.as_deref())?;
|
if version == 0 {
|
||||||
|
return Err(not_ours("format 0".into()));
|
||||||
|
}
|
||||||
Ok((version, applied))
|
Ok((version, applied))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What says a file of format `version` with the changes `applied` came from a newer build:
|
||||||
|
/// a number past this build's, or a change this build doesn't have anywhere in its history.
|
||||||
|
/// `None` for a file this build fully knows.
|
||||||
|
pub fn newer(&self, version: u32, applied: Option<&[String]>) -> Option<Newer> {
|
||||||
|
self.formats.newer(version, applied)
|
||||||
|
}
|
||||||
|
|
||||||
/// Bring a file of format `version` up to this build's; `applied` is its `format_pending`
|
/// Bring a file of format `version` up to this build's; `applied` is its `format_pending`
|
||||||
/// (both from [`Family::take_version`]).
|
/// (both from [`Family::take_version`]).
|
||||||
pub fn upgrade(&self, v: &mut Value, version: u32, applied: Option<&[String]>) -> Result<()> {
|
pub fn upgrade(&self, v: &mut Value, version: u32, applied: Option<&[String]>) -> Result<()> {
|
||||||
@@ -350,6 +360,17 @@ impl Family<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How a file shows a newer build saved it ([`Family::newer`]).
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Newer {
|
||||||
|
/// The number the file says.
|
||||||
|
pub format: u32,
|
||||||
|
/// The number this build reads up to.
|
||||||
|
pub reads_up_to: u32,
|
||||||
|
/// The changes the file names that this build doesn't have.
|
||||||
|
pub unknown_changes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// The names of the pending changes this build writes into a design (`format_pending`).
|
/// The names of the pending changes this build writes into a design (`format_pending`).
|
||||||
pub fn pending_names() -> Vec<String> {
|
pub fn pending_names() -> Vec<String> {
|
||||||
DESIGN.pending_names()
|
DESIGN.pending_names()
|
||||||
@@ -394,35 +415,25 @@ impl Formats<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refuse a `noun` of format `version` that this build can't read: format 0, a newer number,
|
/// [`Family::newer`]. Changes are recognized across the entire history: a branch's pending
|
||||||
/// or a change `applied` (its `format_pending`) names that this build doesn't have anywhere in
|
/// change may have landed at a later number. A list beside a format that isn't made of changes
|
||||||
/// its history, which only a newer build's file can. Changes are recognized across the entire
|
/// means nothing (a tool or a test set an older number on a newer file).
|
||||||
/// history: a branch's pending change may have landed at a later number. A list beside a format
|
fn newer(&self, version: u32, applied: Option<&[String]>) -> Option<Newer> {
|
||||||
/// that isn't made of changes means nothing (a tool or a test set an older number on a newer
|
|
||||||
/// file).
|
|
||||||
fn check(&self, noun: &str, version: u32, applied: Option<&[String]>) -> Result<()> {
|
|
||||||
let newer = |what: String| DocError::Invalid(format!("this {noun} was saved by a newer SolidCraft ({what}): update SolidCraft to open it"));
|
|
||||||
let current = self.current();
|
let current = self.current();
|
||||||
if version == 0 {
|
let listed = applied.filter(|_| version > current || self.parts(version).is_some()).unwrap_or_default();
|
||||||
return Err(DocError::Invalid(format!("not a SolidCraft {noun} (format 0)")));
|
let unknown_changes: Vec<String> =
|
||||||
}
|
listed.iter().filter(|name| !self.knows_part_name(self.canonical_applied_name(version, name))).cloned().collect();
|
||||||
if version > current {
|
(version > current || !unknown_changes.is_empty()).then_some(Newer { format: version, reads_up_to: current, unknown_changes })
|
||||||
return Err(newer(format!("file format {version}; this version reads formats up to {current}")));
|
|
||||||
}
|
|
||||||
let applied = applied.filter(|_| self.parts(version).is_some()).unwrap_or_default();
|
|
||||||
match applied.iter().find(|name| !self.knows_part_name(self.canonical_applied_name(version, name))) {
|
|
||||||
Some(unknown) => Err(newer(format!("file format {version} with `{unknown}`, which this version doesn't know"))),
|
|
||||||
None => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bring a `noun` of format `version` up to [`Formats::current`]. `applied` is the file's
|
/// Bring a `noun` of format `version` up to [`Formats::current`]. `applied` is the file's
|
||||||
/// `format_pending`: the changes of its format it already has, when it was written while they
|
/// `format_pending`: the changes of its format it already has, when it was written while they
|
||||||
/// were pending; the others' steps run first. A file [`Formats::check`] refuses is refused,
|
/// were pending; the others' steps run first, and already-applied changes never run again at
|
||||||
/// whoever calls, before anything is changed; already-applied changes never run again at
|
/// their later number. A file past this build's number has no steps to run.
|
||||||
/// their later number.
|
|
||||||
fn upgrade(&self, noun: &str, v: &mut Value, version: u32, applied: Option<&[String]>) -> Result<()> {
|
fn upgrade(&self, noun: &str, v: &mut Value, version: u32, applied: Option<&[String]>) -> Result<()> {
|
||||||
self.check(noun, version, applied)?;
|
if version == 0 {
|
||||||
|
return Err(DocError::Invalid(format!("not a SolidCraft {noun} (format 0)")));
|
||||||
|
}
|
||||||
// A list beside a format that isn't made of changes is ignored, like any field an upgrade
|
// A list beside a format that isn't made of changes is ignored, like any field an upgrade
|
||||||
// drops.
|
// drops.
|
||||||
let applied = applied.filter(|_| self.parts(version).is_some());
|
let applied = applied.filter(|_| self.parts(version).is_some());
|
||||||
@@ -677,18 +688,53 @@ fn v2_to_v3(v: &mut Value) -> Result<()> {
|
|||||||
pub fn read(s: &str) -> Result<Document> {
|
pub fn read(s: &str) -> Result<Document> {
|
||||||
let mut v: Value = serde_json::from_str(s).map_err(|e| DocError::Invalid(format!("not a SolidCraft design: {e}")))?;
|
let mut v: Value = serde_json::from_str(s).map_err(|e| DocError::Invalid(format!("not a SolidCraft design: {e}")))?;
|
||||||
let (version, applied) = DESIGN.take_version(&mut v)?;
|
let (version, applied) = DESIGN.take_version(&mut v)?;
|
||||||
check_design_numbers(&v)?;
|
match DESIGN.newer(version, applied.as_deref()) {
|
||||||
crate::config::validate_rules_json(&v)?;
|
Some(newer) => crate::newer_file::read(v, version, applied, newer),
|
||||||
crate::config::validate_styles_json(&v)?;
|
None => decode(v, version, applied.as_deref()),
|
||||||
crate::face_anchor::validate_face_anchors_json(&v)?;
|
}
|
||||||
validate_versioned_geometry(&v)?;
|
}
|
||||||
validate_emboss_targets(&v)?;
|
|
||||||
validate_freeform_offsets(&v)?;
|
/// Whether a design's text says a newer build saved it, read before anything else in it is.
|
||||||
validate_sketch_sizes(&v)?;
|
pub fn says_newer(text: &str) -> bool {
|
||||||
crate::format_offsets::validate_raw(&v)?;
|
#[derive(serde::Deserialize)]
|
||||||
let legacy_wires = validate_wires(&v, version)?;
|
struct Says {
|
||||||
|
format: Option<String>,
|
||||||
|
format_pending: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
serde_json::from_str::<Says>(text)
|
||||||
|
.ok()
|
||||||
|
.and_then(|says| Some((DESIGN.version_of(&says.format?)?, says.format_pending)))
|
||||||
|
.is_some_and(|(version, applied)| DESIGN.newer(version, applied.as_deref()).is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The checks of a design's JSON that need nothing outside the part of it they are given.
|
||||||
|
pub(crate) fn check_raw(v: &Value, version: u32) -> Result<Vec<(usize, usize)>> {
|
||||||
|
check_design_numbers(v)?;
|
||||||
|
crate::config::validate_rules_json(v)?;
|
||||||
|
crate::config::validate_styles_json(v)?;
|
||||||
|
crate::face_anchor::validate_face_anchors_json(v)?;
|
||||||
|
validate_versioned_geometry(v)?;
|
||||||
|
validate_emboss_targets(v)?;
|
||||||
|
validate_freeform_offsets(v)?;
|
||||||
|
validate_sketch_sizes(v)?;
|
||||||
|
crate::format_offsets::validate_raw(v)?;
|
||||||
|
validate_wires(v, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`check_raw`], the upgrade and the typed record alone: what a piece of a design must pass by
|
||||||
|
/// itself ([`crate::newer_file`]), with nothing else in the file for it to refer to.
|
||||||
|
pub(crate) fn check_piece(mut v: Value, version: u32, applied: Option<&[String]>) -> Result<()> {
|
||||||
|
check_raw(&v, version)?;
|
||||||
|
DESIGN.upgrade(&mut v, version, applied)?;
|
||||||
|
let d: Document = serde_json::from_value(v).map_err(|e| DocError::Invalid(format!("design file: {e}")))?;
|
||||||
|
check_typed_numbers(&d)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A design's JSON, its format taken out, as the document it holds.
|
||||||
|
pub(crate) fn decode(mut v: Value, version: u32, applied: Option<&[String]>) -> Result<Document> {
|
||||||
|
let legacy_wires = check_raw(&v, version)?;
|
||||||
validate_body_transfers(&v)?;
|
validate_body_transfers(&v)?;
|
||||||
DESIGN.upgrade(&mut v, version, applied.as_deref())?;
|
DESIGN.upgrade(&mut v, version, applied)?;
|
||||||
let mut d: Document = serde_json::from_value(v).map_err(|e| DocError::Invalid(format!("design file: {e}")))?;
|
let mut d: Document = serde_json::from_value(v).map_err(|e| DocError::Invalid(format!("design file: {e}")))?;
|
||||||
check_typed_numbers(&d)?;
|
check_typed_numbers(&d)?;
|
||||||
let mut legacy_by_feature = std::collections::BTreeMap::<usize, Vec<usize>>::new();
|
let mut legacy_by_feature = std::collections::BTreeMap::<usize, Vec<usize>>::new();
|
||||||
@@ -739,20 +785,24 @@ pub fn read(s: &str) -> Result<Document> {
|
|||||||
return Err(DocError::Invalid("damaged design: timeline folder cannot contain itself".into()));
|
return Err(DocError::Invalid("damaged design: timeline folder cannot contain itself".into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Each folder's chain of parents ends at the top, walked once: a chain stops at a folder
|
||||||
|
// already known to end there. Walking every chain with a search per step took minutes on a
|
||||||
|
// design with 10 000 nested folders.
|
||||||
|
let parents: std::collections::BTreeMap<u64, Option<u64>> = d.timeline_groups.iter().map(|g| (g.id, g.parent)).collect();
|
||||||
|
let mut ends = std::collections::BTreeSet::new();
|
||||||
for group in &d.timeline_groups {
|
for group in &d.timeline_groups {
|
||||||
if let Some(parent) = group.parent {
|
if group.parent.is_some_and(|parent| !group_ids.contains(&parent)) {
|
||||||
if !group_ids.contains(&parent) {
|
return Err(DocError::Invalid("damaged design: timeline folder has an unknown parent".into()));
|
||||||
return Err(DocError::Invalid("damaged design: timeline folder has an unknown parent".into()));
|
|
||||||
}
|
|
||||||
let mut seen = std::collections::BTreeSet::new();
|
|
||||||
let mut current = Some(parent);
|
|
||||||
while let Some(id) = current {
|
|
||||||
if !seen.insert(id) {
|
|
||||||
return Err(DocError::Invalid("damaged design: cyclic timeline folders".into()));
|
|
||||||
}
|
|
||||||
current = d.timeline_groups.iter().find(|candidate| candidate.id == id).and_then(|candidate| candidate.parent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let mut chain = std::collections::BTreeSet::new();
|
||||||
|
let mut current = Some(group.id);
|
||||||
|
while let Some(id) = current.filter(|id| !ends.contains(id)) {
|
||||||
|
if !chain.insert(id) {
|
||||||
|
return Err(DocError::Invalid("damaged design: cyclic timeline folders".into()));
|
||||||
|
}
|
||||||
|
current = parents.get(&id).copied().flatten();
|
||||||
|
}
|
||||||
|
ends.extend(chain);
|
||||||
}
|
}
|
||||||
if let Some(id) = crate::non_contiguous_timeline_group(&d.features, &d.timeline_groups) {
|
if let Some(id) = crate::non_contiguous_timeline_group(&d.features, &d.timeline_groups) {
|
||||||
return Err(DocError::Invalid(format!("damaged design: timeline folder {id} is not contiguous")));
|
return Err(DocError::Invalid(format!("damaged design: timeline folder {id} is not contiguous")));
|
||||||
@@ -1327,9 +1377,9 @@ mod tests {
|
|||||||
let mut v = serde_json::json!({});
|
let mut v = serde_json::json!({});
|
||||||
formats.upgrade("design", &mut v, next, Some(&["a-first".into(), "b-second".into()])).unwrap();
|
formats.upgrade("design", &mut v, next, Some(&["a-first".into(), "b-second".into()])).unwrap();
|
||||||
assert_eq!(trace_of(&v), Vec::<&str>::new());
|
assert_eq!(trace_of(&v), Vec::<&str>::new());
|
||||||
// A change this build doesn't know is a newer build's: refused, not guessed at.
|
// A change this build doesn't know is a newer build's: said, and kept.
|
||||||
let err = formats.upgrade("design", &mut serde_json::json!({}), next, Some(&["c-later".into()])).unwrap_err();
|
let said = formats.newer(next, Some(&["c-later".into()])).unwrap();
|
||||||
assert!(err.to_string().contains("newer SolidCraft"), "{err}");
|
assert_eq!(said, Newer { format: next, reads_up_to: next, unknown_changes: vec!["c-later".into()] });
|
||||||
// A list on a format that isn't made of changes means nothing.
|
// A list on a format that isn't made of changes means nothing.
|
||||||
let mut v = serde_json::json!({});
|
let mut v = serde_json::json!({});
|
||||||
formats.upgrade("design", &mut v, HAND_WRITTEN, Some(&["a-first".into()])).unwrap();
|
formats.upgrade("design", &mut v, HAND_WRITTEN, Some(&["a-first".into()])).unwrap();
|
||||||
@@ -1372,15 +1422,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn personal_card_alias_without_support_refuses_before_mutation() {
|
fn personal_card_alias_without_support_is_a_newer_builds() {
|
||||||
let hand: [JsonStep; 27] = [|_| Ok(()); 27];
|
let hand: [JsonStep; 27] = [|_| Ok(()); 27];
|
||||||
let first = [traced("a-first")];
|
let first = [traced("a-first")];
|
||||||
let formats = Formats { hand: &hand, folded: &[(29, &first)], pending: &[] };
|
let formats = Formats { hand: &hand, folded: &[(29, &first)], pending: &[] };
|
||||||
let mut file = serde_json::json!({"trace": [], "sentinel": 7});
|
let said = formats.newer(29, Some(&["physical-material-cards".into()])).unwrap();
|
||||||
let original = file.clone();
|
assert_eq!(said.unknown_changes, ["physical-material-cards"]);
|
||||||
let error = formats.upgrade("design", &mut file, 29, Some(&["physical-material-cards".into()])).unwrap_err();
|
|
||||||
assert!(error.to_string().contains("physical-material-cards"), "{error}");
|
|
||||||
assert_eq!(file, original);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1391,11 +1438,8 @@ mod tests {
|
|||||||
let cards = [traced("material-engineering-cards")];
|
let cards = [traced("material-engineering-cards")];
|
||||||
let formats = Formats { hand: &hand, folded: &[(28, &first), (29, &second), (30, &cards)], pending: &[] };
|
let formats = Formats { hand: &hand, folded: &[(28, &first), (29, &second), (30, &cards)], pending: &[] };
|
||||||
for version in [28, 30] {
|
for version in [28, 30] {
|
||||||
let mut file = serde_json::json!({"trace": []});
|
let said = formats.newer(version, Some(&["physical-material-cards".into()])).unwrap();
|
||||||
let original = file.clone();
|
assert_eq!(said.unknown_changes, ["physical-material-cards"]);
|
||||||
let error = formats.upgrade("design", &mut file, version, Some(&["physical-material-cards".into()])).unwrap_err();
|
|
||||||
assert!(error.to_string().contains("physical-material-cards"), "{error}");
|
|
||||||
assert_eq!(file, original);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1408,11 +1452,7 @@ mod tests {
|
|||||||
for names in
|
for names in
|
||||||
[["physical-material-cards".into(), "unknown-card-codec".into()], ["unknown-card-codec".into(), "physical-material-cards".into()]]
|
[["physical-material-cards".into(), "unknown-card-codec".into()], ["unknown-card-codec".into(), "physical-material-cards".into()]]
|
||||||
{
|
{
|
||||||
let mut file = serde_json::json!({"trace": ["b"]});
|
assert_eq!(formats.newer(29, Some(&names)).unwrap().unknown_changes, ["unknown-card-codec"]);
|
||||||
let original = file.clone();
|
|
||||||
let error = formats.upgrade("design", &mut file, 29, Some(&names)).unwrap_err();
|
|
||||||
assert!(error.to_string().contains("unknown-card-codec"), "{error}");
|
|
||||||
assert_eq!(file, original);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1446,18 +1486,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An experimental producer can use a number that a release later folds without its
|
/// An experimental producer can use a number that a release later folds without its
|
||||||
/// change. Its unknown concept must still be refused before any migration runs.
|
/// change. Its unknown concept is still a newer build's.
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_pending_concepts_are_refused_after_folding() {
|
fn unknown_pending_concepts_are_said_after_folding() {
|
||||||
let parts = [traced("a-first")];
|
let parts = [traced("a-first")];
|
||||||
let later = [traced("b-second")];
|
let later = [traced("b-second")];
|
||||||
for pending in [&[][..], &later[..]] {
|
for pending in [&[][..], &later[..]] {
|
||||||
let formats = Formats { hand: &[], folded: &[(2, &parts)], pending };
|
let formats = Formats { hand: &[], folded: &[(2, &parts)], pending };
|
||||||
let mut file = serde_json::json!({"trace": []});
|
assert_eq!(formats.newer(2, Some(&["experimental-card".into()])).unwrap().unknown_changes, ["experimental-card"]);
|
||||||
let original = file.clone();
|
|
||||||
let err = formats.upgrade("design", &mut file, 2, Some(&["experimental-card".into()])).unwrap_err();
|
|
||||||
assert!(err.to_string().contains("experimental-card") && err.to_string().contains("update SolidCraft"), "{err}");
|
|
||||||
assert_eq!(file, original, "refuse before applying even a known migration");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1479,23 +1515,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A design naming a pending change this build doesn't have is a newer build's, refused as
|
/// A design naming a pending change this build doesn't have is a newer build's: what it can't
|
||||||
/// such before anything else in it is checked (as a newer number is), so a newer build's
|
/// read of it doesn't read as damage.
|
||||||
/// fields never read as damage.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_newer_builds_pending_change_is_refused_before_the_design_is_checked() {
|
fn a_newer_builds_pending_change_opens_and_its_fields_are_not_damage() {
|
||||||
let mut file: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap();
|
let mut file: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap();
|
||||||
file["format_pending"] = serde_json::json!(["not-a-change-anyone-made"]);
|
file["format_pending"] = serde_json::json!(["not-a-change-anyone-made"]);
|
||||||
file["features"] = serde_json::json!([{"type": "emboss", "face": {"body": "Body", "at": [0, 0, 0], "a_newer_key": true}}]);
|
file["features"] = serde_json::json!([{"type": "emboss", "face": {"body": "Body", "at": [0, 0, 0], "a_newer_key": true}}]);
|
||||||
let error = read(&file.to_string()).unwrap_err().to_string();
|
let opened = read(&file.to_string()).unwrap();
|
||||||
// With changes pending, FORMAT is made of them; once they are folded it is the folded
|
// With changes pending, FORMAT is made of them; once they are folded it is the folded
|
||||||
// format, which is made of changes too. Either way an unknown change is a newer build's.
|
// format, which is made of changes too. Either way an unknown change is a newer build's.
|
||||||
assert_eq!(
|
let said = opened.newer_file.as_ref().unwrap();
|
||||||
error,
|
assert_eq!(said.unknown_changes, ["not-a-change-anyone-made"]);
|
||||||
format!(
|
assert!(said.notice().contains(&format!("format {FORMAT} with `not-a-change-anyone-made`")), "{}", said.notice());
|
||||||
"this design was saved by a newer SolidCraft (file format {FORMAT} with `not-a-change-anyone-made`, which this version doesn't know): update SolidCraft to open it"
|
assert!(matches!(opened.features[0].kind, crate::FeatureKind::NewerFileFeature { .. }));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drawings follow the same rules from their own first format: folded formats follow on from 1
|
/// Drawings follow the same rules from their own first format: folded formats follow on from 1
|
||||||
@@ -1575,17 +1608,17 @@ mod tests {
|
|||||||
fn tagged(family: &Family<'_>, n: u32) -> Value {
|
fn tagged(family: &Family<'_>, n: u32) -> Value {
|
||||||
serde_json::json!({"format": family.format_string(n)})
|
serde_json::json!({"format": family.format_string(n)})
|
||||||
}
|
}
|
||||||
fn newer(noun: &str, n: u32, current: u32) -> String {
|
// A newer number isn't refused: it is said.
|
||||||
format!(
|
let says = |family: &Family<'_>, mut file: Value| {
|
||||||
"this {noun} was saved by a newer SolidCraft (file format {n}; this version reads formats up to {current}): update SolidCraft to open it"
|
let (version, applied) = family.take_version(&mut file).unwrap();
|
||||||
)
|
family.newer(version, applied.as_deref())
|
||||||
}
|
};
|
||||||
assert_eq!(refusal(&DESIGN, tagged(&DESIGN, FORMAT + 1)), newer("design", FORMAT + 1, FORMAT));
|
assert_eq!(says(&DESIGN, tagged(&DESIGN, FORMAT + 1)), Some(Newer { format: FORMAT + 1, reads_up_to: FORMAT, unknown_changes: vec![] }));
|
||||||
assert_eq!(read(&tagged(&DESIGN, FORMAT + 1).to_string()).unwrap_err().to_string(), newer("design", FORMAT + 1, FORMAT));
|
assert_eq!(read(&tagged(&DESIGN, FORMAT + 1).to_string()).unwrap().newer_file.unwrap().format, FORMAT + 1);
|
||||||
let current = DRAWING.current();
|
let current = DRAWING.current();
|
||||||
assert!(matches!(DRAWING.take_version(&mut tagged(&DRAWING, current)), Ok((v, None)) if v == current));
|
assert!(matches!(DRAWING.take_version(&mut tagged(&DRAWING, current)), Ok((v, None)) if v == current));
|
||||||
for n in [current + 1, u32::MAX] {
|
for n in [current + 1, u32::MAX] {
|
||||||
assert_eq!(refusal(&DRAWING, tagged(&DRAWING, n)), newer("drawing", n, current));
|
assert_eq!(says(&DRAWING, tagged(&DRAWING, n)), Some(Newer { format: n, reads_up_to: current, unknown_changes: vec![] }));
|
||||||
}
|
}
|
||||||
for (family, noun) in [(&DESIGN, "design"), (&DRAWING, "drawing")] {
|
for (family, noun) in [(&DESIGN, "design"), (&DRAWING, "drawing")] {
|
||||||
assert_eq!(refusal(family, tagged(family, 0)), format!("not a SolidCraft {noun} (format 0)"));
|
assert_eq!(refusal(family, tagged(family, 0)), format!("not a SolidCraft {noun} (format 0)"));
|
||||||
@@ -1607,12 +1640,7 @@ mod tests {
|
|||||||
let design = Family { formats: Formats { hand: &JSON_STEPS, folded: FOLDED, pending: &pending }, ..DESIGN };
|
let design = Family { formats: Formats { hand: &JSON_STEPS, folded: FOLDED, pending: &pending }, ..DESIGN };
|
||||||
let next = design.current();
|
let next = design.current();
|
||||||
let file = json!({"format": format_string(next), "format_pending": ["c-later"]});
|
let file = json!({"format": format_string(next), "format_pending": ["c-later"]});
|
||||||
assert_eq!(
|
assert_eq!(says(&design, file), Some(Newer { format: next, reads_up_to: next, unknown_changes: vec!["c-later".into()] }));
|
||||||
refusal(&design, file),
|
|
||||||
format!(
|
|
||||||
"this design was saved by a newer SolidCraft (file format {next} with `c-later`, which this version doesn't know): update SolidCraft to open it"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert!(matches!(DESIGN.take_version(&mut json!({})), Ok((1, None))));
|
assert!(matches!(DESIGN.take_version(&mut json!({})), Ok((1, None))));
|
||||||
assert_eq!(refusal(&DRAWING, json!({})), "not a SolidCraft drawing (no format)");
|
assert_eq!(refusal(&DRAWING, json!({})), "not a SolidCraft drawing (no format)");
|
||||||
}
|
}
|
||||||
@@ -1639,19 +1667,13 @@ mod tests {
|
|||||||
assert_eq!(open(json!({"format": "solidcraft-drawing/2", "format_pending": ["a-first"]})).unwrap(), Vec::<String>::new());
|
assert_eq!(open(json!({"format": "solidcraft-drawing/2", "format_pending": ["a-first"]})).unwrap(), Vec::<String>::new());
|
||||||
// A list beside format 1 means nothing: the step still runs.
|
// A list beside format 1 means nothing: the step still runs.
|
||||||
assert_eq!(open(json!({"format": "solidcraft-drawing/1", "format_pending": ["c-later"]})).unwrap(), ["a"]);
|
assert_eq!(open(json!({"format": "solidcraft-drawing/1", "format_pending": ["c-later"]})).unwrap(), ["a"]);
|
||||||
let error = open(json!({"format": "solidcraft-drawing/2", "format_pending": ["a-first", "c-later"]})).unwrap_err().to_string();
|
// Of a newer build: said, and what can run runs.
|
||||||
assert_eq!(
|
let list = ["a-first".to_string(), "c-later".to_string()];
|
||||||
error,
|
assert_eq!(drawing.newer(2, Some(&list)).unwrap().unknown_changes, ["c-later"]);
|
||||||
"this drawing was saved by a newer SolidCraft (file format 2 with `c-later`, which this version doesn't know): update SolidCraft to open it"
|
assert_eq!(open(json!({"format": "solidcraft-drawing/2", "format_pending": list})).unwrap(), Vec::<String>::new());
|
||||||
);
|
assert_eq!(drawing.newer(3, None).unwrap(), Newer { format: 3, reads_up_to: 2, unknown_changes: vec![] });
|
||||||
let error = open(json!({"format": "solidcraft-drawing/3"})).unwrap_err().to_string();
|
assert_eq!(open(json!({"format": "solidcraft-drawing/3"})).unwrap(), Vec::<String>::new());
|
||||||
assert_eq!(
|
// An upgrade called without `take_version` still says format 0 isn't one.
|
||||||
error,
|
|
||||||
"this drawing was saved by a newer SolidCraft (file format 3; this version reads formats up to 2): update SolidCraft to open it"
|
|
||||||
);
|
|
||||||
// An upgrade called without `take_version` refuses in the drawing's words too, format 0 included.
|
|
||||||
let error = drawing.upgrade(&mut json!({}), 3, None).unwrap_err().to_string();
|
|
||||||
assert!(error.starts_with("this drawing was saved by a newer SolidCraft (file format 3;"), "{error}");
|
|
||||||
assert_eq!(drawing.upgrade(&mut json!({}), 0, None).unwrap_err().to_string(), "not a SolidCraft drawing (format 0)");
|
assert_eq!(drawing.upgrade(&mut json!({}), 0, None).unwrap_err().to_string(), "not a SolidCraft drawing (format 0)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1666,7 +1688,8 @@ mod tests {
|
|||||||
assert!(read(&file.to_string()).is_err());
|
assert!(read(&file.to_string()).is_err());
|
||||||
}
|
}
|
||||||
file["format_pending"] = serde_json::json!(["not-a-change-anyone-made"]);
|
file["format_pending"] = serde_json::json!(["not-a-change-anyone-made"]);
|
||||||
assert!(read(&file.to_string()).is_err(), "unknown pending concepts are refused even after folding");
|
let newer = read(&file.to_string()).unwrap();
|
||||||
|
assert_eq!(newer.newer_file.as_ref().unwrap().unknown_changes, ["not-a-change-anyone-made"], "even after folding");
|
||||||
file["format_pending"] = Value::from(pending_names());
|
file["format_pending"] = Value::from(pending_names());
|
||||||
let opened = read(&file.to_string()).unwrap();
|
let opened = read(&file.to_string()).unwrap();
|
||||||
assert_eq!(opened.format_pending, pending_names());
|
assert_eq!(opened.format_pending, pending_names());
|
||||||
@@ -1895,6 +1918,7 @@ mod tests {
|
|||||||
distance2: None,
|
distance2: None,
|
||||||
start_offset: None,
|
start_offset: None,
|
||||||
through_all: false,
|
through_all: false,
|
||||||
|
second_follows_first: false,
|
||||||
taper: Some("0 deg".into()),
|
taper: Some("0 deg".into()),
|
||||||
corner: Default::default(),
|
corner: Default::default(),
|
||||||
to: Some(crate::FacePick(solidcore_record::Vec3::new(0.0, 0.0, 10.0))),
|
to: Some(crate::FacePick(solidcore_record::Vec3::new(0.0, 0.0, 10.0))),
|
||||||
@@ -2138,8 +2162,9 @@ mod tests {
|
|||||||
value["features"] = serde_json::json!([{"type":"sketch", "sketch":{"freeform_offsets":records}}]);
|
value["features"] = serde_json::json!([{"type":"sketch", "sketch":{"freeform_offsets":records}}]);
|
||||||
let error = read(&value.to_string()).unwrap_err().to_string();
|
let error = read(&value.to_string()).unwrap_err().to_string();
|
||||||
assert!(error.contains("free-form offset metadata"), "{error}");
|
assert!(error.contains("free-form offset metadata"), "{error}");
|
||||||
|
// A newer build's file isn't held to this: the feature waits for that build.
|
||||||
value["format"] = Value::from(format_string(FORMAT + 1));
|
value["format"] = Value::from(format_string(FORMAT + 1));
|
||||||
assert!(read(&value.to_string()).unwrap_err().to_string().contains("newer SolidCraft"));
|
assert!(matches!(read(&value.to_string()).unwrap().features[0].kind, crate::FeatureKind::NewerFileFeature { .. }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2163,8 +2188,7 @@ mod tests {
|
|||||||
let error = read(&value.to_string()).unwrap_err().to_string();
|
let error = read(&value.to_string()).unwrap_err().to_string();
|
||||||
assert!(error.contains("emboss face"), "{error}");
|
assert!(error.contains("emboss face"), "{error}");
|
||||||
value["format"] = Value::from(format_string(FORMAT + 1));
|
value["format"] = Value::from(format_string(FORMAT + 1));
|
||||||
let error = read(&value.to_string()).unwrap_err().to_string();
|
assert!(matches!(read(&value.to_string()).unwrap().features[0].kind, crate::FeatureKind::NewerFileFeature { .. }));
|
||||||
assert!(error.contains("newer SolidCraft"), "{error}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2207,7 +2231,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn version_seven_styles_upgrade_and_newer_versions_are_refused_before_columns() {
|
fn version_seven_styles_upgrade_and_a_newer_versions_columns_are_kept() {
|
||||||
use crate::config::{Column, ConfigRow};
|
use crate::config::{Column, ConfigRow};
|
||||||
let mut d = Document::new("configured styles");
|
let mut d = Document::new("configured styles");
|
||||||
d.configs.columns = vec![Column::Material("Body".into()), Column::Appearance("Body".into())];
|
d.configs.columns = vec![Column::Material("Body".into()), Column::Appearance("Body".into())];
|
||||||
@@ -2227,9 +2251,11 @@ mod tests {
|
|||||||
value["format"] = Value::from(format_string(FORMAT + 1));
|
value["format"] = Value::from(format_string(FORMAT + 1));
|
||||||
value["configs"]["columns"] = serde_json::json!([{"unknown_future_column":"Body"}]);
|
value["configs"]["columns"] = serde_json::json!([{"unknown_future_column":"Body"}]);
|
||||||
value["configs"]["rules"] = serde_json::json!({"unknown_future_rule": true});
|
value["configs"]["rules"] = serde_json::json!({"unknown_future_rule": true});
|
||||||
let error = read(&value.to_string()).unwrap_err().to_string();
|
let opened = read(&value.to_string()).unwrap();
|
||||||
assert!(error.contains("newer SolidCraft") && error.contains("update SolidCraft"), "{error}");
|
let said = opened.newer_file.as_ref().unwrap();
|
||||||
assert!(!error.contains("unknown variant"), "format refusal precedes decoding new variants");
|
assert!(said.not_understood.iter().any(|what| what.contains("configs")), "{:?}", said.not_understood);
|
||||||
|
let saved: Value = serde_json::from_str(&opened.to_json()).unwrap();
|
||||||
|
assert_eq!(saved["configs"], value["configs"], "kept for the build that reads them");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2284,8 +2310,10 @@ mod tests {
|
|||||||
assert_eq!(read(&saved.to_string()).unwrap(), d);
|
assert_eq!(read(&saved.to_string()).unwrap(), d);
|
||||||
saved["format"] = Value::from(format_string(FORMAT + 1));
|
saved["format"] = Value::from(format_string(FORMAT + 1));
|
||||||
saved["features"][0]["sketch"]["constraints"][0]["type"] = Value::from("future_dimension");
|
saved["features"][0]["sketch"]["constraints"][0]["type"] = Value::from("future_dimension");
|
||||||
let error = read(&saved.to_string()).unwrap_err().to_string();
|
let opened = read(&saved.to_string()).unwrap();
|
||||||
assert!(error.contains("newer SolidCraft") && !error.contains("unknown variant"), "{error}");
|
assert!(matches!(opened.features[0].kind, crate::FeatureKind::NewerFileFeature { .. }));
|
||||||
|
let again: Value = serde_json::from_str(&opened.to_json()).unwrap();
|
||||||
|
assert_eq!(again["features"][0], saved["features"][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2392,15 +2420,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn older_upgraded_newer_refused_others_rejected() {
|
fn older_upgraded_newer_opened_others_rejected() {
|
||||||
let mut v: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap();
|
let mut v: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap();
|
||||||
v["format"] = Value::from("solidcraft/1");
|
v["format"] = Value::from("solidcraft/1");
|
||||||
assert_eq!(read(&v.to_string()).unwrap().format, format_string(FORMAT));
|
assert_eq!(read(&v.to_string()).unwrap().format, format_string(FORMAT));
|
||||||
v.as_object_mut().unwrap().remove("format");
|
v.as_object_mut().unwrap().remove("format");
|
||||||
assert!(read(&v.to_string()).is_ok(), "a file without a format is format 1");
|
assert!(read(&v.to_string()).is_ok(), "a file without a format is format 1");
|
||||||
v["format"] = Value::from(format_string(FORMAT + 1));
|
v["format"] = Value::from(format_string(FORMAT + 1));
|
||||||
let e = read(&v.to_string()).unwrap_err().to_string();
|
assert_eq!(read(&v.to_string()).unwrap().newer_file.unwrap().format, FORMAT + 1);
|
||||||
assert!(e.contains("newer SolidCraft"), "{e}");
|
|
||||||
for bad in ["solidcraft/0", "acad/3", "solidcraft/-1", "solidcraft/99999999999999999999"] {
|
for bad in ["solidcraft/0", "acad/3", "solidcraft/-1", "solidcraft/99999999999999999999"] {
|
||||||
v["format"] = Value::from(bad);
|
v["format"] = Value::from(bad);
|
||||||
assert!(read(&v.to_string()).is_err(), "{bad}");
|
assert!(read(&v.to_string()).is_err(), "{bad}");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ here is the whole format bump: never edit `FORMAT`, `JSON_STEPS` or another chan
|
|||||||
|
|
||||||
- Name it after the change, lowercase with dashes: `placed-plane-origins.rs`.
|
- Name it after the change, lowercase with dashes: `placed-plane-origins.rs`.
|
||||||
- Start it with a `//!` note: what is saved, what an older design gets without it, and what
|
- Start it with a `//!` note: what is saved, what an older design gets without it, and what
|
||||||
the last release does with a design that has it (it refuses the newer format number).
|
the last release does with a design that has it (it warns about the newer format number, opens what it can read and keeps the rest).
|
||||||
- If older designs must be rewritten as they open, add the step to the same file:
|
- If older designs must be rewritten as they open, add the step to the same file:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//! A canvas can be locked (#12310): `locked: true` on the canvas keeps its place, size, angle and
|
||||||
|
//! flip, and keeps it from being deleted, until it is unlocked. Older designs need no
|
||||||
|
//! transformation: without the field a canvas is unlocked, as before. The last release warns about
|
||||||
|
//! the newer format number and opens the design with its canvases unlocked.
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//! CPU render jobs can save optional `options.output` presentation: tone map, finishing,
|
||||||
|
//! backdrop and raw linear EXR retention. Older designs omit it and retain the exact previous
|
||||||
|
//! f64 PNG output; exposure remains owned by RenderSetup. Readers missing these changes open
|
||||||
|
//! the newer design with a warning and preserve saved values they cannot read.
|
||||||
|
//! CPU jobs also save optional `options.lens` using the shared physical thin-lens
|
||||||
|
//! aperture radius and axial focus distance. Positive aperture requires perspective
|
||||||
|
//! plus explicit progressive camera sampling; absent/zero aperture keeps old pixels.
|
||||||
|
//! CPU raytrace jobs also retain default-false `transparent_background` beside options.
|
||||||
|
//! It exports straight-alpha primary geometry coverage; absent fields keep opaque PNGs.
|
||||||
|
//! CPU jobs also save opt-in `options.denoise`; omission/false preserves the prior
|
||||||
|
//! raw PNG calculation. Every complete published pass is filtered without changing
|
||||||
|
//! raw film or the final unfiltered EXR. Older files need no rewrite for this flag.
|
||||||
|
//! CPU jobs also preserve optional `options.path: {max_depth: 1..16}` for explicit
|
||||||
|
//! progressive diffuse/specular transport and certified Glass boundaries. The depth
|
||||||
|
//! counts shaded surfaces, optical boundaries and active scattering events. Traced shadows are required;
|
||||||
|
//! selected reflection/anisotropy overrides and unsupported closed-Glass
|
||||||
|
//! shadow connections refuse with authored intent retained. Omission keeps the legacy producer.
|
||||||
|
//!
|
||||||
|
//! CPU raytrace jobs save optional `options.aging` using the existing ordered
|
||||||
|
//! SavedAgingStack schema and stable definition/feature/component/occurrence targets.
|
||||||
|
//! Empty/absent stacks preserve prior saved JSON, CPU pixel arithmetic and work.
|
||||||
|
//! Check and Start resolve stable targets against the captured scene; stale/hidden
|
||||||
|
//! targets are refused rather than made global. Captured indices/names remain
|
||||||
|
//! runtime-only. Aging changes the opaque BRDF before lighting and denoising guides;
|
||||||
|
//! it does not change geometry, emission, physical materials or mass. Certified
|
||||||
|
//! glass targets and passive stages remain excluded; independently authored anisotropic axes
|
||||||
|
//! cannot be replaced by scalar aging roughness and are refused explicitly.
|
||||||
|
//!
|
||||||
|
//! Saves up to eight hard GPU directional sources alongside the existing optional
|
||||||
|
//! finite Sun (eight combined). Older designs omit the new list and retain their
|
||||||
|
//! exact implicit studio or finite-Sun lighting. Readers missing the new list warn
|
||||||
|
//! about the newer design and preserve these unread sources.
|
||||||
|
//! Also saves up to eight GPU point/spot sources with exact world-mm positions,
|
||||||
|
//! reference/minimum distances and optional world-axis/radian half cones. Missing
|
||||||
|
//! punctual lists stay empty. CPU cone intent retains its wider f64 domain; GPU
|
||||||
|
//! cone feathers below the documented precision bound refuse without rewriting.
|
||||||
|
//!
|
||||||
|
//! CPU jobs may also consume the existing portable saved `surfaces` bindings for
|
||||||
|
//! opaque built-in base color, roughness, metallic and normal maps. No new map
|
||||||
|
//! schema is added; raster and unsupported CPU optical combinations refuse the
|
||||||
|
//! authored bindings instead of dropping them. Old files without maps keep the
|
||||||
|
//! old CPU allocation/work/pixel path.
|
||||||
|
//! CPU jobs also save optional `options.adaptive` using the existing independent-pilot
|
||||||
|
//! policy. Explicit progressive production sampling is required; the unchanged CPU
|
||||||
|
//! profile budgets pilots plus the production cap. Exact per-pixel counts and pilot
|
||||||
|
//! statistics stay runtime-only; final SamplingReport and PreviewInfo.adaptive reuse
|
||||||
|
//! the existing metadata contract without changing linear-master schema/channels.
|
||||||
|
|
||||||
|
//! CPU `options.path` may consume the existing saved ClosedMesh optical boundary
|
||||||
|
//! after exact f64 edge, vertex-link, embedding/contact, outward and conservative
|
||||||
|
//! nested-parent certification. Runtime-only ClosedMesh and thin-shadow lowering
|
||||||
|
//! preserve stable authored optics. No new saved field or geometry repair is added.
|
||||||
|
//! Absent-path sheet/box transport and default serialized bytes remain unchanged.
|
||||||
|
//! Closed-shadow connections, Glass combined with external volume and selected
|
||||||
|
//! reflection/anisotropy combinations refuse explicitly with authored values retained.
|
||||||
|
//! Perspective CPU Path captures classify each physical eye/pupil cooperatively
|
||||||
|
//! against the certified boundaries and initialize the nested Beer medium stack.
|
||||||
|
//! Exact origin/near-clip boundary contacts refuse without changing saved cameras
|
||||||
|
//! or optics. This runtime-only extension adds no serialized field.
|
||||||
|
//! Existing saved OpticalMaterial.subsurface may also lower to optional captured
|
||||||
|
//! CPU dielectric subsurface parameters with explicit progressive Path. Selected
|
||||||
|
//! certified closed media use scalar free flight, HG directions and separate Beer
|
||||||
|
//! absorption, including physical eye/pupil starts and nested current media. No
|
||||||
|
//! refracting-boundary next-event approximation is added. Active scattering and
|
||||||
|
//! surface denoising refuse together; unsupported thin/legacy routes retain intent.
|
||||||
|
//! Some(rate0) remains serialized but adds no collision work/RNG or pixel change.
|
||||||
|
//! CPU jobs may also preserve optional options.volume using the same bounded
|
||||||
|
//! HomogeneousVolume world-box/extinction/RGB albedo/HG contract as GPU Atmosphere.
|
||||||
|
//! Positive extinction requires explicit progressive Path and disables no existing
|
||||||
|
//! option implicitly: Glass/SSS and surface denoising refuse with intent retained.
|
||||||
|
//! Absent/zero volume keeps the original transport work/RNG/heap owner count/pixels.
|
||||||
|
//! Inline intent's actual fixed retained-request size is prepaid even when absent;
|
||||||
|
//! regrade owns its separate header reservation until its last status handle drops.
|
||||||
|
//! Physical eye/pupil prefixes and the orthographic signed line/box union do not
|
||||||
|
//! alter saved camera/framing/model bounds or its model near interval. Existing
|
||||||
|
//! ray-distance/profile/task/frame/codec/host caps remain unchanged.
|
||||||
|
|
||||||
|
//! CPU jobs also retain optional `sun` and `sky` intent. `sun` reuses the shared
|
||||||
|
//! finite-cap direction/angular-radius/radiance domain; `sky` reuses the portable
|
||||||
|
//! gradient/rotation/intensity schema. Both require explicit CPU Path transport.
|
||||||
|
//! Omitted fields preserve old designs and the absent producer allocation/work/RNG
|
||||||
|
//! path. Constant, Sky and HDR sources are exclusive; finite Sun is additive.
|
||||||
|
//! No generated sky image or runtime resource is serialized.
|
||||||
|
//!
|
||||||
|
//! CPU and GPU jobs also save `coats`, up to the existing 64 runtime coat target
|
||||||
|
//! bound, with stable definition/feature/component/occurrence identities and exact
|
||||||
|
//! weight, roughness and dielectric IOR. Empty/absent lists keep prior JSON and
|
||||||
|
//! capture behavior. Coats require opaque targets; even a zero-weight authored coat
|
||||||
|
//! cannot share a target with Glass. Capture resolves targets without changing
|
||||||
|
//! appearances, engineering materials or geometry. Unsupported CPU selected
|
||||||
|
//! reflection/anisotropy overrides refuse instead of dropping the coat.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
//! Saves exact encoded texture bytes, inert provenance and explicit saved surface roles.
|
||||||
|
//! Older designs have no embedded textures and retain their built-in surface bindings.
|
||||||
|
//! The last release asks for an update instead of silently dropping the new resources.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//! Glass assignments can save optional `model` roughness, Abbe dispersion and a
|
||||||
|
//! coherent dielectric film (IOR and thickness in nanometres). Omission retains
|
||||||
|
//! the preceding smooth, nondispersive interface and incoherent sheet response.
|
||||||
|
//! CPU and GPU jobs can save optional `caustics` photon count, gather radius in
|
||||||
|
//! millimetres, light-path depth and seed. Omission creates no photon map and
|
||||||
|
//! retains preceding sampling. Older releases ask for an update rather than
|
||||||
|
//! discard these authored material and light-transport choices. No migration of
|
||||||
|
//! existing designs, geometry, engineering material or saved camera is required.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//! A sketch's intersection link can save a curve of another sketch as either side (`a` or `b`,
|
||||||
|
//! `{"type": "sketch_curve", sketch, curve}`), standing in for that curve's extrusion along its
|
||||||
|
//! sketch's normal (#11048). An older design has only bodies and faces there, and opens as before.
|
||||||
|
//! The last release refuses a design that has one (it asks for an update) instead of opening the
|
||||||
|
//! link as lost.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//! An at-angle construction plane may save `direction_only` and an optional `pivot` point: the
|
||||||
|
//! plane turns about the line through the pivot (or the base origin) using only the axis
|
||||||
|
//! direction projected onto the base. An older design has neither and keeps turning about the
|
||||||
|
//! axis as a full line. The last release refuses a design that has this change (the newer
|
||||||
|
//! format number) instead of opening it without the option.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user