Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5728f284b | |||
| dc32cff620 | |||
| 531fc04db8 | |||
| 4907429439 | |||
| 192ef78ff3 | |||
| 44a0be8d94 | |||
| 9cba66c14d | |||
| 4afd93331f | |||
| 698621dfdf | |||
| 5fc23436ca | |||
| 76879f8b39 | |||
| 514b0c84f9 | |||
| ddca1054ab | |||
| 783ee91404 | |||
| 38dedcca3b | |||
| 99cca6a11a | |||
| af07f4e0e5 | |||
| 0c16cfefbb | |||
| b2189e3f4d | |||
| 6e12f5954f | |||
| 3ad5a360f2 | |||
| 6848c78424 | |||
| b47f82d406 | |||
| 614e006eac | |||
| 4e07b6c4b7 | |||
| efa2cae0b4 | |||
| 78d80e0144 | |||
| 13f650b695 | |||
| a7c1909766 | |||
| 702f35ae6e | |||
| 2d629069c6 | |||
| 4b327bab1f | |||
| 237341423c | |||
| b99761a3f7 | |||
| 3726959d82 | |||
| af1d15b5ea | |||
| 84a3a0c478 | |||
| 1551c0acef | |||
| f1cc49bcdb | |||
| 622a392a83 | |||
| 5622963b91 | |||
| 3bea744823 | |||
| e8eb7ee11a | |||
| ebb6cc53ab | |||
| 924724dc89 | |||
| 56bcd872cb | |||
| 6c0fca30a7 | |||
| ef1d02f274 | |||
| 367aa249bc | |||
| afd6a5c584 | |||
| 65366ba131 | |||
| f077f48318 | |||
| bd44f6f496 | |||
| 3918f23c3d |
@@ -292,6 +292,53 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/model/colored_components_off.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/combine.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/component.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
|
||||
### Render workspace icon drafts
|
||||
|
||||
Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/icons/ui/design/svg/model/render_all_shots.svg | SolidCraft contributors | original Camera icon master (64 grid): stacked camera bodies with a lower-right play badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_appearance.svg | SolidCraft contributors | original Looks icon master (64 grid): shaded look sphere on the top corner of an accent-shaded cube | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_aspect_frame.svg | SolidCraft contributors | original Camera icon master (64 grid): wide rounded frame with two opposite corner brackets | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_backdrop.svg | SolidCraft contributors | original Studio icon master (64 grid): grey cube in front of a tall upright accent panel | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_cancel.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round stop badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_check.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round check badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_compare.svg | SolidCraft contributors | original Output icon master (64 grid): two grey pictures split by a vertical slider with opposed arrows | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_copy_look.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere in a square frame offset from a second frame outline | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_execute.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round play badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_export_image.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a large outward diagonal arrow at the upper right | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_exposure.svg | SolidCraft contributors | original Camera icon master (64 grid): split light and accent disc with plus and minus marks | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_fit_product.svg | SolidCraft contributors | original Camera icon master (64 grid): grey cube inside four corner brackets | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_frame_guides.svg | SolidCraft contributors | original Camera icon master (64 grid): rounded frame with a rule-of-thirds grid and four accent nodes | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_from_material.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere struck through above a plain grey slab | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_ground.svg | SolidCraft contributors | original Studio icon master (64 grid): grey cube on a slab with an accent contact patch | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_lens.svg | SolidCraft contributors | original Camera icon master (64 grid): concentric lens rings around an accent centre | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_library.svg | SolidCraft contributors | original Studio icon master (64 grid): row of three look spheres, middle one accent, on a shelf bar | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_area.svg | SolidCraft contributors | original Lights icon master (64 grid): slanted flat panel above a dotted accent stem | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_by_highlight.svg | SolidCraft contributors | original Lights icon master (64 grid): shaded sphere with a ringed highlight target and dotted stem arriving from above | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_point.svg | SolidCraft contributors | original Lights icon master (64 grid): round source disc with centre dot above a dotted accent stem | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_spot.svg | SolidCraft contributors | original Lights icon master (64 grid): cone source with curved rim above a dotted accent stem | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_lighting_presets.svg | SolidCraft contributors | original Studio icon master (64 grid): two stacked cards, the front one holding a light bulb | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_output_size.svg | SolidCraft contributors | original Output icon master (64 grid): picture frame with a diagonal double-headed dimension arrow | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_paint.svg | SolidCraft contributors | original Looks icon master (64 grid): flat brush with an accent tip, light ferrule and slate handle | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_pick_up.svg | SolidCraft contributors | original Looks icon master (64 grid): eyedropper with a grey bulb, slate crossbar and accent sampling tip | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_quality.svg | SolidCraft contributors | original Output icon master (64 grid): three rising bars, the tallest in accent | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_replace_all.svg | SolidCraft contributors | original Looks icon master (64 grid): grey sample sphere with an arrow to an accent sphere beside a second one | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_results.svg | SolidCraft contributors | original Output icon master (64 grid): stack of two pictures, the front one showing a landscape | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_same_look.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere and three cubes, the two matching cubes ringed | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_shot.svg | SolidCraft contributors | original Camera icon master (64 grid): camera body with a lower-right plus badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_shots_strip.svg | SolidCraft contributors | original Camera icon master (64 grid): strip of three small frames, the middle one in accent | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_solo_light.svg | SolidCraft contributors | original Lights icon master (64 grid): point source and dotted stem inside four corner focus brackets | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_sun.svg | SolidCraft contributors | original Lights icon master (64 grid): source disc with eight short radial rays | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_transparent_background.svg | SolidCraft contributors | original Output icon master (64 grid): grey cube on a checker tile | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_update_shot.svg | SolidCraft contributors | original Camera icon master (64 grid): camera body with a lower-right refresh badge | MIT OR Apache-2.0 |
|
||||
|
||||
### UI icon masters (continued)
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/icons/ui/design/svg/model/compute.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/configuration_table.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/conic.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -381,6 +428,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/flat/shield.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/status_recovery.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/frame_member.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_environment.svg | SolidCraft contributors | original Studio icon master (64 grid): dome with a grey cube inside | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/placeholder.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/plane.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/plane_angle.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -527,6 +575,9 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| docs/book/scripts/hem.json | SolidCraft contributors | original parametric recipe for an 80 × 50 mm sheet with an 8 mm returned edge, authored for the command guide | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/clay.json | SolidCraft contributors | original SolidCraft theme file (Clay, light): warm paper palette chosen for SolidCraft | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/clay-dark.json | SolidCraft contributors | original SolidCraft theme file (Clay Dark): warm charcoal palette chosen for SolidCraft | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/cone.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/locales/ar.json | Abdullah (A-AL-ANAZI) | Original Arabic translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/locales/he.json | SolidCraft contributors | Original Hebrew translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
|
||||
## Code adapted from sibling projects
|
||||
|
||||
|
||||
@@ -4208,7 +4208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"eframe",
|
||||
@@ -4217,6 +4217,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"hidapi",
|
||||
"keyring",
|
||||
"libc",
|
||||
"log",
|
||||
"muda",
|
||||
"objc2 0.6.5",
|
||||
@@ -4228,6 +4229,7 @@ dependencies = [
|
||||
"rfd",
|
||||
"ron",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcraft-3dx-client",
|
||||
"solidcraft-engine",
|
||||
@@ -4240,11 +4242,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-3dx-client"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-cli"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4256,7 +4258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-derive"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4265,7 +4267,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-doc"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -4282,8 +4284,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-engine"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -4302,7 +4305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-geom"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4311,7 +4314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-gpu"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4331,7 +4334,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-io"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"cadmpeg-ir",
|
||||
@@ -4355,7 +4358,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-kernel"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"proptest",
|
||||
@@ -4375,7 +4378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-mcp"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
@@ -4384,7 +4387,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-render"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"moxcms",
|
||||
@@ -4398,7 +4401,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-sketch"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4409,14 +4412,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-space-mouse"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-egui"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"egui",
|
||||
@@ -4438,6 +4441,7 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
"toml",
|
||||
"ttf-parser",
|
||||
"unicode-bidi",
|
||||
"web-time",
|
||||
"winreg",
|
||||
"zip",
|
||||
@@ -4445,7 +4449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -4947,6 +4951,12 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "1.1.0"
|
||||
@@ -5984,7 +5994,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"serde",
|
||||
|
||||
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
exclude = ["vendor"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Third-party software in SolidCraft
|
||||
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 559 crates below.
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 560 crates below.
|
||||
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
|
||||
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
|
||||
|
||||
@@ -439,6 +439,7 @@ typed-path 0.12.3 (MIT OR Apache-2.0)
|
||||
typeid 1.0.3 (MIT OR Apache-2.0)
|
||||
typenum 1.20.1 (MIT OR Apache-2.0)
|
||||
uds_windows 1.2.1 (MIT)
|
||||
unicode-bidi 0.3.18 (MIT OR Apache-2.0)
|
||||
unicode-general-category 1.1.0 (Apache-2.0)
|
||||
unicode-ident 1.0.26 ((MIT OR Apache-2.0) AND Unicode-3.0)
|
||||
unicode-segmentation 1.13.3 (MIT OR Apache-2.0)
|
||||
@@ -1123,9 +1124,9 @@ parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, plain 0.2.3, png 0
|
||||
rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard 1.2.0, secret-service 4.0.0,
|
||||
security-framework 2.11.1, security-framework 3.7.0, security-framework-sys 2.17.0,
|
||||
signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.16.2, smol_str 0.2.2, stable_deref_trait
|
||||
1.2.1, syn 1.0.109, tempfile 3.27.0, ttf-parser 0.25.1, unicode-segmentation 1.13.3, unicode-width
|
||||
0.2.2, url 2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12,
|
||||
wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129,
|
||||
1.2.1, syn 1.0.109, tempfile 3.27.0, ttf-parser 0.25.1, unicode-bidi 0.3.18, unicode-segmentation
|
||||
1.13.3, unicode-width 0.2.2, url 2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2
|
||||
1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129,
|
||||
wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12,
|
||||
wit-bindgen 0.57.1
|
||||
================================================================================
|
||||
@@ -14524,6 +14525,49 @@ MIT License
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
|
||||
================================================================================
|
||||
unicode-bidi 0.3.18
|
||||
================================================================================
|
||||
|
||||
This project is copyright 2015, The Servo Project Developers (given in the
|
||||
file AUTHORS).
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
http://opensource.org/licenses/MIT>, at your option. All files in the project
|
||||
carrying such notice may not be copied, modified, or distributed except
|
||||
according to those terms.
|
||||
|
||||
================================================================================
|
||||
unicode-bidi 0.3.18, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2015 The Rust Project Developers
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
unicode-ident 1.0.26
|
||||
================================================================================
|
||||
@@ -14572,36 +14616,6 @@ authorization of the copyright holder.
|
||||
unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2015 The Rust Project Developers
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
================================================================================
|
||||
|
||||
Licensed under the Apache License, Version 2.0
|
||||
<LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! ```text
|
||||
//! solidcraft-cli run <script.json|design.solidcraft|part.step|mesh.3mf> [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]
|
||||
//! solidcraft-cli eval <script.json|design.solidcraft|part.step> [--json-out FILE] (alias: inspect) measurements as JSON
|
||||
//! solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] (alias: inspect) measurements as JSON
|
||||
//! solidcraft-cli script <design.solidcraft> [--out script.json] the design's history as a command script
|
||||
//! solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json] loading timings as JSON lines
|
||||
//! solidcraft-cli bench-agent [--scale F] [--save F] [--script-out F] a ~1,300-body design built command by command, timed
|
||||
@@ -58,7 +58,7 @@ struct Command {
|
||||
/// `run`'s lines in the usage.
|
||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\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";
|
||||
/// `eval`'s lines in the usage.
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.solidcraft|part.step> [--json-out FILE] (alias: inspect; or --in FILE)\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";
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.solidcraft|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";
|
||||
/// `script`'s lines in the usage.
|
||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.solidcraft|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.
|
||||
@@ -392,7 +392,7 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The session `run` and `snapshot` start from: `--in` (a design or STEP file) with the
|
||||
/// The session `run`, `eval` and `snapshot` start from: `--in` (a design or STEP file) with the
|
||||
/// positional script run on top of it, or either one alone.
|
||||
fn open(base: Option<&str>, path: Option<&String>) -> Result<Session, String> {
|
||||
match (base, path) {
|
||||
@@ -445,8 +445,7 @@ fn timeline_ok(s: &Session) -> Result<(), String> {
|
||||
fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||
let json_out = json_out(args)?;
|
||||
known_args("eval", args, &["--in", "--json-out"], &[], true)?;
|
||||
let path = flag(args, "--in").or_else(|| positional(args).map(String::as_str)).ok_or_else(usage)?;
|
||||
let mut s = load(path)?;
|
||||
let mut s = open(flag(args, "--in"), positional(args))?;
|
||||
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 out = json!({
|
||||
|
||||
@@ -139,11 +139,13 @@ fn eval_json_out_holds_what_stdout_would() {
|
||||
fn json_out_needs_a_file_and_fails_when_it_cannot_write() {
|
||||
let dir = Scratch::new();
|
||||
let script = dir.script();
|
||||
for cmd in ["run", "eval"] {
|
||||
let output = cli().arg(cmd).arg(&script).arg("--json-out").output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1), "{cmd}");
|
||||
assert!(String::from_utf8_lossy(&output.stderr).contains("--json-out needs a FILE"), "{cmd}");
|
||||
assert!(output.stdout.is_empty(), "{cmd}");
|
||||
for cmd in ["run", "eval", "inspect"] {
|
||||
for trailing in [&[][..], &["--in"][..]] {
|
||||
let output = cli().arg(cmd).arg(&script).arg("--json-out").args(trailing).output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1), "{cmd}");
|
||||
assert!(String::from_utf8_lossy(&output.stderr).contains("--json-out needs a FILE"), "{cmd}");
|
||||
assert!(output.stdout.is_empty(), "{cmd}");
|
||||
}
|
||||
|
||||
let missing = dir.0.join("missing").join("out.json");
|
||||
let output = cli().arg(cmd).arg(&script).arg("--json-out").arg(&missing).output().unwrap();
|
||||
|
||||
@@ -267,6 +267,110 @@ fn results_with_base_design_and_quiet_still_report_and_save() {
|
||||
assert_eq!(body_count(&base), 1);
|
||||
}
|
||||
|
||||
/// `eval` and its alias must measure the script applied to the input design, rather than
|
||||
/// silently reporting the input alone (#6861).
|
||||
#[test]
|
||||
fn eval_runs_its_script_on_the_in_design() {
|
||||
let dir = Scratch::new();
|
||||
let base = dir.0.join("base.solidcraft");
|
||||
let script = dir.0.join("add.json");
|
||||
json_output(cli().arg("run").arg(dir.script()).arg("--save").arg(&base).output().unwrap());
|
||||
let original = std::fs::read(&base).unwrap();
|
||||
std::fs::write(
|
||||
&script,
|
||||
json!({"commands": [{"command": "solid.box", "params": {"length": 5, "width": 3, "height": 4, "corner": [10, 0, 0]}}]}).to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
for command in ["eval", "inspect"] {
|
||||
// Both existing one-input forms still measure just the original body.
|
||||
for input in [&base, &dir.script()] {
|
||||
for named_input in [false, true] {
|
||||
let mut c = cli();
|
||||
c.arg(command);
|
||||
if named_input {
|
||||
c.arg("--in");
|
||||
}
|
||||
let measured = json_output(c.arg(input).output().unwrap());
|
||||
assert_eq!(measured["body_count"], 1);
|
||||
assert!((measured["total"]["volume_mm3"].as_f64().unwrap() - 24.0).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
for input_first in [false, true] {
|
||||
for to_file in [false, true] {
|
||||
let mut c = cli();
|
||||
c.arg(command);
|
||||
if input_first {
|
||||
c.arg("--in").arg(&base).arg(&script);
|
||||
} else {
|
||||
c.arg(&script).arg("--in").arg(&base);
|
||||
}
|
||||
let report = dir.0.join("measurement.json");
|
||||
if to_file {
|
||||
c.arg("--json-out").arg(&report);
|
||||
}
|
||||
let output = c.output().unwrap();
|
||||
let measured = if to_file {
|
||||
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
assert!(output.stdout.is_empty());
|
||||
serde_json::from_slice(&std::fs::read(&report).unwrap()).unwrap()
|
||||
} else {
|
||||
json_output(output)
|
||||
};
|
||||
assert_eq!(measured["body_count"], 2, "{command}, input_first={input_first}, to_file={to_file}");
|
||||
assert!((measured["total"]["volume_mm3"].as_f64().unwrap() - 84.0).abs() < 1e-6);
|
||||
assert_eq!(measured["timeline"].as_array().unwrap().len(), 2, "the script runs exactly once");
|
||||
assert_eq!(std::fs::read(&base).unwrap(), original, "evaluation never saves its changes to the input");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_propagates_script_errors_before_writing_a_report() {
|
||||
let dir = Scratch::new();
|
||||
let base = dir.script();
|
||||
let script = dir.0.join("bad.json");
|
||||
for command in ["eval", "inspect"] {
|
||||
for contents in ["{", r#"{"commands":[{"command":"missing.command","params":{}}]}"#] {
|
||||
std::fs::write(&script, contents).unwrap();
|
||||
let report = dir.0.join("report.json");
|
||||
let output = cli().arg(command).arg(&script).arg("--in").arg(&base).arg("--json-out").arg(&report).output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
assert!(!output.stderr.is_empty());
|
||||
assert!(output.stdout.is_empty());
|
||||
assert!(!report.exists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_rejects_unknown_options_and_missing_input_values_before_writing_a_report() {
|
||||
let dir = Scratch::new();
|
||||
let script = dir.script();
|
||||
let report = dir.0.join("report.json");
|
||||
for command in ["eval", "inspect"] {
|
||||
for (options, message) in [(&["--frobnicate", "1"][..], "doesn't take --frobnicate"), (&["--in"][..], "--in needs a value")] {
|
||||
for options_first in [false, true] {
|
||||
std::fs::write(&report, b"previous report").unwrap();
|
||||
let mut c = cli();
|
||||
c.arg(command);
|
||||
if options_first {
|
||||
c.args(options).arg("--json-out").arg(&report).arg(&script);
|
||||
} else {
|
||||
c.arg(&script).args(options).arg("--json-out").arg(&report);
|
||||
}
|
||||
// A following option also proves that --in cannot consume a flag as its value.
|
||||
let output = c.output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1), "{command}, {options:?}");
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(error.contains(message), "{command}: {error}");
|
||||
assert!(output.stdout.is_empty());
|
||||
assert_eq!(std::fs::read(&report).unwrap(), b"previous report");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn results_propagate_output_errors() {
|
||||
let dir = Scratch::new();
|
||||
|
||||
@@ -37,5 +37,5 @@ web-sys = { version = "0.3", features = [
|
||||
"WritableStream", "File", "FileList", "Blob", "BlobPropertyBag", "Url", "HtmlAnchorElement", "HtmlInputElement", "Storage",
|
||||
"IdbFactory", "IdbDatabase", "IdbObjectStore", "IdbOpenDbRequest", "IdbRequest", "IdbTransaction", "IdbTransactionMode",
|
||||
"Headers", "ReadableStream", "ReadableStreamDefaultReader",
|
||||
"BeforeUnloadEvent", "Event", "EventTarget", "KeyboardEvent", "Response", "MediaQueryList", "Gamepad", "Performance",
|
||||
"BeforeUnloadEvent", "Event", "EventTarget", "KeyboardEvent", "Response", "FormData", "RequestInit", "MediaQueryList", "Gamepad", "Performance",
|
||||
] }
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//! Open with EasyPrint in the browser: upload the model to Printables' import endpoint with
|
||||
//! `fetch` (it allows any origin) and answer with the address to open.
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use js_sys::{Array, Uint8Array};
|
||||
use solidcraft_engine::easyprint::{IMPORT_URL, parse_import_response};
|
||||
use wasm_bindgen::JsCast as _;
|
||||
use wasm_bindgen_futures::{JsFuture, spawn_local};
|
||||
|
||||
use crate::files::err;
|
||||
|
||||
/// Start the upload; the answer arrives on `done`.
|
||||
pub fn upload(name: &str, bytes: Vec<u8>, done: Sender<Result<String, String>>) {
|
||||
let name = name.to_string();
|
||||
spawn_local(async move {
|
||||
let _ = done.send(post(&name, &bytes).await);
|
||||
});
|
||||
}
|
||||
|
||||
async fn post(name: &str, bytes: &[u8]) -> Result<String, String> {
|
||||
let window = web_sys::window().ok_or("no window")?;
|
||||
let parts = Array::new();
|
||||
parts.push(&Uint8Array::from(bytes));
|
||||
let blob = web_sys::Blob::new_with_u8_array_sequence(&parts).map_err(err)?;
|
||||
let form = web_sys::FormData::new().map_err(err)?;
|
||||
form.append_with_blob_and_filename("file", &blob, name).map_err(err)?;
|
||||
form.append_with_str("file_name", name).map_err(err)?;
|
||||
let headers = web_sys::Headers::new().map_err(err)?;
|
||||
headers.set("Accept", "application/json").map_err(err)?;
|
||||
let init = web_sys::RequestInit::new();
|
||||
init.set_method("POST");
|
||||
init.set_body(&form);
|
||||
init.set_headers(&headers);
|
||||
let response = JsFuture::from(window.fetch_with_str_and_init(IMPORT_URL, &init)).await.map_err(err)?;
|
||||
let response: web_sys::Response = response.dyn_into().map_err(|_| "response cast failed".to_string())?;
|
||||
let status = response.status();
|
||||
let text = JsFuture::from(response.text().map_err(err)?).await.map_err(err)?;
|
||||
parse_import_response(status, text.as_string().unwrap_or_default().as_bytes())
|
||||
}
|
||||
@@ -11,6 +11,8 @@ mod api;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod disk;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod easyprint;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod files;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod install;
|
||||
@@ -777,6 +779,7 @@ mod web {
|
||||
})),
|
||||
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path))),
|
||||
download_design: Some(Box::new(files::download)),
|
||||
easyprint_upload: Some(std::sync::Arc::new(crate::easyprint::upload)),
|
||||
browser_files: Some(Box::new(move || shown.panel.set(true))),
|
||||
install_app: Some(crate::install::service(&cc.egui_ctx)),
|
||||
..Default::default()
|
||||
|
||||
@@ -13,8 +13,9 @@ bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
|
||||
# The workspace lints, except unsafe_code: denied, not forbidden, so that src/open_documents.rs
|
||||
# (macOS file-open events), src/native_crash.rs (Windows exception filter) and src/native_frame.rs
|
||||
# (Windows resize border, taskbar icon) can allow it: none has a safe API. Keep the rest in sync with [workspace.lints].
|
||||
# (macOS file-open events), src/native_crash.rs (Windows exception filter), src/native_crash_macos.rs
|
||||
# (macOS crash signal handler) and src/native_frame.rs (Windows resize border, taskbar icon) can
|
||||
# allow it: none has a safe API. Keep the rest in sync with [workspace.lints].
|
||||
[lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
||||
@@ -31,6 +32,7 @@ solidcraft-ui-egui = { workspace = true }
|
||||
solidcraft-space-mouse = { workspace = true }
|
||||
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11", "links"] }
|
||||
egui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rfd = { workspace = true }
|
||||
@@ -88,6 +90,9 @@ objc2 = "0.6"
|
||||
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSResponder", "NSWindow"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "block2", "NSArray", "NSNotification", "NSOperation", "NSProcessInfo", "NSString", "NSURL"] }
|
||||
block2 = "0.6"
|
||||
# Native crash capture (src/native_crash_macos.rs): the signal handler, the faulting module
|
||||
# (already in the tree through many crates).
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||
winit = { version = "0.30", default-features = false, features = ["x11"] }
|
||||
|
||||
@@ -187,6 +187,35 @@ fn send(out: &mut impl Write, reply: &Value) -> std::io::Result<()> {
|
||||
out.write_all(&line)
|
||||
}
|
||||
|
||||
/// The `"token"` of a request line that is a JSON object, read without building the rest of it.
|
||||
/// Until a connection shows the token, a line costs only its own bytes: parsed whole, a 16 MiB
|
||||
/// line of `[0,0,…]` is hundreds of MB, and 16 such connections could exhaust memory before any
|
||||
/// token was checked.
|
||||
fn token_of(line: &str) -> Option<String> {
|
||||
struct Token;
|
||||
impl<'de> serde::de::Visitor<'de> for Token {
|
||||
type Value = Option<String>;
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str("a request object")
|
||||
}
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||
let mut token = None;
|
||||
while let Some(key) = map.next_key::<String>()? {
|
||||
if key == "token" {
|
||||
token = Some(map.next_value::<String>()?);
|
||||
} else {
|
||||
map.next_value::<serde::de::IgnoredAny>()?;
|
||||
}
|
||||
}
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
let mut de = serde_json::Deserializer::from_str(line);
|
||||
let token = serde::Deserializer::deserialize_map(&mut de, Token).ok()?;
|
||||
de.end().ok()?;
|
||||
token
|
||||
}
|
||||
|
||||
/// The reply to a connection that didn't show the token; the connection is then closed.
|
||||
fn unauthorized() -> Value {
|
||||
json!({"ok": false, "error": "unauthorized: send the control token as \"token\" (see docs/control-protocol.md)"})
|
||||
@@ -486,18 +515,18 @@ fn serve_with(
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
// The first request must carry the token; later ones on the same connection may.
|
||||
if !authenticated {
|
||||
match token_of(&line) {
|
||||
Some(t) if control_auth::tokens_match(&t, token) => authenticated = true,
|
||||
_ => {
|
||||
refuse(&mut out, &mut reader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let reply = match serde_json::from_str::<Value>(&line) {
|
||||
Ok(msg) => {
|
||||
// The first request must carry the token; later ones on the same connection may.
|
||||
if !authenticated {
|
||||
match msg.get("token").and_then(Value::as_str) {
|
||||
Some(t) if control_auth::tokens_match(t, token) => authenticated = true,
|
||||
_ => {
|
||||
refuse(&mut out, &mut reader);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let id = msg.get("id").cloned().unwrap_or(Value::Null);
|
||||
let method = msg.get("method").and_then(Value::as_str).unwrap_or("").to_string();
|
||||
let params = msg.get("params").cloned().unwrap_or(json!({}));
|
||||
@@ -563,10 +592,6 @@ fn serve_with(
|
||||
}
|
||||
r
|
||||
}
|
||||
Err(_) if !authenticated => {
|
||||
refuse(&mut out, &mut reader);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
bad += 1;
|
||||
json!({"ok": false, "error": format!("bad JSON: {e}")})
|
||||
@@ -869,6 +894,19 @@ mod tests {
|
||||
assert!(render.contains("will still write the file"), "{render}");
|
||||
}
|
||||
|
||||
/// The token is read from a request object without building the rest of it: other fields,
|
||||
/// however large or nested, are only skipped. Anything but an object with a string token is
|
||||
/// no token.
|
||||
#[test]
|
||||
fn token_is_read_without_parsing_the_request() {
|
||||
assert_eq!(token_of(r#"{"id":1,"token":"abc","params":{"a":[1,[2,{"b":null}]]}}"#).as_deref(), Some("abc"));
|
||||
let flood = format!(r#"{{"params":[{}0],"token":"t"}}"#, "0,".repeat(1 << 20));
|
||||
assert_eq!(token_of(&flood).as_deref(), Some("t"));
|
||||
for no in [r#"["abc"]"#, r#"{"token":5}"#, r#"{"token":"a"} x"#, r#"{"id":1}"#, "GET / HTTP/1.1", r#""token""#] {
|
||||
assert_eq!(token_of(no), None, "{no}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #205: a connection runs nothing until a request carries the launch's token; a wrong or
|
||||
/// missing token, or garbage, is refused and the connection closed. Once shown, the token
|
||||
/// need not be repeated on that connection.
|
||||
|
||||
@@ -24,6 +24,8 @@ mod control_server;
|
||||
mod device_lost;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod native_crash;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod native_crash_macos;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod native_frame;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -356,6 +358,7 @@ fn services(cc: &eframe::CreationContext<'_>) -> Services {
|
||||
can_restore: None,
|
||||
restore_file: None,
|
||||
download_design: None,
|
||||
easyprint_upload: Some(std::sync::Arc::new(easyprint_upload)),
|
||||
browser_files: None,
|
||||
agent_http: Some(std::sync::Arc::new(agent_http)),
|
||||
secrets: Some(std::sync::Arc::new(Keychain)),
|
||||
@@ -452,6 +455,24 @@ fn agent_http(r: &solidcraft_ui_egui::agent_chat::backend::HttpRequest) -> Resul
|
||||
Ok((status, body))
|
||||
}
|
||||
|
||||
/// Open with EasyPrint: upload the model to Printables' import endpoint on a worker and answer on
|
||||
/// `done` with the address to open. Through [`agent_http`]: no redirects, a timeout, no cookies.
|
||||
fn easyprint_upload(name: &str, bytes: Vec<u8>, done: std::sync::mpsc::Sender<Result<String, String>>) {
|
||||
use solidcraft_engine::easyprint;
|
||||
let name = name.to_string();
|
||||
std::thread::spawn(move || {
|
||||
let (content_type, body) = easyprint::multipart(&name, &bytes);
|
||||
let request = solidcraft_ui_egui::agent_chat::backend::HttpRequest {
|
||||
method: "POST",
|
||||
url: easyprint::IMPORT_URL.into(),
|
||||
headers: vec![("Content-Type".into(), content_type), ("Accept".into(), "application/json".into())],
|
||||
body: Some(body),
|
||||
timeout_s: 180,
|
||||
};
|
||||
let _ = done.send(agent_http(&request).and_then(|(status, body)| easyprint::parse_import_response(status, &body)));
|
||||
});
|
||||
}
|
||||
|
||||
/// API keys in the system keychain (service "SolidCraft agent chat", one entry per agent).
|
||||
struct Keychain;
|
||||
|
||||
@@ -738,10 +759,12 @@ fn main() -> eframe::Result {
|
||||
solidcraft_engine::crash::install(dir, build)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
// A crash in native code (a graphics driver) says where it happened: the exception and the
|
||||
// module on Windows, and on every system the startup step the app was in.
|
||||
// A crash in native code (a graphics driver) says where it happened: the exception or signal
|
||||
// and the module on Windows and macOS, and on every system the startup step the app was in.
|
||||
#[cfg(target_os = "windows")]
|
||||
native_crash::install();
|
||||
#[cfg(target_os = "macos")]
|
||||
native_crash_macos::install();
|
||||
// Windows with only OpenGL to start: the system title bar instead of ours (#2063).
|
||||
let started = match &wgpu_options.wgpu_setup {
|
||||
eframe::egui_wgpu::WgpuSetup::CreateNew(setup) => setup.instance_descriptor.backends,
|
||||
@@ -910,6 +933,10 @@ fn main() -> eframe::Result {
|
||||
open_documents::set_ui(&cc.egui_ctx);
|
||||
[files, open_documents::take()].concat()
|
||||
};
|
||||
// No design given: the designs open at the last quit, when the preference asks (#5542).
|
||||
// Not when driven over the control channel, whose clients expect a fresh design.
|
||||
let files =
|
||||
if files.is_empty() && !sample && control_port.is_none() { solidcraft_ui_egui::documents::designs_to_reopen(&app) } else { files };
|
||||
for f in &files {
|
||||
app.open_path_async(f);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,879 @@
|
||||
//! macOS: what a crash in native code leaves behind (docs/crash-reports.md).
|
||||
//!
|
||||
//! A crash inside a graphics driver or another library (a segfault, an `abort()`, a stack
|
||||
//! overflow) is no Rust panic: the panic hook never runs and the next start only found "an
|
||||
//! unclean exit". A signal handler for SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP (how a trap
|
||||
//! instruction arrives on Apple silicon) and SIGABRT now writes, before the process goes, the
|
||||
//! same small JSON file as the Windows filter (`native_crash.rs`): the signal, the faulting
|
||||
//! module and the offset into it, and a short frame-pointer walk, next to the run's crash journal
|
||||
//! (`solidcraft_engine::crash::native_paths`). The next start puts it into the unclean exit's
|
||||
//! report. The signal then goes on to the handler that was there before (the standard library's
|
||||
//! stack overflow message) or to the default action, so the process still dies of it and macOS
|
||||
//! still writes its own crash report (`~/Library/Logs/DiagnosticReports`; the file names the
|
||||
//! `pid` the JSON records).
|
||||
//!
|
||||
//! Installing a signal handler and asking dyld which image holds an address have no safe API,
|
||||
//! hence the `unsafe_code` allowance on this module (the app crate denies it). The handler runs
|
||||
//! on a thread that may have a broken heap, or on the small alternate stack after a stack
|
||||
//! overflow: it doesn't allocate, formats into a buffer on the stack, and only reads memory on
|
||||
//! the thread's own stack. It writes what it knows from the signal first and only then calls
|
||||
//! `dladdr` (not on POSIX's async-signal-safe list; it takes dyld's recursive reader lock and
|
||||
//! doesn't allocate) to name the modules, rewriting the file, so a hang or a fault in there
|
||||
//! still leaves a record.
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::ffi::{CStr, CString, c_int, c_void};
|
||||
use std::fmt::Write as _;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
||||
|
||||
use libc::{SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, siginfo_t};
|
||||
|
||||
/// The signals a crash arrives as, in the order of [`PREVIOUS`].
|
||||
const SIGNALS: [c_int; 6] = [SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP, SIGABRT];
|
||||
/// Frames the walk records (the faulting instruction first).
|
||||
const MAX_FRAMES: usize = 24;
|
||||
/// A fault this far below a thread's stack is taken for a stack overflow.
|
||||
const GUARD: usize = 64 << 10;
|
||||
|
||||
/// Where the handler writes (NUL-terminated), set once by [`install`].
|
||||
static PATH: OnceLock<CString> = OnceLock::new();
|
||||
/// The handler that was installed before ours for each of [`SIGNALS`] (called after ours), as an
|
||||
/// address (`SIG_DFL` is 0, `SIG_IGN` 1), and its `sa_flags`.
|
||||
static PREVIOUS: [AtomicUsize; 6] = [const { AtomicUsize::new(libc::SIG_DFL) }; 6];
|
||||
static PREVIOUS_FLAGS: [AtomicI32; 6] = [const { AtomicI32::new(0) }; 6];
|
||||
/// The handler has started writing (a second signal while it does is only passed on).
|
||||
static BUSY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Catch native crashes from now on (after `solidcraft_engine::crash::install`; nothing without it).
|
||||
pub fn install() {
|
||||
let Some((info, _)) = solidcraft_engine::crash::native_paths() else { return };
|
||||
let Ok(path) = CString::new(info.as_os_str().as_bytes()) else { return };
|
||||
if PATH.set(path).is_err() {
|
||||
return;
|
||||
}
|
||||
for (slot, &signal) in SIGNALS.iter().enumerate() {
|
||||
// SAFETY: `sigaction` is plain data; all zeros is a valid (empty) value.
|
||||
let mut ours: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||
ours.sa_sigaction = handler as *const () as usize;
|
||||
// On the alternate stack the standard library gives each of its threads, so that a stack
|
||||
// overflow can still be recorded.
|
||||
ours.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
|
||||
// SAFETY: as above.
|
||||
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||
// SAFETY: `handler` has the signature SA_SIGINFO calls it with and stays valid for the
|
||||
// process's life; both structs are valid for the call.
|
||||
if unsafe { libc::sigaction(signal, &ours, &mut old) } == 0
|
||||
&& old.sa_sigaction != ours.sa_sigaction
|
||||
&& let (Some(p), Some(f)) = (PREVIOUS.get(slot), PREVIOUS_FLAGS.get(slot))
|
||||
{
|
||||
f.store(old.sa_flags, Ordering::SeqCst);
|
||||
p.store(old.sa_sigaction, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The signal handler: record, then pass the signal on.
|
||||
unsafe extern "C" fn handler(signal: c_int, info: *mut siginfo_t, context: *mut c_void) {
|
||||
// SAFETY: `__error` is this thread's errno, restored for whatever runs after us.
|
||||
let errno = unsafe { *libc::__error() };
|
||||
let slot = SIGNALS.iter().position(|&s| s == signal).unwrap_or(0);
|
||||
let previous = PREVIOUS.get(slot).map_or(libc::SIG_DFL, |p| p.load(Ordering::SeqCst));
|
||||
// SAFETY: the system passes a valid siginfo and the interrupted thread's context for the
|
||||
// duration of an SA_SIGINFO handler.
|
||||
let f = unsafe { fault(signal, info, context) };
|
||||
// Someone sent a signal the app ignored before: still nothing to record.
|
||||
let ignored = previous == libc::SIG_IGN && !f.fault;
|
||||
if !ignored && !BUSY.swap(true, Ordering::SeqCst) {
|
||||
record(&f);
|
||||
}
|
||||
// SAFETY: as above.
|
||||
unsafe { pass_on(signal, slot, previous, f.fault, info, context) };
|
||||
// SAFETY: as above.
|
||||
unsafe { *libc::__error() = errno };
|
||||
}
|
||||
|
||||
/// The signal comes from the faulting instruction itself (not `kill`, `raise` or `abort`), so
|
||||
/// returning runs it again: on arm64, the exception the thread last took matches the signal.
|
||||
/// macOS gives a SIGSEGV sent with `kill` the same `si_code` as a real one (SEGV_ACCERR), but
|
||||
/// the exception is then the thread's last system call (or something long past, whose fault
|
||||
/// address doesn't match).
|
||||
#[cfg(any(target_arch = "aarch64", test))]
|
||||
fn arm64_fault(signal: c_int, si_code: c_int, addr: usize, esr: u32, far: u64) -> bool {
|
||||
const ABORTS: [u32; 4] = [0x20, 0x21, 0x24, 0x25];
|
||||
const SVC: u32 = 0x15;
|
||||
let class = esr >> 26;
|
||||
signal != SIGABRT
|
||||
&& (1..0x10000).contains(&si_code)
|
||||
&& match signal {
|
||||
SIGSEGV | SIGBUS => ABORTS.contains(&class) && far as usize == addr,
|
||||
_ => class != SVC && !ABORTS.contains(&class),
|
||||
}
|
||||
}
|
||||
|
||||
/// As [`arm64_fault`], from the x86 trap number and page-fault address.
|
||||
#[cfg(any(target_arch = "x86_64", test))]
|
||||
fn x86_fault(signal: c_int, si_code: c_int, addr: usize, trapno: u16, faultvaddr: u64) -> bool {
|
||||
signal != SIGABRT
|
||||
&& (1..0x10000).contains(&si_code)
|
||||
&& match signal {
|
||||
SIGSEGV | SIGBUS => (trapno == 14 && faultvaddr as usize == addr) || matches!(trapno, 12 | 13),
|
||||
SIGILL => trapno == 6,
|
||||
SIGFPE => matches!(trapno, 0 | 16 | 19),
|
||||
SIGTRAP => matches!(trapno, 1 | 3),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Hand the signal to the previous handler, or to the default action. A crash signal that was
|
||||
/// only sent goes to the default action: the handler before ours (the standard library's, for
|
||||
/// SIGSEGV and SIGBUS) expects a fault that happens again when it returns.
|
||||
unsafe fn pass_on(signal: c_int, slot: usize, previous: usize, fault: bool, info: *mut siginfo_t, context: *mut c_void) {
|
||||
let ours = handler as *const () as usize;
|
||||
if previous != libc::SIG_DFL && previous != libc::SIG_IGN && previous != ours && (fault || signal == SIGABRT) {
|
||||
let flags = PREVIOUS_FLAGS.get(slot).map_or(0, |f| f.load(Ordering::SeqCst));
|
||||
if flags & libc::SA_SIGINFO != 0 {
|
||||
// SAFETY: the address came from `sigaction` as an SA_SIGINFO handler.
|
||||
let previous: unsafe extern "C" fn(c_int, *mut siginfo_t, *mut c_void) = unsafe { std::mem::transmute(previous) };
|
||||
// SAFETY: as for this handler.
|
||||
unsafe { previous(signal, info, context) };
|
||||
} else {
|
||||
// SAFETY: the address came from `sigaction` as a plain handler.
|
||||
let previous: unsafe extern "C" fn(c_int) = unsafe { std::mem::transmute(previous) };
|
||||
// SAFETY: as for this handler.
|
||||
unsafe { previous(signal) };
|
||||
}
|
||||
return;
|
||||
}
|
||||
if previous == libc::SIG_IGN && !fault {
|
||||
return;
|
||||
}
|
||||
// SAFETY: as in `install`.
|
||||
let mut default: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||
default.sa_sigaction = libc::SIG_DFL;
|
||||
// SAFETY: a valid struct; restoring the default action is async-signal-safe.
|
||||
unsafe { libc::sigaction(signal, &default, std::ptr::null_mut()) };
|
||||
// A fault happens again when we return, now with the default action, so macOS reports it at
|
||||
// the instruction; anything else is raised again, and arrives once this handler returns.
|
||||
if !fault {
|
||||
// SAFETY: async-signal-safe.
|
||||
unsafe { libc::raise(signal) };
|
||||
}
|
||||
}
|
||||
|
||||
/// The darwin `ucontext_t`, up to the pointer to the machine context (`<sys/_types/_ucontext.h>`).
|
||||
#[repr(C)]
|
||||
struct UContext {
|
||||
_onstack: c_int,
|
||||
_sigmask: u32,
|
||||
_stack: libc::stack_t,
|
||||
_link: *mut c_void,
|
||||
_mcsize: usize,
|
||||
mcontext: *const MContext,
|
||||
}
|
||||
|
||||
/// The darwin arm64 machine context: exception state, then the general registers
|
||||
/// (`<mach/arm/_structs.h>`; the floating-point state that follows isn't read).
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[repr(C)]
|
||||
struct MContext {
|
||||
far: u64,
|
||||
esr: u32,
|
||||
_exception: u32,
|
||||
_x: [u64; 29],
|
||||
fp: u64,
|
||||
_lr: u64,
|
||||
_sp: u64,
|
||||
pc: u64,
|
||||
}
|
||||
|
||||
/// The darwin x86_64 machine context: exception state, then the general registers
|
||||
/// (`<mach/i386/_structs.h>`; rax rbx rcx rdx rdi rsi rbp rsp r8–r15, then rip).
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[repr(C)]
|
||||
struct MContext {
|
||||
trapno: u16,
|
||||
_cpu: u16,
|
||||
err: u32,
|
||||
faultvaddr: u64,
|
||||
gpr: [u64; 16],
|
||||
rip: u64,
|
||||
}
|
||||
|
||||
/// What the faulting instruction did to its address, when the processor says.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Access {
|
||||
Unknown,
|
||||
Read,
|
||||
Write,
|
||||
Execute,
|
||||
}
|
||||
|
||||
/// What a crash signal says, read out of the signal before anything is looked up.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Fault {
|
||||
signal: c_int,
|
||||
si_code: c_int,
|
||||
/// `si_addr`: the address that faulted (for SIGILL, SIGFPE and SIGTRAP, the instruction).
|
||||
addr: usize,
|
||||
pc: usize,
|
||||
fp: usize,
|
||||
access: Access,
|
||||
/// It comes from the faulting instruction (not `kill`, `raise` or `abort`).
|
||||
fault: bool,
|
||||
/// The process that sent the signal (0 or our own for a fault or an `abort()`).
|
||||
sender: i32,
|
||||
pid: i32,
|
||||
thread: u64,
|
||||
thread_name: [u8; 64],
|
||||
/// The thread's stack, `[low, high)` (0, 0 when unknown).
|
||||
stack: (usize, usize),
|
||||
}
|
||||
|
||||
impl Fault {
|
||||
/// The fault hit just below the thread's stack: the stack ran out.
|
||||
fn overflow(&self) -> bool {
|
||||
let (low, high) = self.stack;
|
||||
matches!(self.signal, SIGSEGV | SIGBUS) && high > low && self.addr < low.saturating_add(4096) && self.addr >= low.saturating_sub(GUARD)
|
||||
}
|
||||
|
||||
fn signal_name(&self) -> &'static str {
|
||||
match self.signal {
|
||||
SIGSEGV => "SIGSEGV",
|
||||
SIGBUS => "SIGBUS",
|
||||
SIGILL => "SIGILL",
|
||||
SIGFPE => "SIGFPE",
|
||||
SIGTRAP => "SIGTRAP",
|
||||
SIGABRT => "SIGABRT",
|
||||
_ => "signal",
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
if self.overflow() {
|
||||
return "stack overflow";
|
||||
}
|
||||
match self.signal {
|
||||
SIGSEGV => "segmentation fault",
|
||||
SIGBUS => "bus error",
|
||||
SIGILL => "illegal instruction",
|
||||
SIGFPE => "arithmetic exception",
|
||||
SIGTRAP => "trap",
|
||||
SIGABRT => "abort",
|
||||
_ => "signal",
|
||||
}
|
||||
}
|
||||
|
||||
/// The `si_code` names that say something about a crash.
|
||||
fn code_name(&self) -> Option<&'static str> {
|
||||
Some(match (self.signal, self.si_code) {
|
||||
(SIGSEGV, 1) => "SEGV_MAPERR",
|
||||
(SIGSEGV, 2) => "SEGV_ACCERR",
|
||||
(SIGBUS, 1) => "BUS_ADRALN",
|
||||
(SIGBUS, 2) => "BUS_ADRERR",
|
||||
(SIGBUS, 3) => "BUS_OBJERR",
|
||||
(SIGFPE, 7) => "FPE_INTDIV",
|
||||
(SIGFPE, 8) => "FPE_INTOVF",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// One line on what happened: the address read or written, a stack overflow, who sent it.
|
||||
fn detail(&self, b: &mut Buf) {
|
||||
if self.sender != 0 && self.sender != self.pid {
|
||||
let _ = write!(b, "sent by process {}", self.sender);
|
||||
return;
|
||||
}
|
||||
if !self.fault {
|
||||
if self.signal != SIGABRT {
|
||||
let _ = b.write_str("sent with kill or raise, not a fault");
|
||||
}
|
||||
return;
|
||||
}
|
||||
let what = match self.access {
|
||||
Access::Read => "read of",
|
||||
Access::Write => "write to",
|
||||
Access::Execute => "execution at",
|
||||
Access::Unknown => "access to",
|
||||
};
|
||||
if self.overflow() {
|
||||
let _ = write!(b, "{what} 0x{:016X}, below the thread's stack", self.addr);
|
||||
} else if matches!(self.signal, SIGSEGV | SIGBUS) {
|
||||
let _ = write!(b, "{what} 0x{:016X}", self.addr);
|
||||
} else {
|
||||
let _ = write!(b, "at 0x{:016X}", self.addr);
|
||||
}
|
||||
if let Some(c) = self.code_name() {
|
||||
let _ = write!(b, " ({c})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An image that holds an address, as `dladdr` names it.
|
||||
struct Module<'a> {
|
||||
path: &'a [u8],
|
||||
base: usize,
|
||||
symbol: Option<(&'a [u8], usize)>,
|
||||
}
|
||||
|
||||
/// A fixed buffer to format into without allocating (what doesn't fit is cut off).
|
||||
struct Buf {
|
||||
bytes: [u8; 8192],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Buf {
|
||||
fn new() -> Buf {
|
||||
Buf { bytes: [0; 8192], len: 0 }
|
||||
}
|
||||
|
||||
fn room(&self) -> usize {
|
||||
self.bytes.len().saturating_sub(self.len)
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
self.bytes.get(..self.len).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// `s` as the inside of a JSON string, at most `max` bytes of it (bytes that aren't UTF-8
|
||||
/// and control characters become `?`).
|
||||
fn escaped(&mut self, s: &[u8], max: usize) {
|
||||
for chunk in s.get(..s.len().min(max)).unwrap_or_default().utf8_chunks() {
|
||||
for c in chunk.valid().chars() {
|
||||
let _ = match c {
|
||||
'"' => self.write_str("\\\""),
|
||||
'\\' => self.write_str("\\\\"),
|
||||
c if (c as u32) < 0x20 => self.write_str("?"),
|
||||
c => self.write_char(c),
|
||||
};
|
||||
}
|
||||
if !chunk.invalid().is_empty() {
|
||||
let _ = self.write_str("?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Write for Buf {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
let n = s.len().min(self.room());
|
||||
if let (Some(to), Some(from)) = (self.bytes.get_mut(self.len..self.len + n), s.as_bytes().get(..n)) {
|
||||
to.copy_from_slice(from);
|
||||
self.len += n;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The last part of a path.
|
||||
fn file_name(path: &[u8]) -> &[u8] {
|
||||
path.rsplit(|&c| c == b'/').next().unwrap_or(path)
|
||||
}
|
||||
|
||||
/// The report: what the signal says, then (with `resolve`) the module of the faulting
|
||||
/// instruction and the `frames`. Always valid JSON: frames stop while there is room to close it.
|
||||
fn json(b: &mut Buf, f: &Fault, frames: &[usize], resolve: impl Fn(usize, bool) -> Option<Module<'static>>) {
|
||||
let signal = f.signal_name();
|
||||
let _ = write!(b, "{{\"signal\":\"{signal}\",\"number\":{},\"code\":\"{signal}\",\"name\":\"{}\"", f.signal, f.name());
|
||||
let _ = write!(b, ",\"si_code\":{},\"address\":\"0x{:016X}\",\"thread\":{},\"pid\":{}", f.si_code, f.pc, f.thread, f.pid);
|
||||
let name = f.thread_name.split(|&c| c == 0).next().unwrap_or_default();
|
||||
if !name.is_empty() {
|
||||
let _ = b.write_str(",\"thread_name\":\"");
|
||||
b.escaped(name, 64);
|
||||
let _ = b.write_str("\"");
|
||||
}
|
||||
let mark = b.len;
|
||||
let _ = b.write_str(",\"detail\":\"");
|
||||
let start = b.len;
|
||||
f.detail(b);
|
||||
if b.len == start {
|
||||
b.len = mark;
|
||||
} else {
|
||||
let _ = b.write_str("\"");
|
||||
}
|
||||
if let Some(m) = resolve(f.pc, false) {
|
||||
let _ = write!(b, ",\"offset\":\"0x{:X}\",\"module\":\"", f.pc.wrapping_sub(m.base));
|
||||
b.escaped(m.path, 300);
|
||||
let _ = b.write_str("\"");
|
||||
}
|
||||
if !frames.is_empty() {
|
||||
let _ = b.write_str(",\"frames\":[");
|
||||
for (i, &at) in frames.iter().enumerate() {
|
||||
// A frame is at most ~300 bytes; keep room for it and the closing `]}`.
|
||||
if b.room() < 400 {
|
||||
break;
|
||||
}
|
||||
if i > 0 {
|
||||
let _ = b.write_str(",");
|
||||
}
|
||||
let _ = b.write_str("\"");
|
||||
// A return address points after the call: look up the call itself.
|
||||
match resolve(at, i > 0) {
|
||||
Some(m) => {
|
||||
b.escaped(file_name(m.path), 100);
|
||||
let _ = write!(b, "+0x{:X}", at.wrapping_sub(m.base));
|
||||
if let Some((symbol, start)) = m.symbol {
|
||||
let _ = b.write_str(" ");
|
||||
b.escaped(symbol, 160);
|
||||
let _ = write!(b, "+0x{:X}", at.wrapping_sub(start));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let _ = write!(b, "0x{at:016X}");
|
||||
}
|
||||
}
|
||||
let _ = b.write_str("\"");
|
||||
}
|
||||
let _ = b.write_str("]");
|
||||
}
|
||||
let _ = b.write_str("}");
|
||||
}
|
||||
|
||||
/// The return addresses up the frame-pointer chain from `fp`, after `pc`. `read` gives the word
|
||||
/// at an address; frames outside `stack`, misaligned or not going up end the walk.
|
||||
fn walk(pc: usize, fp: usize, stack: (usize, usize), read: impl Fn(usize) -> Option<usize>, out: &mut [usize; MAX_FRAMES]) -> usize {
|
||||
let (low, high) = stack;
|
||||
let mut n = 0;
|
||||
if let Some(slot) = out.get_mut(n) {
|
||||
*slot = pc;
|
||||
n += 1;
|
||||
}
|
||||
let mut fp = fp;
|
||||
while n < MAX_FRAMES {
|
||||
let in_stack = fp >= low && fp.checked_add(16).is_some_and(|end| end <= high);
|
||||
if !fp.is_multiple_of(8) || !in_stack {
|
||||
break;
|
||||
}
|
||||
let (Some(next), Some(ret)) = (read(fp), read(fp + 8)) else { break };
|
||||
let ret = strip_pointer_auth(ret);
|
||||
if ret == 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(slot) = out.get_mut(n) {
|
||||
*slot = ret;
|
||||
n += 1;
|
||||
}
|
||||
if next <= fp {
|
||||
break;
|
||||
}
|
||||
fp = next;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// Return addresses saved by arm64e code carry a signature in their top bits.
|
||||
fn strip_pointer_auth(addr: usize) -> usize {
|
||||
if cfg!(target_arch = "aarch64") { addr & 0x0000_7FFF_FFFF_FFFF } else { addr }
|
||||
}
|
||||
|
||||
/// Read what the signal and the interrupted thread's registers say.
|
||||
unsafe fn fault(signal: c_int, info: *const siginfo_t, context: *const c_void) -> Fault {
|
||||
let mut f = Fault {
|
||||
signal,
|
||||
si_code: 0,
|
||||
addr: 0,
|
||||
pc: 0,
|
||||
fp: 0,
|
||||
access: Access::Unknown,
|
||||
fault: false,
|
||||
sender: 0,
|
||||
// SAFETY: async-signal-safe.
|
||||
pid: unsafe { libc::getpid() },
|
||||
thread: 0,
|
||||
thread_name: [0; 64],
|
||||
stack: (0, 0),
|
||||
};
|
||||
if !info.is_null() {
|
||||
// SAFETY: the system passes a valid siginfo for the duration of the handler.
|
||||
let info = unsafe { &*info };
|
||||
f.si_code = info.si_code;
|
||||
f.addr = info.si_addr as usize;
|
||||
f.sender = info.si_pid;
|
||||
// Without registers (never seen), the signal's own code has to do.
|
||||
f.fault = signal != SIGABRT && (1..0x10000).contains(&f.si_code);
|
||||
}
|
||||
let context = context as *const UContext;
|
||||
// SAFETY: an SA_SIGINFO handler's third argument is the interrupted thread's ucontext, whose
|
||||
// machine context pointer the kernel fills in.
|
||||
let mcontext = if context.is_null() { std::ptr::null() } else { unsafe { (*context).mcontext } };
|
||||
if !mcontext.is_null() {
|
||||
// SAFETY: as above.
|
||||
let m = unsafe { &*mcontext };
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
f.pc = strip_pointer_auth(m.pc as usize);
|
||||
f.fp = m.fp as usize;
|
||||
f.fault = arm64_fault(signal, f.si_code, f.addr, m.esr, m.far);
|
||||
// A data abort says whether it was a write (WnR); an instruction abort is execution.
|
||||
f.access = match m.esr >> 26 {
|
||||
0x24 | 0x25 if m.esr & (1 << 6) != 0 => Access::Write,
|
||||
0x24 | 0x25 => Access::Read,
|
||||
0x20 | 0x21 => Access::Execute,
|
||||
_ => Access::Unknown,
|
||||
};
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
f.pc = m.rip as usize;
|
||||
f.fp = m.gpr.get(6).copied().unwrap_or(0) as usize;
|
||||
f.fault = x86_fault(signal, f.si_code, f.addr, m.trapno, m.faultvaddr);
|
||||
// A page fault's error code: bit 4 instruction fetch, bit 1 write.
|
||||
f.access = match (m.trapno, m.err) {
|
||||
(14, e) if e & 0x10 != 0 => Access::Execute,
|
||||
(14, e) if e & 0x2 != 0 => Access::Write,
|
||||
(14, _) => Access::Read,
|
||||
_ => Access::Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
if f.pc == 0 && matches!(signal, SIGILL | SIGFPE | SIGTRAP) {
|
||||
f.pc = f.addr;
|
||||
}
|
||||
// SAFETY: these read the calling thread's own pthread structure; the buffers are as long as
|
||||
// we say.
|
||||
unsafe {
|
||||
let me = libc::pthread_self();
|
||||
libc::pthread_threadid_np(me, &mut f.thread);
|
||||
libc::pthread_getname_np(me, f.thread_name.as_mut_ptr().cast(), f.thread_name.len());
|
||||
let high = libc::pthread_get_stackaddr_np(me) as usize;
|
||||
let size = libc::pthread_get_stacksize_np(me);
|
||||
if let Some(low) = high.checked_sub(size) {
|
||||
f.stack = (low, high);
|
||||
}
|
||||
}
|
||||
f
|
||||
}
|
||||
|
||||
/// The image that holds `addr` (the call before it, for a return address).
|
||||
fn resolve(addr: usize, return_address: bool) -> Option<Module<'static>> {
|
||||
let at = if return_address { addr.saturating_sub(1) } else { addr };
|
||||
// SAFETY: `Dl_info` is plain data; all zeros is a valid value.
|
||||
let mut info: libc::Dl_info = unsafe { std::mem::zeroed() };
|
||||
// SAFETY: `dladdr` only reads dyld's image list; the strings it returns live as long as the
|
||||
// image, which a crashing process never unloads.
|
||||
unsafe {
|
||||
if libc::dladdr(at as *const c_void, &mut info) == 0 || info.dli_fname.is_null() {
|
||||
return None;
|
||||
}
|
||||
let symbol = (!info.dli_sname.is_null()).then(|| (CStr::from_ptr(info.dli_sname).to_bytes(), info.dli_saddr as usize));
|
||||
Some(Module { path: CStr::from_ptr(info.dli_fname).to_bytes(), base: info.dli_fbase as usize, symbol })
|
||||
}
|
||||
}
|
||||
|
||||
/// Write what the signal says into the info file, then again with the modules and frames.
|
||||
fn record(f: &Fault) {
|
||||
let Some(path) = PATH.get() else { return };
|
||||
// SAFETY: NUL-terminated path; the descriptor is closed below.
|
||||
let fd = unsafe { libc::open(path.as_ptr(), libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC | libc::O_CLOEXEC, 0o600) };
|
||||
if fd < 0 {
|
||||
return;
|
||||
}
|
||||
let mut b = Buf::new();
|
||||
json(&mut b, f, &[], |_, _| None);
|
||||
let bytes = b.as_bytes();
|
||||
// SAFETY: the buffer is as long as we say.
|
||||
unsafe { libc::write(fd, bytes.as_ptr().cast(), bytes.len()) };
|
||||
let mut frames = [0usize; MAX_FRAMES];
|
||||
// SAFETY: `walk` only reads inside the thread's own stack, 8-byte aligned.
|
||||
let n = walk(f.pc, f.fp, f.stack, |at| Some(unsafe { (at as *const usize).read() }), &mut frames);
|
||||
b.len = 0;
|
||||
json(&mut b, f, frames.get(..n).unwrap_or_default(), resolve);
|
||||
let bytes = b.as_bytes();
|
||||
// SAFETY: as above.
|
||||
unsafe {
|
||||
if libc::pwrite(fd, bytes.as_ptr().cast(), bytes.len(), 0) == bytes.len() as isize {
|
||||
libc::ftruncate(fd, bytes.len() as libc::off_t);
|
||||
}
|
||||
libc::close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn fault(signal: c_int, si_code: c_int, addr: usize) -> Fault {
|
||||
let mut thread_name = [0u8; 64];
|
||||
thread_name[..4].copy_from_slice(b"main");
|
||||
Fault {
|
||||
signal,
|
||||
si_code,
|
||||
addr,
|
||||
pc: 0x1_0000_1234,
|
||||
fp: 0,
|
||||
access: Access::Read,
|
||||
fault: true,
|
||||
sender: 0,
|
||||
pid: 77,
|
||||
thread: 4242,
|
||||
thread_name,
|
||||
stack: (0x7000_0000, 0x7080_0000),
|
||||
}
|
||||
}
|
||||
|
||||
fn report(f: &Fault, frames: &[usize]) -> serde_json::Value {
|
||||
let mut b = Buf::new();
|
||||
json(&mut b, f, frames, |at, _| {
|
||||
(at >= 0x1_0000_0000).then_some(Module {
|
||||
path: b"/Applications/SolidCraft.app/Contents/MacOS/solidcraft",
|
||||
base: 0x1_0000_0000,
|
||||
symbol: Some((b"_ZN4main17h0123456789abcdefE", 0x1_0000_1200)),
|
||||
})
|
||||
});
|
||||
serde_json::from_slice(b.as_bytes()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_segfault_says_the_signal_the_address_read_and_the_module() {
|
||||
let v = report(&fault(SIGSEGV, 1, 0x10), &[0x1_0000_1234, 0x1_0000_2000, 0x5000]);
|
||||
assert_eq!(v["signal"], "SIGSEGV");
|
||||
assert_eq!(v["code"], "SIGSEGV");
|
||||
assert_eq!(v["name"], "segmentation fault");
|
||||
assert_eq!(v["detail"], "read of 0x0000000000000010 (SEGV_MAPERR)");
|
||||
assert_eq!(v["address"], "0x0000000100001234");
|
||||
assert_eq!(v["module"], "/Applications/SolidCraft.app/Contents/MacOS/solidcraft");
|
||||
assert_eq!(v["offset"], "0x1234");
|
||||
assert_eq!(v["thread"], 4242);
|
||||
assert_eq!(v["thread_name"], "main");
|
||||
assert_eq!(v["pid"], 77);
|
||||
let frames: Vec<&str> = v["frames"].as_array().unwrap().iter().map(|f| f.as_str().unwrap()).collect();
|
||||
assert_eq!(
|
||||
frames,
|
||||
["solidcraft+0x1234 _ZN4main17h0123456789abcdefE+0x34", "solidcraft+0x2000 _ZN4main17h0123456789abcdefE+0xE00", "0x0000000000005000"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fault_below_the_stack_is_a_stack_overflow() {
|
||||
let mut f = fault(SIGBUS, 2, 0x7000_0000 - 0x100);
|
||||
f.access = Access::Write;
|
||||
let v = report(&f, &[]);
|
||||
assert_eq!(v["name"], "stack overflow");
|
||||
assert_eq!(v["code"], "SIGBUS");
|
||||
assert_eq!(v["detail"], "write to 0x000000006FFFFF00, below the thread's stack (BUS_ADRERR)");
|
||||
// Far below it, or inside it, it is not.
|
||||
assert_eq!(report(&fault(SIGBUS, 2, 0x10), &[])["name"], "bus error");
|
||||
assert_eq!(report(&fault(SIGSEGV, 2, 0x7040_0000), &[])["name"], "segmentation fault");
|
||||
// Nor without knowing the stack.
|
||||
let mut f = fault(SIGSEGV, 1, 0x7000_0000 - 0x100);
|
||||
f.stack = (0, 0);
|
||||
assert_eq!(report(&f, &[])["name"], "segmentation fault");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_abort_or_a_signal_sent_by_another_process_has_no_address_detail() {
|
||||
let mut f = fault(SIGABRT, 0, 0);
|
||||
f.fault = false;
|
||||
f.sender = 77;
|
||||
let v = report(&f, &[]);
|
||||
assert_eq!(v["signal"], "SIGABRT");
|
||||
assert_eq!(v["name"], "abort");
|
||||
assert!(v.get("detail").is_none(), "{v}");
|
||||
let mut f = fault(SIGSEGV, 0x10001, 0);
|
||||
f.fault = false;
|
||||
f.sender = 501;
|
||||
assert_eq!(report(&f, &[])["detail"], "sent by process 501");
|
||||
// `kill` without a sender, or `raise`.
|
||||
f.sender = 0;
|
||||
assert_eq!(report(&f, &[])["detail"], "sent with kill or raise, not a fault");
|
||||
// A trap names the instruction.
|
||||
assert_eq!(report(&fault(SIGTRAP, 1, 0x1_0000_1234), &[])["detail"], "at 0x0000000100001234");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_fault_runs_again_when_the_handler_returns() {
|
||||
// Measured on an M5 Pro, macOS 27: a read of address 16, then `kill -SEGV` while the
|
||||
// thread waits in a system call. Both have si_code 2.
|
||||
assert!(arm64_fault(SIGSEGV, 2, 0x10, 0x9200_0046, 0x10));
|
||||
assert!(!arm64_fault(SIGSEGV, 2, 0, 0x5600_0080, 0));
|
||||
assert!(!arm64_fault(SIGBUS, 1, 0, 0x5600_0080, 0));
|
||||
// A data abort long past, while the signal names no address.
|
||||
assert!(!arm64_fault(SIGSEGV, 2, 0, 0x9200_0046, 0x10));
|
||||
// A trap instruction (BRK) or an undefined one; but not a system call.
|
||||
assert!(arm64_fault(SIGTRAP, 1, 0x1000, 0xF200_0001, 0));
|
||||
assert!(arm64_fault(SIGILL, 1, 0x1000, 0x0200_0000, 0));
|
||||
assert!(!arm64_fault(SIGILL, 1, 0x1000, 0x5600_0080, 0));
|
||||
// Sent: SI_USER, no code, or an abort.
|
||||
assert!(!arm64_fault(SIGSEGV, 0x10001, 0x10, 0x9200_0046, 0x10));
|
||||
assert!(!arm64_fault(SIGTRAP, 0, 0, 0xF200_0001, 0));
|
||||
assert!(!arm64_fault(SIGABRT, 1, 0, 0x5600_0080, 0));
|
||||
// x86_64: a page fault at its address, a general protection fault, ud2, int3.
|
||||
assert!(x86_fault(SIGSEGV, 1, 0x10, 14, 0x10));
|
||||
assert!(!x86_fault(SIGSEGV, 1, 0, 14, 0x10));
|
||||
assert!(x86_fault(SIGSEGV, 1, 0, 13, 0));
|
||||
assert!(x86_fault(SIGILL, 1, 0x1000, 6, 0));
|
||||
assert!(x86_fault(SIGTRAP, 1, 0x1000, 3, 0));
|
||||
assert!(!x86_fault(SIGILL, 1, 0x1000, 14, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_with_quotes_control_characters_or_bad_utf8_stay_valid_json() {
|
||||
let mut f = fault(SIGILL, 1, 0x1_0000_1234);
|
||||
f.thread_name = [0; 64];
|
||||
f.thread_name[..7].copy_from_slice(b"a\"b\\\n\xFFc");
|
||||
let v = report(&f, &[]);
|
||||
assert_eq!(v["thread_name"], "a\"b\\??c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_report_with_long_names_still_closes_its_json() {
|
||||
let long: &'static [u8] = Box::leak(vec![b'x'; 4000].into_boxed_slice());
|
||||
let f = fault(SIGSEGV, 1, 0x10);
|
||||
let mut b = Buf::new();
|
||||
json(&mut b, &f, &[0x1_0000_1234; MAX_FRAMES], |_, _| Some(Module { path: long, base: 0x1_0000_0000, symbol: Some((long, 0x1_0000_0000)) }));
|
||||
let v: serde_json::Value = serde_json::from_slice(b.as_bytes()).unwrap();
|
||||
assert_eq!(v["module"].as_str().unwrap().len(), 300);
|
||||
let frames = v["frames"].as_array().unwrap();
|
||||
assert!(!frames.is_empty() && frames.len() <= MAX_FRAMES, "{}", frames.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_frame_walk_stays_on_the_stack_and_goes_up() {
|
||||
let stack = (0x1000, 0x2000);
|
||||
// Three frames: 0x1100 -> 0x1200 -> 0x1300 -> 0 (end).
|
||||
let memory = |at: usize| match at {
|
||||
0x1100 => Some(0x1200),
|
||||
0x1108 => Some(0xA),
|
||||
0x1200 => Some(0x1300),
|
||||
0x1208 => Some(0xB),
|
||||
0x1300 => Some(0),
|
||||
0x1308 => Some(0xC),
|
||||
_ => None,
|
||||
};
|
||||
let mut out = [0; MAX_FRAMES];
|
||||
let n = walk(0x9, 0x1100, stack, memory, &mut out);
|
||||
assert_eq!(&out[..n], &[0x9, 0xA, 0xB, 0xC]);
|
||||
// A frame pointer off the stack, misaligned, or one that doesn't go up ends the walk.
|
||||
assert_eq!(walk(0x9, 0x3000, stack, memory, &mut out), 1);
|
||||
assert_eq!(walk(0x9, 0x1101, stack, memory, &mut out), 1);
|
||||
let looping = |at: usize| if at.is_multiple_of(16) { Some(at) } else { Some(0xA) };
|
||||
assert_eq!(walk(0x9, 0x1100, stack, looping, &mut out), 2);
|
||||
// A chain that never ends stops at MAX_FRAMES.
|
||||
let endless = |at: usize| if at.is_multiple_of(16) { Some(at + 16) } else { Some(0xA) };
|
||||
assert_eq!(walk(0x9, 0x1000, stack, endless, &mut out), MAX_FRAMES);
|
||||
}
|
||||
|
||||
/// The child half: install crash reporting and the handler, then crash the way
|
||||
/// `SOLIDCRAFT_NATIVE_CRASH_FAULT` says.
|
||||
#[test]
|
||||
fn child() {
|
||||
let Some(dir) = std::env::var_os("SOLIDCRAFT_NATIVE_CRASH_CHILD") else { return };
|
||||
let build = solidcraft_engine::crash::Build { version: "9.9.9", commit: "abc1234", built: "2026-10-25" };
|
||||
let fresh = solidcraft_engine::crash::install(PathBuf::from(dir), build);
|
||||
println!("FRESH {}", serde_json::to_string(&fresh).unwrap_or_default());
|
||||
let Some(fault) = std::env::var_os("SOLIDCRAFT_NATIVE_CRASH_FAULT") else {
|
||||
solidcraft_engine::crash::end_session();
|
||||
return;
|
||||
};
|
||||
super::install();
|
||||
solidcraft_engine::crash::phase("device creation");
|
||||
match fault.to_str() {
|
||||
Some("abort") => std::process::abort(),
|
||||
Some("overflow") => println!("unreachable {}", recurse(0)),
|
||||
Some("raise") => {
|
||||
// SAFETY: none needed; this is the crash under test.
|
||||
unsafe { libc::raise(SIGSEGV) };
|
||||
println!("survived");
|
||||
}
|
||||
_ => segfault(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs out of stack.
|
||||
#[inline(never)]
|
||||
fn recurse(n: u64) -> u64 {
|
||||
let a = std::hint::black_box([n; 64]);
|
||||
if n == u64::MAX { 0 } else { recurse(n + 1).wrapping_add(a[0]) }
|
||||
}
|
||||
|
||||
/// The crash under test: a read of address 16, as a null struct pointer's field would be.
|
||||
#[inline(never)]
|
||||
fn segfault() {
|
||||
// SAFETY: none; this is the crash under test.
|
||||
let v = unsafe { std::ptr::read_volatile(std::hint::black_box(16usize) as *const u64) };
|
||||
println!("unreachable {v}");
|
||||
}
|
||||
|
||||
fn run(dir: &Path, fault: Option<&str>) -> std::process::Output {
|
||||
let mut c = std::process::Command::new(std::env::current_exe().unwrap());
|
||||
c.args(["--exact", "native_crash_macos::tests::child", "--test-threads=1", "--nocapture"]).env("SOLIDCRAFT_NATIVE_CRASH_CHILD", dir);
|
||||
if let Some(f) = fault {
|
||||
c.env("SOLIDCRAFT_NATIVE_CRASH_FAULT", f);
|
||||
}
|
||||
c.output().unwrap()
|
||||
}
|
||||
|
||||
/// Crash a child the way `fault` says; returns what the handler wrote and the next start's report.
|
||||
fn crash(name: &str, fault: &str, signal: c_int) -> (serde_json::Value, serde_json::Value, String) {
|
||||
let dir = std::env::temp_dir().join(format!("solidcraft-native-crash-{name}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let out = run(&dir, Some(fault));
|
||||
assert_eq!(out.status.signal(), Some(signal), "the child died of the signal itself: {out:?}");
|
||||
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
|
||||
let info =
|
||||
std::fs::read_dir(dir.join("sessions")).unwrap().flatten().map(|e| e.path()).find(|p| p.to_string_lossy().ends_with(".native.json"));
|
||||
let info = info.expect("the handler wrote what it caught");
|
||||
let v: serde_json::Value = serde_json::from_slice(&std::fs::read(info).unwrap()).unwrap();
|
||||
// The next start reports it.
|
||||
let out = run(&dir, None);
|
||||
assert!(out.status.success(), "{out:?}");
|
||||
let reports = solidcraft_engine::crash::list(&dir);
|
||||
assert_eq!(reports.len(), 1, "{reports:?}");
|
||||
let r = solidcraft_engine::crash::read(&dir, &reports[0].id).unwrap();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
(v, r, stderr)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_segfault_is_reported_with_its_signal_module_frames_and_step() {
|
||||
let (v, r, _) = crash("segv", "segv", SIGSEGV);
|
||||
assert_eq!(v["signal"], "SIGSEGV", "{v}");
|
||||
// Address 16 is in __PAGEZERO, mapped without access: SEGV_ACCERR.
|
||||
assert_eq!(v["detail"], "read of 0x0000000000000010 (SEGV_ACCERR)", "{v}");
|
||||
assert!(v["module"].as_str().unwrap().contains("solidcraft"), "the fault is in the test binary: {v}");
|
||||
let frames = v["frames"].as_array().unwrap();
|
||||
assert!(frames.len() >= 3, "{v}");
|
||||
assert!(frames.iter().any(|f| f.as_str().unwrap().contains("child")), "the walk reaches the test that crashed: {v}");
|
||||
let message = r["message"].as_str().unwrap();
|
||||
assert!(message.contains("segmentation fault (SIGSEGV) in solidcraft"), "{r}");
|
||||
assert!(message.contains("during startup (device creation"), "{r}");
|
||||
assert_eq!(r["kind"], "unclean_exit");
|
||||
assert_eq!(r["native"]["signal"], "SIGSEGV");
|
||||
assert!(r["backtrace"].as_array().unwrap().iter().any(|f| f.as_str().unwrap().contains("segfault")), "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_abort_is_reported_as_sigabrt_with_the_code_that_called_it() {
|
||||
let (v, r, _) = crash("abrt", "abort", SIGABRT);
|
||||
assert_eq!(v["signal"], "SIGABRT", "{v}");
|
||||
assert!(v.get("detail").is_none(), "{v}");
|
||||
let frames = v["frames"].as_array().unwrap();
|
||||
assert!(frames.iter().any(|f| f.as_str().unwrap().contains("abort")), "{v}");
|
||||
assert!(r["message"].as_str().unwrap().contains("abort (SIGABRT)"), "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stack_overflow_is_recorded_and_the_standard_library_still_says_so() {
|
||||
// The fault on the guard page is recorded on the alternate stack; the standard library's
|
||||
// handler, called after ours, prints its message and aborts.
|
||||
let (v, r, stderr) = crash("overflow", "overflow", SIGABRT);
|
||||
assert!(matches!(v["signal"].as_str(), Some("SIGSEGV" | "SIGBUS")), "{v}");
|
||||
assert_eq!(v["name"], "stack overflow", "{v}");
|
||||
assert!(v["detail"].as_str().unwrap().contains("below the thread's stack"), "{v}");
|
||||
assert!(stderr.contains("has overflowed its stack"), "{stderr}");
|
||||
assert!(r["message"].as_str().unwrap().contains("stack overflow ("), "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_segfault_signal_that_was_only_sent_still_ends_the_app() {
|
||||
// The standard library's handler, which comes before ours, returns expecting the fault
|
||||
// to happen again; a sent signal never does, so it goes to the default action instead.
|
||||
let (v, r, _) = crash("raise", "raise", SIGSEGV);
|
||||
assert_eq!(v["detail"], "sent with kill or raise, not a fault", "{v}");
|
||||
assert!(r["message"].as_str().unwrap().contains("(SIGSEGV)"), "{r}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# Render workspace icon drafts
|
||||
|
||||
All ids below are **Open** for owner review, except `render_scene` and `render_image`,
|
||||
which are existing accepted icons used only as references. No commands, toolbar
|
||||
entries, browser selectors or runtime request manifests are changed by this draft set.
|
||||
`render_remove_override` is the one remaining alias (of `render_from_material`);
|
||||
`render_execute`, `render_environment` and `render_fit_product` now have their own
|
||||
masters in `design/svg/model/`. Accepted source masters stay unchanged.
|
||||
|
||||
Review with `python3 -B assets/icons/ui/design/tools/icon_status.py --serve`,
|
||||
search `render_`, and select Open. See [STYLE_GUIDE.md](STYLE_GUIDE.md) for
|
||||
acceptance; drafts do not imply implemented commands.
|
||||
|
||||
The finished references in each row were read and previewed in both themes
|
||||
before authoring. Draft contact sheets (96, 24 and 16 px, both themes) are local
|
||||
review evidence, not shipped assets. The icons are grouped in five families, each
|
||||
with one shared design rule.
|
||||
|
||||
## Lights
|
||||
|
||||
Rule: a slate source glyph with light passive fill, 2.55-unit outlines and a dotted
|
||||
accent stem (three dots, 4.25 diameter, 7 units apart) showing the direction.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Light area | `render_light_area` | `box`, `camera` | Slanted flat panel on the canonical axes above the dotted stem. |
|
||||
| Light spot | `render_light_spot` | `camera`, `material` | Cone with a curved rim above the dotted stem. |
|
||||
| Light point | `render_light_point` | `camera`, `material` | Round source disc with a centre dot above the dotted stem. |
|
||||
| Sun | `render_sun` | `sphere`, `camera` | Source disc of the same size with eight short radial rays instead of a stem. |
|
||||
| Light by highlight | `render_light_by_highlight` | `sphere`, `material` | Shaded sphere with a ringed highlight target; the dotted stem arrives from above. |
|
||||
| Solo light | `render_solo_light` | `fit`, `camera` | Point source and stem inside four corner focus brackets. |
|
||||
|
||||
## Looks
|
||||
|
||||
Rule: the lit look sphere (top-left highlight, bottom-right shade) plus one cue that
|
||||
says what happens to it; receiving bodies stay canonical grey or cube shapes.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Appearance | `render_appearance` | `material`, `box` | Shaded look sphere on the top corner of an accent-shaded cube: a look assigned to a body. |
|
||||
| Paint | `render_paint` | `material`, `paste` | Flat brush with an accent tip, light ferrule and slate handle. |
|
||||
| Pick up | `render_pick_up` | `material`, `copy` | Eyedropper with a grey bulb, slate crossbar and accent sampling tip. |
|
||||
| Replace all | `render_replace_all` | `copy`, `box` | Grey sample sphere with an arrow to an accent sphere, beside a second accent sphere. |
|
||||
| Copy look | `render_copy_look` | `material`, `copy` | Look sphere in a square frame, offset from a second frame outline. |
|
||||
| Remove override | `render_remove_override` | `appearance`, `box` | Alias of `render_from_material`: the custom look is crossed off. |
|
||||
| Same look | `render_same_look` | `select`, `box` | Look sphere and three cubes; the two matching cubes are ringed. |
|
||||
| From material | `render_from_material` | `material`, `extrude` | Look sphere struck through above a plain grey slab, so the material shows. |
|
||||
|
||||
## Studio
|
||||
|
||||
Rule: the standard grey cube stands in the set-up it is lit or placed in.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Library | `render_library` | `library`, `copy` | Row of three look spheres, the middle one accent, on a shelf bar. |
|
||||
| Lighting presets | `render_lighting_presets` | `new`, `render_scene` | Two stacked cards, the front one holding a light bulb. |
|
||||
| Environment | `render_environment` | `environment_map`, `render_scene` | Dome with a grey cube inside. |
|
||||
| Ground | `render_ground` | `box`, `render_scene` | Grey cube on a slab with an accent contact patch. |
|
||||
| Backdrop | `render_backdrop` | `render_scene`, `render_image` | Grey cube in front of a tall upright accent panel. |
|
||||
|
||||
## Camera and shots
|
||||
|
||||
Rule: one chunky camera body with a lower-right round badge for shot actions; the
|
||||
other camera icons are simple frames.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Shot | `render_shot` | `camera`, `new` | Camera body with a plus badge: save a shot. |
|
||||
| Update shot | `render_update_shot` | `camera`, `undo` | Camera body with a refresh badge. |
|
||||
| Render all shots | `render_all_shots` | `copy`, `render_image` | Stacked camera bodies with a play badge. |
|
||||
| Shots strip | `render_shots_strip` | `camera`, `copy` | Strip of three small frames, the middle one in accent. |
|
||||
| Fit product | `render_fit_product` | `fit`, `camera` | Grey cube inside four corner brackets. |
|
||||
| Lens / field of view | `render_lens` | `camera`, `fit` | Concentric lens rings around an accent centre. |
|
||||
| Exposure | `render_exposure` | `material`, `environment_map` | Disc split light and accent, with plus and minus marks. |
|
||||
| Aspect frame | `render_aspect_frame` | `render_image`, `fit` | Wide rounded frame with two opposite corner brackets. |
|
||||
| Frame guides | `render_frame_guides` | `render_image`, `fit` | Rounded frame with a rule-of-thirds grid and four accent nodes. |
|
||||
|
||||
## Output and result
|
||||
|
||||
Rule: one picture base (frame with landscape) with a lower-right round badge; the
|
||||
size, background and quality icons draw their own simple subject.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Render image | `render_execute` | `render_image`, `camera` | Picture with a play badge: create a rendered image. |
|
||||
| Render output size | `render_output_size` | `render_image`, `fit` | Picture frame with a diagonal double-headed dimension arrow. |
|
||||
| Transparent background | `render_transparent_background` | `material`, `render_image` | Grey cube on a checker tile. |
|
||||
| Quality | `render_quality` | `list`, `library` | Three rising bars, the tallest in accent. |
|
||||
| Check render | `render_check` | `render_image`, `new` | Picture with a check badge. |
|
||||
| Cancel render | `render_cancel` | `render_image`, `delete` | Picture with a square stop badge. |
|
||||
| Export image | `render_export_image` | `render_image`, `copy` | Picture with a large outward diagonal arrow at the upper right. |
|
||||
| Compare | `render_compare` | `render_image`, `material` | Two grey pictures split by a vertical slider with opposed arrows. |
|
||||
| Results | `render_results` | `render_image`, `copy` | Stack of two pictures, the front one showing a landscape. |
|
||||
@@ -45,3 +45,7 @@ sketch_chamfer_equal = { master = "sketch_chamfer", accent = "sketch" }
|
||||
sketch_chamfer_two = { master = "sketch_chamfer_two_distances", accent = "sketch" }
|
||||
offset_one_side = { master = "offset", accent = "sketch" }
|
||||
bom = "list"
|
||||
|
||||
# Render workspace drafts: shared meanings; owner review remains Open.
|
||||
# Removing a look override returns the body to its material look.
|
||||
render_remove_override = "render_from_material"
|
||||
|
||||
@@ -63,6 +63,7 @@ combine false
|
||||
compare false
|
||||
component false
|
||||
compute false
|
||||
cone false
|
||||
configuration_table false
|
||||
conic false
|
||||
constrainer false
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"error": "b7188fcdfadf1512ddd2896805a14618179789f87047b219560a7dab74bd360f",
|
||||
"explode": "2aecb05d5f05aff1ab629066cb9bea8718ea213ac70948ef71a3ced3321f4cb2",
|
||||
"export": "c8c2abf007e43e896479d78798e3976ba9eda809507c820bb38538f77db696ac",
|
||||
"export_tray": "f18606e1f63726c57a0787dced4fb8a221b4a4fc18b88b0bbdee743e546cb489",
|
||||
"extend": "28c63df352c77d02feffe44cf09fb0fc1d48f7905aaabbfa6dba601dbb70f8e8",
|
||||
"extrude": "9cc1538971c5d25467aaed23f739899d9d102fd07f86472ac3197c54ca2e1144",
|
||||
"eye": "2449167c8410826dc1106b687c12a309d5bea8f7817b897943e80f182a163c2d",
|
||||
@@ -122,6 +123,7 @@
|
||||
"hole": "f82d47751f826e08682db847a270897474661bfe9f35b6d3678f49a203172563",
|
||||
"home": "ab5f1c41d377687e4c5a808dfdccf2757b33dd43f934d44df43d6e9118176853",
|
||||
"import": "e343540eb49fdb1e53c4d54b9e60ae513b3103df6c9133048bed878e58067455",
|
||||
"import_tray": "c6aec9556dc8ecb6c953d6bf1f384f220c68167a92e444950c24ded81f66a0b6",
|
||||
"include": "f5b80ad3e06e3b863fcfbe415ec912e4e09653dd519f9ac207819339fe34730f",
|
||||
"infinite": "53fb781131078d2f8e71be23aa610d9f9d9c8bb79ba5f97fa397475e9ff8c3bd",
|
||||
"inspect": "d2b4644b2d5f1aa50cb576b9265e275fa91201e296c2bc79865f810067d538e9",
|
||||
@@ -206,8 +208,45 @@
|
||||
"redo": "28e925493fa9e5f52e2c8e3beeb288af13fdb4f85f3cd92270096a10561e7a1a",
|
||||
"refold": "1fa6a8604af7d04c01bc31d0e5875eb2cc91f5fa6bfc5491dd6680d3299eb26f",
|
||||
"rename": "6887c5467101dca1d213154020d3b03be9b2704ac310de9d0a8ebde62c013bf5",
|
||||
"render_all_shots": "9c04f49624cc2685472041cb3f3e95da2c5f32deeebb9723f4a8e3c369680a5b",
|
||||
"render_appearance": "53287b30915a266d8a3858d5bef24b93ed11f8d612c20c445d3032f400a925cc",
|
||||
"render_aspect_frame": "5d74b263eeb03eb0494b3774817135b8f119caff205a8b31c480d7dd0e5a8554",
|
||||
"render_backdrop": "d27f7d6da7a31c5944dd0ff67c21838690f000f2d87b0bf5274bef0744de34ce",
|
||||
"render_cancel": "2f1d8b3039007ccfc224390758e05d6202fa2eabf63724c744217dcab5d58e5e",
|
||||
"render_check": "65ccf0407d80937c6931cbf87b6d0e628cfb62ca3e841483d02dd9de19c3717c",
|
||||
"render_compare": "1b2d3f3920782490b96b4d14a7cc9506eff98ff2efae00f86d911fabcabc7cb0",
|
||||
"render_copy_look": "25e9138a839a7e6b8225b48cd2f36daba42a928eff541fe616f3df39a1bc82b7",
|
||||
"render_environment": "50cb18380d9310f6e04084c24ac85529599966b1d90c6bef514f4e63f9b30854",
|
||||
"render_execute": "e4d9a66f4569e00c4b4b2ac05a80a9f3eba4e59802c62d3af5bb951ba9153cbf",
|
||||
"render_export_image": "2456f55fa3b50ab84ae871963d4a6b6e92f486cf6ed3039f3a2f07f11192a655",
|
||||
"render_exposure": "60eb5fa040aefe872ab2de66099493513a494ab758a23d443753d033eaf3a05d",
|
||||
"render_fit_product": "8e58b293a39a06fca4cda0543adc2afef3d0041fe34b0bc0c800afb2d5635123",
|
||||
"render_frame_guides": "cb1df01051de42047e38de9dd2a96fcb5b67bd5979cb5756412890400b69e5bc",
|
||||
"render_from_material": "c928daa803a28f90ce24096731b8de711c6cd3215259ac9cd761b6be2e1b9618",
|
||||
"render_ground": "2b900b433870cbc4bd0282e54c604ec696aa28354c753a13b09806f5e87d99c3",
|
||||
"render_image": "e3dd622591de4fdc211f9bd3c6dcb54b01a308e8bcebd02bb80dd6b3982ce2a5",
|
||||
"render_lens": "79bb615d46a05ea5ffc9e2e905cc25f266b3b7e761c160f7d25bbdb3dea0a430",
|
||||
"render_library": "828af74034a62616c774ab0cbe70b7cf9fd42778b574febfb1812f8aa17fd49b",
|
||||
"render_light_area": "b1f3ee596ee1d35f6e8444b54e94f36a34c2129697c88e5286dbf38565f50816",
|
||||
"render_light_by_highlight": "5b37c8c6aa27ac2002dd8c8094e415c64e9eb315a55c4f146e328314e8f639a2",
|
||||
"render_light_point": "a602cb5fa26f04f1bb9be7c14e5a9a9ce52b37550dfd3211a55e915f941f5803",
|
||||
"render_light_spot": "a9413de906266bec0165b4cb06ecac8e36dce0b6ddec4bc4cc6ff4f152601483",
|
||||
"render_lighting_presets": "e0a08433f548a9dd139296107b04b1d4c57ff04303c9bee32d896e2ea06a8d07",
|
||||
"render_output_size": "f08fe354e4d5a6dd21af29cb077ceed96c413c68f48d9f72548ae1614337f88b",
|
||||
"render_paint": "e3d3202205a5a90906348cae519340d38f475e42039a7f60c835cf7a21081da1",
|
||||
"render_pick_up": "df67d10f333a3c9e367897d599a4be510f10ed36c24763c26817f22d24ff35b7",
|
||||
"render_quality": "28fe44cdd2b2f58b91d91dcf20a6dfbd22cf3db6008e1db179717523ecc4889e",
|
||||
"render_remove_override": "c928daa803a28f90ce24096731b8de711c6cd3215259ac9cd761b6be2e1b9618",
|
||||
"render_replace_all": "3bbf830619a91be186b9dc91726b8e47c293556d2ac8c93d87c71014c9f3abcd",
|
||||
"render_results": "94e78898d88eef415e56ca5ec1e056089a2a5aa709abc69e5b8103d6096a019c",
|
||||
"render_same_look": "2b5c69c523a1c949c22638c378803e77123095fddd36cfa58e94b2fd91ca61d7",
|
||||
"render_scene": "1c4d7350f6f17ad637a7d633cb65a64c298af20321b1dbdac01b51d47a0e2822",
|
||||
"render_shot": "4428080faa94f276c8a1d20eb38d04835ee5ae9d6337402371a5edf09f97ddc5",
|
||||
"render_shots_strip": "1dcf5cc8e704691985284e0ca06e03b22540ea9d4cd23f4fb8675801ae7e69ec",
|
||||
"render_solo_light": "abb9f8993be108a79d491bfaca32cde59d07a69db3ead58195e8ca0d8e8d909b",
|
||||
"render_sun": "d86b51f3d4d83df4e932a22bb20d567513529a2eaaa42b93f744412eed2be901",
|
||||
"render_transparent_background": "71dab4763516053734d4eec964b0899716d617597fe0d09e3bfa0cc36224dd7a",
|
||||
"render_update_shot": "22d82ad332f8325395596c4bec4c1ec9bbb72d9fc4cd62ad658a93d7f5f803b9",
|
||||
"reorder": "c45755c8f23575415397bffb316eed9f2d8c014f451ab6f8c3123aca1a99b675",
|
||||
"replace_face": "fcee556c684a1138a30a2574ea565c98ff29f9368c2e861d0fb861155a57d28b",
|
||||
"rest": "eab64961b5340bdbbd92affe0c9b8477dc297a3b49e05e39d5fa71c0f2fda66c",
|
||||
@@ -224,10 +263,17 @@
|
||||
"screw": "5e30570785360d6a591ea22862a82f1cf62228385c1e96382536db8a453ac791",
|
||||
"section": "cdd13f99f15f6d59bfa21c747810608d6afd00cb8207faf0337928c8521e7eae",
|
||||
"select": "fdf6431270006ca7409c60c37cfed59572de04229f104ec723e6010ffa2b7163",
|
||||
"send": "f83b6cdab792f9567a81d46d7828ed20795513c57a2c7d00fbbe6bd14d2f5848",
|
||||
"settings": "9193dd3a1007c43660d0d47b6499cb2571338b67949e1a99df112057b9bf2b8a",
|
||||
"settings_keyboard": "4968c70788094365af9c6e2b1c15b2aac45cf21212a14b8a256daf1ffe17f41a",
|
||||
"settings_monitor": "6fcea26e2fc1673c76c6e7fadac4a91812ed43250cfb363c4f40c0cacfbd7aa5",
|
||||
"settings_mouse": "47754752ea6fb37ec9686c0460740935a4db30fb267f1136a9246aec9a17b3f1",
|
||||
"settings_palette": "a060015a750ff4367c65c04b83bf43720e13a38d8dee35330e1d8880f89eaa22",
|
||||
"settings_plug": "79c00b84b2d1d28e3bf6096639c75476b7e1ee0a326c34ad76a79ad866c0102e",
|
||||
"sheet": "774e30923ef6f37dd9c0b2bc3e45705e83b3a76700bf071cd41155a365d0daa4",
|
||||
"sheet_rules": "5d3a2f37d3b4a4262b3ddbdae53d61f29e0f8e284dd54006da25a7acebfa1114",
|
||||
"shell": "a1959ea0d0441a479bf654b6e549e2e44a0bfdc9cda4eb46fe1fbfd884de7e38",
|
||||
"shield": "5e2d2d157309e0daa72f7c53073dfdb0a474def972a041cd172567f1d1e485ec",
|
||||
"silhouette": "2a848aed2f247b1c0439f830a34a513c93bc5493c40044c821d493d863ad996e",
|
||||
"sketch": "b52e5862b1709a9a7e3ca00d7436d126fbfc01b38080006fa4e17dc581dfa103",
|
||||
"sketch_break": "67a5fab332616e6d6a9aeae3bedc04ee0768179143ba34b795bdd98cea04bb24",
|
||||
@@ -261,6 +307,7 @@
|
||||
"status_error": "b7188fcdfadf1512ddd2896805a14618179789f87047b219560a7dab74bd360f",
|
||||
"status_warning": "1ac4f0ccfac2c01f6f66cb42188f9ae053c4aef9c5bbd249f1fb09611eb4d825",
|
||||
"stitch": "6c3205d64007b771ed78d6f8055867578439fb25d591563b32dbd3343c98732d",
|
||||
"stop": "36967eb4d423370d0d6c679b0a71f8d09f4fb2c73eed3e8cc50c604c44b0c32b",
|
||||
"style": "6c2b04234b82ea4858c80190cc7208c60f549358253005afc35ab4e29506b578",
|
||||
"suppress": "c54fbc01c12c0175d78cd63f692e8aaf73fc2cac79752b2a3ad821ac32734343",
|
||||
"surface": "ba0e839ce0ee03e7ff95a2389a5ecb20bd80c713064d9b1bd5f461f4a80d1921",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Cone</title>
|
||||
<path d="M32 5L12.7 45.97A20 11.55 0 1 0 51.3 45.97Z" fill="#328bd3"/>
|
||||
<path d="M32 5L39 59.82A20 11.55 0 0 0 51.3 45.97Z" fill="#236ead"/>
|
||||
<path d="M32 5L12.7 45.97A20 11.55 0 1 0 51.3 45.97Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render all shots — every saved camera view at once</title>
|
||||
<path d="M27.1 12L29.26 12L31.42 6.96Q31.85 5.52 33.22 5.52L36.82 5.52Q38.19 5.52 38.62 6.96L40.78 12L51.58 12Q55.18 12 55.18 15.6L55.18 31.44Q55.18 35.04 51.58 35.04L27.1 35.04Q23.5 35.04 23.5 31.44L23.5 15.6Q23.5 12 27.1 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M16.6 21L18.76 21L20.92 15.96Q21.35 14.52 22.72 14.52L26.32 14.52Q27.69 14.52 28.12 15.96L30.28 21L41.08 21Q44.68 21 44.68 24.6L44.68 40.44Q44.68 44.04 41.08 44.04L16.6 44.04Q13 44.04 13 40.44L13 24.6Q13 21 16.6 21Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6.1 30L8.26 30L10.42 24.96Q10.85 23.52 12.22 23.52L15.82 23.52Q17.19 23.52 17.62 24.96L19.78 30L30.58 30Q34.18 30 34.18 33.6L34.18 49.44Q34.18 53.04 30.58 53.04L6.1 53.04Q2.5 53.04 2.5 49.44L2.5 33.6Q2.5 30 6.1 30Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="18.34" cy="41.52" r="7.56" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M43.9 41.7L53.3 47.5L43.9 53.3Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Appearance — a body wearing the look sphere</title>
|
||||
<path d="M17.33 24.8L38.98 37.3L38.98 62.3L17.33 49.8Z" fill="#328bd3"/>
|
||||
<path d="M60.63 24.8L38.98 37.3L38.98 62.3L60.63 49.8Z" fill="#236ead"/>
|
||||
<path d="M38.98 12.3L60.63 24.8L38.98 37.3L17.33 24.8Z" fill="#b9ddf8"/>
|
||||
<path d="M38.98 12.3L60.63 24.8L60.63 49.8L38.98 62.3L17.33 49.8L17.33 24.8Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17.33 24.8L38.98 37.3L60.63 24.8" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M38.98 37.3L38.98 62.3" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="18.98" cy="17.3" r="16.88" fill="#ffffff"/>
|
||||
<circle cx="18.98" cy="17.3" r="14" fill="#328bd3"/>
|
||||
<path d="M32.14 12.51A14 14 0 0 1 14.19 30.46A18.31 18.31 0 0 0 32.14 12.51Z" fill="#236ead"/>
|
||||
<circle cx="14.22" cy="12.54" r="3.78" fill="#b9ddf8"/>
|
||||
<circle cx="18.98" cy="17.3" r="14" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Aspect frame — the shape of the picture</title>
|
||||
<path d="M13 17L51 17A4 4 0 0 1 55 21L55 43A4 4 0 0 1 51 47L13 47A4 4 0 0 1 9 43L9 21A4 4 0 0 1 13 17Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M4.5 23.5L4.5 12.5L15.5 12.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M59.5 40.5L59.5 51.5L48.5 51.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 614 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Backdrop — the coloured wall behind the product</title>
|
||||
<path d="M30.69 4L54.25 17.6L54.25 41.6L30.69 28Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L30 48.4L18.91 42Z" fill="#b8c3cf"/>
|
||||
<path d="M41.09 29.2L30 35.6L30 48.4L41.09 42Z" fill="#8e9faf"/>
|
||||
<path d="M30 22.8L41.09 29.2L30 35.6L18.91 29.2Z" fill="#e2e8ef"/>
|
||||
<path d="M30 22.8L41.09 29.2L41.09 42L30 48.4L18.91 42L18.91 29.2Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L41.09 29.2" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L30 48.4" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 928 B |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Cancel render — stop the running render</title>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 40.8L17.8 27.12L30.68 38.64L38.04 32.88L50 42.96L50 48L4 48Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<circle cx="36.2" cy="22.8" r="4.2" fill="#8e9faf"/>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M44.1 42.9L50.9 42.9A1.2 1.2 0 0 1 52.1 44.1L52.1 50.9A1.2 1.2 0 0 1 50.9 52.1L44.1 52.1A1.2 1.2 0 0 1 42.9 50.9L42.9 44.1A1.2 1.2 0 0 1 44.1 42.9Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Check render — verify the request is ready, without rendering</title>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 40.8L17.8 27.12L30.68 38.64L38.04 32.88L50 42.96L50 48L4 48Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<circle cx="36.2" cy="22.8" r="4.2" fill="#8e9faf"/>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M41.9 47.7L45.9 51.9L53.3 43.1" fill="none" stroke="#ffffff" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Compare — live preview and render side by side</title>
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="cmpL"><path d="M4 12L32 12L32 52L4 52Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#cmpL)">
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="#b8c3cf"/>
|
||||
</g>
|
||||
<defs><clipPath id="cmp"><path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#cmp)">
|
||||
<path d="M4 44L20.8 28.8L36.48 41.6L45.44 35.2L60 46.4L60 52L4 52Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 5L32 59" fill="none" stroke="#ffffff" stroke-width="8.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="14.5" fill="#ffffff"/>
|
||||
<path d="M32 5L32 59" fill="none" stroke="#236ead" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="12.8" fill="#236ead"/>
|
||||
<path d="M23.4 32L29.8 25.6L29.8 38.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M40.6 32L34.2 25.6L34.2 38.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Copy Look — the look in front of a copy outline</title>
|
||||
<path d="M8 40L8 12.5A4.5 4.5 0 0 1 12.5 8L40 8" fill="none" stroke="currentColor" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M24 19L51 19A5 5 0 0 1 56 24L56 51A5 5 0 0 1 51 56L24 56A5 5 0 0 1 19 51L19 24A5 5 0 0 1 24 19Z" fill="none" stroke="currentColor" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="37.5" cy="37.5" r="12" fill="#328bd3"/>
|
||||
<path d="M48.78 33.4A12 12 0 0 1 33.4 48.78A15.69 15.69 0 0 0 48.78 33.4Z" fill="#236ead"/>
|
||||
<circle cx="33.42" cy="33.42" r="3.24" fill="#b9ddf8"/>
|
||||
<circle cx="37.5" cy="37.5" r="12" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 841 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Environment — a sky dome lighting the product</title>
|
||||
<path d="M4.04 42A25.96 25.96 0 0 1 55.96 42A25.96 14.99 0 0 1 4.04 42Z" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L30 48.4L18.91 42Z" fill="#b8c3cf"/>
|
||||
<path d="M41.09 29.2L30 35.6L30 48.4L41.09 42Z" fill="#8e9faf"/>
|
||||
<path d="M30 22.8L41.09 29.2L30 35.6L18.91 29.2Z" fill="#e2e8ef"/>
|
||||
<path d="M30 22.8L41.09 29.2L41.09 42L30 48.4L18.91 42L18.91 29.2Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L41.09 29.2" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L30 48.4" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 948 B |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render image — start rendering the picture</title>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 40.8L17.8 27.12L30.68 38.64L38.04 32.88L50 42.96L50 48L4 48Z" fill="#328bd3"/>
|
||||
</g>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M43.9 41.7L53.3 47.5L43.9 53.3Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 993 B |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Export image — save the finished picture as a PNG file</title>
|
||||
<path d="M9 18L43 18A5 5 0 0 1 48 23L48 49A5 5 0 0 1 43 54L9 54A5 5 0 0 1 4 49L4 23A5 5 0 0 1 9 18Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 18L43 18A5 5 0 0 1 48 23L48 49A5 5 0 0 1 43 54L9 54A5 5 0 0 1 4 49L4 23A5 5 0 0 1 9 18Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 46.8L17.2 33.12L29.52 44.64L36.56 38.88L48 48.96L48 54L4 54Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<path d="M9 18L43 18A5 5 0 0 1 48 23L48 49A5 5 0 0 1 43 54L9 54A5 5 0 0 1 4 49L4 23A5 5 0 0 1 9 18Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28 36L47.77 17.55" fill="none" stroke="#ffffff" stroke-width="11.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58 8L53.52 25.18L40.55 11.29Z" fill="#ffffff" stroke="#ffffff" stroke-width="6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28 36L47.77 17.55" fill="none" stroke="#236ead" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58 8L53.52 25.18L40.55 11.29Z" fill="#236ead" stroke="#236ead" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Exposure — brighten or darken the picture</title>
|
||||
<circle cx="32" cy="32" r="25" fill="#e2e8ef"/>
|
||||
<path d="M32 7A25 25 0 0 1 32 57Z" fill="#236ead"/>
|
||||
<circle cx="32" cy="32" r="25" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M14 32L27 32M20.5 25.5L20.5 38.5" fill="none" stroke="#34465b" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37 32L50 32" fill="none" stroke="#ffffff" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 630 B |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Fit product — frame the whole product in the view</title>
|
||||
<path d="M15.98 22.75L32 32L32 50.5L15.98 41.25Z" fill="#b8c3cf"/>
|
||||
<path d="M48.02 22.75L32 32L32 50.5L48.02 41.25Z" fill="#8e9faf"/>
|
||||
<path d="M32 13.5L48.02 22.75L32 32L15.98 22.75Z" fill="#e2e8ef"/>
|
||||
<path d="M32 13.5L48.02 22.75L48.02 41.25L32 50.5L15.98 41.25L15.98 22.75Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15.98 22.75L32 32L48.02 22.75" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L32 50.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.5 18.5L5.5 5.5L18.5 5.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58.5 18.5L58.5 5.5L45.5 5.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58.5 45.5L58.5 58.5L45.5 58.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.5 45.5L5.5 58.5L18.5 58.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Frame guides — rule of thirds over the picture</title>
|
||||
<path d="M10 14L54 14A4 4 0 0 1 58 18L58 46A4 4 0 0 1 54 50L10 50A4 4 0 0 1 6 46L6 18A4 4 0 0 1 10 14Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23.33 15.3L23.33 48.7M7.3 26L56.7 26M40.67 15.3L40.67 48.7M7.3 38L56.7 38" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="23.33" cy="26" r="3.6" fill="#236ead"/>
|
||||
<circle cx="23.33" cy="38" r="3.6" fill="#236ead"/>
|
||||
<circle cx="40.67" cy="26" r="3.6" fill="#236ead"/>
|
||||
<circle cx="40.67" cy="38" r="3.6" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 742 B |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>From Material — custom look crossed off, material shows</title>
|
||||
<path d="M6 38L32 53.01L32 57.01L6 42Z" fill="#b8c3cf"/>
|
||||
<path d="M58 38L32 53.01L32 57.01L58 42Z" fill="#8e9faf"/>
|
||||
<path d="M32 22.99L58 38L32 53.01L6 38Z" fill="#e2e8ef"/>
|
||||
<path d="M32 22.99L58 38L58 42L32 57.01L6 42L6 38Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6 38L32 53.01L58 38" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 53.01L32 57.01" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="22" r="15" fill="#328bd3"/>
|
||||
<path d="M46.1 16.87A15 15 0 0 1 26.87 36.1A19.62 19.62 0 0 0 46.1 16.87Z" fill="#236ead"/>
|
||||
<circle cx="26.9" cy="16.9" r="4.05" fill="#b9ddf8"/>
|
||||
<circle cx="32" cy="22" r="15" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21 11L43 33M43 11L21 33" fill="none" stroke="#ffffff" stroke-width="6.65" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21 11L43 33M43 11L21 33" fill="none" stroke="currentColor" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Ground — the floor the product stands on, with its contact shadow</title>
|
||||
<path d="M5.06 42L30 56.4L30 58.8L5.06 44.4Z" fill="#328bd3"/>
|
||||
<path d="M54.94 42L30 56.4L30 58.8L54.94 44.4Z" fill="#236ead"/>
|
||||
<path d="M30 27.6L54.94 42L30 56.4L5.06 42Z" fill="#b9ddf8"/>
|
||||
<path d="M30 27.6L54.94 42L54.94 44.4L30 58.8L5.06 44.4L5.06 42Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.06 42L30 56.4L54.94 42" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 56.4L30 58.8" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L45.93 44.8L34.85 51.2L18.91 42Z" fill="#328bd3"/>
|
||||
<path d="M18.91 29.2L30 35.6L30 48.4L18.91 42Z" fill="#b8c3cf"/>
|
||||
<path d="M41.09 29.2L30 35.6L30 48.4L41.09 42Z" fill="#8e9faf"/>
|
||||
<path d="M30 22.8L41.09 29.2L30 35.6L18.91 29.2Z" fill="#e2e8ef"/>
|
||||
<path d="M30 22.8L41.09 29.2L41.09 42L30 48.4L18.91 42L18.91 29.2Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L41.09 29.2" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L30 48.4" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Lens — the camera lens and its field of view</title>
|
||||
<circle cx="32" cy="32" r="27" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="19" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="8.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="21.75" cy="21.75" r="3" fill="#e2e8ef"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Appearance library — looks on a shelf</title>
|
||||
<circle cx="13" cy="28.35" r="9.5" fill="#b8c3cf"/>
|
||||
<path d="M21.93 25.1A9.5 9.5 0 0 1 9.75 37.28A12.42 12.42 0 0 0 21.93 25.1Z" fill="#8e9faf"/>
|
||||
<circle cx="9.77" cy="25.12" r="2.57" fill="#e2e8ef"/>
|
||||
<circle cx="13" cy="28.35" r="9.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="51" cy="28.35" r="9.5" fill="#b8c3cf"/>
|
||||
<path d="M59.93 25.1A9.5 9.5 0 0 1 47.75 37.28A12.42 12.42 0 0 0 59.93 25.1Z" fill="#8e9faf"/>
|
||||
<circle cx="47.77" cy="25.12" r="2.57" fill="#e2e8ef"/>
|
||||
<circle cx="51" cy="28.35" r="9.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="28.35" r="13.97" fill="#ffffff"/>
|
||||
<circle cx="32" cy="28.35" r="11.5" fill="#328bd3"/>
|
||||
<path d="M42.81 24.42A11.5 11.5 0 0 1 28.07 39.16A15.04 15.04 0 0 0 42.81 24.42Z" fill="#236ead"/>
|
||||
<circle cx="28.09" cy="24.44" r="3.11" fill="#b9ddf8"/>
|
||||
<circle cx="32" cy="28.35" r="11.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M7 42.35L57 42.35A2 2 0 0 1 59 44.35L59 46.35A2 2 0 0 1 57 48.35L7 48.35A2 2 0 0 1 5 46.35L5 44.35A2 2 0 0 1 7 42.35Z" fill="#8e9faf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Area light — angled panel and dotted direction stem</title>
|
||||
<path d="M23.34 11L54.52 29L40.66 37L9.48 19Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Light by highlight — dotted aim stem and sphere target</title>
|
||||
<circle cx="32" cy="9" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="16" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="23" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="42" r="15" fill="#e2e8ef"/>
|
||||
<path d="M44.29 33.4A15 15 0 0 1 23.4 54.29A19.62 19.62 0 0 0 44.29 33.4Z" fill="#b8c3cf"/>
|
||||
<circle cx="32" cy="42" r="15" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="37.5" r="5.5" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 712 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Point light — source disc and dotted direction stem</title>
|
||||
<circle cx="32" cy="23" r="15" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="23" r="2.125" fill="#34465b"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 485 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Spot light — cone source and dotted direction stem</title>
|
||||
<path d="M32 8L49 33Q32 43 15 33Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 435 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Lighting presets — a stack of ready-made light set-ups</title>
|
||||
<path d="M24 5L54 5A5 5 0 0 1 59 10L59 40A5 5 0 0 1 54 45L24 45A5 5 0 0 1 19 40L19 10A5 5 0 0 1 24 5Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 17L42 17A5 5 0 0 1 47 22L47 54A5 5 0 0 1 42 59L10 59A5 5 0 0 1 5 54L5 22A5 5 0 0 1 10 17Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.3 41.55A11.5 11.5 0 1 1 33.7 41.55L33.2 45.55L18.8 45.55Z" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21.3 45.55L30.7 45.55A2.5 2.5 0 0 1 33.2 48.05L33.2 50.05A2.5 2.5 0 0 1 30.7 52.55L21.3 52.55A2.5 2.5 0 0 1 18.8 50.05L18.8 48.05A2.5 2.5 0 0 1 21.3 45.55Z" fill="#8e9faf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Output size — the picture size in pixels</title>
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.08 38.79L41.92 25.21" fill="none" stroke="#236ead" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M51 19L45.33 31.97L36.86 19.59Z" fill="#236ead" stroke="#236ead" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M13 45L18.67 32.03L27.14 44.41Z" fill="#236ead" stroke="#236ead" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 750 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Paint — flat brush with look-coloured bristles</title>
|
||||
<path d="M26.13 59.09L22.24 55.2L22.94 48.83L16.58 49.54L14.46 47.42L15.17 41.06L8.8 41.76L4.91 37.87L19.06 23.73L40.27 44.94Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.24 20.55L28.25 14.54L37.09 16.31L46.28 7.11A7.5 7.5 0 0 1 56.89 17.72L47.69 26.91L49.46 35.75L43.45 41.76Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 589 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Pick Up — eyedropper with a look-coloured tube</title>
|
||||
<path d="M8.96 52.22Q15.32 44.44 14.97 37.01L28.4 23.58L40.42 35.6L26.99 49.03Q19.56 48.68 11.78 55.04Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30.52 20.04L39.01 11.56A9.5 9.5 0 0 1 52.44 24.99L43.96 33.48Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M25.22 18.98L29.46 14.74L49.26 34.54L45.02 38.78Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 682 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render quality — draft, balanced or high</title>
|
||||
<path d="M10 38L17 38A3.5 3.5 0 0 1 20.5 41.5L20.5 52.5A3.5 3.5 0 0 1 17 56L10 56A3.5 3.5 0 0 1 6.5 52.5L6.5 41.5A3.5 3.5 0 0 1 10 38Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28.5 24L35.5 24A3.5 3.5 0 0 1 39 27.5L39 52.5A3.5 3.5 0 0 1 35.5 56L28.5 56A3.5 3.5 0 0 1 25 52.5L25 27.5A3.5 3.5 0 0 1 28.5 24Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M47 8L54 8A3.5 3.5 0 0 1 57.5 11.5L57.5 52.5A3.5 3.5 0 0 1 54 56L47 56A3.5 3.5 0 0 1 43.5 52.5L43.5 11.5A3.5 3.5 0 0 1 47 8Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 848 B |
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Replace All — every use of a look switched to another</title>
|
||||
<circle cx="12.5" cy="13.3" r="10" fill="#b8c3cf"/>
|
||||
<path d="M21.9 9.88A10 10 0 0 1 9.08 22.7A13.08 13.08 0 0 0 21.9 9.88Z" fill="#8e9faf"/>
|
||||
<circle cx="9.1" cy="9.9" r="2.7" fill="#e2e8ef"/>
|
||||
<circle cx="12.5" cy="13.3" r="10" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M36 36.8L25.39 34.68L33.88 26.19Z" fill="#236ead"/>
|
||||
<path d="M21 21.8L30.34 31.14" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="52.5" cy="35.3" r="9" fill="#328bd3"/>
|
||||
<path d="M60.96 32.22A9 9 0 0 1 49.42 43.76A11.77 11.77 0 0 0 60.96 32.22Z" fill="#236ead"/>
|
||||
<circle cx="49.44" cy="32.24" r="2.43" fill="#b9ddf8"/>
|
||||
<circle cx="52.5" cy="35.3" r="9" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="46.5" cy="47.3" r="14.68" fill="#ffffff"/>
|
||||
<circle cx="46.5" cy="47.3" r="12" fill="#328bd3"/>
|
||||
<path d="M57.78 43.2A12 12 0 0 1 42.4 58.58A15.69 15.69 0 0 0 57.78 43.2Z" fill="#236ead"/>
|
||||
<circle cx="42.42" cy="43.22" r="3.24" fill="#b9ddf8"/>
|
||||
<circle cx="46.5" cy="47.3" r="12" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render results — open the finished renders</title>
|
||||
<path d="M21 6L55 6A5 5 0 0 1 60 11L60 35A5 5 0 0 1 55 40L21 40A5 5 0 0 1 16 35L16 11A5 5 0 0 1 21 6Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9 17.5L43 17.5A7.5 7.5 0 0 1 50.5 25L50.5 51A7.5 7.5 0 0 1 43 58.5L9 58.5A7.5 7.5 0 0 1 1.5 51L1.5 25A7.5 7.5 0 0 1 9 17.5Z" fill="#ffffff"/>
|
||||
<path d="M9 20L43 20A5 5 0 0 1 48 25L48 51A5 5 0 0 1 43 56L9 56A5 5 0 0 1 4 51L4 25A5 5 0 0 1 9 20Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="res"><path d="M9 20L43 20A5 5 0 0 1 48 25L48 51A5 5 0 0 1 43 56L9 56A5 5 0 0 1 4 51L4 25A5 5 0 0 1 9 20Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#res)">
|
||||
<path d="M4 48.8L17.2 35.12L29.52 46.64L36.56 40.88L48 50.96L48 56L4 56Z" fill="#328bd3"/>
|
||||
</g>
|
||||
<path d="M9 20L43 20A5 5 0 0 1 48 25L48 51A5 5 0 0 1 43 56L9 56A5 5 0 0 1 4 51L4 25A5 5 0 0 1 9 20Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Same Look — the bodies wearing this look selected</title>
|
||||
<path d="M6.3 41.5L15.83 47L15.83 58L6.3 52.5Z" fill="#b8c3cf"/>
|
||||
<path d="M25.36 41.5L15.83 47L15.83 58L25.36 52.5Z" fill="#8e9faf"/>
|
||||
<path d="M15.83 36L25.36 41.5L15.83 47L6.3 41.5Z" fill="#e2e8ef"/>
|
||||
<path d="M15.83 36L25.36 41.5L25.36 52.5L15.83 58L6.3 52.5L6.3 41.5Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6.3 41.5L15.83 47L25.36 41.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15.83 47L15.83 58" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 12L45.83 17L45.83 27L37.17 22Z" fill="#328bd3"/>
|
||||
<path d="M54.48 12L45.83 17L45.83 27L54.48 22Z" fill="#236ead"/>
|
||||
<path d="M45.83 7L54.48 12L45.83 17L37.17 12Z" fill="#b9ddf8"/>
|
||||
<path d="M45.83 7L54.48 12L54.48 22L45.83 27L37.17 22L37.17 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 12L45.83 17L54.48 12" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M45.83 17L45.83 27" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.33 4L54.33 4A4.5 4.5 0 0 1 58.83 8.5L58.83 25.5A4.5 4.5 0 0 1 54.33 30L37.33 30A4.5 4.5 0 0 1 32.83 25.5L32.83 8.5A4.5 4.5 0 0 1 37.33 4Z" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 42L45.83 47L45.83 57L37.17 52Z" fill="#328bd3"/>
|
||||
<path d="M54.48 42L45.83 47L45.83 57L54.48 52Z" fill="#236ead"/>
|
||||
<path d="M45.83 37L54.48 42L45.83 47L37.17 42Z" fill="#b9ddf8"/>
|
||||
<path d="M45.83 37L54.48 42L54.48 52L45.83 57L37.17 52L37.17 42Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 42L45.83 47L54.48 42" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M45.83 47L45.83 57" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.33 34L54.33 34A4.5 4.5 0 0 1 58.83 38.5L58.83 55.5A4.5 4.5 0 0 1 54.33 60L37.33 60A4.5 4.5 0 0 1 32.83 55.5L32.83 38.5A4.5 4.5 0 0 1 37.33 34Z" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.83" cy="17" r="11.5" fill="#328bd3"/>
|
||||
<path d="M26.64 13.07A11.5 11.5 0 0 1 11.9 27.81A15.04 15.04 0 0 0 26.64 13.07Z" fill="#236ead"/>
|
||||
<circle cx="11.92" cy="13.09" r="3.11" fill="#b9ddf8"/>
|
||||
<circle cx="15.83" cy="17" r="11.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shot — save the camera view as a named shot</title>
|
||||
<path d="M9 19L12 19L15 12Q15.6 10 17.5 10L22.5 10Q24.4 10 25 12L28 19L43 19Q48 19 48 24L48 46Q48 51 43 51L9 51Q4 51 4 46L4 24Q4 19 9 19Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="26" cy="35" r="10.5" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M42 47.5L53 47.5M47.5 42L47.5 53" fill="none" stroke="#ffffff" stroke-width="3.4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 759 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shots strip — show or hide the strip of shot thumbnails</title>
|
||||
<path d="M8 19L56 19A5 5 0 0 1 61 24L61 40A5 5 0 0 1 56 45L8 45A5 5 0 0 1 3 40L3 24A5 5 0 0 1 8 19Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10.5 24.5L19.5 24.5A2.5 2.5 0 0 1 22 27L22 37A2.5 2.5 0 0 1 19.5 39.5L10.5 39.5A2.5 2.5 0 0 1 8 37L8 27A2.5 2.5 0 0 1 10.5 24.5Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27.5 24.5L36.5 24.5A2.5 2.5 0 0 1 39 27L39 37A2.5 2.5 0 0 1 36.5 39.5L27.5 39.5A2.5 2.5 0 0 1 25 37L25 27A2.5 2.5 0 0 1 27.5 24.5Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M44.5 24.5L53.5 24.5A2.5 2.5 0 0 1 56 27L56 37A2.5 2.5 0 0 1 53.5 39.5L44.5 39.5A2.5 2.5 0 0 1 42 37L42 27A2.5 2.5 0 0 1 44.5 24.5Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Solo light — point source and dotted stem inside focus brackets</title>
|
||||
<path d="M13.5 5L5.5 5L5.5 13M50.5 5L58.5 5L58.5 13M58.5 51.5L58.5 59.5L50.5 59.5M13.5 59.5L5.5 59.5L5.5 51.5" fill="none" stroke="currentColor" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="23" r="15" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="23" r="2.125" fill="#34465b"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Sun — source disc with short radial rays</title>
|
||||
<circle cx="32" cy="32" r="15" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 5.5L32 10.5M32 53.5L32 58.5M5.5 32L10.5 32M53.5 32L58.5 32M13.26 13.26L16.8 16.8M47.2 47.2L50.74 50.74M13.26 50.74L16.8 47.2M47.2 16.8L50.74 13.26" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 523 B |
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Transparent background — render with an alpha background</title>
|
||||
<path d="M11 6L53 6A5 5 0 0 1 58 11L58 53A5 5 0 0 1 53 58L11 58A5 5 0 0 1 6 53L6 11A5 5 0 0 1 11 6Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="chk"><path d="M11 6L53 6A5 5 0 0 1 58 11L58 53A5 5 0 0 1 53 58L11 58A5 5 0 0 1 6 53L6 11A5 5 0 0 1 11 6Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#chk)">
|
||||
<path d="M6 19L19 19L19 32L6 32ZM6 45L19 45L19 58L6 58ZM19 6L32 6L32 19L19 19ZM19 32L32 32L32 45L19 45ZM32 19L45 19L45 32L32 32ZM32 45L45 45L45 58L32 58ZM45 6L58 6L58 19L45 19ZM45 32L58 32L58 45L45 45Z" fill="#b9ddf8"/>
|
||||
</g>
|
||||
<path d="M11 6L53 6A5 5 0 0 1 58 11L58 53A5 5 0 0 1 53 58L11 58A5 5 0 0 1 6 53L6 11A5 5 0 0 1 11 6Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 18L44.12 25L44.12 39L32 46L19.88 39L19.88 25Z" fill="#ffffff" stroke="#ffffff" stroke-width="7.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M19.88 25L32 32L32 46L19.88 39Z" fill="#b8c3cf"/>
|
||||
<path d="M44.12 25L32 32L32 46L44.12 39Z" fill="#8e9faf"/>
|
||||
<path d="M32 18L44.12 25L32 32L19.88 25Z" fill="#e2e8ef"/>
|
||||
<path d="M32 18L44.12 25L44.12 39L32 46L19.88 39L19.88 25Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M19.88 25L32 32L44.12 25" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L32 46" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Update shot — overwrite the active shot with the current camera</title>
|
||||
<path d="M9 19L12 19L15 12Q15.6 10 17.5 10L22.5 10Q24.4 10 25 12L28 19L43 19Q48 19 48 24L48 46Q48 51 43 51L9 51Q4 51 4 46L4 24Q4 19 9 19Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="26" cy="35" r="10.5" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M50.97 43.36A5.4 5.4 0 1 1 43.08 44.4" fill="none" stroke="#ffffff" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M46.06 40.14L39.8 42.11L46.35 46.7Z" fill="#ffffff" stroke="#ffffff" stroke-width="0.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 930 B |
@@ -457,6 +457,7 @@
|
||||
"cmd.canvas_delete": "Smazat plátno",
|
||||
"cmd.canvas_edit": "Upravit plátno",
|
||||
"cmd.canvas_insert": "Plátno",
|
||||
"cmd.canvas_to_sketch": "Náčrt z plátna",
|
||||
"cmd.component_activate": "Aktivovat komponentu",
|
||||
"cmd.component_break_link": "Zrušit odkaz",
|
||||
"cmd.component_capture_position": "Zachytit polohu",
|
||||
@@ -534,6 +535,7 @@
|
||||
"cmd.file_export": "Exportovat",
|
||||
"cmd.file_insert_mesh": "Vložit síť",
|
||||
"cmd.file_insert_step": "Vložit STEP",
|
||||
"cmd.file_open_in_easyprint": "Otevřít v EasyPrint",
|
||||
"cmd.file_save_mesh": "Uložit jako síť",
|
||||
"cmd.file_slicers": "Nainstalované slicery",
|
||||
"cmd.frame_member": "Prvek rámu",
|
||||
@@ -1493,7 +1495,7 @@
|
||||
"ui.agent_chat_pop_out": "Otevřít ve vlastním okně, které lze přesunout na jinou obrazovku",
|
||||
"ui.agent_chat_previous_picture": "Předchozí obrázek",
|
||||
"ui.agent_chat_privacy": "Nic se nikam neodesílá, jen agentovi, kterého spouštíte pod vlastním účtem.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft nic nikam neodesílá: agent, kterého spouštíte, komunikuje se svou službou pod vaším účtem. SolidCraft nikdy nevidí ani neukládá klíč či heslo.",
|
||||
"ui.agent_chat_privacy_note": "Chat sám nic nikam neodesílá: agent, kterého spouštíte, komunikuje se svou službou pod vaším účtem. SolidCraft nikdy nevidí ani neukládá klíč či heslo.",
|
||||
"ui.agent_chat_queue": "Zařadit",
|
||||
"ui.agent_chat_queue_full": "Čeká příliš mnoho zpráv: některou odeberte nebo počkejte na agenta.",
|
||||
"ui.agent_chat_queued": "Ve frontě",
|
||||
@@ -1678,6 +1680,7 @@
|
||||
"ui.command_done": "{command} hotovo",
|
||||
"ui.command_palette_hint": "Zadejte příkaz, např. {example} — nebo stiskněte S pro hledání",
|
||||
"ui.concentric": "Soustřednost",
|
||||
"ui.cone": "Kužel",
|
||||
"ui.conic_curve": "Kuželosečka",
|
||||
"ui.constant": "Konstantní",
|
||||
"ui.constraints_conflict": "Konflikt vazeb",
|
||||
@@ -1736,6 +1739,11 @@
|
||||
"ui.draft": "Úkos",
|
||||
"ui.drawing": "Výkres",
|
||||
"ui.drill_point": "Špička vrtáku",
|
||||
"ui.easyprint_consent": "EasyPrint je slicer od Printables v prohlížeči. Aby se v něm model otevřel, SolidCraft jej nahraje na servery Printables. Odkaz vyprší po 30 minutách a musíte být přihlášeni k Printables. Pokračovat?",
|
||||
"ui.easyprint_failed": "Nahrání do EasyPrint se nezdařilo: {error}",
|
||||
"ui.easyprint_opened": "Otevřeno v EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, v prohlížeči)",
|
||||
"ui.easyprint_uploading": "Nahrávám na Printables…",
|
||||
"ui.edit": "Upravit",
|
||||
"ui.editJointLimits": "Upravit limity kloubu",
|
||||
"ui.edit_section_analysis": "Upravit analýzu řezu",
|
||||
@@ -1793,6 +1801,7 @@
|
||||
"ui.heading_circular_pattern": "KRUHOVÉ POLE",
|
||||
"ui.heading_coil": "ŠROUBOVICE",
|
||||
"ui.heading_combine": "KOMBINOVAT",
|
||||
"ui.heading_cone": "KUŽEL",
|
||||
"ui.heading_configure": "KONFIGURACE",
|
||||
"ui.heading_constraints": "VAZBY",
|
||||
"ui.heading_construct": "KONSTRUKCE",
|
||||
@@ -2030,9 +2039,7 @@
|
||||
"ui.right": "Zprava",
|
||||
"ui.save": "Uložit",
|
||||
"ui.save_as": "Uložit jako",
|
||||
"ui.save_mesh_no_slicer": "Nebyl nalezen žádný slicer: zvolte Vlastní… a vyberte svůj slicer, nebo vypněte Odeslat do sliceru",
|
||||
"ui.save_mesh_pick_bodies": "Vyberte tělesa k uložení",
|
||||
"ui.save_mesh_temp_failed": "Nelze vytvořit {folder}: {error}",
|
||||
"ui.saved_to": "Uloženo {path}",
|
||||
"ui.sbox_pin": "Připnout nahoru ve vyhledávání S",
|
||||
"ui.sbox_unpin": "Odepnout",
|
||||
@@ -2047,7 +2054,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klik pouze přidává",
|
||||
"ui.selection_shift_add_only_tip": "Zapnuto: Shift přidává položky, Ctrl/Cmd přepíná. Vypnuto (výchozí): Shift i Ctrl/Cmd přepínají.",
|
||||
"ui.send": "Odeslat",
|
||||
"ui.send_to_slicer": "Odeslat do sliceru",
|
||||
"ui.sent_to_slicer": "Odesláno do {slicer}",
|
||||
"ui.sheet_gap": "Mezera",
|
||||
"ui.shell": "Skořepina",
|
||||
@@ -2139,6 +2145,7 @@
|
||||
"ui.untitled": "Bez názvu",
|
||||
"ui.up_axis_note": "Která osa v pohledech míří nahoru (pohled podél osy, výchozí pohled, pohled kolmo). Návrhy se nemění.",
|
||||
"ui.updateToCurrentView": "Aktualizovat na aktuální pohled",
|
||||
"ui.upload": "Nahrát",
|
||||
"ui.view_centre": "Střed pohledu",
|
||||
"ui.visual_shaded_hidden_edges": "Stínovaný se skrytými hranami",
|
||||
"ui.visual_shaded_with_edges": "Stínovaný s hranami",
|
||||
|
||||
@@ -649,6 +649,7 @@
|
||||
"cmd.canvas_delete": "Slet referencebillede",
|
||||
"cmd.canvas_edit": "Redigér referencebillede",
|
||||
"cmd.canvas_insert": "Referencebillede",
|
||||
"cmd.canvas_to_sketch": "Skitse fra referencebillede",
|
||||
"cmd.component_activate": "Aktivér komponent",
|
||||
"cmd.component_break_link": "Bryd forbindelse",
|
||||
"cmd.component_capture_position": "Registrér position",
|
||||
@@ -729,6 +730,7 @@
|
||||
"cmd.file_export": "Eksportér",
|
||||
"cmd.file_insert_mesh": "Indsæt mesh",
|
||||
"cmd.file_insert_step": "Indsæt STEP",
|
||||
"cmd.file_open_in_easyprint": "Åbn med EasyPrint",
|
||||
"cmd.file_save_mesh": "Gem som mesh",
|
||||
"cmd.file_slicers": "Installerede slicere",
|
||||
"cmd.frame_member": "Rammeelement",
|
||||
@@ -1670,7 +1672,7 @@
|
||||
"ui.agent_chat_pop_out": "Åbn i eget vindue, som du kan flytte til en anden skærm",
|
||||
"ui.agent_chat_previous_picture": "Forrige billede",
|
||||
"ui.agent_chat_privacy": "Intet sendes andre steder end til den agent, du kører, under din egen konto.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft sender intet nogen steder: Agenten, du kører, kommunikerer med sin egen tjeneste under din egen konto. SolidCraft ser eller gemmer aldrig en nøgle eller adgangskode.",
|
||||
"ui.agent_chat_privacy_note": "Chatten sender selv intet nogen steder: Agenten, du kører, kommunikerer med sin egen tjeneste under din egen konto. SolidCraft ser eller gemmer aldrig en nøgle eller adgangskode.",
|
||||
"ui.agent_chat_queue": "Sæt i kø",
|
||||
"ui.agent_chat_queue_full": "For mange beskeder venter: Fjern en, eller vent på agenten.",
|
||||
"ui.agent_chat_queued": "I kø",
|
||||
@@ -1863,6 +1865,7 @@
|
||||
"ui.command_done": "{command} udført",
|
||||
"ui.command_palette_hint": "Skriv en kommando, f.eks. {example} — eller tryk S for at søge",
|
||||
"ui.concentric": "Koncentrisk",
|
||||
"ui.cone": "Kegle",
|
||||
"ui.conic_curve": "Keglesnitskurve",
|
||||
"ui.constant": "Konstant",
|
||||
"ui.constraints_conflict": "Bindinger er i konflikt",
|
||||
@@ -1921,6 +1924,11 @@
|
||||
"ui.draft": "Slipvinkel",
|
||||
"ui.drawing": "Tegning",
|
||||
"ui.drill_point": "Borspids",
|
||||
"ui.easyprint_consent": "EasyPrint er Printables' slicer i browseren. For at åbne modellen der uploader SolidCraft den til Printables' servere. Linket udløber efter 30 minutter, og du skal være logget ind på Printables. Fortsæt?",
|
||||
"ui.easyprint_failed": "Upload til EasyPrint mislykkedes: {error}",
|
||||
"ui.easyprint_opened": "Åbnet i EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, i browseren)",
|
||||
"ui.easyprint_uploading": "Uploader til Printables…",
|
||||
"ui.edit": "Redigér",
|
||||
"ui.editJointLimits": "Redigér ledgrænser",
|
||||
"ui.edit_section_analysis": "Redigér snitanalyse",
|
||||
@@ -1982,6 +1990,7 @@
|
||||
"ui.heading_circular_pattern": "CIRKULÆRT MØNSTER",
|
||||
"ui.heading_coil": "SPIRAL",
|
||||
"ui.heading_combine": "KOMBINÉR",
|
||||
"ui.heading_cone": "KEGLE",
|
||||
"ui.heading_configure": "KONFIGURÉR",
|
||||
"ui.heading_constraints": "BINDINGER",
|
||||
"ui.heading_construct": "KONSTRUKTION",
|
||||
@@ -2232,9 +2241,7 @@
|
||||
"ui.right": "Fra højre",
|
||||
"ui.save": "Gem",
|
||||
"ui.save_as": "Gem som",
|
||||
"ui.save_mesh_no_slicer": "Ingen slicer fundet: Vælg Brugerdefineret…, og vælg dit slicerprogram, eller slå Send til slicer fra",
|
||||
"ui.save_mesh_pick_bodies": "Vælg de emner, der skal gemmes",
|
||||
"ui.save_mesh_temp_failed": "Kunne ikke oprette {folder}: {error}",
|
||||
"ui.saved_to": "Gemt: {path}",
|
||||
"ui.sbox_pin": "Fastgør øverst i S-menuen",
|
||||
"ui.sbox_unpin": "Fjern fastgørelse",
|
||||
@@ -2249,7 +2256,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klik tilføjer uden at fjerne",
|
||||
"ui.selection_shift_add_only_tip": "Slået til: Shift tilføjer til markeringen. Ctrl/Cmd tilføjer eller fjerner. Slået fra (standard): Både Shift og Ctrl/Cmd tilføjer eller fjerner.",
|
||||
"ui.send": "Send",
|
||||
"ui.send_to_slicer": "Send til slicer",
|
||||
"ui.sent_to_slicer": "Sendt til {slicer}",
|
||||
"ui.sheet_gap": "Mellemrum",
|
||||
"ui.shell": "Udhulning",
|
||||
@@ -2347,6 +2353,7 @@
|
||||
"ui.untitled": "Uden titel",
|
||||
"ui.up_axis_note": "Hvilken akse der peger opad i visningerne (aksevisning, Hjem, Se på). Designs ændres ikke.",
|
||||
"ui.updateToCurrentView": "Opdatér til aktuel visning",
|
||||
"ui.upload": "Upload",
|
||||
"ui.view_centre": "Visningens centrum",
|
||||
"ui.visual_shaded_hidden_edges": "Skyggelagt med skjulte kanter",
|
||||
"ui.visual_shaded_with_edges": "Skyggelagt med kanter",
|
||||
|
||||
@@ -565,6 +565,7 @@
|
||||
"cmd.canvas_delete": "Zeichenfläche löschen",
|
||||
"cmd.canvas_edit": "Zeichenfläche bearbeiten",
|
||||
"cmd.canvas_insert": "Zeichenfläche",
|
||||
"cmd.canvas_to_sketch": "Skizze aus Zeichenfläche",
|
||||
"cmd.component_activate": "Komponente aktivieren",
|
||||
"cmd.component_break_link": "Verknüpfung trennen",
|
||||
"cmd.component_capture_position": "Position erfassen",
|
||||
@@ -646,6 +647,7 @@
|
||||
"cmd.file_export": "Exportieren",
|
||||
"cmd.file_insert_mesh": "Netz einfügen",
|
||||
"cmd.file_insert_step": "STEP einfügen",
|
||||
"cmd.file_open_in_easyprint": "Mit EasyPrint öffnen",
|
||||
"cmd.file_save_mesh": "Als Netz speichern",
|
||||
"cmd.file_slicers": "Installierte Slicer",
|
||||
"cmd.frame_member": "Rahmenelement",
|
||||
@@ -852,6 +854,7 @@
|
||||
"cmd.sketch_spline_fit_point": "Spline mit Fitpunkten",
|
||||
"cmd.sketch_spun_profile": "Gedrehtes Profil",
|
||||
"cmd.sketch_toggle_driven": "Abhängiges Maß umschalten",
|
||||
"cmd.sketch_transform": "Transformieren",
|
||||
"cmd.sketch_trim_path": "Entlang Pfad trimmen",
|
||||
"cmd.solid_align": "Ausrichten",
|
||||
"cmd.solid_boundary_fill": "Begrenzungsfüllung",
|
||||
@@ -1115,6 +1118,7 @@
|
||||
"nav.blender.help": "Mittlere Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt; rechtes Ziehen lässt die Kamera unverändert",
|
||||
"nav.free_orbit.help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollneigung bei. Das Einrasten mit Umschalt ist deaktiviert.",
|
||||
"nav.free_orbit.label": "Freie Drehung",
|
||||
"nav.free_orbit.snap_help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollausrichtung bei. Halten Sie beim Ziehen zum Drehen die Umschalttaste gedrückt, um Blickrichtung und Rollausrichtung einrasten zu lassen.",
|
||||
"nav.fusion.help": "Mittlere Maustaste verschiebt, Umschalt+Mitte dreht, rechte Maustaste dreht",
|
||||
"nav.inventor.help": "Mittlere Maustaste verschiebt, Umschalt+Mitte dreht, Strg+Mitte zoomt, rechte Maustaste dreht",
|
||||
"nav.openscad.help": "Linksziehen auf leerem Hintergrund dreht, Umschalt+Linksziehen zieht ein Auswahlrechteck auf, Skizzengeometrie wird durch Ziehen bearbeitet; rechtes oder mittleres Ziehen verschiebt, Umschalt+rechtes oder Umschalt+mittleres Ziehen zoomt",
|
||||
@@ -1144,6 +1148,8 @@
|
||||
"pick a hole to seat it, or OK places it at the origin": "Wählen Sie eine Bohrung zum Einsetzen, oder OK platziert es im Ursprung",
|
||||
"pick one or two vertices, edges or faces": "Wählen Sie ein oder zwei Ecken, Kanten oder Flächen",
|
||||
"prefs.cpu": "CPU (Software)",
|
||||
"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.minutes_suffix": " min",
|
||||
"properties.title": "Eigenschaften: {name}",
|
||||
"recovery.days_ago": "vor {count} Tagen",
|
||||
@@ -1580,7 +1586,7 @@
|
||||
"ui.agent_chat_pop_out": "In eigenem Fenster öffnen, das Sie auf einen anderen Bildschirm verschieben können",
|
||||
"ui.agent_chat_previous_picture": "Vorheriges Bild",
|
||||
"ui.agent_chat_privacy": "Nichts geht irgendwohin außer an den Agenten, den Sie unter Ihrem eigenen Konto ausführen.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft sendet nichts irgendwohin: Der Agent, den Sie starten, spricht unter Ihrem eigenen Konto mit seinem eigenen Dienst. SolidCraft sieht und speichert nie einen Schlüssel oder ein Passwort.",
|
||||
"ui.agent_chat_privacy_note": "Der Chat sendet von sich aus nichts irgendwohin: Der Agent, den Sie starten, spricht unter Ihrem eigenen Konto mit seinem eigenen Dienst. SolidCraft sieht und speichert nie einen Schlüssel oder ein Passwort.",
|
||||
"ui.agent_chat_queue": "Einreihen",
|
||||
"ui.agent_chat_queue_full": "Zu viele Nachrichten warten: Entfernen Sie eine oder warten Sie auf den Agenten.",
|
||||
"ui.agent_chat_queued": "Eingereiht",
|
||||
@@ -1765,6 +1771,7 @@
|
||||
"ui.command_done": "{command} fertig",
|
||||
"ui.command_palette_hint": "Befehl eingeben, z. B. {example} — oder S drücken, um zu suchen",
|
||||
"ui.concentric": "Konzentrisch",
|
||||
"ui.cone": "Kegel",
|
||||
"ui.conic_curve": "Kegelschnitt",
|
||||
"ui.constant": "Konstant",
|
||||
"ui.constraints_conflict": "Bedingungen konfliktreich",
|
||||
@@ -1823,6 +1830,11 @@
|
||||
"ui.draft": "Abziehen",
|
||||
"ui.drawing": "Zeichnung",
|
||||
"ui.drill_point": "Bohrpunkt",
|
||||
"ui.easyprint_consent": "EasyPrint ist der Slicer von Printables im Browser. Um das Modell dort zu öffnen, lädt SolidCraft es auf die Server von Printables hoch. Der Link läuft nach 30 Minuten ab, und Sie müssen bei Printables angemeldet sein. Fortfahren?",
|
||||
"ui.easyprint_failed": "Upload zu EasyPrint fehlgeschlagen: {error}",
|
||||
"ui.easyprint_opened": "In EasyPrint geöffnet",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, im Browser)",
|
||||
"ui.easyprint_uploading": "Wird zu Printables hochgeladen …",
|
||||
"ui.edit": "Bearbeiten",
|
||||
"ui.editJointLimits": "Gelenkgrenzen bearbeiten",
|
||||
"ui.edit_section_analysis": "Querschnittsanalyse bearbeiten",
|
||||
@@ -1882,6 +1894,7 @@
|
||||
"ui.heading_circular_pattern": "RADIALES MUSTER",
|
||||
"ui.heading_coil": "SCHRAUBE",
|
||||
"ui.heading_combine": "KOMBINIEREN",
|
||||
"ui.heading_cone": "KEGEL",
|
||||
"ui.heading_configure": "KONFIGURIEREN",
|
||||
"ui.heading_constraints": "RANDBEDINGUNGEN",
|
||||
"ui.heading_construct": "KONSTRUKTION",
|
||||
@@ -2123,9 +2136,7 @@
|
||||
"ui.right": "Rechts",
|
||||
"ui.save": "Speichern",
|
||||
"ui.save_as": "Speichern unter",
|
||||
"ui.save_mesh_no_slicer": "Kein Slicer gefunden: „Benutzerdefiniert…“ wählen und das Slicer-Programm angeben, oder „An Slicer senden“ ausschalten",
|
||||
"ui.save_mesh_pick_bodies": "Wähle die Körper zum Speichern aus",
|
||||
"ui.save_mesh_temp_failed": "{folder} konnte nicht angelegt werden: {error}",
|
||||
"ui.saved_to": "Gespeichert: {path}",
|
||||
"ui.sbox_pin": "Oben im S-Feld anheften",
|
||||
"ui.sbox_unpin": "Lösen",
|
||||
@@ -2140,7 +2151,6 @@
|
||||
"ui.selection_shift_add_only": "Umschalt+Klick fügt hinzu, ohne zu entfernen",
|
||||
"ui.selection_shift_add_only_tip": "An: Umschalt fügt Elemente hinzu, Strg/Cmd schaltet um. Aus (Standard): Umschalt und Strg/Cmd schalten beide um.",
|
||||
"ui.send": "Senden",
|
||||
"ui.send_to_slicer": "An Slicer senden",
|
||||
"ui.sent_to_slicer": "An {slicer} gesendet",
|
||||
"ui.sheet_gap": "Spalt",
|
||||
"ui.shell": "Hülle",
|
||||
@@ -2238,6 +2248,7 @@
|
||||
"ui.untitled": "Ohne Titel",
|
||||
"ui.up_axis_note": "Welche Achse in den Ansichten nach oben zeigt (Achsenansicht, Home, Ansehen). Designs bleiben unverändert.",
|
||||
"ui.updateToCurrentView": "Auf aktuelle Ansicht aktualisieren",
|
||||
"ui.upload": "Hochladen",
|
||||
"ui.view_centre": "Ansichtszentrum",
|
||||
"ui.viewport_theme_follow": "Dem Design folgen",
|
||||
"ui.visual_shaded_hidden_edges": "Schattiert mit verdeckten Kanten",
|
||||
|
||||
@@ -430,6 +430,8 @@
|
||||
"Remove this point": "Remove this point",
|
||||
"Rename Feature": "Rename Feature",
|
||||
"Rename Folder": "Rename Folder",
|
||||
"Reopen designs": "Reopen designs",
|
||||
"Reopen the designs that were open when SolidCraft was last closed": "Reopen the designs that were open when SolidCraft was last closed",
|
||||
"Replace existing library file": "Replace existing library file",
|
||||
"Report a bug — review a screenshot and open a GitHub draft": "Report a bug — review a screenshot and open a GitHub draft",
|
||||
"Report text copied. Paste it into GitHub and submit your report there.": "Report text copied. Paste it into GitHub and submit your report there.",
|
||||
@@ -692,10 +694,12 @@
|
||||
"cmd.canvas_delete": "Delete Canvas",
|
||||
"cmd.canvas_edit": "Edit Canvas",
|
||||
"cmd.canvas_insert": "Canvas",
|
||||
"cmd.canvas_to_sketch": "Canvas to Sketch",
|
||||
"cmd.component_activate": "Activate Component",
|
||||
"cmd.component_break_link": "Break Link",
|
||||
"cmd.component_capture_position": "Capture Position",
|
||||
"cmd.component_copies": "Find Identical Copies",
|
||||
"cmd.component_copy_bodies": "Paste Bodies",
|
||||
"cmd.component_create": "New Component",
|
||||
"cmd.component_cycle_color": "Cycle Component Color",
|
||||
"cmd.component_delete": "Delete Component",
|
||||
@@ -774,7 +778,9 @@
|
||||
"cmd.file_export": "Export",
|
||||
"cmd.file_insert_mesh": "Insert Mesh",
|
||||
"cmd.file_insert_step": "Insert STEP",
|
||||
"cmd.file_open_in_easyprint": "Open with EasyPrint",
|
||||
"cmd.file_save_mesh": "Save As Mesh",
|
||||
"cmd.file_send_to_slicer": "Send to Slicer",
|
||||
"cmd.file_slicers": "Installed Slicers",
|
||||
"cmd.frame_member": "Frame Member",
|
||||
"cmd.frame_profile_insert": "Insert Profile",
|
||||
@@ -846,6 +852,7 @@
|
||||
"cmd.occurrence_ground": "Ground",
|
||||
"cmd.occurrence_move": "Move Occurrence",
|
||||
"cmd.occurrence_properties": "Occurrence Physical Properties",
|
||||
"cmd.occurrence_reparent": "Move to Component",
|
||||
"cmd.parameters_add": "Add User Parameter",
|
||||
"cmd.parameters_change": "Change Parameters",
|
||||
"cmd.parameters_comment": "Comment Parameter",
|
||||
@@ -954,6 +961,7 @@
|
||||
"cmd.sketch_drag": "Drag Sketch Geometry",
|
||||
"cmd.sketch_drag_end": "End Sketch Drag",
|
||||
"cmd.sketch_draw_dimensioned": "Draw Dimensioned Sketch Geometry",
|
||||
"cmd.sketch_drawing_bounds": "Drawing Size",
|
||||
"cmd.sketch_edit": "Edit Sketch",
|
||||
"cmd.sketch_edit_text": "Edit Text",
|
||||
"cmd.sketch_export_dxf": "Save As DXF",
|
||||
@@ -1000,6 +1008,7 @@
|
||||
"cmd.sketch_spline_fit_point": "Fit Point Spline",
|
||||
"cmd.sketch_spun_profile": "Spun Profile",
|
||||
"cmd.sketch_toggle_driven": "Toggle Driven Dimension",
|
||||
"cmd.sketch_transform": "Transform",
|
||||
"cmd.sketch_trim_path": "Trim Along Path",
|
||||
"cmd.solid_align": "Align",
|
||||
"cmd.solid_boundary_fill": "Boundary Fill",
|
||||
@@ -1371,6 +1380,7 @@
|
||||
"nav.blender.help": "Middle drag orbits, Shift+middle pans, Ctrl+middle zooms; right drag leaves the camera alone",
|
||||
"nav.free_orbit.help": "Mouse and 3D mouse rotate through both poles and preserve roll. Shift axis snapping is disabled.",
|
||||
"nav.free_orbit.label": "Free orbit",
|
||||
"nav.free_orbit.snap_help": "Mouse and 3D mouse rotate through both poles and preserve roll. Hold Shift during an orbit drag to snap the view direction and roll.",
|
||||
"nav.fusion.help": "Middle drag pans, Shift+middle orbits, right drag orbits",
|
||||
"nav.inventor.help": "Middle drag pans, Shift+middle orbits, Ctrl+middle zooms, right drag orbits",
|
||||
"nav.openscad.help": "Left drag on empty space orbits, Shift+left drag box-selects, sketch geometry drags edit; right or middle drag pans, Shift+right or Shift+middle drag zooms",
|
||||
@@ -1445,6 +1455,7 @@
|
||||
"prefs.design": "Design defaults",
|
||||
"prefs.design_note": "What new designs start with. Open designs keep their own.",
|
||||
"prefs.devices": "3D mouse",
|
||||
"prefs.dialog_buttons": "Dialog buttons",
|
||||
"prefs.display": "Display",
|
||||
"prefs.edges": "Edges and shading",
|
||||
"prefs.extend": "Extend",
|
||||
@@ -1467,6 +1478,12 @@
|
||||
"prefs.graphics_note": "Trade speed for smoothness. Each choice shows its effect on a sample.",
|
||||
"prefs.graphics_presets_note": "Presets set the choices below together.",
|
||||
"prefs.history": "History",
|
||||
"prefs.history_origin_bodies": "Show origin and bodies at the top",
|
||||
"prefs.history_origin_bodies_note": "The History on the left or under the browser starts with the active component's Bodies folder and the Origin, with their eyes and menus as in the browser. The History at the bottom is unchanged.",
|
||||
"prefs.history_sketches": "Sketches",
|
||||
"prefs.history_sketches_in_order": "In order",
|
||||
"prefs.history_sketches_note": "With Sketches set to Under their feature, the History at the left lists each sketch one level in, under the first feature built from it, whenever it is wide enough for names. The timeline at the bottom always keeps the creation order.",
|
||||
"prefs.history_sketches_under_feature": "Under their feature",
|
||||
"prefs.hold_stop_snapping": "Hold to stop snapping",
|
||||
"prefs.icons_only": "Icons only",
|
||||
"prefs.imported_file": "Imported file",
|
||||
@@ -1543,6 +1560,7 @@
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
"prefs.toggle_item": "Add the item, or take it out again",
|
||||
"prefs.toolbar": "Toolbar",
|
||||
"prefs.top": "Top",
|
||||
"prefs.translucent": "Translucent",
|
||||
"prefs.under_browser": "Under browser",
|
||||
"prefs.under_cursor": "Under cursor",
|
||||
@@ -2477,7 +2495,7 @@
|
||||
"ui.agent_chat_pop_out": "Open in its own window, which you can move to another screen",
|
||||
"ui.agent_chat_previous_picture": "Previous picture",
|
||||
"ui.agent_chat_privacy": "Nothing goes anywhere except to the agent you run, under your own account.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft sends nothing anywhere: the agent you run talks to its own service under your own account. SolidCraft never sees or stores a key or password.",
|
||||
"ui.agent_chat_privacy_note": "The chat itself sends nothing anywhere: the agent you run talks to its own service under your own account. SolidCraft never sees or stores a key or password.",
|
||||
"ui.agent_chat_provider_models": "{count} models",
|
||||
"ui.agent_chat_provider_other": "Other",
|
||||
"ui.agent_chat_provider_tip": "The provider whose models the next list shows.",
|
||||
@@ -2695,6 +2713,7 @@
|
||||
"ui.command_done": "{command} done",
|
||||
"ui.command_palette_hint": "Type a command, e.g. {example} — or press S to search",
|
||||
"ui.concentric": "Concentric",
|
||||
"ui.cone": "Cone",
|
||||
"ui.conic_curve": "Conic Curve",
|
||||
"ui.constant": "Constant",
|
||||
"ui.constraints_conflict": "Constraints conflict",
|
||||
@@ -2755,6 +2774,11 @@
|
||||
"ui.draft": "Draft",
|
||||
"ui.drawing": "Drawing",
|
||||
"ui.drill_point": "Drill Point",
|
||||
"ui.easyprint_consent": "EasyPrint is Printables' slicer in the browser. To open the model there, SolidCraft uploads it to Printables' servers. The link expires after 30 minutes, and you'll need to be signed in to Printables. Continue?",
|
||||
"ui.easyprint_failed": "EasyPrint upload failed: {error}",
|
||||
"ui.easyprint_opened": "Opened in EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, in the browser)",
|
||||
"ui.easyprint_uploading": "Uploading to Printables…",
|
||||
"ui.edit": "Edit",
|
||||
"ui.editJointLimits": "Edit Joint Limits",
|
||||
"ui.edit_section_analysis": "Edit Section Analysis",
|
||||
@@ -2817,6 +2841,7 @@
|
||||
"ui.heading_circular_pattern": "CIRCULAR PATTERN",
|
||||
"ui.heading_coil": "COIL",
|
||||
"ui.heading_combine": "COMBINE",
|
||||
"ui.heading_cone": "CONE",
|
||||
"ui.heading_configure": "CONFIGURE",
|
||||
"ui.heading_constraints": "CONSTRAINTS",
|
||||
"ui.heading_construct": "CONSTRUCT",
|
||||
@@ -2897,6 +2922,7 @@
|
||||
"ui.history_names": "Names",
|
||||
"ui.history_play": "Play to the end",
|
||||
"ui.history_show_names": "Show names",
|
||||
"ui.history_sketch_listed_under": "Listed under {feature}",
|
||||
"ui.history_title": "History",
|
||||
"ui.history_toggle_names": "Show or hide feature names",
|
||||
"ui.hole": "Hole",
|
||||
@@ -3085,9 +3111,8 @@
|
||||
"ui.right": "Right",
|
||||
"ui.save": "Save",
|
||||
"ui.save_as": "Save As",
|
||||
"ui.save_mesh_no_slicer": "No slicer found: choose Custom… and pick your slicer program, or turn off Send to slicer",
|
||||
"ui.save_mesh_no_slicer_open": "No slicer found: choose Custom… and pick your slicer program",
|
||||
"ui.save_mesh_pick_bodies": "Select the bodies to save",
|
||||
"ui.save_mesh_temp_failed": "Couldn't make {folder}: {error}",
|
||||
"ui.saved_to": "Saved {path}",
|
||||
"ui.sbox_pin": "Pin to the top of the S box",
|
||||
"ui.sbox_unpin": "Unpin",
|
||||
@@ -3102,8 +3127,9 @@
|
||||
"ui.selection_shift_add_only": "Shift+click adds without removing",
|
||||
"ui.selection_shift_add_only_tip": "On: Shift adds items, Ctrl/Cmd toggles. Off (default): Shift and Ctrl/Cmd both toggle.",
|
||||
"ui.send": "Send",
|
||||
"ui.send_to_slicer": "Send to slicer",
|
||||
"ui.send_to_slicer_unavailable": "Send to Slicer needs the desktop app: a browser can't start a program",
|
||||
"ui.sent_to_slicer": "Sent to {slicer}",
|
||||
"ui.sent_to_slicer_copy": "Sent to {slicer} as {file}, because the first file is still open there",
|
||||
"ui.sheet_gap": "Gap",
|
||||
"ui.shell": "Shell",
|
||||
"ui.shortcut_capture_add_key": "Add one ordinary key. Escape cancels.",
|
||||
@@ -3138,6 +3164,15 @@
|
||||
"ui.slice": "Slice",
|
||||
"ui.slicer": "Slicer",
|
||||
"ui.slicer_delete_tip": "{program}\nPress Delete to remove it from the list",
|
||||
"ui.slicer_lost": "{slicer} was started but can't be found again to send it the file. The file is saved at {path}: open it from there.",
|
||||
"ui.slicer_no_file": "{slicer} is ready, but there is no file to send to it.",
|
||||
"ui.slicer_no_response": "{slicer} didn't respond in time. The file is already saved at {path}: send it again once the slicer has started.",
|
||||
"ui.slicer_resend_hint": "Sending again replaces the file the slicer has open.",
|
||||
"ui.slicer_single_instance": "Open files in a slicer that is already running",
|
||||
"ui.slicer_single_instance_tip": "PrusaSlicer, SuperSlicer and Cura load the file in the window that is open instead of starting another. Other slicers are started as they are. PrusaSlicer 3.0 always does this: its launcher can't take a file any other way.",
|
||||
"ui.slicer_starting": "Starting {slicer}…",
|
||||
"ui.slicer_still_starting": "Still waiting for {slicer} to start. Nothing else was sent.",
|
||||
"ui.slicer_stl_fallback": "The 3MF could not be written, so this is an STL.",
|
||||
"ui.slot": "Slot",
|
||||
"ui.smooth": "Smooth",
|
||||
"ui.smooth_wheel_zoom": "Smooth wheel zoom",
|
||||
@@ -3205,6 +3240,7 @@
|
||||
"ui.untitled": "Untitled",
|
||||
"ui.up_axis_note": "Which axis points up in the views (axis view, Home, Look At). Designs are unchanged.",
|
||||
"ui.updateToCurrentView": "Update to Current View",
|
||||
"ui.upload": "Upload",
|
||||
"ui.vertical_constraint": "Vertical Constraint",
|
||||
"ui.view_centre": "View centre",
|
||||
"ui.viewport_theme_follow": "Follow theme",
|
||||
|
||||
@@ -463,6 +463,7 @@
|
||||
"cmd.canvas_delete": "Eliminar lienzo",
|
||||
"cmd.canvas_edit": "Editar lienzo",
|
||||
"cmd.canvas_insert": "Lienzo",
|
||||
"cmd.canvas_to_sketch": "Boceto desde lienzo",
|
||||
"cmd.component_activate": "Activar componente",
|
||||
"cmd.component_break_link": "Romper vínculo",
|
||||
"cmd.component_capture_position": "Capturar posición",
|
||||
@@ -540,6 +541,7 @@
|
||||
"cmd.file_export": "Exportar",
|
||||
"cmd.file_insert_mesh": "Insertar malla",
|
||||
"cmd.file_insert_step": "Insertar STEP",
|
||||
"cmd.file_open_in_easyprint": "Abrir con EasyPrint",
|
||||
"cmd.file_save_mesh": "Guardar como malla",
|
||||
"cmd.file_slicers": "Laminadores instalados",
|
||||
"cmd.frame_member": "Miembro estructural",
|
||||
@@ -1975,7 +1977,7 @@
|
||||
"ui.agent_chat_pop_out": "Abrir en su propia ventana, que puedes mover a otra pantalla",
|
||||
"ui.agent_chat_previous_picture": "Imagen anterior",
|
||||
"ui.agent_chat_privacy": "Solo se envían datos al agente que ejecutes, con tu propia cuenta.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft no envía nada a otros destinos: el agente que ejecutas se comunica con su propio servicio utilizando tu cuenta. SolidCraft nunca ve ni almacena claves o contraseñas.",
|
||||
"ui.agent_chat_privacy_note": "El chat no envía nada por sí mismo a otros destinos: el agente que ejecutas se comunica con su propio servicio utilizando tu cuenta. SolidCraft nunca ve ni almacena claves o contraseñas.",
|
||||
"ui.agent_chat_queue": "Poner en cola",
|
||||
"ui.agent_chat_queue_full": "Hay demasiados mensajes en espera: elimina uno o espera a que termine el agente.",
|
||||
"ui.agent_chat_queued": "En cola",
|
||||
@@ -2162,6 +2164,7 @@
|
||||
"ui.command_done": "{command} completado",
|
||||
"ui.command_palette_hint": "Escribe un comando, p. ej., {example}, o pulsa S para buscar",
|
||||
"ui.concentric": "Concéntrico",
|
||||
"ui.cone": "Cono",
|
||||
"ui.conic_curve": "Curva cónica",
|
||||
"ui.constant": "Constante",
|
||||
"ui.constraints_conflict": "Conflicto entre restricciones",
|
||||
@@ -2220,6 +2223,11 @@
|
||||
"ui.draft": "Ángulo de salida",
|
||||
"ui.drawing": "Dibujo",
|
||||
"ui.drill_point": "Punta de broca",
|
||||
"ui.easyprint_consent": "EasyPrint es el slicer de Printables en el navegador. Para abrir el modelo allí, SolidCraft lo sube a los servidores de Printables. El enlace caduca a los 30 minutos y tendrás que haber iniciado sesión en Printables. ¿Continuar?",
|
||||
"ui.easyprint_failed": "No se pudo subir a EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Abierto en EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, en el navegador)",
|
||||
"ui.easyprint_uploading": "Subiendo a Printables…",
|
||||
"ui.edit": "Editar",
|
||||
"ui.editJointLimits": "Editar límites de unión",
|
||||
"ui.edit_section_analysis": "Editar análisis de sección",
|
||||
@@ -2276,6 +2284,7 @@
|
||||
"ui.heading_circular_pattern": "PATRÓN CIRCULAR",
|
||||
"ui.heading_coil": "HÉLICE",
|
||||
"ui.heading_combine": "COMBINAR",
|
||||
"ui.heading_cone": "CONO",
|
||||
"ui.heading_configure": "CONFIGURAR",
|
||||
"ui.heading_constraints": "RESTRICCIONES",
|
||||
"ui.heading_construct": "CONSTRUIR",
|
||||
@@ -2512,9 +2521,7 @@
|
||||
"ui.right": "Derecha",
|
||||
"ui.save": "Guardar",
|
||||
"ui.save_as": "Guardar como",
|
||||
"ui.save_mesh_no_slicer": "No se ha encontrado ningún laminador: elige Personalizado… y selecciona tu laminador, o desactiva Enviar al laminador",
|
||||
"ui.save_mesh_pick_bodies": "Selecciona los cuerpos que se van a guardar",
|
||||
"ui.save_mesh_temp_failed": "No se ha podido crear {folder}: {error}",
|
||||
"ui.saved_to": "Guardado en {path}",
|
||||
"ui.sbox_pin": "Fijar arriba en la búsqueda S",
|
||||
"ui.sbox_unpin": "Desfijar",
|
||||
@@ -2529,7 +2536,6 @@
|
||||
"ui.selection_shift_add_only": "Mayús+clic añade sin quitar",
|
||||
"ui.selection_shift_add_only_tip": "Activado: Mayús añade elementos, Ctrl/Cmd alterna. Desactivado (predeterminado): Mayús y Ctrl/Cmd alternan.",
|
||||
"ui.send": "Enviar",
|
||||
"ui.send_to_slicer": "Enviar al laminador",
|
||||
"ui.sent_to_slicer": "Enviado a {slicer}",
|
||||
"ui.sheet_gap": "Separación",
|
||||
"ui.shell": "Vaciado",
|
||||
@@ -2621,6 +2627,7 @@
|
||||
"ui.untitled": "Sin título",
|
||||
"ui.up_axis_note": "Eje que apunta hacia arriba en las vistas (vista de eje, Inicio, Mirar hacia). Los diseños no cambian.",
|
||||
"ui.updateToCurrentView": "Actualizar a vista actual",
|
||||
"ui.upload": "Subir",
|
||||
"ui.view_centre": "Centro de la vista",
|
||||
"ui.visual_shaded_hidden_edges": "Sombreado con aristas ocultas",
|
||||
"ui.visual_shaded_with_edges": "Sombreado con aristas",
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
"cmd.canvas_delete": "Supprimer le canevas",
|
||||
"cmd.canvas_edit": "Modifier le canevas",
|
||||
"cmd.canvas_insert": "Canevas",
|
||||
"cmd.canvas_to_sketch": "Esquisse depuis le canevas",
|
||||
"cmd.component_activate": "Activer le composant",
|
||||
"cmd.component_break_link": "Rompre le lien",
|
||||
"cmd.component_capture_position": "Capturer la position",
|
||||
@@ -534,6 +535,7 @@
|
||||
"cmd.file_export": "Exporter",
|
||||
"cmd.file_insert_mesh": "Insérer un maillage",
|
||||
"cmd.file_insert_step": "Insérer un STEP",
|
||||
"cmd.file_open_in_easyprint": "Ouvrir avec EasyPrint",
|
||||
"cmd.file_save_mesh": "Enregistrer comme maillage",
|
||||
"cmd.file_slicers": "Slicers installés",
|
||||
"cmd.frame_member": "Élément de structure",
|
||||
@@ -949,7 +951,7 @@
|
||||
"issue.note_opened_copied": "Rapport copié. Collez-le dans le brouillon GitHub, joignez la capture d’écran, puis cliquez sur Submit new issue sur GitHub.",
|
||||
"issue.note_removed": "Capture d’écran retirée de ce brouillon.",
|
||||
"issue.note_report_copied": "Rapport copié. Collez-le dans le brouillon GitHub si nécessaire.",
|
||||
"issue.note_save_failed": "Impossible d’enregistrer la capture d’écran : {error}",
|
||||
"issue.note_save_failed": "Impossible d’enregistrer la capture d’écran : {error}",
|
||||
"issue.note_saved": "Capture d’écran enregistrée. Joignez le PNG dans le brouillon GitHub.",
|
||||
"issue.save_screenshot": "Enregistrer la capture d’écran…",
|
||||
"joint.from_bodies_hint": "Chaque corps sélectionné passe dans un nouveau composant à lui et reste où il est.",
|
||||
@@ -1080,7 +1082,7 @@
|
||||
"search.parameter": "Paramètre",
|
||||
"search.recent": "Récents",
|
||||
"search.run": "Exécuter la commande",
|
||||
"search.suggest": "Vouliez-vous dire {label} ?",
|
||||
"search.suggest": "Vouliez-vous dire {label} ?",
|
||||
"search.try": "Saisissez un nom de commande, une entité de cette conception ou un paramètre. Pour exécuter une commande avec des valeurs, saisissez son id et un objet JSON : {example}",
|
||||
"search.valid": "Valide",
|
||||
"settings.display_quality_note": "Les niveaux plus fins affichent plus lisses les faces et arêtes courbes, mais leur maillage prend plus de temps. Seule la vue change, jamais la conception ni les exports.",
|
||||
@@ -1095,7 +1097,7 @@
|
||||
"settings.reset_intro": "Ces réglages ne fonctionnent pas sur cet ordinateur et empêcheraient SolidCraft de démarrer ; ils ont donc été modifiés :",
|
||||
"settings.reset_title": "Des réglages ont été modifiés",
|
||||
"settings.supersampling_note": "Dessine la vue à une résolution plus élevée puis la réduit : arêtes, lignes et grille plus lisses sur les écrans de définition inférieure à la 4K. Cela coûte du temps GPU : environ 2,25× plus de pixels à 1,5× et 4× plus à 2×.",
|
||||
"shortcut.json_error": "les paramètres doivent être au format JSON : {error}",
|
||||
"shortcut.json_error": "les paramètres doivent être au format JSON : {error}",
|
||||
"shortcut.no_command": "la commande `{id}` n’existe pas ; le texte avant le JSON doit être un ID de commande",
|
||||
"shortcut.pin": "Épingler en haut de la boîte S",
|
||||
"shortcut.unpin": "Désépingler",
|
||||
@@ -1340,7 +1342,7 @@
|
||||
"ui.agent_chat_pop_out": "Ouvrir dans une fenêtre séparée, déplaçable vers un autre écran",
|
||||
"ui.agent_chat_previous_picture": "Image précédente",
|
||||
"ui.agent_chat_privacy": "Rien n'est envoyé ailleurs qu'à l'agent que vous lancez, sous votre propre compte.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft n'envoie rien nulle part : l'agent que vous lancez dialogue avec son propre service, sous votre propre compte. SolidCraft ne voit ni ne stocke jamais de clé ni de mot de passe.",
|
||||
"ui.agent_chat_privacy_note": "Le chat n'envoie rien nulle part de lui-même : l'agent que vous lancez dialogue avec son propre service, sous votre propre compte. SolidCraft ne voit ni ne stocke jamais de clé ni de mot de passe.",
|
||||
"ui.agent_chat_queue": "Mettre en file",
|
||||
"ui.agent_chat_queue_full": "Trop de messages sont en attente : retirez-en un ou attendez l'agent.",
|
||||
"ui.agent_chat_queued": "En file d'attente",
|
||||
@@ -1525,6 +1527,7 @@
|
||||
"ui.command_done": "{command} terminé",
|
||||
"ui.command_palette_hint": "Tapez une commande, ex. {example} — ou S pour chercher",
|
||||
"ui.concentric": "Concentricité",
|
||||
"ui.cone": "Cône",
|
||||
"ui.conic_curve": "Courbe conique",
|
||||
"ui.constant": "Constant",
|
||||
"ui.constraints_conflict": "Contraintes en conflit",
|
||||
@@ -1583,6 +1586,11 @@
|
||||
"ui.draft": "Dépouille",
|
||||
"ui.drawing": "Mise en plan",
|
||||
"ui.drill_point": "Pointe de foret",
|
||||
"ui.easyprint_consent": "EasyPrint est le slicer de Printables dans le navigateur. Pour y ouvrir le modèle, SolidCraft l'envoie sur les serveurs de Printables. Le lien expire au bout de 30 minutes et vous devrez être connecté à Printables. Continuer ?",
|
||||
"ui.easyprint_failed": "Échec de l'envoi vers EasyPrint : {error}",
|
||||
"ui.easyprint_opened": "Ouvert dans EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, dans le navigateur)",
|
||||
"ui.easyprint_uploading": "Envoi vers Printables…",
|
||||
"ui.edit": "Modifier",
|
||||
"ui.editJointLimits": "Modifier les limites de liaison",
|
||||
"ui.edit_section_analysis": "Modifier l'analyse de coupe",
|
||||
@@ -1639,6 +1647,7 @@
|
||||
"ui.heading_circular_pattern": "RÉPÉTITION CIRCULAIRE",
|
||||
"ui.heading_coil": "HÉLICE",
|
||||
"ui.heading_combine": "COMBINER",
|
||||
"ui.heading_cone": "CÔNE",
|
||||
"ui.heading_configure": "CONFIGURER",
|
||||
"ui.heading_constraints": "CONTRAINTES",
|
||||
"ui.heading_construct": "CONSTRUIRE",
|
||||
@@ -1874,9 +1883,7 @@
|
||||
"ui.right": "Droite",
|
||||
"ui.save": "Enregistrer",
|
||||
"ui.save_as": "Enregistrer sous",
|
||||
"ui.save_mesh_no_slicer": "Aucun slicer trouvé : choisissez Personnalisé… et sélectionnez votre slicer, ou désactivez Envoyer au slicer",
|
||||
"ui.save_mesh_pick_bodies": "Sélectionner les corps à enregistrer",
|
||||
"ui.save_mesh_temp_failed": "Impossible de créer {folder} : {error}",
|
||||
"ui.saved_to": "Enregistré : {path}",
|
||||
"ui.sbox_pin": "Épingler en haut de la boîte S",
|
||||
"ui.sbox_unpin": "Désépingler",
|
||||
@@ -1891,7 +1898,6 @@
|
||||
"ui.selection_shift_add_only": "Maj+clic ajoute sans retirer",
|
||||
"ui.selection_shift_add_only_tip": "Activé : Maj ajoute des éléments, Ctrl/Cmd bascule. Désactivé (par défaut) : Maj et Ctrl/Cmd basculent tous deux.",
|
||||
"ui.send": "Envoyer",
|
||||
"ui.send_to_slicer": "Envoyer au slicer",
|
||||
"ui.sent_to_slicer": "Envoyé à {slicer}",
|
||||
"ui.sheet_gap": "Jeu",
|
||||
"ui.shell": "Coque",
|
||||
@@ -1983,6 +1989,7 @@
|
||||
"ui.untitled": "Sans titre",
|
||||
"ui.up_axis_note": "Axe orienté vers le haut dans les vues (vue d'axe, Accueil, Vue normale). Les conceptions ne sont pas modifiées.",
|
||||
"ui.updateToCurrentView": "Mettre à jour avec la vue actuelle",
|
||||
"ui.upload": "Envoyer",
|
||||
"ui.view_centre": "Centre de la vue",
|
||||
"ui.visual_shaded_hidden_edges": "Ombré avec arêtes cachées",
|
||||
"ui.visual_shaded_with_edges": "Ombré avec arêtes",
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
"cmd.canvas_delete": "Eliminar o lenzo",
|
||||
"cmd.canvas_edit": "Editar o lenzo",
|
||||
"cmd.canvas_insert": "Lenzo",
|
||||
"cmd.canvas_to_sketch": "Bosquexo desde o lenzo",
|
||||
"cmd.component_activate": "Activar o compoñente",
|
||||
"cmd.component_break_link": "Romper a ligazón",
|
||||
"cmd.component_capture_position": "Capturar a posición",
|
||||
@@ -534,6 +535,7 @@
|
||||
"cmd.file_export": "Exportar",
|
||||
"cmd.file_insert_mesh": "Inserir malla",
|
||||
"cmd.file_insert_step": "Inserir STEP",
|
||||
"cmd.file_open_in_easyprint": "Abrir con EasyPrint",
|
||||
"cmd.file_save_mesh": "Gardar como malla",
|
||||
"cmd.file_slicers": "Laminadores instalados",
|
||||
"cmd.frame_member": "Membro estrutural",
|
||||
@@ -1326,7 +1328,7 @@
|
||||
"ui.agent_chat_pop_out": "Abrir nunha xanela propia, que podes mover a outra pantalla",
|
||||
"ui.agent_chat_previous_picture": "Imaxe anterior",
|
||||
"ui.agent_chat_privacy": "Nada vai a ningún sitio agás ao axente que executas, coa túa propia conta.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft non envía nada a ningún sitio: o axente que executas fala co seu propio servizo coa túa propia conta. SolidCraft nunca ve nin garda ningunha clave nin contrasinal.",
|
||||
"ui.agent_chat_privacy_note": "O chat non envía nada por si mesmo a ningún sitio: o axente que executas fala co seu propio servizo coa túa propia conta. SolidCraft nunca ve nin garda ningunha clave nin contrasinal.",
|
||||
"ui.agent_chat_queue": "Pór na cola",
|
||||
"ui.agent_chat_queue_full": "Hai demasiadas mensaxes agardando: quita unha ou agarda polo axente.",
|
||||
"ui.agent_chat_queued": "Na cola",
|
||||
@@ -1511,6 +1513,7 @@
|
||||
"ui.command_done": "{command} feito",
|
||||
"ui.command_palette_hint": "Escribe unha orde, p. ex. {example} — ou preme S para buscar",
|
||||
"ui.concentric": "Concéntrica",
|
||||
"ui.cone": "Cono",
|
||||
"ui.conic_curve": "Curva cónica",
|
||||
"ui.constant": "Constante",
|
||||
"ui.constraints_conflict": "Conflito de restricións",
|
||||
@@ -1569,6 +1572,11 @@
|
||||
"ui.draft": "Ángulo de desmoldaxe",
|
||||
"ui.drawing": "Debuxo",
|
||||
"ui.drill_point": "Punta de broca",
|
||||
"ui.easyprint_consent": "EasyPrint é o slicer de Printables no navegador. Para abrir o modelo alí, SolidCraft cárgao nos servidores de Printables. A ligazón caduca aos 30 minutos e terás que ter a sesión iniciada en Printables. Continuar?",
|
||||
"ui.easyprint_failed": "Non se puido cargar a EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Aberto en EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, no navegador)",
|
||||
"ui.easyprint_uploading": "Cargando a Printables…",
|
||||
"ui.edit": "Editar",
|
||||
"ui.editJointLimits": "Editar os límites da unión",
|
||||
"ui.edit_section_analysis": "Editar a análise de sección",
|
||||
@@ -1625,6 +1633,7 @@
|
||||
"ui.heading_circular_pattern": "PATRÓN CIRCULAR",
|
||||
"ui.heading_coil": "HÉLICE",
|
||||
"ui.heading_combine": "COMBINAR",
|
||||
"ui.heading_cone": "CONO",
|
||||
"ui.heading_configure": "CONFIGURAR",
|
||||
"ui.heading_constraints": "RESTRICIÓNS",
|
||||
"ui.heading_construct": "CONSTRUÍR",
|
||||
@@ -1860,9 +1869,7 @@
|
||||
"ui.right": "Dereita",
|
||||
"ui.save": "Gardar",
|
||||
"ui.save_as": "Gardar como",
|
||||
"ui.save_mesh_no_slicer": "Non se atopou ningún laminador: escolle Personalizado… e selecciona o teu programa laminador, ou desactiva Enviar ao laminador",
|
||||
"ui.save_mesh_pick_bodies": "Selecciona os corpos que queres gardar",
|
||||
"ui.save_mesh_temp_failed": "Non se puido crear {folder}: {error}",
|
||||
"ui.saved_to": "Gardado {path}",
|
||||
"ui.sbox_pin": "Fixar arriba na caixa S",
|
||||
"ui.sbox_unpin": "Desfixar",
|
||||
@@ -1877,7 +1884,6 @@
|
||||
"ui.selection_shift_add_only": "Maiús+clic engade sen quitar",
|
||||
"ui.selection_shift_add_only_tip": "Activado: Maiús engade elementos, Ctrl/Cmd alterna. Desactivado (predeterminado): Maiús e Ctrl/Cmd alternan ambos.",
|
||||
"ui.send": "Enviar",
|
||||
"ui.send_to_slicer": "Enviar ao laminador",
|
||||
"ui.sent_to_slicer": "Enviado a {slicer}",
|
||||
"ui.sheet_gap": "Separación",
|
||||
"ui.shell": "Baleirado",
|
||||
@@ -1969,6 +1975,7 @@
|
||||
"ui.untitled": "Sen título",
|
||||
"ui.up_axis_note": "Que eixe apunta cara arriba nas vistas (vista de eixe, Inicio, Mirar cara a). Os deseños non cambian.",
|
||||
"ui.updateToCurrentView": "Actualizar á vista actual",
|
||||
"ui.upload": "Cargar",
|
||||
"ui.view_centre": "Centro da vista",
|
||||
"ui.visual_shaded_hidden_edges": "Sombreado con arestas ocultas",
|
||||
"ui.visual_shaded_with_edges": "Sombreado con arestas",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Vászon törlése",
|
||||
"cmd.canvas_edit": "Vászon szerkesztése",
|
||||
"cmd.canvas_insert": "Vászon",
|
||||
"cmd.canvas_to_sketch": "Vázlat vászonból",
|
||||
"cmd.component_activate": "Komponens aktiválása",
|
||||
"cmd.component_break_link": "Kapcsolat megszakítása",
|
||||
"cmd.component_capture_position": "Pozíció rögzítése",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Exportálás",
|
||||
"cmd.file_insert_mesh": "Háló beszúrása",
|
||||
"cmd.file_insert_step": "STEP beszúrása",
|
||||
"cmd.file_open_in_easyprint": "Megnyitás EasyPrinttel",
|
||||
"cmd.file_save_mesh": "Mentés hálóként",
|
||||
"cmd.file_slicers": "Telepített szeletelők",
|
||||
"cmd.frame_member": "Szerkezeti elem",
|
||||
@@ -1269,7 +1271,7 @@
|
||||
"ui.agent_chat_pop_out": "Megnyitás saját ablakban, amelyet másik képernyőre mozgathat",
|
||||
"ui.agent_chat_previous_picture": "Előző kép",
|
||||
"ui.agent_chat_privacy": "Semmi sem megy sehová, csak az Ön által futtatott ügynökhöz, az Ön saját fiókja alatt.",
|
||||
"ui.agent_chat_privacy_note": "A SolidCraft semmit sem küld sehová: az Ön által futtatott ügynök a saját szolgáltatásával kommunikál, az Ön saját fiókja alatt. A SolidCraft soha nem lát és nem tárol kulcsot vagy jelszót.",
|
||||
"ui.agent_chat_privacy_note": "A csevegés önmagától semmit sem küld sehová: az Ön által futtatott ügynök a saját szolgáltatásával kommunikál, az Ön saját fiókja alatt. A SolidCraft soha nem lát és nem tárol kulcsot vagy jelszót.",
|
||||
"ui.agent_chat_queue": "Sorba",
|
||||
"ui.agent_chat_queue_full": "Túl sok üzenet vár: távolítson el egyet, vagy várja meg az ügynököt.",
|
||||
"ui.agent_chat_queued": "Sorban",
|
||||
@@ -1453,6 +1455,7 @@
|
||||
"ui.command_done": "{command} kész",
|
||||
"ui.command_palette_hint": "Írjon be egy parancsot, pl. {example} – vagy nyomja meg az S-t a kereséshez",
|
||||
"ui.concentric": "Koncentrikus",
|
||||
"ui.cone": "Kúp",
|
||||
"ui.conic_curve": "Kúpgörbe",
|
||||
"ui.constant": "Állandó",
|
||||
"ui.constraints_conflict": "Kényszerek ütköznek",
|
||||
@@ -1511,6 +1514,11 @@
|
||||
"ui.draft": "Dőlés",
|
||||
"ui.drawing": "Rajz",
|
||||
"ui.drill_point": "Fúrócsúcs",
|
||||
"ui.easyprint_consent": "Az EasyPrint a Printables szeletelője a böngészőben. A modell ott való megnyitásához a SolidCraft feltölti azt a Printables szervereire. A hivatkozás 30 perc után lejár, és be kell jelentkeznie a Printablesbe. Folytatja?",
|
||||
"ui.easyprint_failed": "Az EasyPrintre feltöltés sikertelen: {error}",
|
||||
"ui.easyprint_opened": "Megnyitva az EasyPrintben",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, a böngészőben)",
|
||||
"ui.easyprint_uploading": "Feltöltés a Printablesbe…",
|
||||
"ui.edit": "Szerkesztés",
|
||||
"ui.editJointLimits": "Csuklókorlátok szerkesztése",
|
||||
"ui.edit_section_analysis": "Metszetelemzés szerkesztése",
|
||||
@@ -1567,6 +1575,7 @@
|
||||
"ui.heading_circular_pattern": "KÖRMINTA",
|
||||
"ui.heading_coil": "SPIRÁL",
|
||||
"ui.heading_combine": "EGYESÍTÉS",
|
||||
"ui.heading_cone": "KÚP",
|
||||
"ui.heading_configure": "KONFIGURÁLÁS",
|
||||
"ui.heading_constraints": "KÉNYSZEREK",
|
||||
"ui.heading_construct": "SZERKESZTÉS",
|
||||
@@ -1802,9 +1811,7 @@
|
||||
"ui.right": "Jobb",
|
||||
"ui.save": "Mentés",
|
||||
"ui.save_as": "Mentés másként",
|
||||
"ui.save_mesh_no_slicer": "Nem található szeletelő: válassza az Egyéni… lehetőséget és jelölje ki a szeletelőprogramot, vagy kapcsolja ki a Küldés szeletelőnek beállítást",
|
||||
"ui.save_mesh_pick_bodies": "Jelölje ki a mentendő testeket",
|
||||
"ui.save_mesh_temp_failed": "A(z) {folder} nem hozható létre: {error}",
|
||||
"ui.saved_to": "Mentve: {path}",
|
||||
"ui.sbox_pin": "Rögzítés az S doboz tetejére",
|
||||
"ui.sbox_unpin": "Rögzítés feloldása",
|
||||
@@ -1819,7 +1826,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+kattintás eltávolítás nélkül hozzáad",
|
||||
"ui.selection_shift_add_only_tip": "Be: a Shift hozzáad, a Ctrl/Cmd vált. Ki (alapért.): a Shift és a Ctrl/Cmd is vált.",
|
||||
"ui.send": "Küldés",
|
||||
"ui.send_to_slicer": "Küldés szeletelőnek",
|
||||
"ui.sent_to_slicer": "Elküldve ide: {slicer}",
|
||||
"ui.sheet_gap": "Rés",
|
||||
"ui.shell": "Héjazás",
|
||||
@@ -1911,6 +1917,7 @@
|
||||
"ui.untitled": "Névtelen",
|
||||
"ui.up_axis_note": "Melyik tengely mutat felfelé a nézetekben (tengelynézet, Kezdőlap, Ránézés). A tervek változatlanok.",
|
||||
"ui.updateToCurrentView": "Frissítés az aktuális nézetre",
|
||||
"ui.upload": "Feltöltés",
|
||||
"ui.view_centre": "Nézet középpontja",
|
||||
"ui.visual_shaded_hidden_edges": "Árnyalt rejtett élekkel",
|
||||
"ui.visual_shaded_with_edges": "Árnyalt élekkel",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Hapus Kanvas",
|
||||
"cmd.canvas_edit": "Edit Kanvas",
|
||||
"cmd.canvas_insert": "Kanvas",
|
||||
"cmd.canvas_to_sketch": "Kanvas ke Sketsa",
|
||||
"cmd.component_activate": "Aktifkan Komponen",
|
||||
"cmd.component_break_link": "Putuskan Tautan",
|
||||
"cmd.component_capture_position": "Tangkap Posisi",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Ekspor",
|
||||
"cmd.file_insert_mesh": "Sisipkan Mesh",
|
||||
"cmd.file_insert_step": "Sisipkan STEP",
|
||||
"cmd.file_open_in_easyprint": "Buka dengan EasyPrint",
|
||||
"cmd.file_save_mesh": "Simpan Sebagai Mesh",
|
||||
"cmd.file_slicers": "Slicer Terpasang",
|
||||
"cmd.frame_member": "Batang Rangka",
|
||||
@@ -1269,7 +1271,7 @@
|
||||
"ui.agent_chat_pop_out": "Buka di jendela sendiri, yang dapat dipindah ke layar lain",
|
||||
"ui.agent_chat_previous_picture": "Gambar sebelumnya",
|
||||
"ui.agent_chat_privacy": "Tidak ada yang dikirim ke mana pun kecuali ke agen yang Anda jalankan, di bawah akun Anda sendiri.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft tidak mengirim apa pun ke mana pun: agen yang Anda jalankan berbicara ke layanannya sendiri di bawah akun Anda sendiri. SolidCraft tidak pernah melihat atau menyimpan key atau kata sandi.",
|
||||
"ui.agent_chat_privacy_note": "Obrolan tidak mengirim apa pun ke mana pun dengan sendirinya: agen yang Anda jalankan berbicara ke layanannya sendiri di bawah akun Anda sendiri. SolidCraft tidak pernah melihat atau menyimpan key atau kata sandi.",
|
||||
"ui.agent_chat_queue": "Antre",
|
||||
"ui.agent_chat_queue_full": "Terlalu banyak pesan menunggu: hapus satu atau tunggu agen.",
|
||||
"ui.agent_chat_queued": "Mengantre",
|
||||
@@ -1453,6 +1455,7 @@
|
||||
"ui.command_done": "{command} selesai",
|
||||
"ui.command_palette_hint": "Ketik perintah, mis. {example} — atau tekan S untuk mencari",
|
||||
"ui.concentric": "Konsentris",
|
||||
"ui.cone": "Kerucut",
|
||||
"ui.conic_curve": "Kurva Konik",
|
||||
"ui.constant": "Konstan",
|
||||
"ui.constraints_conflict": "Batasan bertentangan",
|
||||
@@ -1511,6 +1514,11 @@
|
||||
"ui.draft": "Draft",
|
||||
"ui.drawing": "Gambar Kerja",
|
||||
"ui.drill_point": "Ujung Bor",
|
||||
"ui.easyprint_consent": "EasyPrint adalah slicer Printables di browser. Untuk membuka model di sana, SolidCraft mengunggahnya ke server Printables. Tautan kedaluwarsa setelah 30 menit, dan Anda harus masuk ke Printables. Lanjutkan?",
|
||||
"ui.easyprint_failed": "Gagal mengunggah ke EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Dibuka di EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, di browser)",
|
||||
"ui.easyprint_uploading": "Mengunggah ke Printables…",
|
||||
"ui.edit": "Edit",
|
||||
"ui.editJointLimits": "Edit Batas Sambungan",
|
||||
"ui.edit_section_analysis": "Edit Analisis Penampang",
|
||||
@@ -1567,6 +1575,7 @@
|
||||
"ui.heading_circular_pattern": "POLA MELINGKAR",
|
||||
"ui.heading_coil": "KOIL",
|
||||
"ui.heading_combine": "GABUNGKAN",
|
||||
"ui.heading_cone": "KERUCUT",
|
||||
"ui.heading_configure": "KONFIGURASI",
|
||||
"ui.heading_constraints": "BATASAN",
|
||||
"ui.heading_construct": "KONSTRUKSI",
|
||||
@@ -1802,9 +1811,7 @@
|
||||
"ui.right": "Kanan",
|
||||
"ui.save": "Simpan",
|
||||
"ui.save_as": "Simpan Sebagai",
|
||||
"ui.save_mesh_no_slicer": "Slicer tidak ditemukan: pilih Kustom… dan tunjuk program slicer Anda, atau matikan Kirim ke slicer",
|
||||
"ui.save_mesh_pick_bodies": "Pilih bodi yang akan disimpan",
|
||||
"ui.save_mesh_temp_failed": "Tidak dapat membuat {folder}: {error}",
|
||||
"ui.saved_to": "Disimpan {path}",
|
||||
"ui.sbox_pin": "Sematkan di atas kotak S",
|
||||
"ui.sbox_unpin": "Lepas sematan",
|
||||
@@ -1819,7 +1826,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klik menambah tanpa menghapus",
|
||||
"ui.selection_shift_add_only_tip": "Aktif: Shift menambah item, Ctrl/Cmd mengalihkan. Mati (default): Shift dan Ctrl/Cmd sama-sama mengalihkan.",
|
||||
"ui.send": "Kirim",
|
||||
"ui.send_to_slicer": "Kirim ke slicer",
|
||||
"ui.sent_to_slicer": "Dikirim ke {slicer}",
|
||||
"ui.sheet_gap": "Celah",
|
||||
"ui.shell": "Shell",
|
||||
@@ -1911,6 +1917,7 @@
|
||||
"ui.untitled": "Tanpa judul",
|
||||
"ui.up_axis_note": "Sumbu mana yang mengarah ke atas di tampilan (tampilan sumbu, Home, Look At). Desain tidak berubah.",
|
||||
"ui.updateToCurrentView": "Perbarui ke Tampilan Saat Ini",
|
||||
"ui.upload": "Unggah",
|
||||
"ui.view_centre": "Pusat tampilan",
|
||||
"ui.visual_shaded_hidden_edges": "Shaded dengan tepi tersembunyi",
|
||||
"ui.visual_shaded_with_edges": "Shaded dengan tepi",
|
||||
|
||||
@@ -462,6 +462,7 @@
|
||||
"cmd.canvas_delete": "Elimina immagine di riferimento",
|
||||
"cmd.canvas_edit": "Modifica immagine di riferimento",
|
||||
"cmd.canvas_insert": "Immagine di riferimento",
|
||||
"cmd.canvas_to_sketch": "Schizzo da immagine di riferimento",
|
||||
"cmd.component_activate": "Attiva componente",
|
||||
"cmd.component_break_link": "Interrompi collegamento",
|
||||
"cmd.component_capture_position": "Acquisisci posizione",
|
||||
@@ -539,6 +540,7 @@
|
||||
"cmd.file_export": "Esporta",
|
||||
"cmd.file_insert_mesh": "Inserisci mesh",
|
||||
"cmd.file_insert_step": "Inserisci STEP",
|
||||
"cmd.file_open_in_easyprint": "Apri con EasyPrint",
|
||||
"cmd.file_save_mesh": "Salva come mesh",
|
||||
"cmd.file_slicers": "Slicer installati",
|
||||
"cmd.frame_member": "Membro strutturale",
|
||||
@@ -1365,7 +1367,7 @@
|
||||
"ui.agent_chat_pop_out": "Apri in una finestra separata, che puoi spostare su un altro schermo",
|
||||
"ui.agent_chat_previous_picture": "Immagine precedente",
|
||||
"ui.agent_chat_privacy": "I dati vengono inviati solo all'agente che esegui, con il tuo account.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft non invia dati ad alcun servizio: l'agente che esegui comunica con il proprio servizio usando il tuo account. SolidCraft non vede né memorizza mai chiavi o password.",
|
||||
"ui.agent_chat_privacy_note": "La chat non invia da sola dati ad alcun servizio: l'agente che esegui comunica con il proprio servizio usando il tuo account. SolidCraft non vede né memorizza mai chiavi o password.",
|
||||
"ui.agent_chat_queue": "Metti in coda",
|
||||
"ui.agent_chat_queue_full": "Troppi messaggi in attesa: rimuovine uno o attendi l'agente.",
|
||||
"ui.agent_chat_queued": "In coda",
|
||||
@@ -1550,6 +1552,7 @@
|
||||
"ui.command_done": "{command} completato",
|
||||
"ui.command_palette_hint": "Digita un comando, ad es. {example} — oppure premi S per cercare",
|
||||
"ui.concentric": "Concentrico",
|
||||
"ui.cone": "Cono",
|
||||
"ui.conic_curve": "Curva conica",
|
||||
"ui.constant": "Costante",
|
||||
"ui.constraints_conflict": "Vincoli in conflitto",
|
||||
@@ -1608,6 +1611,11 @@
|
||||
"ui.draft": "Sformo",
|
||||
"ui.drawing": "Disegno",
|
||||
"ui.drill_point": "Punta del trapano",
|
||||
"ui.easyprint_consent": "EasyPrint è lo slicer di Printables nel browser. Per aprire lì il modello, SolidCraft lo carica sui server di Printables. Il link scade dopo 30 minuti e dovrai aver effettuato l'accesso a Printables. Continuare?",
|
||||
"ui.easyprint_failed": "Caricamento su EasyPrint non riuscito: {error}",
|
||||
"ui.easyprint_opened": "Aperto in EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, nel browser)",
|
||||
"ui.easyprint_uploading": "Caricamento su Printables…",
|
||||
"ui.edit": "Modifica",
|
||||
"ui.editJointLimits": "Modifica limiti del giunto",
|
||||
"ui.edit_section_analysis": "Modifica analisi in sezione",
|
||||
@@ -1665,6 +1673,7 @@
|
||||
"ui.heading_circular_pattern": "RIPETIZIONE CIRCOLARE",
|
||||
"ui.heading_coil": "ELICA",
|
||||
"ui.heading_combine": "COMBINA",
|
||||
"ui.heading_cone": "CONO",
|
||||
"ui.heading_configure": "CONFIGURA",
|
||||
"ui.heading_constraints": "VINCOLI",
|
||||
"ui.heading_construct": "COSTRUISCI",
|
||||
@@ -1900,9 +1909,7 @@
|
||||
"ui.right": "Destra",
|
||||
"ui.save": "Salva",
|
||||
"ui.save_as": "Salva con nome",
|
||||
"ui.save_mesh_no_slicer": "Nessuno slicer trovato: scegli Personalizzato… e seleziona lo slicer, oppure disattiva Invia allo slicer",
|
||||
"ui.save_mesh_pick_bodies": "Seleziona i corpi da salvare",
|
||||
"ui.save_mesh_temp_failed": "Impossibile creare {folder}: {error}",
|
||||
"ui.saved_to": "Salvato {path}",
|
||||
"ui.sbox_pin": "Fissa in cima al riquadro S",
|
||||
"ui.sbox_unpin": "Sblocca",
|
||||
@@ -1917,7 +1924,6 @@
|
||||
"ui.selection_shift_add_only": "Maiusc+clic aggiunge senza rimuovere",
|
||||
"ui.selection_shift_add_only_tip": "Attivo: Maiusc aggiunge elementi, Ctrl/Cmd commuta. Disattivo (predefinito): Maiusc e Ctrl/Cmd commutano entrambi.",
|
||||
"ui.send": "Invia",
|
||||
"ui.send_to_slicer": "Invia allo slicer",
|
||||
"ui.sent_to_slicer": "Inviato a {slicer}",
|
||||
"ui.sheet_gap": "Gioco",
|
||||
"ui.shell": "Svuota",
|
||||
@@ -2009,6 +2015,7 @@
|
||||
"ui.untitled": "Senza titolo",
|
||||
"ui.up_axis_note": "L'asse verticale nelle viste (viste degli assi, Vista iniziale, Vista normale). I progetti restano invariati.",
|
||||
"ui.updateToCurrentView": "Aggiorna alla vista corrente",
|
||||
"ui.upload": "Carica",
|
||||
"ui.view_centre": "Centro della vista",
|
||||
"ui.visual_shaded_hidden_edges": "Ombreggiato con spigoli nascosti",
|
||||
"ui.visual_shaded_with_edges": "Ombreggiato con spigoli",
|
||||
|
||||
@@ -430,6 +430,8 @@
|
||||
"Remove this point": "この点を削除",
|
||||
"Rename Feature": "フィーチャーの名前を変更",
|
||||
"Rename Folder": "フォルダーの名前を変更",
|
||||
"Reopen designs": "デザインを再度開く",
|
||||
"Reopen the designs that were open when SolidCraft was last closed": "前回 SolidCraft を閉じたときに開いていたデザインを再度開く",
|
||||
"Replace existing library file": "既存のライブラリファイルを置き換える",
|
||||
"Report a bug — review a screenshot and open a GitHub draft": "バグを報告 — スクリーンショットを確認してGitHub下書きを作成",
|
||||
"Report text copied. Paste it into GitHub and submit your report there.": "報告本文をコピーしました。GitHubに貼り付けて投稿してください。",
|
||||
@@ -692,10 +694,12 @@
|
||||
"cmd.canvas_delete": "キャンバスを削除",
|
||||
"cmd.canvas_edit": "キャンバスを編集",
|
||||
"cmd.canvas_insert": "キャンバス",
|
||||
"cmd.canvas_to_sketch": "キャンバスからスケッチ",
|
||||
"cmd.component_activate": "コンポーネントをアクティブ化",
|
||||
"cmd.component_break_link": "リンクを解除",
|
||||
"cmd.component_capture_position": "位置をキャプチャ",
|
||||
"cmd.component_copies": "同一のコピーを検索",
|
||||
"cmd.component_copy_bodies": "ボディを貼り付け",
|
||||
"cmd.component_create": "新規コンポーネント",
|
||||
"cmd.component_cycle_color": "コンポーネントの色を順に変更",
|
||||
"cmd.component_delete": "コンポーネントを削除",
|
||||
@@ -774,7 +778,9 @@
|
||||
"cmd.file_export": "エクスポート",
|
||||
"cmd.file_insert_mesh": "メッシュを挿入",
|
||||
"cmd.file_insert_step": "STEP を挿入",
|
||||
"cmd.file_open_in_easyprint": "EasyPrint で開く",
|
||||
"cmd.file_save_mesh": "メッシュとして保存",
|
||||
"cmd.file_send_to_slicer": "スライサーに送る",
|
||||
"cmd.file_slicers": "インストール済みのスライサー",
|
||||
"cmd.frame_member": "フレーム部材",
|
||||
"cmd.frame_profile_insert": "形材を挿入",
|
||||
@@ -846,6 +852,7 @@
|
||||
"cmd.occurrence_ground": "空間固定",
|
||||
"cmd.occurrence_move": "オカレンスを移動",
|
||||
"cmd.occurrence_properties": "オカレンスの物理的プロパティ",
|
||||
"cmd.occurrence_reparent": "コンポーネントへ移動",
|
||||
"cmd.parameters_add": "ユーザー パラメータを追加",
|
||||
"cmd.parameters_change": "パラメータを変更",
|
||||
"cmd.parameters_comment": "パラメータにコメントを付与",
|
||||
@@ -954,6 +961,7 @@
|
||||
"cmd.sketch_drag": "スケッチ ジオメトリをドラッグ",
|
||||
"cmd.sketch_drag_end": "スケッチのドラッグを終了",
|
||||
"cmd.sketch_draw_dimensioned": "寸法付きスケッチ ジオメトリを描画",
|
||||
"cmd.sketch_drawing_bounds": "図面のサイズ",
|
||||
"cmd.sketch_edit": "スケッチを編集",
|
||||
"cmd.sketch_edit_text": "テキストを編集",
|
||||
"cmd.sketch_export_dxf": "DXF として保存",
|
||||
@@ -1000,6 +1008,7 @@
|
||||
"cmd.sketch_spline_fit_point": "通過点スプライン",
|
||||
"cmd.sketch_spun_profile": "回転プロファイル",
|
||||
"cmd.sketch_toggle_driven": "従動寸法の切り替え",
|
||||
"cmd.sketch_transform": "変形",
|
||||
"cmd.sketch_trim_path": "パスに沿ってトリム",
|
||||
"cmd.solid_align": "整列",
|
||||
"cmd.solid_boundary_fill": "境界フィル",
|
||||
@@ -1371,6 +1380,7 @@
|
||||
"nav.blender.help": "中ドラッグで回転、Shift+中ドラッグでパン、Ctrl+中ドラッグでズーム。右ドラッグではカメラは動きません。",
|
||||
"nav.free_orbit.help": "マウスと3Dマウスで、極点を越えて回転し、ロールを保ちます。\nShiftによる軸スナップは無効になります。",
|
||||
"nav.free_orbit.label": "自由回転",
|
||||
"nav.free_orbit.snap_help": "マウスと3Dマウスで両極を越えて回転でき、ロールも保持されます。オービットのドラッグ中にShiftを押すと、視線方向とロールがスナップします。",
|
||||
"nav.fusion.help": "中ドラッグでパン、Shift+中ドラッグで回転、右ドラッグで回転。",
|
||||
"nav.inventor.help": "中ドラッグでパン、Shift+中ドラッグで回転、Ctrl+中ドラッグでズーム、右ドラッグで回転。",
|
||||
"nav.openscad.help": "空きスペースで左ドラッグすると回転、Shift+左ドラッグで矩形選択、スケッチ ジオメトリはドラッグで編集。右または中ドラッグでパン、Shift+右または Shift+中ドラッグでズーム。",
|
||||
@@ -1445,6 +1455,7 @@
|
||||
"prefs.design": "設計の既定値",
|
||||
"prefs.design_note": "新しいデザインの初期設定です。開いているデザインはそれぞれの設定を保持します。",
|
||||
"prefs.devices": "3D マウス",
|
||||
"prefs.dialog_buttons": "ダイアログのボタン",
|
||||
"prefs.display": "表示",
|
||||
"prefs.edges": "エッジと陰影",
|
||||
"prefs.extend": "拡張",
|
||||
@@ -1467,6 +1478,12 @@
|
||||
"prefs.graphics_note": "速度と滑らかさのバランスを調整します。各選択肢の効果をサンプルで確認できます。",
|
||||
"prefs.graphics_presets_note": "プリセットは以下の設定をまとめて変更します。",
|
||||
"prefs.history": "履歴",
|
||||
"prefs.history_origin_bodies": "原点とボディを先頭に表示",
|
||||
"prefs.history_origin_bodies_note": "左側またはブラウザー下の履歴の先頭に、アクティブなコンポーネントのボディフォルダーと原点を、ブラウザーと同じ表示切り替えとメニュー付きで表示します。下部の履歴は変わりません。",
|
||||
"prefs.history_sketches": "スケッチ",
|
||||
"prefs.history_sketches_in_order": "作成順",
|
||||
"prefs.history_sketches_note": "スケッチ:「フィーチャーの下」では、左側の履歴に名前が表示される幅があるとき、スケッチをそれから作成した最初のフィーチャーの下に 1 段字下げして表示します。下部のタイムラインは常に作成順です。",
|
||||
"prefs.history_sketches_under_feature": "フィーチャーの下",
|
||||
"prefs.hold_stop_snapping": "押している間スナップを解除",
|
||||
"prefs.icons_only": "アイコンのみ",
|
||||
"prefs.imported_file": "読み込んだファイル",
|
||||
@@ -1543,6 +1560,7 @@
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
"prefs.toggle_item": "項目を追加、または選択から外す",
|
||||
"prefs.toolbar": "ツールバー",
|
||||
"prefs.top": "上",
|
||||
"prefs.translucent": "半透明",
|
||||
"prefs.under_browser": "ブラウザー下",
|
||||
"prefs.under_cursor": "カーソル下",
|
||||
@@ -2477,7 +2495,7 @@
|
||||
"ui.agent_chat_pop_out": "独立したウィンドウで開く(別の画面に移動できます)",
|
||||
"ui.agent_chat_previous_picture": "前の画像",
|
||||
"ui.agent_chat_privacy": "あなたのアカウントで実行するエージェント以外には、何も送信されません。",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft はどこにも何も送信しません。あなたが実行するエージェントが、あなた自身のアカウントでそのサービスと通信します。SolidCraft がキーやパスワードを見たり保存したりすることはありません。",
|
||||
"ui.agent_chat_privacy_note": "チャット自体はどこにも何も送信しません。あなたが実行するエージェントが、あなた自身のアカウントでそのサービスと通信します。SolidCraft がキーやパスワードを見たり保存したりすることはありません。",
|
||||
"ui.agent_chat_provider_models": "{count} 個のモデル",
|
||||
"ui.agent_chat_provider_other": "その他",
|
||||
"ui.agent_chat_provider_tip": "隣の一覧にモデルを表示するプロバイダー。",
|
||||
@@ -2695,6 +2713,7 @@
|
||||
"ui.command_done": "{command} 完了",
|
||||
"ui.command_palette_hint": "コマンドを入力(例: {example})— または S キーで検索",
|
||||
"ui.concentric": "同心",
|
||||
"ui.cone": "円錐",
|
||||
"ui.conic_curve": "円錐曲線",
|
||||
"ui.constant": "一定",
|
||||
"ui.constraints_conflict": "拘束が競合しています",
|
||||
@@ -2755,6 +2774,11 @@
|
||||
"ui.draft": "勾配",
|
||||
"ui.drawing": "図面",
|
||||
"ui.drill_point": "ドリル先端",
|
||||
"ui.easyprint_consent": "EasyPrint は Printables のブラウザ版スライサーです。モデルを EasyPrint で開くため、SolidCraft はモデルを Printables のサーバーにアップロードします。リンクは 30 分で期限切れになり、Printables にサインインしている必要があります。続行しますか?",
|
||||
"ui.easyprint_failed": "EasyPrint へのアップロードに失敗しました: {error}",
|
||||
"ui.easyprint_opened": "EasyPrint で開きました",
|
||||
"ui.easyprint_row": "EasyPrint(Printables、ブラウザ上)",
|
||||
"ui.easyprint_uploading": "Printables にアップロード中…",
|
||||
"ui.edit": "編集",
|
||||
"ui.editJointLimits": "ジョイント制限を編集",
|
||||
"ui.edit_section_analysis": "断面解析を編集",
|
||||
@@ -2817,6 +2841,7 @@
|
||||
"ui.heading_circular_pattern": "円形パターン",
|
||||
"ui.heading_coil": "コイル",
|
||||
"ui.heading_combine": "結合",
|
||||
"ui.heading_cone": "円錐",
|
||||
"ui.heading_configure": "構成",
|
||||
"ui.heading_constraints": "拘束",
|
||||
"ui.heading_construct": "構築",
|
||||
@@ -2897,6 +2922,7 @@
|
||||
"ui.history_names": "名前",
|
||||
"ui.history_play": "最後まで再生",
|
||||
"ui.history_show_names": "名前を表示",
|
||||
"ui.history_sketch_listed_under": "{feature} の下に表示",
|
||||
"ui.history_title": "履歴",
|
||||
"ui.history_toggle_names": "フィーチャー名を表示・非表示",
|
||||
"ui.hole": "穴",
|
||||
@@ -3085,9 +3111,8 @@
|
||||
"ui.right": "右側面",
|
||||
"ui.save": "保存",
|
||||
"ui.save_as": "名前を付けて保存",
|
||||
"ui.save_mesh_no_slicer": "スライサーが見つかりません。「カスタム…」でスライサーのプログラムを選ぶか、「スライサーに送る」をオフにしてください",
|
||||
"ui.save_mesh_no_slicer_open": "スライサーが見つかりません。「カスタム…」でスライサーのプログラムを選んでください",
|
||||
"ui.save_mesh_pick_bodies": "保存するボディを選択してください",
|
||||
"ui.save_mesh_temp_failed": "{folder} を作成できません: {error}",
|
||||
"ui.saved_to": "保存しました: {path}",
|
||||
"ui.sbox_pin": "Sボックスの上部に固定",
|
||||
"ui.sbox_unpin": "固定を解除",
|
||||
@@ -3102,8 +3127,9 @@
|
||||
"ui.selection_shift_add_only": "Shift+クリックで追加のみ(解除なし)",
|
||||
"ui.selection_shift_add_only_tip": "オン: Shift は追加、Ctrl/Cmd はトグル。オフ(既定): Shift と Ctrl/Cmd の両方がトグル。",
|
||||
"ui.send": "送信",
|
||||
"ui.send_to_slicer": "スライサーに送る",
|
||||
"ui.send_to_slicer_unavailable": "「スライサーに送る」にはデスクトップアプリが必要です。ブラウザーではプログラムを起動できません",
|
||||
"ui.sent_to_slicer": "{slicer} に送信しました",
|
||||
"ui.sent_to_slicer_copy": "{slicer} に {file} として送信しました(元のファイルがまだ開かれているため)",
|
||||
"ui.sheet_gap": "ギャップ",
|
||||
"ui.shell": "シェル",
|
||||
"ui.shortcut_capture_add_key": "通常のキーを1つ追加してください。Escでキャンセルします。",
|
||||
@@ -3138,6 +3164,15 @@
|
||||
"ui.slice": "スライス",
|
||||
"ui.slicer": "スライサー",
|
||||
"ui.slicer_delete_tip": "{program}\nDelete キーでリストから削除",
|
||||
"ui.slicer_lost": "{slicer} は起動しましたが、ファイルを送るために再び見つけられませんでした。ファイルは {path} に保存されています。そこから開いてください。",
|
||||
"ui.slicer_no_file": "{slicer} は準備できましたが、送信するファイルがありません。",
|
||||
"ui.slicer_no_response": "{slicer} が時間内に応答しませんでした。ファイルは {path} に保存済みです。スライサーが起動したら、もう一度送ってください。",
|
||||
"ui.slicer_resend_hint": "もう一度送ると、スライサーで開いているファイルが置き換わります。",
|
||||
"ui.slicer_single_instance": "すでに起動しているスライサーでファイルを開く",
|
||||
"ui.slicer_single_instance_tip": "PrusaSlicer、SuperSlicer、Cura は、別のウィンドウを起動せず、開いているウィンドウでファイルを読み込みます。ほかのスライサーはそのまま起動します。PrusaSlicer 3.0 は常にこの動作です。ランチャーはほかの方法ではファイルを受け取れません。",
|
||||
"ui.slicer_starting": "{slicer} を起動しています…",
|
||||
"ui.slicer_still_starting": "まだ {slicer} の起動を待っています。ほかには何も送信していません。",
|
||||
"ui.slicer_stl_fallback": "3MF を書き込めなかったため、STL で送信しました。",
|
||||
"ui.slot": "スロット",
|
||||
"ui.smooth": "スムーズ",
|
||||
"ui.smooth_wheel_zoom": "滑らかなホイールズーム",
|
||||
@@ -3205,6 +3240,7 @@
|
||||
"ui.untitled": "無題",
|
||||
"ui.up_axis_note": "ビュー(軸ビュー、ホーム、正面視)で上向きにする軸を指定します。デザインデータ自体は変更されません。",
|
||||
"ui.updateToCurrentView": "現在のビューに更新",
|
||||
"ui.upload": "アップロード",
|
||||
"ui.vertical_constraint": "垂直拘束",
|
||||
"ui.view_centre": "ビュー中心",
|
||||
"ui.viewport_theme_follow": "テーマに従う",
|
||||
|
||||
@@ -611,6 +611,7 @@
|
||||
"cmd.canvas_delete": "캔버스 삭제",
|
||||
"cmd.canvas_edit": "캔버스 편집",
|
||||
"cmd.canvas_insert": "캔버스",
|
||||
"cmd.canvas_to_sketch": "캔버스에서 스케치",
|
||||
"cmd.component_activate": "구성 요소 활성화",
|
||||
"cmd.component_break_link": "링크 끊기",
|
||||
"cmd.component_capture_position": "위치 캡처",
|
||||
@@ -688,6 +689,7 @@
|
||||
"cmd.file_export": "내보내기",
|
||||
"cmd.file_insert_mesh": "메쉬 삽입",
|
||||
"cmd.file_insert_step": "STEP 삽입",
|
||||
"cmd.file_open_in_easyprint": "EasyPrint로 열기",
|
||||
"cmd.file_save_mesh": "메쉬로 저장",
|
||||
"cmd.file_slicers": "설치된 슬라이서",
|
||||
"cmd.frame_member": "프레임 부재",
|
||||
@@ -894,6 +896,7 @@
|
||||
"cmd.sketch_spline_fit_point": "맞춤점 스플라인",
|
||||
"cmd.sketch_spun_profile": "회전 프로파일",
|
||||
"cmd.sketch_toggle_driven": "종속 치수 전환",
|
||||
"cmd.sketch_transform": "변환",
|
||||
"cmd.sketch_trim_path": "경로를 따라 트림",
|
||||
"cmd.solid_align": "정렬",
|
||||
"cmd.solid_boundary_fill": "경계 채우기",
|
||||
@@ -1161,6 +1164,7 @@
|
||||
"nav.blender.help": "가운데 드래그는 회전, Shift+가운데 드래그는 이동, Ctrl+가운데 드래그는 확대/축소입니다. 오른쪽 드래그는 카메라에 영향을 주지 않습니다",
|
||||
"nav.free_orbit.help": "마우스와 3D 마우스로 롤을 유지하며 양 극점을 넘어 회전합니다. Shift 축 스냅은 비활성화됩니다.",
|
||||
"nav.free_orbit.label": "자유 회전",
|
||||
"nav.free_orbit.snap_help": "마우스와 3D 마우스로 양쪽 극점을 지나 회전할 수 있으며 롤 각도가 유지됩니다. 궤도 회전 드래그 중 Shift를 누르면 보기 방향과 롤 각도가 스냅됩니다.",
|
||||
"nav.fusion.help": "가운데 드래그는 이동, Shift+가운데 드래그는 회전, 오른쪽 드래그는 회전입니다",
|
||||
"nav.inventor.help": "가운데 드래그는 이동, Shift+가운데 드래그는 회전, Ctrl+가운데 드래그는 확대/축소, 오른쪽 드래그는 회전입니다",
|
||||
"nav.openscad.help": "빈 공간에서 왼쪽 드래그는 회전, Shift+왼쪽 드래그는 상자 선택, 스케치 형상 드래그는 편집입니다. 오른쪽 또는 가운데 드래그는 이동, Shift+오른쪽 또는 Shift+가운데 드래그는 확대/축소입니다",
|
||||
@@ -1219,6 +1223,7 @@
|
||||
"prefs.design": "설계 기본값",
|
||||
"prefs.design_note": "새 설계가 시작할 때의 설정입니다. 열려 있는 설계는 자체 설정을 유지합니다.",
|
||||
"prefs.devices": "3D 마우스",
|
||||
"prefs.dialog_buttons": "대화 상자 버튼",
|
||||
"prefs.display": "표시",
|
||||
"prefs.edges": "모서리 및 음영",
|
||||
"prefs.extend": "확장",
|
||||
@@ -1237,6 +1242,8 @@
|
||||
"prefs.graphics_note": "속도와 부드러움 중에서 선택합니다. 각 선택의 효과가 샘플에 표시됩니다.",
|
||||
"prefs.graphics_presets_note": "프리셋은 아래의 선택 항목을 한 번에 설정합니다.",
|
||||
"prefs.history": "이력",
|
||||
"prefs.history_origin_bodies": "원점과 바디를 맨 위에 표시",
|
||||
"prefs.history_origin_bodies_note": "왼쪽 또는 브라우저 아래의 히스토리가 활성 구성 요소의 바디 폴더와 원점으로 시작하며, 브라우저처럼 표시 아이콘과 메뉴가 있습니다. 아래쪽 히스토리는 바뀌지 않습니다.",
|
||||
"prefs.hold_stop_snapping": "누르고 있으면 스냅 중지",
|
||||
"prefs.icons_only": "아이콘만",
|
||||
"prefs.imported_file": "가져온 파일",
|
||||
@@ -1302,6 +1309,7 @@
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
"prefs.toggle_item": "항목을 추가하거나 다시 제거",
|
||||
"prefs.toolbar": "도구 모음",
|
||||
"prefs.top": "위쪽",
|
||||
"prefs.translucent": "반투명",
|
||||
"prefs.under_browser": "브라우저 아래",
|
||||
"prefs.under_cursor": "커서 아래",
|
||||
@@ -1748,7 +1756,7 @@
|
||||
"ui.agent_chat_pop_out": "다른 화면으로 옮길 수 있는 별도 창으로 열기",
|
||||
"ui.agent_chat_previous_picture": "이전 이미지",
|
||||
"ui.agent_chat_privacy": "사용자의 계정으로 실행하는 에이전트 외에는 아무 데도 전송되지 않습니다.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft는 아무것도 어디로도 보내지 않습니다. 실행한 에이전트가 사용자의 계정으로 자신의 서비스와 통신합니다. SolidCraft는 키나 비밀번호를 보거나 저장하지 않습니다.",
|
||||
"ui.agent_chat_privacy_note": "채팅 자체는 아무것도 어디로도 보내지 않습니다. 실행한 에이전트가 사용자의 계정으로 자신의 서비스와 통신합니다. SolidCraft는 키나 비밀번호를 보거나 저장하지 않습니다.",
|
||||
"ui.agent_chat_queue": "대기열",
|
||||
"ui.agent_chat_queue_full": "대기 중인 메시지가 너무 많습니다. 하나를 제거하거나 에이전트를 기다리세요.",
|
||||
"ui.agent_chat_queued": "대기 중",
|
||||
@@ -1935,6 +1943,7 @@
|
||||
"ui.command_done": "{command} 완료",
|
||||
"ui.command_palette_hint": "명령 입력(예: {example}) — 또는 S 키를 눌러 검색",
|
||||
"ui.concentric": "동심",
|
||||
"ui.cone": "원뿔",
|
||||
"ui.conic_curve": "원뿔 곡선",
|
||||
"ui.constant": "상수",
|
||||
"ui.constraints_conflict": "구속 조건 충돌",
|
||||
@@ -1993,6 +2002,11 @@
|
||||
"ui.draft": "구배",
|
||||
"ui.drawing": "도면",
|
||||
"ui.drill_point": "드릴 선단",
|
||||
"ui.easyprint_consent": "EasyPrint는 Printables의 브라우저용 슬라이서입니다. 모델을 그곳에서 열기 위해 SolidCraft가 모델을 Printables 서버에 업로드합니다. 링크는 30분 후 만료되며 Printables에 로그인되어 있어야 합니다. 계속할까요?",
|
||||
"ui.easyprint_failed": "EasyPrint 업로드 실패: {error}",
|
||||
"ui.easyprint_opened": "EasyPrint에서 열었습니다",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, 브라우저)",
|
||||
"ui.easyprint_uploading": "Printables에 업로드하는 중…",
|
||||
"ui.edit": "편집",
|
||||
"ui.editJointLimits": "조인트 제한 편집",
|
||||
"ui.edit_section_analysis": "단면 분석 편집",
|
||||
@@ -2049,6 +2063,7 @@
|
||||
"ui.heading_circular_pattern": "원형 패턴",
|
||||
"ui.heading_coil": "코일",
|
||||
"ui.heading_combine": "결합",
|
||||
"ui.heading_cone": "원뿔",
|
||||
"ui.heading_configure": "구성",
|
||||
"ui.heading_constraints": "구속 조건",
|
||||
"ui.heading_construct": "구성",
|
||||
@@ -2292,9 +2307,7 @@
|
||||
"ui.right": "우측면",
|
||||
"ui.save": "저장",
|
||||
"ui.save_as": "다른 이름으로 저장",
|
||||
"ui.save_mesh_no_slicer": "슬라이서를 찾을 수 없습니다. '사용자 지정…'에서 슬라이서 프로그램을 선택하거나 '슬라이서로 보내기'를 끄세요",
|
||||
"ui.save_mesh_pick_bodies": "저장할 바디를 선택하세요",
|
||||
"ui.save_mesh_temp_failed": "{folder}을(를) 만들 수 없습니다: {error}",
|
||||
"ui.saved_to": "저장됨: {path}",
|
||||
"ui.sbox_pin": "S 상자 맨 위에 고정",
|
||||
"ui.sbox_unpin": "고정 해제",
|
||||
@@ -2309,7 +2322,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+클릭은 추가만 함",
|
||||
"ui.selection_shift_add_only_tip": "켬: Shift는 항목을 추가하고 Ctrl/Cmd는 토글합니다. 끔(기본값): Shift와 Ctrl/Cmd 모두 토글합니다.",
|
||||
"ui.send": "보내기",
|
||||
"ui.send_to_slicer": "슬라이서로 보내기",
|
||||
"ui.sent_to_slicer": "{slicer}(으)로 보냈습니다",
|
||||
"ui.sheet_gap": "틈",
|
||||
"ui.shell": "쉘",
|
||||
@@ -2407,6 +2419,7 @@
|
||||
"ui.untitled": "제목 없음",
|
||||
"ui.up_axis_note": "뷰(축 뷰, 홈, 보기)에서 위쪽을 가리키는 축입니다. 디자인은 변경되지 않습니다.",
|
||||
"ui.updateToCurrentView": "현재 보기로 업데이트",
|
||||
"ui.upload": "업로드",
|
||||
"ui.view_centre": "뷰 중심",
|
||||
"ui.viewport_theme_follow": "테마 따르기",
|
||||
"ui.visual_shaded_hidden_edges": "음영 처리 및 숨겨진 모서리",
|
||||
|
||||
@@ -457,6 +457,7 @@
|
||||
"cmd.canvas_delete": "Padam Kanvas",
|
||||
"cmd.canvas_edit": "Sunting Kanvas",
|
||||
"cmd.canvas_insert": "Kanvas",
|
||||
"cmd.canvas_to_sketch": "Kanvas ke Lakaran",
|
||||
"cmd.component_activate": "Aktifkan Komponen",
|
||||
"cmd.component_break_link": "Putuskan Pautan",
|
||||
"cmd.component_capture_position": "Rakam Kedudukan",
|
||||
@@ -534,6 +535,7 @@
|
||||
"cmd.file_export": "Eksport",
|
||||
"cmd.file_insert_mesh": "Sisip Mesh",
|
||||
"cmd.file_insert_step": "Sisip STEP",
|
||||
"cmd.file_open_in_easyprint": "Buka dengan EasyPrint",
|
||||
"cmd.file_save_mesh": "Simpan sebagai Mesh",
|
||||
"cmd.file_slicers": "Penghiris Terpasang",
|
||||
"cmd.frame_member": "Anggota Rangka",
|
||||
@@ -1326,7 +1328,7 @@
|
||||
"ui.agent_chat_pop_out": "Buka dalam tetingkap sendiri, yang boleh dialihkan ke skrin lain",
|
||||
"ui.agent_chat_previous_picture": "Gambar sebelumnya",
|
||||
"ui.agent_chat_privacy": "Data hanya dihantar kepada ejen yang anda jalankan, melalui akaun anda sendiri.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft tidak menghantar data. Ejen berhubung dengan perkhidmatannya melalui akaun anda. SolidCraft tidak melihat atau menyimpan kunci atau kata laluan.",
|
||||
"ui.agent_chat_privacy_note": "Sembang tidak menghantar data dengan sendirinya. Ejen berhubung dengan perkhidmatannya melalui akaun anda. SolidCraft tidak melihat atau menyimpan kunci atau kata laluan.",
|
||||
"ui.agent_chat_queue": "Hantar Nanti",
|
||||
"ui.agent_chat_queue_full": "Terlalu banyak mesej menunggu: buang satu atau tunggu ejen selesai.",
|
||||
"ui.agent_chat_queued": "Menunggu",
|
||||
@@ -1511,6 +1513,7 @@
|
||||
"ui.command_done": "{command} selesai",
|
||||
"ui.command_palette_hint": "Taip arahan, cth. {example} — atau tekan S untuk mencari",
|
||||
"ui.concentric": "Sepusat",
|
||||
"ui.cone": "Kon",
|
||||
"ui.conic_curve": "Lengkung Kon",
|
||||
"ui.constant": "Malar",
|
||||
"ui.constraints_conflict": "Kekangan bercanggah",
|
||||
@@ -1569,6 +1572,11 @@
|
||||
"ui.draft": "Sudut Draf",
|
||||
"ui.drawing": "Lukisan",
|
||||
"ui.drill_point": "Hujung Gerudi",
|
||||
"ui.easyprint_consent": "EasyPrint ialah slicer Printables dalam pelayar. Untuk membuka model di sana, SolidCraft memuat naiknya ke pelayan Printables. Pautan tamat tempoh selepas 30 minit, dan anda perlu log masuk ke Printables. Teruskan?",
|
||||
"ui.easyprint_failed": "Muat naik ke EasyPrint gagal: {error}",
|
||||
"ui.easyprint_opened": "Dibuka dalam EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, dalam pelayar)",
|
||||
"ui.easyprint_uploading": "Memuat naik ke Printables…",
|
||||
"ui.edit": "Sunting",
|
||||
"ui.editJointLimits": "Sunting Had Sendi",
|
||||
"ui.edit_section_analysis": "Sunting Analisis Keratan",
|
||||
@@ -1625,6 +1633,7 @@
|
||||
"ui.heading_circular_pattern": "CORAK MEMBULAT",
|
||||
"ui.heading_coil": "GEGELUNG",
|
||||
"ui.heading_combine": "GABUNG",
|
||||
"ui.heading_cone": "KON",
|
||||
"ui.heading_configure": "KONFIGURASI",
|
||||
"ui.heading_constraints": "KEKANGAN",
|
||||
"ui.heading_construct": "BINA",
|
||||
@@ -1860,9 +1869,7 @@
|
||||
"ui.right": "Kanan",
|
||||
"ui.save": "Simpan",
|
||||
"ui.save_as": "Simpan Sebagai",
|
||||
"ui.save_mesh_no_slicer": "Tiada penghiris: pilih program melalui Tersuai… atau matikan Hantar ke penghiris",
|
||||
"ui.save_mesh_pick_bodies": "Pilih jasad untuk disimpan",
|
||||
"ui.save_mesh_temp_failed": "Tidak dapat mencipta {folder}: {error}",
|
||||
"ui.saved_to": "{path} disimpan",
|
||||
"ui.sbox_pin": "Semat di atas kotak S",
|
||||
"ui.sbox_unpin": "Nyahsemat",
|
||||
@@ -1877,7 +1884,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klik menambah tanpa membuang",
|
||||
"ui.selection_shift_add_only_tip": "Hidup: Shift menambah item, Ctrl/Cmd menogol. Mati (lalai): Shift dan Ctrl/Cmd kedua-duanya menogol.",
|
||||
"ui.send": "Hantar",
|
||||
"ui.send_to_slicer": "Hantar ke penghiris",
|
||||
"ui.sent_to_slicer": "Dihantar ke {slicer}",
|
||||
"ui.sheet_gap": "Jarak",
|
||||
"ui.shell": "Ronggakan",
|
||||
@@ -1969,6 +1975,7 @@
|
||||
"ui.untitled": "Tanpa Tajuk",
|
||||
"ui.up_axis_note": "Paksi yang menghala ke atas dalam pandangan paksi, Utama dan Pandang Tepat. Reka bentuk tidak berubah.",
|
||||
"ui.updateToCurrentView": "Kemas Kini Pandangan Semasa",
|
||||
"ui.upload": "Muat naik",
|
||||
"ui.view_centre": "Pusat pandangan",
|
||||
"ui.visual_shaded_hidden_edges": "Berlorek dengan tepi tersembunyi",
|
||||
"ui.visual_shaded_with_edges": "Berlorek dengan tepi",
|
||||
|
||||
@@ -2066,9 +2066,7 @@
|
||||
"ui.right": "Høyre",
|
||||
"ui.save": "Lagre",
|
||||
"ui.save_as": "Lagre som",
|
||||
"ui.save_mesh_no_slicer": "Ingen slicer funnet: velg Egendefinert… og velg slicer-programmet ditt, eller slå av Send til slicer",
|
||||
"ui.save_mesh_pick_bodies": "Velg kroppene som skal lagres",
|
||||
"ui.save_mesh_temp_failed": "Kunne ikke lage {folder}: {error}",
|
||||
"ui.saved_to": "Lagret {path}",
|
||||
"ui.sbox_pin": "Fest øverst i S-boksen",
|
||||
"ui.sbox_unpin": "Løsne",
|
||||
@@ -2083,7 +2081,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klikk legger til uten å fjerne",
|
||||
"ui.selection_shift_add_only_tip": "På: Shift legger til elementer, Ctrl/Cmd veksler. Av (standard): Shift og Ctrl/Cmd veksler begge.",
|
||||
"ui.send": "Send",
|
||||
"ui.send_to_slicer": "Send til slicer",
|
||||
"ui.sent_to_slicer": "Sendt til {slicer}",
|
||||
"ui.sheet_gap": "Mellomrom",
|
||||
"ui.shell": "Skall",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Canvas verwijderen",
|
||||
"cmd.canvas_edit": "Canvas bewerken",
|
||||
"cmd.canvas_insert": "Canvas",
|
||||
"cmd.canvas_to_sketch": "Canvas naar schets",
|
||||
"cmd.component_activate": "Component activeren",
|
||||
"cmd.component_break_link": "Koppeling verbreken",
|
||||
"cmd.component_capture_position": "Positie vastleggen",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Exporteren",
|
||||
"cmd.file_insert_mesh": "Mesh invoegen",
|
||||
"cmd.file_insert_step": "STEP invoegen",
|
||||
"cmd.file_open_in_easyprint": "Openen met EasyPrint",
|
||||
"cmd.file_save_mesh": "Opslaan als mesh",
|
||||
"cmd.file_slicers": "Geïnstalleerde slicers",
|
||||
"cmd.frame_member": "Constructiedeel",
|
||||
@@ -1269,7 +1271,7 @@
|
||||
"ui.agent_chat_pop_out": "Openen in een eigen venster, dat u naar een ander scherm kunt verplaatsen",
|
||||
"ui.agent_chat_previous_picture": "Vorige afbeelding",
|
||||
"ui.agent_chat_privacy": "Er gaat niets naar buiten behalve naar de agent die u uitvoert, onder uw eigen account.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft verstuurt niets: de agent die u uitvoert praat met zijn eigen service onder uw eigen account. SolidCraft ziet of bewaart nooit een sleutel of wachtwoord.",
|
||||
"ui.agent_chat_privacy_note": "De chat verstuurt zelf niets: de agent die u uitvoert praat met zijn eigen service onder uw eigen account. SolidCraft ziet of bewaart nooit een sleutel of wachtwoord.",
|
||||
"ui.agent_chat_queue": "In wachtrij",
|
||||
"ui.agent_chat_queue_full": "Te veel berichten wachten: verwijder er een of wacht op de agent.",
|
||||
"ui.agent_chat_queued": "In wachtrij",
|
||||
@@ -1453,6 +1455,7 @@
|
||||
"ui.command_done": "{command} gereed",
|
||||
"ui.command_palette_hint": "Typ een opdracht, bijv. {example} — of druk op S om te zoeken",
|
||||
"ui.concentric": "Concentrisch",
|
||||
"ui.cone": "Kegel",
|
||||
"ui.conic_curve": "Kegelsnede",
|
||||
"ui.constant": "Constant",
|
||||
"ui.constraints_conflict": "Beperkingen conflicteren",
|
||||
@@ -1511,6 +1514,11 @@
|
||||
"ui.draft": "Aanzet",
|
||||
"ui.drawing": "Tekening",
|
||||
"ui.drill_point": "Boorpunt",
|
||||
"ui.easyprint_consent": "EasyPrint is de slicer van Printables in de browser. Om het model daar te openen, uploadt SolidCraft het naar de servers van Printables. De link verloopt na 30 minuten en u moet bij Printables zijn ingelogd. Doorgaan?",
|
||||
"ui.easyprint_failed": "Uploaden naar EasyPrint mislukt: {error}",
|
||||
"ui.easyprint_opened": "Geopend in EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, in de browser)",
|
||||
"ui.easyprint_uploading": "Uploaden naar Printables…",
|
||||
"ui.edit": "Bewerken",
|
||||
"ui.editJointLimits": "Verbindingslimieten bewerken",
|
||||
"ui.edit_section_analysis": "Doorsnedeanalyse bewerken",
|
||||
@@ -1567,6 +1575,7 @@
|
||||
"ui.heading_circular_pattern": "CIRKELVORMIG PATROON",
|
||||
"ui.heading_coil": "SPIRAAL",
|
||||
"ui.heading_combine": "COMBINEREN",
|
||||
"ui.heading_cone": "KEGEL",
|
||||
"ui.heading_configure": "CONFIGUREREN",
|
||||
"ui.heading_constraints": "BEPERKINGEN",
|
||||
"ui.heading_construct": "CONSTRUEREN",
|
||||
@@ -1802,9 +1811,7 @@
|
||||
"ui.right": "Rechts",
|
||||
"ui.save": "Opslaan",
|
||||
"ui.save_as": "Opslaan als",
|
||||
"ui.save_mesh_no_slicer": "Geen slicer gevonden: kies Aangepast… en selecteer uw slicerprogramma, of schakel Naar slicer verzenden uit",
|
||||
"ui.save_mesh_pick_bodies": "Selecteer de op te slaan lichamen",
|
||||
"ui.save_mesh_temp_failed": "{folder} kon niet worden gemaakt: {error}",
|
||||
"ui.saved_to": "{path} opgeslagen",
|
||||
"ui.sbox_pin": "Bovenaan het S-vak vastpinnen",
|
||||
"ui.sbox_unpin": "Losmaken",
|
||||
@@ -1819,7 +1826,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klik voegt toe zonder te verwijderen",
|
||||
"ui.selection_shift_add_only_tip": "Aan: Shift voegt items toe, Ctrl/Cmd schakelt om. Uit (standaard): Shift en Ctrl/Cmd schakelen beide om.",
|
||||
"ui.send": "Verzenden",
|
||||
"ui.send_to_slicer": "Naar slicer verzenden",
|
||||
"ui.sent_to_slicer": "Verzonden naar {slicer}",
|
||||
"ui.sheet_gap": "Opening",
|
||||
"ui.shell": "Schalen",
|
||||
@@ -1911,6 +1917,7 @@
|
||||
"ui.untitled": "Naamloos",
|
||||
"ui.up_axis_note": "Welke as in de weergaven omhoog wijst (aanzicht op as, Home, Kijken naar). Ontwerpen blijven ongewijzigd.",
|
||||
"ui.updateToCurrentView": "Bijwerken naar huidige weergave",
|
||||
"ui.upload": "Uploaden",
|
||||
"ui.view_centre": "Weergavemidden",
|
||||
"ui.visual_shaded_hidden_edges": "Gearceerd met verborgen randen",
|
||||
"ui.visual_shaded_with_edges": "Gearceerd met randen",
|
||||
|
||||
@@ -510,6 +510,7 @@
|
||||
"cmd.canvas_delete": "Usuń płótno",
|
||||
"cmd.canvas_edit": "Edytuj płótno",
|
||||
"cmd.canvas_insert": "Płótno",
|
||||
"cmd.canvas_to_sketch": "Szkic z płótna",
|
||||
"cmd.component_activate": "Aktywuj komponent",
|
||||
"cmd.component_break_link": "Zerwij łącze",
|
||||
"cmd.component_capture_position": "Zapamiętaj położenie",
|
||||
@@ -588,6 +589,7 @@
|
||||
"cmd.file_export": "Eksportuj",
|
||||
"cmd.file_insert_mesh": "Wstaw siatkę",
|
||||
"cmd.file_insert_step": "Wstaw STEP",
|
||||
"cmd.file_open_in_easyprint": "Otwórz w EasyPrint",
|
||||
"cmd.file_save_mesh": "Zapisz jako siatkę",
|
||||
"cmd.file_slicers": "Zainstalowane slicery",
|
||||
"cmd.frame_member": "Element ramy",
|
||||
@@ -1517,7 +1519,7 @@
|
||||
"ui.agent_chat_pop_out": "Otwórz w osobnym oknie, które możesz przenieść na inny ekran",
|
||||
"ui.agent_chat_previous_picture": "Poprzedni obraz",
|
||||
"ui.agent_chat_privacy": "Nic nie trafia nigdzie poza uruchomionym przez Ciebie agentem, na Twoim własnym koncie.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft nigdzie niczego nie wysyła: uruchomiony przez Ciebie agent łączy się z własną usługą na Twoim koncie. SolidCraft nigdy nie widzi ani nie przechowuje klucza ani hasła.",
|
||||
"ui.agent_chat_privacy_note": "Czat sam z siebie nigdzie niczego nie wysyła: uruchomiony przez Ciebie agent łączy się z własną usługą na Twoim koncie. SolidCraft nigdy nie widzi ani nie przechowuje klucza ani hasła.",
|
||||
"ui.agent_chat_queue": "Kolejka",
|
||||
"ui.agent_chat_queue_full": "Za dużo wiadomości czeka: usuń jedną lub poczekaj na agenta.",
|
||||
"ui.agent_chat_queued": "W kolejce",
|
||||
@@ -1704,6 +1706,7 @@
|
||||
"ui.command_done": "Wykonano: {command}",
|
||||
"ui.command_palette_hint": "Wpisz polecenie, np. {example} — lub naciśnij S, aby wyszukać",
|
||||
"ui.concentric": "Współśrodkowość",
|
||||
"ui.cone": "Stożek",
|
||||
"ui.conic_curve": "Krzywa stożkowa",
|
||||
"ui.constant": "Stały",
|
||||
"ui.constraints_conflict": "Konflikt wiązań",
|
||||
@@ -1762,6 +1765,11 @@
|
||||
"ui.draft": "Pochylenie",
|
||||
"ui.drawing": "Rysunek",
|
||||
"ui.drill_point": "Wierzchołek wiertła",
|
||||
"ui.easyprint_consent": "EasyPrint to slicer Printables w przeglądarce. Aby otworzyć w nim model, SolidCraft wysyła go na serwery Printables. Link wygasa po 30 minutach, a Ty musisz być zalogowany w Printables. Kontynuować?",
|
||||
"ui.easyprint_failed": "Nie udało się wysłać do EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Otwarto w EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, w przeglądarce)",
|
||||
"ui.easyprint_uploading": "Wysyłanie do Printables…",
|
||||
"ui.edit": "Edytuj",
|
||||
"ui.editJointLimits": "Edytuj limity złącza",
|
||||
"ui.edit_section_analysis": "Edytuj analizę przekroju",
|
||||
@@ -1818,6 +1826,7 @@
|
||||
"ui.heading_circular_pattern": "SZYK KOŁOWY",
|
||||
"ui.heading_coil": "SPIRALA",
|
||||
"ui.heading_combine": "KOMBINACJA",
|
||||
"ui.heading_cone": "STOŻEK",
|
||||
"ui.heading_configure": "KONFIGURACJA",
|
||||
"ui.heading_constraints": "WIĄZANIA",
|
||||
"ui.heading_construct": "KONSTRUKCJA",
|
||||
@@ -2067,9 +2076,7 @@
|
||||
"ui.right": "Prawy",
|
||||
"ui.save": "Zapisz",
|
||||
"ui.save_as": "Zapisz jako",
|
||||
"ui.save_mesh_no_slicer": "Nie znaleziono slicera: wybierz Niestandardowy… i wskaż program slicera albo wyłącz Wyślij do slicera",
|
||||
"ui.save_mesh_pick_bodies": "Zaznacz bryły do zapisania",
|
||||
"ui.save_mesh_temp_failed": "Nie udało się utworzyć {folder}: {error}",
|
||||
"ui.saved_to": "Zapisano {path}",
|
||||
"ui.sbox_pin": "Przypnij na górze okna S",
|
||||
"ui.sbox_unpin": "Odepnij",
|
||||
@@ -2084,7 +2091,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+kliknięcie tylko dodaje",
|
||||
"ui.selection_shift_add_only_tip": "Wł.: Shift dodaje elementy, Ctrl/Cmd przełącza. Wył. (domyślnie): Shift i Ctrl/Cmd przełączają.",
|
||||
"ui.send": "Wyślij",
|
||||
"ui.send_to_slicer": "Wyślij do slicera",
|
||||
"ui.sent_to_slicer": "Wysłano do {slicer}",
|
||||
"ui.sheet_gap": "Szczelina",
|
||||
"ui.shell": "Skorupa",
|
||||
@@ -2176,6 +2182,7 @@
|
||||
"ui.untitled": "Bez tytułu",
|
||||
"ui.up_axis_note": "Która oś wskazuje górę w widokach (widok osi, Widok główny, Patrz na). Projekty pozostają bez zmian.",
|
||||
"ui.updateToCurrentView": "Aktualizuj do bieżącego widoku",
|
||||
"ui.upload": "Wyślij",
|
||||
"ui.view_centre": "Środek widoku",
|
||||
"ui.visual_shaded_hidden_edges": "Cieniowany z krawędziami niewidocznymi",
|
||||
"ui.visual_shaded_with_edges": "Cieniowany z krawędziami",
|
||||
|
||||
@@ -613,6 +613,7 @@
|
||||
"cmd.canvas_delete": "Excluir tela de referência",
|
||||
"cmd.canvas_edit": "Editar tela de referência",
|
||||
"cmd.canvas_insert": "Tela de referência",
|
||||
"cmd.canvas_to_sketch": "Esboço a partir da tela de referência",
|
||||
"cmd.component_activate": "Ativar componente",
|
||||
"cmd.component_break_link": "Quebrar vínculo",
|
||||
"cmd.component_capture_position": "Capturar posição",
|
||||
@@ -693,6 +694,7 @@
|
||||
"cmd.file_export": "Exportar",
|
||||
"cmd.file_insert_mesh": "Inserir malha",
|
||||
"cmd.file_insert_step": "Inserir STEP",
|
||||
"cmd.file_open_in_easyprint": "Abrir com o EasyPrint",
|
||||
"cmd.file_save_mesh": "Salvar como malha",
|
||||
"cmd.file_slicers": "Fatiadores instalados",
|
||||
"cmd.frame_member": "Membro estrutural",
|
||||
@@ -1155,6 +1157,7 @@
|
||||
"nav.blender.help": "Arrastar com o meio orbita, Shift+meio desloca, Ctrl+meio aplica zoom; arrastar com o direito não mexe na câmera",
|
||||
"nav.free_orbit.help": "O mouse e o mouse 3D giram passando pelos dois polos e preservam o giro lateral (roll). O encaixe em eixo com Shift fica desativado.",
|
||||
"nav.free_orbit.label": "Órbita livre",
|
||||
"nav.free_orbit.snap_help": "Mouse e mouse 3D giram através dos dois polos e preservam a rotação da imagem. Mantenha Shift pressionado durante uma órbita por arraste para alinhar a direção da vista e a rotação da imagem.",
|
||||
"nav.fusion.help": "Arrastar com o meio desloca, Shift+meio orbita, arrastar com o direito orbita",
|
||||
"nav.inventor.help": "Arrastar com o meio desloca, Shift+meio orbita, Ctrl+meio aplica zoom, arrastar com o direito orbita",
|
||||
"nav.openscad.help": "Arrastar com o esquerdo no espaço vazio orbita, Shift+arrastar com o esquerdo seleciona por caixa, arrastar a geometria do esboço a edita; arrastar com o direito ou o meio desloca, Shift+arrastar com o direito ou o meio aplica zoom",
|
||||
@@ -1637,7 +1640,7 @@
|
||||
"ui.agent_chat_pop_out": "Abrir em janela própria, que você pode mover para outra tela",
|
||||
"ui.agent_chat_previous_picture": "Imagem anterior",
|
||||
"ui.agent_chat_privacy": "Nada vai para lugar algum além do agente que você executa, na sua própria conta.",
|
||||
"ui.agent_chat_privacy_note": "O SolidCraft não envia nada a lugar nenhum: o agente que você executa fala com o próprio serviço, na sua própria conta. O SolidCraft nunca vê nem guarda chaves ou senhas.",
|
||||
"ui.agent_chat_privacy_note": "O chat não envia nada a lugar nenhum por conta própria: o agente que você executa fala com o próprio serviço, na sua própria conta. O SolidCraft nunca vê nem guarda chaves ou senhas.",
|
||||
"ui.agent_chat_queue": "Fila",
|
||||
"ui.agent_chat_queue_full": "Há mensagens demais esperando: remova uma ou aguarde o agente.",
|
||||
"ui.agent_chat_queued": "Na fila",
|
||||
@@ -1830,6 +1833,7 @@
|
||||
"ui.command_done": "{command} concluído",
|
||||
"ui.command_palette_hint": "Digite um comando, p. ex. {example} — ou pressione S para pesquisar",
|
||||
"ui.concentric": "Concêntrico",
|
||||
"ui.cone": "Cone",
|
||||
"ui.conic_curve": "Curva cônica",
|
||||
"ui.constant": "Constante",
|
||||
"ui.constraints_conflict": "Restrições em conflito",
|
||||
@@ -1888,6 +1892,11 @@
|
||||
"ui.draft": "Ângulo de saída",
|
||||
"ui.drawing": "Desenho",
|
||||
"ui.drill_point": "Ponta de broca",
|
||||
"ui.easyprint_consent": "O EasyPrint é o slicer do Printables no navegador. Para abrir o modelo lá, o SolidCraft o envia para os servidores do Printables. O link expira em 30 minutos e você precisa estar conectado ao Printables. Continuar?",
|
||||
"ui.easyprint_failed": "Falha ao enviar para o EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Aberto no EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, no navegador)",
|
||||
"ui.easyprint_uploading": "Enviando para o Printables…",
|
||||
"ui.edit": "Editar",
|
||||
"ui.editJointLimits": "Editar limites da junta",
|
||||
"ui.edit_section_analysis": "Editar análise de seção",
|
||||
@@ -1947,6 +1956,7 @@
|
||||
"ui.heading_circular_pattern": "PADRÃO CIRCULAR",
|
||||
"ui.heading_coil": "ESPIRAL",
|
||||
"ui.heading_combine": "COMBINAR",
|
||||
"ui.heading_cone": "CONE",
|
||||
"ui.heading_configure": "CONFIGURAR",
|
||||
"ui.heading_constraints": "RESTRIÇÕES",
|
||||
"ui.heading_construct": "CONSTRUIR",
|
||||
@@ -2194,9 +2204,7 @@
|
||||
"ui.right": "Direita",
|
||||
"ui.save": "Salvar",
|
||||
"ui.save_as": "Salvar como",
|
||||
"ui.save_mesh_no_slicer": "Nenhum fatiador encontrado: escolha Personalizado… e selecione seu programa fatiador, ou desative Enviar ao fatiador",
|
||||
"ui.save_mesh_pick_bodies": "Selecione os corpos a salvar",
|
||||
"ui.save_mesh_temp_failed": "Não foi possível criar {folder}: {error}",
|
||||
"ui.saved_to": "Salvo {path}",
|
||||
"ui.sbox_pin": "Fixar no topo da caixa S",
|
||||
"ui.sbox_unpin": "Desafixar",
|
||||
@@ -2211,7 +2219,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+clique adiciona sem remover",
|
||||
"ui.selection_shift_add_only_tip": "Ativado: Shift adiciona itens, Ctrl/Cmd alterna. Desativado (padrão): Shift e Ctrl/Cmd alternam.",
|
||||
"ui.send": "Enviar",
|
||||
"ui.send_to_slicer": "Enviar ao fatiador",
|
||||
"ui.sent_to_slicer": "Enviado para {slicer}",
|
||||
"ui.sheet_gap": "Folga",
|
||||
"ui.shell": "Casca",
|
||||
@@ -2303,6 +2310,7 @@
|
||||
"ui.untitled": "Sem título",
|
||||
"ui.up_axis_note": "Qual eixo aponta para cima nas vistas (vista do eixo, Início, Olhar para). Os projetos não mudam.",
|
||||
"ui.updateToCurrentView": "Atualizar para a vista atual",
|
||||
"ui.upload": "Enviar",
|
||||
"ui.view_centre": "Centro da vista",
|
||||
"ui.visual_shaded_hidden_edges": "Sombreado com arestas ocultas",
|
||||
"ui.visual_shaded_with_edges": "Sombreado com arestas",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Șterge pânza",
|
||||
"cmd.canvas_edit": "Editează pânza",
|
||||
"cmd.canvas_insert": "Pânză",
|
||||
"cmd.canvas_to_sketch": "Schiță din pânză",
|
||||
"cmd.component_activate": "Activează componenta",
|
||||
"cmd.component_break_link": "Rupe legătura",
|
||||
"cmd.component_capture_position": "Capturează poziția",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Exportă",
|
||||
"cmd.file_insert_mesh": "Inserează plasă",
|
||||
"cmd.file_insert_step": "Inserează STEP",
|
||||
"cmd.file_open_in_easyprint": "Deschide cu EasyPrint",
|
||||
"cmd.file_save_mesh": "Salvează ca plasă",
|
||||
"cmd.file_slicers": "Slicere instalate",
|
||||
"cmd.frame_member": "Element de cadru",
|
||||
@@ -1269,7 +1271,7 @@
|
||||
"ui.agent_chat_pop_out": "Deschide în propria fereastră, pe care o puteți muta pe alt ecran",
|
||||
"ui.agent_chat_previous_picture": "Imaginea anterioară",
|
||||
"ui.agent_chat_privacy": "Nimic nu merge nicăieri în afară de agentul pe care îl rulați, sub propriul cont.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft nu trimite nimic nicăieri: agentul pe care îl rulați comunică cu propriul serviciu sub contul dumneavoastră. SolidCraft nu vede și nu stochează niciodată o cheie sau o parolă.",
|
||||
"ui.agent_chat_privacy_note": "Chatul nu trimite nimic nicăieri de la sine: agentul pe care îl rulați comunică cu propriul serviciu sub contul dumneavoastră. SolidCraft nu vede și nu stochează niciodată o cheie sau o parolă.",
|
||||
"ui.agent_chat_queue": "Coadă",
|
||||
"ui.agent_chat_queue_full": "Prea multe mesaje așteaptă: eliminați unul sau așteptați agentul.",
|
||||
"ui.agent_chat_queued": "În coadă",
|
||||
@@ -1453,6 +1455,7 @@
|
||||
"ui.command_done": "{command} efectuat",
|
||||
"ui.command_palette_hint": "Tastați o comandă, ex. {example} — sau apăsați S pentru căutare",
|
||||
"ui.concentric": "Concentric",
|
||||
"ui.cone": "Con",
|
||||
"ui.conic_curve": "Curbă conică",
|
||||
"ui.constant": "Constant",
|
||||
"ui.constraints_conflict": "Conflict de constrângeri",
|
||||
@@ -1511,6 +1514,11 @@
|
||||
"ui.draft": "Înclinare",
|
||||
"ui.drawing": "Desen",
|
||||
"ui.drill_point": "Vârf de burghiu",
|
||||
"ui.easyprint_consent": "EasyPrint este slicerul Printables din browser. Pentru a deschide modelul acolo, SolidCraft îl încarcă pe serverele Printables. Linkul expiră după 30 de minute, iar dumneavoastră trebuie să fiți autentificat pe Printables. Continuați?",
|
||||
"ui.easyprint_failed": "Încărcarea în EasyPrint a eșuat: {error}",
|
||||
"ui.easyprint_opened": "Deschis în EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, în browser)",
|
||||
"ui.easyprint_uploading": "Se încarcă pe Printables…",
|
||||
"ui.edit": "Editează",
|
||||
"ui.editJointLimits": "Editează limitele îmbinării",
|
||||
"ui.edit_section_analysis": "Editează analiza secțiunii",
|
||||
@@ -1567,6 +1575,7 @@
|
||||
"ui.heading_circular_pattern": "MATRICE CIRCULARĂ",
|
||||
"ui.heading_coil": "SPIRALĂ",
|
||||
"ui.heading_combine": "COMBINARE",
|
||||
"ui.heading_cone": "CON",
|
||||
"ui.heading_configure": "CONFIGURARE",
|
||||
"ui.heading_constraints": "CONSTRÂNGERI",
|
||||
"ui.heading_construct": "CONSTRUCȚIE",
|
||||
@@ -1802,9 +1811,7 @@
|
||||
"ui.right": "Dreapta",
|
||||
"ui.save": "Salvează",
|
||||
"ui.save_as": "Salvează ca",
|
||||
"ui.save_mesh_no_slicer": "Niciun slicer găsit: alegeți Personalizat… și selectați programul slicer sau dezactivați Trimite la slicer",
|
||||
"ui.save_mesh_pick_bodies": "Selectați corpurile de salvat",
|
||||
"ui.save_mesh_temp_failed": "Nu s-a putut crea {folder}: {error}",
|
||||
"ui.saved_to": "Salvat {path}",
|
||||
"ui.sbox_pin": "Fixează în partea de sus a casetei S",
|
||||
"ui.sbox_unpin": "Defixează",
|
||||
@@ -1819,7 +1826,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+clic adaugă fără a elimina",
|
||||
"ui.selection_shift_add_only_tip": "Activat: Shift adaugă elemente, Ctrl/Cmd comută. Dezactivat (implicit): Shift și Ctrl/Cmd comută ambele.",
|
||||
"ui.send": "Trimite",
|
||||
"ui.send_to_slicer": "Trimite la slicer",
|
||||
"ui.sent_to_slicer": "Trimis la {slicer}",
|
||||
"ui.sheet_gap": "Spațiu",
|
||||
"ui.shell": "Scobire",
|
||||
@@ -1911,6 +1917,7 @@
|
||||
"ui.untitled": "Fără titlu",
|
||||
"ui.up_axis_note": "Ce axă indică în sus în vizualizări (vizualizare pe axă, Acasă, Privește spre). Proiectele rămân neschimbate.",
|
||||
"ui.updateToCurrentView": "Actualizează la vizualizarea curentă",
|
||||
"ui.upload": "Încarcă",
|
||||
"ui.view_centre": "Centrul vizualizării",
|
||||
"ui.visual_shaded_hidden_edges": "Umbrit cu muchii ascunse",
|
||||
"ui.visual_shaded_with_edges": "Umbrit cu muchii",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Удалить подложку",
|
||||
"cmd.canvas_edit": "Изменить подложку",
|
||||
"cmd.canvas_insert": "Подложка",
|
||||
"cmd.canvas_to_sketch": "Эскиз из подложки",
|
||||
"cmd.component_activate": "Активировать компонент",
|
||||
"cmd.component_break_link": "Разорвать связь",
|
||||
"cmd.component_capture_position": "Зафиксировать положение",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Экспорт",
|
||||
"cmd.file_insert_mesh": "Вставить меш",
|
||||
"cmd.file_insert_step": "Вставить STEP",
|
||||
"cmd.file_open_in_easyprint": "Открыть в EasyPrint",
|
||||
"cmd.file_save_mesh": "Сохранить как меш",
|
||||
"cmd.file_slicers": "Установленные слайсеры",
|
||||
"cmd.frame_member": "Элемент рамы",
|
||||
@@ -1325,7 +1327,7 @@
|
||||
"ui.agent_chat_pop_out": "Открыть в отдельном окне, которое можно перенести на другой экран",
|
||||
"ui.agent_chat_previous_picture": "Предыдущее изображение",
|
||||
"ui.agent_chat_privacy": "Ничего не уходит никуда, кроме запускаемого вами агента, под вашей учётной записью.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft ничего никуда не отправляет: запускаемый вами агент обращается к своему сервису под вашей учётной записью. SolidCraft никогда не видит и не хранит ключ или пароль.",
|
||||
"ui.agent_chat_privacy_note": "Чат сам ничего никуда не отправляет: запускаемый вами агент обращается к своему сервису под вашей учётной записью. SolidCraft никогда не видит и не хранит ключ или пароль.",
|
||||
"ui.agent_chat_queue": "В очередь",
|
||||
"ui.agent_chat_queue_full": "Слишком много сообщений в ожидании: удалите одно или дождитесь агента.",
|
||||
"ui.agent_chat_queued": "В очереди",
|
||||
@@ -1510,6 +1512,7 @@
|
||||
"ui.command_done": "{command} — готово",
|
||||
"ui.command_palette_hint": "Введите команду, напр. {example} — или нажмите S для поиска",
|
||||
"ui.concentric": "Концентричность",
|
||||
"ui.cone": "Конус",
|
||||
"ui.conic_curve": "Коническая кривая",
|
||||
"ui.constant": "Постоянный",
|
||||
"ui.constraints_conflict": "Ограничения конфликтуют",
|
||||
@@ -1568,6 +1571,11 @@
|
||||
"ui.draft": "Уклон",
|
||||
"ui.drawing": "Чертёж",
|
||||
"ui.drill_point": "Конус сверла",
|
||||
"ui.easyprint_consent": "EasyPrint — это слайсер Printables в браузере. Чтобы открыть модель там, SolidCraft загружает её на серверы Printables. Ссылка действует 30 минут, и вам нужно войти в Printables. Продолжить?",
|
||||
"ui.easyprint_failed": "Не удалось загрузить в EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Открыто в EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, в браузере)",
|
||||
"ui.easyprint_uploading": "Загрузка в Printables…",
|
||||
"ui.edit": "Изменить",
|
||||
"ui.editJointLimits": "Изменить ограничения сопряжения",
|
||||
"ui.edit_section_analysis": "Изменить анализ сечения",
|
||||
@@ -1624,6 +1632,7 @@
|
||||
"ui.heading_circular_pattern": "КРУГОВОЙ МАССИВ",
|
||||
"ui.heading_coil": "СПИРАЛЬ",
|
||||
"ui.heading_combine": "ОБЪЕДИНЕНИЕ",
|
||||
"ui.heading_cone": "КОНУС",
|
||||
"ui.heading_configure": "КОНФИГУРАЦИЯ",
|
||||
"ui.heading_constraints": "ОГРАНИЧЕНИЯ",
|
||||
"ui.heading_construct": "ПОСТРОЕНИЕ",
|
||||
@@ -1859,9 +1868,7 @@
|
||||
"ui.right": "Справа",
|
||||
"ui.save": "Сохранить",
|
||||
"ui.save_as": "Сохранить как",
|
||||
"ui.save_mesh_no_slicer": "Слайсер не найден: выберите «Другая…» и укажите программу или отключите «Отправить в слайсер»",
|
||||
"ui.save_mesh_pick_bodies": "Выберите тела для сохранения",
|
||||
"ui.save_mesh_temp_failed": "Не удалось создать {folder}: {error}",
|
||||
"ui.saved_to": "Сохранено: {path}",
|
||||
"ui.sbox_pin": "Закрепить вверху панели S",
|
||||
"ui.sbox_unpin": "Открепить",
|
||||
@@ -1876,7 +1883,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+щелчок добавляет без удаления",
|
||||
"ui.selection_shift_add_only_tip": "Вкл.: Shift добавляет объекты, Ctrl/Cmd переключает. Выкл. (по умолчанию): Shift и Ctrl/Cmd оба переключают.",
|
||||
"ui.send": "Отправить",
|
||||
"ui.send_to_slicer": "Отправить в слайсер",
|
||||
"ui.sent_to_slicer": "Отправлено в {slicer}",
|
||||
"ui.sheet_gap": "Зазор",
|
||||
"ui.shell": "Оболочка",
|
||||
@@ -1968,6 +1974,7 @@
|
||||
"ui.untitled": "Без имени",
|
||||
"ui.up_axis_note": "Какая ось направлена вверх в видах (осевые виды, «Главная», «Взгляд на»). Дизайны не меняются.",
|
||||
"ui.updateToCurrentView": "Обновить до текущего вида",
|
||||
"ui.upload": "Загрузить",
|
||||
"ui.view_centre": "Центр вида",
|
||||
"ui.visual_shaded_hidden_edges": "Заполненный с невидимыми рёбрами",
|
||||
"ui.visual_shaded_with_edges": "Заполненный с рёбрами",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Ta bort duk",
|
||||
"cmd.canvas_edit": "Redigera duk",
|
||||
"cmd.canvas_insert": "Duk",
|
||||
"cmd.canvas_to_sketch": "Skiss från duk",
|
||||
"cmd.component_activate": "Aktivera komponent",
|
||||
"cmd.component_break_link": "Bryt länk",
|
||||
"cmd.component_capture_position": "Fånga läge",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Exportera",
|
||||
"cmd.file_insert_mesh": "Infoga mesh",
|
||||
"cmd.file_insert_step": "Infoga STEP",
|
||||
"cmd.file_open_in_easyprint": "Öppna med EasyPrint",
|
||||
"cmd.file_save_mesh": "Spara som mesh",
|
||||
"cmd.file_slicers": "Installerade slicers",
|
||||
"cmd.frame_member": "Ramdel",
|
||||
@@ -1269,7 +1271,7 @@
|
||||
"ui.agent_chat_pop_out": "Öppna i ett eget fönster som du kan flytta till en annan skärm",
|
||||
"ui.agent_chat_previous_picture": "Föregående bild",
|
||||
"ui.agent_chat_privacy": "Inget skickas någonstans utom till agenten du kör, under ditt eget konto.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft skickar ingenting någonstans: agenten du kör talar med sin egen tjänst under ditt eget konto. SolidCraft ser eller lagrar aldrig en nyckel eller ett lösenord.",
|
||||
"ui.agent_chat_privacy_note": "Chatten skickar inget någonstans av sig själv: agenten du kör talar med sin egen tjänst under ditt eget konto. SolidCraft ser eller lagrar aldrig en nyckel eller ett lösenord.",
|
||||
"ui.agent_chat_queue": "Kö",
|
||||
"ui.agent_chat_queue_full": "För många meddelanden väntar: ta bort ett eller vänta på agenten.",
|
||||
"ui.agent_chat_queued": "Köad",
|
||||
@@ -1453,6 +1455,7 @@
|
||||
"ui.command_done": "{command} klart",
|
||||
"ui.command_palette_hint": "Skriv ett kommando, t.ex. {example} — eller tryck på S för att söka",
|
||||
"ui.concentric": "Koncentrisk",
|
||||
"ui.cone": "Kon",
|
||||
"ui.conic_curve": "Konisk kurva",
|
||||
"ui.constant": "Konstant",
|
||||
"ui.constraints_conflict": "Villkor i konflikt",
|
||||
@@ -1511,6 +1514,11 @@
|
||||
"ui.draft": "Släppvinkel",
|
||||
"ui.drawing": "Ritning",
|
||||
"ui.drill_point": "Borrspets",
|
||||
"ui.easyprint_consent": "EasyPrint är Printables slicer i webbläsaren. För att öppna modellen där laddar SolidCraft upp den till Printables servrar. Länken upphör efter 30 minuter och du måste vara inloggad på Printables. Fortsätta?",
|
||||
"ui.easyprint_failed": "Uppladdningen till EasyPrint misslyckades: {error}",
|
||||
"ui.easyprint_opened": "Öppnad i EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, i webbläsaren)",
|
||||
"ui.easyprint_uploading": "Laddar upp till Printables…",
|
||||
"ui.edit": "Redigera",
|
||||
"ui.editJointLimits": "Redigera ledgränser",
|
||||
"ui.edit_section_analysis": "Redigera sektionsanalys",
|
||||
@@ -1567,6 +1575,7 @@
|
||||
"ui.heading_circular_pattern": "CIRKULÄRT MÖNSTER",
|
||||
"ui.heading_coil": "SPIRAL",
|
||||
"ui.heading_combine": "KOMBINERA",
|
||||
"ui.heading_cone": "KON",
|
||||
"ui.heading_configure": "KONFIGURERA",
|
||||
"ui.heading_constraints": "VILLKOR",
|
||||
"ui.heading_construct": "KONSTRUERA",
|
||||
@@ -1802,9 +1811,7 @@
|
||||
"ui.right": "Höger",
|
||||
"ui.save": "Spara",
|
||||
"ui.save_as": "Spara som",
|
||||
"ui.save_mesh_no_slicer": "Ingen slicer hittades: välj Egen… och välj ditt slicer-program, eller stäng av Skicka till slicer",
|
||||
"ui.save_mesh_pick_bodies": "Markera kropparna som ska sparas",
|
||||
"ui.save_mesh_temp_failed": "Kunde inte skapa {folder}: {error}",
|
||||
"ui.saved_to": "Sparade {path}",
|
||||
"ui.sbox_pin": "Fäst överst i S-rutan",
|
||||
"ui.sbox_unpin": "Lossa",
|
||||
@@ -1819,7 +1826,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+klick lägger till utan att ta bort",
|
||||
"ui.selection_shift_add_only_tip": "På: Shift lägger till objekt, Ctrl/Cmd växlar. Av (standard): Shift och Ctrl/Cmd växlar båda.",
|
||||
"ui.send": "Skicka",
|
||||
"ui.send_to_slicer": "Skicka till slicer",
|
||||
"ui.sent_to_slicer": "Skickat till {slicer}",
|
||||
"ui.sheet_gap": "Spalt",
|
||||
"ui.shell": "Urholka",
|
||||
@@ -1911,6 +1917,7 @@
|
||||
"ui.untitled": "Namnlös",
|
||||
"ui.up_axis_note": "Vilken axel som pekar uppåt i vyerna (axelvy, Hem, Titta på). Konstruktioner ändras inte.",
|
||||
"ui.updateToCurrentView": "Uppdatera till aktuell vy",
|
||||
"ui.upload": "Ladda upp",
|
||||
"ui.view_centre": "Vycentrum",
|
||||
"ui.visual_shaded_hidden_edges": "Skuggad med dolda kanter",
|
||||
"ui.visual_shaded_with_edges": "Skuggad med kanter",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "Tuvali sil",
|
||||
"cmd.canvas_edit": "Tuvali düzenle",
|
||||
"cmd.canvas_insert": "Tuval",
|
||||
"cmd.canvas_to_sketch": "Tuvalden eskiz",
|
||||
"cmd.component_activate": "Bileşeni etkinleştir",
|
||||
"cmd.component_break_link": "Bağı kopar",
|
||||
"cmd.component_capture_position": "Konumu yakala",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "Dışa aktar",
|
||||
"cmd.file_insert_mesh": "Ağ ekle",
|
||||
"cmd.file_insert_step": "STEP ekle",
|
||||
"cmd.file_open_in_easyprint": "EasyPrint ile aç",
|
||||
"cmd.file_save_mesh": "Ağ olarak kaydet",
|
||||
"cmd.file_slicers": "Yüklü dilimleyiciler",
|
||||
"cmd.frame_member": "Çerçeve Elemanı",
|
||||
@@ -1325,7 +1327,7 @@
|
||||
"ui.agent_chat_pop_out": "Başka bir ekrana taşıyabileceğiniz ayrı bir pencerede aç",
|
||||
"ui.agent_chat_previous_picture": "Önceki resim",
|
||||
"ui.agent_chat_privacy": "Kendi hesabınızla çalıştırdığınız ajan dışında hiçbir yere bir şey gitmez.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft hiçbir yere bir şey göndermez: Başlattığınız ajan, sizin hesabınızla kendi hizmetine bağlanır. SolidCraft hiçbir anahtarı veya parolayı görmez ve kaydetmez.",
|
||||
"ui.agent_chat_privacy_note": "Sohbet kendi başına hiçbir yere bir şey göndermez: Başlattığınız ajan, sizin hesabınızla kendi hizmetine bağlanır. SolidCraft hiçbir anahtarı veya parolayı görmez ve kaydetmez.",
|
||||
"ui.agent_chat_queue": "Sıraya al",
|
||||
"ui.agent_chat_queue_full": "Çok fazla ileti bekliyor: birini kaldırın veya ajanı bekleyin.",
|
||||
"ui.agent_chat_queued": "Sırada",
|
||||
@@ -1510,6 +1512,7 @@
|
||||
"ui.command_done": "{command} tamamlandı",
|
||||
"ui.command_palette_hint": "Bir komut yazın (ör. {example}) — veya aramak için S tuşuna basın",
|
||||
"ui.concentric": "Eş merkezli",
|
||||
"ui.cone": "Koni",
|
||||
"ui.conic_curve": "Konik eğri",
|
||||
"ui.constant": "Sabit",
|
||||
"ui.constraints_conflict": "Kısıtlamalar çakışıyor",
|
||||
@@ -1568,6 +1571,11 @@
|
||||
"ui.draft": "Koniklik",
|
||||
"ui.drawing": "Teknik resim",
|
||||
"ui.drill_point": "Matkap ucu",
|
||||
"ui.easyprint_consent": "EasyPrint, Printables'ın tarayıcıdaki dilimleyicisidir. Modeli orada açmak için SolidCraft onu Printables sunucularına yükler. Bağlantı 30 dakika sonra sona erer ve Printables'ta oturum açmış olmanız gerekir. Devam edilsin mi?",
|
||||
"ui.easyprint_failed": "EasyPrint'e yükleme başarısız: {error}",
|
||||
"ui.easyprint_opened": "EasyPrint içinde açıldı",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, tarayıcıda)",
|
||||
"ui.easyprint_uploading": "Printables'a yükleniyor…",
|
||||
"ui.edit": "Düzenle",
|
||||
"ui.editJointLimits": "Bağlantı sınırlarını düzenle",
|
||||
"ui.edit_section_analysis": "Kesit analizini düzenle",
|
||||
@@ -1624,6 +1632,7 @@
|
||||
"ui.heading_circular_pattern": "DAİRESEL DESEN",
|
||||
"ui.heading_coil": "HELEZON",
|
||||
"ui.heading_combine": "BİRLEŞTİR",
|
||||
"ui.heading_cone": "KONİ",
|
||||
"ui.heading_configure": "YAPILANDIR",
|
||||
"ui.heading_constraints": "KISITLAMALAR",
|
||||
"ui.heading_construct": "YAPI",
|
||||
@@ -1859,9 +1868,7 @@
|
||||
"ui.right": "Sağ",
|
||||
"ui.save": "Kaydet",
|
||||
"ui.save_as": "Farklı kaydet",
|
||||
"ui.save_mesh_no_slicer": "Dilimleyici bulunamadı: “Özel…” seçeneğini seçip dilimleyici programını belirtin veya “Dilimleyiciye gönder” seçeneğini kapatın",
|
||||
"ui.save_mesh_pick_bodies": "Kaydedilecek gövdeleri seçin",
|
||||
"ui.save_mesh_temp_failed": "{folder} oluşturulamadı: {error}",
|
||||
"ui.saved_to": "{path} konumuna kaydedildi",
|
||||
"ui.sbox_pin": "S kutusunun en üstüne sabitle",
|
||||
"ui.sbox_unpin": "Sabitlemeyi kaldır",
|
||||
@@ -1876,7 +1883,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+tıklama kaldırmadan ekler",
|
||||
"ui.selection_shift_add_only_tip": "Açık: Shift öğe ekler, Ctrl/Cmd açıp kapatır. Kapalı (varsayılan): Shift ve Ctrl/Cmd ikisi de açıp kapatır.",
|
||||
"ui.send": "Gönder",
|
||||
"ui.send_to_slicer": "Dilimleyiciye gönder",
|
||||
"ui.sent_to_slicer": "{slicer} uygulamasına gönderildi",
|
||||
"ui.sheet_gap": "Boşluk",
|
||||
"ui.shell": "Kabuk",
|
||||
@@ -1968,6 +1974,7 @@
|
||||
"ui.untitled": "Adsız",
|
||||
"ui.up_axis_note": "Görünümlerde (eksen görünümü, Başlangıç, Bak) hangi eksenin yukarıyı gösterdiğini belirler. Tasarımlar değişmez.",
|
||||
"ui.updateToCurrentView": "Geçerli görünüme güncelle",
|
||||
"ui.upload": "Yükle",
|
||||
"ui.view_centre": "Görünüm merkezi",
|
||||
"ui.visual_shaded_hidden_edges": "Gizli kenarlarla gölgeli",
|
||||
"ui.visual_shaded_with_edges": "Kenarlarla gölgeli",
|
||||
|
||||
@@ -612,6 +612,7 @@
|
||||
"cmd.canvas_delete": "Видалити полотно",
|
||||
"cmd.canvas_edit": "Редагувати полотно",
|
||||
"cmd.canvas_insert": "Полотно",
|
||||
"cmd.canvas_to_sketch": "Ескіз з полотна",
|
||||
"cmd.component_activate": "Активувати компонент",
|
||||
"cmd.component_break_link": "Розірвати зв'язок",
|
||||
"cmd.component_capture_position": "Зафіксувати положення",
|
||||
@@ -689,6 +690,7 @@
|
||||
"cmd.file_export": "Експорт",
|
||||
"cmd.file_insert_mesh": "Вставити сітку",
|
||||
"cmd.file_insert_step": "Вставити STEP",
|
||||
"cmd.file_open_in_easyprint": "Відкрити в EasyPrint",
|
||||
"cmd.file_save_mesh": "Зберегти як сітку",
|
||||
"cmd.file_slicers": "Встановлені слайсери",
|
||||
"cmd.frame_member": "Елемент рами",
|
||||
@@ -1619,7 +1621,7 @@
|
||||
"ui.agent_chat_pop_out": "Відкрити у власному вікні, яке можна перемістити на інший екран",
|
||||
"ui.agent_chat_previous_picture": "Попереднє зображення",
|
||||
"ui.agent_chat_privacy": "Нічого нікуди не надсилається, крім агента, який ви запускаєте, під вашим власним обліковим записом.",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft нічого нікуди не надсилає: агент, який ви запускаєте, спілкується з власним сервісом під вашим обліковим записом. SolidCraft ніколи не бачить і не зберігає ключ чи пароль.",
|
||||
"ui.agent_chat_privacy_note": "Чат сам нічого нікуди не надсилає: агент, який ви запускаєте, спілкується з власним сервісом під вашим обліковим записом. SolidCraft ніколи не бачить і не зберігає ключ чи пароль.",
|
||||
"ui.agent_chat_queue": "У чергу",
|
||||
"ui.agent_chat_queue_full": "Забагато повідомлень в очікуванні: приберіть одне або зачекайте на агента.",
|
||||
"ui.agent_chat_queued": "У черзі",
|
||||
@@ -1806,6 +1808,7 @@
|
||||
"ui.command_done": "{command} виконано",
|
||||
"ui.command_palette_hint": "Введіть команду, напр. {example} — або натисніть S для пошуку",
|
||||
"ui.concentric": "Концентричність",
|
||||
"ui.cone": "Конус",
|
||||
"ui.conic_curve": "Конічна крива",
|
||||
"ui.constant": "Стала",
|
||||
"ui.constraints_conflict": "Конфлікт обмежень",
|
||||
@@ -1864,6 +1867,11 @@
|
||||
"ui.draft": "Ухил",
|
||||
"ui.drawing": "Креслення",
|
||||
"ui.drill_point": "Вістря свердла",
|
||||
"ui.easyprint_consent": "EasyPrint — це слайсер Printables у браузері. Щоб відкрити модель там, SolidCraft завантажує її на сервери Printables. Посилання діє 30 хвилин, і вам потрібно увійти в Printables. Продовжити?",
|
||||
"ui.easyprint_failed": "Не вдалося завантажити в EasyPrint: {error}",
|
||||
"ui.easyprint_opened": "Відкрито в EasyPrint",
|
||||
"ui.easyprint_row": "EasyPrint (Printables, у браузері)",
|
||||
"ui.easyprint_uploading": "Завантаження на Printables…",
|
||||
"ui.edit": "Редагувати",
|
||||
"ui.editJointLimits": "Редагувати обмеження з'єднання",
|
||||
"ui.edit_section_analysis": "Редагувати аналіз перерізу",
|
||||
@@ -1920,6 +1928,7 @@
|
||||
"ui.heading_circular_pattern": "КРУГОВИЙ МАСИВ",
|
||||
"ui.heading_coil": "СПІРАЛЬ",
|
||||
"ui.heading_combine": "ОБ'ЄДНАТИ",
|
||||
"ui.heading_cone": "КОНУС",
|
||||
"ui.heading_configure": "НАЛАШТУВАТИ",
|
||||
"ui.heading_constraints": "ОБМЕЖЕННЯ",
|
||||
"ui.heading_construct": "ПОБУДОВА",
|
||||
@@ -2162,9 +2171,7 @@
|
||||
"ui.right": "Справа",
|
||||
"ui.save": "Зберегти",
|
||||
"ui.save_as": "Зберегти як",
|
||||
"ui.save_mesh_no_slicer": "Слайсер не знайдено: виберіть «Власна…» і вкажіть свою програму-слайсер або вимкніть «Надіслати в слайсер»",
|
||||
"ui.save_mesh_pick_bodies": "Виберіть тіла для збереження",
|
||||
"ui.save_mesh_temp_failed": "Не вдалося створити {folder}: {error}",
|
||||
"ui.saved_to": "Збережено {path}",
|
||||
"ui.sbox_pin": "Закріпити вгорі поля S",
|
||||
"ui.sbox_unpin": "Відкріпити",
|
||||
@@ -2179,7 +2186,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+клацання додає без вилучення",
|
||||
"ui.selection_shift_add_only_tip": "Увімк.: Shift додає елементи, Ctrl/Cmd перемикає. Вимк. (типово): Shift і Ctrl/Cmd обидва перемикають.",
|
||||
"ui.send": "Надіслати",
|
||||
"ui.send_to_slicer": "Надіслати в слайсер",
|
||||
"ui.sent_to_slicer": "Надіслано в {slicer}",
|
||||
"ui.sheet_gap": "Проміжок",
|
||||
"ui.shell": "Оболонка",
|
||||
@@ -2271,6 +2277,7 @@
|
||||
"ui.untitled": "Без назви",
|
||||
"ui.up_axis_note": "Яка вісь спрямована вгору у видах (вид осі, головний, наведення). Проєкти не змінюються.",
|
||||
"ui.updateToCurrentView": "Оновити до поточного виду",
|
||||
"ui.upload": "Завантажити",
|
||||
"ui.view_centre": "Центр виду",
|
||||
"ui.visual_shaded_hidden_edges": "Затінений із прихованими ребрами",
|
||||
"ui.visual_shaded_with_edges": "Затінений із ребрами",
|
||||
|
||||
@@ -456,6 +456,7 @@
|
||||
"cmd.canvas_delete": "刪除畫布",
|
||||
"cmd.canvas_edit": "編輯畫布",
|
||||
"cmd.canvas_insert": "畫布",
|
||||
"cmd.canvas_to_sketch": "畫布轉草圖",
|
||||
"cmd.component_activate": "啟用元件",
|
||||
"cmd.component_break_link": "斷開連結",
|
||||
"cmd.component_capture_position": "擷取位置",
|
||||
@@ -533,6 +534,7 @@
|
||||
"cmd.file_export": "匯出",
|
||||
"cmd.file_insert_mesh": "插入網格",
|
||||
"cmd.file_insert_step": "插入 STEP",
|
||||
"cmd.file_open_in_easyprint": "用 EasyPrint 開啟",
|
||||
"cmd.file_save_mesh": "另存為網格",
|
||||
"cmd.file_slicers": "已安裝的切片軟體",
|
||||
"cmd.frame_member": "框架構件",
|
||||
@@ -720,6 +722,7 @@
|
||||
"cmd.sketch_spline_fit_point": "擬合點雲形線",
|
||||
"cmd.sketch_spun_profile": "旋轉輪廓",
|
||||
"cmd.sketch_toggle_driven": "切換從動尺寸",
|
||||
"cmd.sketch_transform": "變換",
|
||||
"cmd.sketch_trim_path": "沿路徑修剪",
|
||||
"cmd.solid_align": "對齊",
|
||||
"cmd.solid_boundary_fill": "邊界填充",
|
||||
@@ -1269,7 +1272,7 @@
|
||||
"ui.agent_chat_pop_out": "在獨立視窗中開啟(可移到另一個螢幕)",
|
||||
"ui.agent_chat_previous_picture": "上一張圖片",
|
||||
"ui.agent_chat_privacy": "除了您以自己的帳號執行的代理之外,不會向任何地方傳送任何內容。",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft 不向任何地方傳送任何內容:您執行的代理以您自己的帳號與它自己的服務通訊。SolidCraft 從不檢視或儲存任何金鑰或密碼。",
|
||||
"ui.agent_chat_privacy_note": "聊天本身不向任何地方傳送任何內容:您執行的代理以您自己的帳號與它自己的服務通訊。SolidCraft 從不檢視或儲存任何金鑰或密碼。",
|
||||
"ui.agent_chat_queue": "加入佇列",
|
||||
"ui.agent_chat_queue_full": "等待中的訊息太多:請移除一則,或等待代理完成。",
|
||||
"ui.agent_chat_queued": "已加入佇列",
|
||||
@@ -1453,6 +1456,7 @@
|
||||
"ui.command_done": "{command} 完成",
|
||||
"ui.command_palette_hint": "輸入命令,例如 {example} — 或按 S 搜尋",
|
||||
"ui.concentric": "同心",
|
||||
"ui.cone": "圓錐體",
|
||||
"ui.conic_curve": "圓錐曲線",
|
||||
"ui.constant": "恆定",
|
||||
"ui.constraints_conflict": "拘束條件衝突",
|
||||
@@ -1511,6 +1515,11 @@
|
||||
"ui.draft": "拔模",
|
||||
"ui.drawing": "工程圖",
|
||||
"ui.drill_point": "鑽尖",
|
||||
"ui.easyprint_consent": "EasyPrint 是 Printables 在瀏覽器中的切片軟體。為了在那裡開啟模型,SolidCraft 會將模型上傳到 Printables 的伺服器。連結 30 分鐘後失效,且您需要登入 Printables。要繼續嗎?",
|
||||
"ui.easyprint_failed": "上傳到 EasyPrint 失敗:{error}",
|
||||
"ui.easyprint_opened": "已在 EasyPrint 中開啟",
|
||||
"ui.easyprint_row": "EasyPrint(Printables,在瀏覽器中)",
|
||||
"ui.easyprint_uploading": "正在上傳到 Printables…",
|
||||
"ui.edit": "編輯",
|
||||
"ui.editJointLimits": "編輯接頭限制",
|
||||
"ui.edit_section_analysis": "編輯截面分析",
|
||||
@@ -1567,6 +1576,7 @@
|
||||
"ui.heading_circular_pattern": "圓形陣列",
|
||||
"ui.heading_coil": "螺旋",
|
||||
"ui.heading_combine": "結合",
|
||||
"ui.heading_cone": "圓錐體",
|
||||
"ui.heading_configure": "組態",
|
||||
"ui.heading_constraints": "拘束條件",
|
||||
"ui.heading_construct": "建構",
|
||||
@@ -1802,9 +1812,7 @@
|
||||
"ui.right": "右視",
|
||||
"ui.save": "儲存",
|
||||
"ui.save_as": "另存為",
|
||||
"ui.save_mesh_no_slicer": "未找到切片軟體:請選擇「自訂…」並指定切片軟體程式,或關閉「傳送到切片軟體」",
|
||||
"ui.save_mesh_pick_bodies": "請選擇要儲存的實體",
|
||||
"ui.save_mesh_temp_failed": "無法建立 {folder}:{error}",
|
||||
"ui.saved_to": "已儲存:{path}",
|
||||
"ui.sbox_pin": "固定到 S 框頂部",
|
||||
"ui.sbox_unpin": "取消固定",
|
||||
@@ -1819,7 +1827,6 @@
|
||||
"ui.selection_shift_add_only": "Shift+點擊僅新增、不取消選擇",
|
||||
"ui.selection_shift_add_only_tip": "開:Shift 新增,Ctrl/Cmd 切換。關(預設):Shift 與 Ctrl/Cmd 均可切換。",
|
||||
"ui.send": "傳送",
|
||||
"ui.send_to_slicer": "傳送到切片軟體",
|
||||
"ui.sent_to_slicer": "已傳送到 {slicer}",
|
||||
"ui.sheet_gap": "間隙",
|
||||
"ui.shell": "薄殼",
|
||||
@@ -1911,6 +1918,7 @@
|
||||
"ui.untitled": "未命名",
|
||||
"ui.up_axis_note": "檢視中朝上的軸(軸檢視、主檢視、注視)。設計本身不變。",
|
||||
"ui.updateToCurrentView": "更新為目前檢視",
|
||||
"ui.upload": "上傳",
|
||||
"ui.view_centre": "檢視中心",
|
||||
"ui.visual_shaded_hidden_edges": "含隱藏邊的著色",
|
||||
"ui.visual_shaded_with_edges": "含邊線的著色",
|
||||
|
||||
@@ -430,6 +430,8 @@
|
||||
"Remove this point": "移除此点",
|
||||
"Rename Feature": "重命名特征",
|
||||
"Rename Folder": "重命名文件夹",
|
||||
"Reopen designs": "重新打开设计",
|
||||
"Reopen the designs that were open when SolidCraft was last closed": "重新打开上次关闭 SolidCraft 时打开的设计",
|
||||
"Replace existing library file": "替换现有库文件",
|
||||
"Report a bug — review a screenshot and open a GitHub draft": "报告缺陷 — 复核截图并开启 GitHub 草稿",
|
||||
"Report text copied. Paste it into GitHub and submit your report there.": "报告文本已复制。请在GitHub中粘贴并提交。",
|
||||
@@ -692,10 +694,12 @@
|
||||
"cmd.canvas_delete": "删除画布",
|
||||
"cmd.canvas_edit": "编辑画布",
|
||||
"cmd.canvas_insert": "画布",
|
||||
"cmd.canvas_to_sketch": "画布转草图",
|
||||
"cmd.component_activate": "激活组件",
|
||||
"cmd.component_break_link": "断开链接",
|
||||
"cmd.component_capture_position": "捕获位置",
|
||||
"cmd.component_copies": "查找相同副本",
|
||||
"cmd.component_copy_bodies": "粘贴实体",
|
||||
"cmd.component_create": "新建组件",
|
||||
"cmd.component_cycle_color": "循环切换组件颜色",
|
||||
"cmd.component_delete": "删除组件",
|
||||
@@ -774,7 +778,9 @@
|
||||
"cmd.file_export": "导出",
|
||||
"cmd.file_insert_mesh": "插入网格",
|
||||
"cmd.file_insert_step": "插入 STEP",
|
||||
"cmd.file_open_in_easyprint": "用 EasyPrint 打开",
|
||||
"cmd.file_save_mesh": "另存为网格",
|
||||
"cmd.file_send_to_slicer": "发送到切片软件",
|
||||
"cmd.file_slicers": "已安装的切片软件",
|
||||
"cmd.frame_member": "框架构件",
|
||||
"cmd.frame_profile_insert": "插入型材",
|
||||
@@ -846,6 +852,7 @@
|
||||
"cmd.occurrence_ground": "固定",
|
||||
"cmd.occurrence_move": "移动实例",
|
||||
"cmd.occurrence_properties": "实例物理属性",
|
||||
"cmd.occurrence_reparent": "移动到组件",
|
||||
"cmd.parameters_add": "添加用户参数",
|
||||
"cmd.parameters_change": "更改参数",
|
||||
"cmd.parameters_comment": "注释参数",
|
||||
@@ -954,6 +961,7 @@
|
||||
"cmd.sketch_drag": "拖动草图几何",
|
||||
"cmd.sketch_drag_end": "结束草图拖动",
|
||||
"cmd.sketch_draw_dimensioned": "绘制带尺寸的草图图元",
|
||||
"cmd.sketch_drawing_bounds": "图纸尺寸",
|
||||
"cmd.sketch_edit": "编辑草图",
|
||||
"cmd.sketch_edit_text": "编辑文字",
|
||||
"cmd.sketch_export_dxf": "另存为 DXF",
|
||||
@@ -1000,6 +1008,7 @@
|
||||
"cmd.sketch_spline_fit_point": "拟合点样条曲线",
|
||||
"cmd.sketch_spun_profile": "旋转轮廓",
|
||||
"cmd.sketch_toggle_driven": "切换被驱动尺寸",
|
||||
"cmd.sketch_transform": "变换",
|
||||
"cmd.sketch_trim_path": "沿路径修剪",
|
||||
"cmd.solid_align": "对齐",
|
||||
"cmd.solid_boundary_fill": "边界填充",
|
||||
@@ -1371,6 +1380,7 @@
|
||||
"nav.blender.help": "中键拖动旋转,Shift+中键拖动平移,Ctrl+中键拖动缩放;右键拖动不会移动相机",
|
||||
"nav.free_orbit.help": "鼠标和3D鼠标可保持滚转并越过两个极点旋转。Shift轴吸附将被禁用。",
|
||||
"nav.free_orbit.label": "自由旋转",
|
||||
"nav.free_orbit.snap_help": "鼠标和3D鼠标可越过两极旋转,并保留画面旋转角度。在拖动环绕时按住Shift,可吸附视图方向和画面旋转角度。",
|
||||
"nav.fusion.help": "中键拖动平移,Shift+中键拖动旋转,右键拖动旋转",
|
||||
"nav.inventor.help": "中键拖动平移,Shift+中键拖动旋转,Ctrl+中键拖动缩放,右键拖动旋转",
|
||||
"nav.openscad.help": "在空白处左键拖动旋转,Shift+左键拖动框选,拖动草图几何可编辑;右键或中键拖动平移,Shift+右键或 Shift+中键拖动缩放",
|
||||
@@ -1445,6 +1455,7 @@
|
||||
"prefs.design": "设计默认值",
|
||||
"prefs.design_note": "新建设计的初始设置。已打开的设计保留各自的设置。",
|
||||
"prefs.devices": "3D 鼠标",
|
||||
"prefs.dialog_buttons": "对话框按钮",
|
||||
"prefs.display": "显示",
|
||||
"prefs.edges": "边缘与着色",
|
||||
"prefs.extend": "扩展",
|
||||
@@ -1467,6 +1478,12 @@
|
||||
"prefs.graphics_note": "在速度与平滑度之间取舍。每个选项均以示例展示效果。",
|
||||
"prefs.graphics_presets_note": "预设会同时设置以下选项。",
|
||||
"prefs.history": "历史",
|
||||
"prefs.history_origin_bodies": "在顶部显示原点和实体",
|
||||
"prefs.history_origin_bodies_note": "左侧或浏览器下方的历史以活动组件的实体文件夹和原点开头,并带有与浏览器相同的显示切换和菜单。底部的历史保持不变。",
|
||||
"prefs.history_sketches": "草图",
|
||||
"prefs.history_sketches_in_order": "按创建顺序",
|
||||
"prefs.history_sketches_note": "草图:选择“位于其特征下”后,左侧历史在宽度足以显示名称时,会将草图缩进一级,列在由其创建的第一个特征下。底部时间轴始终保持创建顺序。",
|
||||
"prefs.history_sketches_under_feature": "位于其特征下",
|
||||
"prefs.hold_stop_snapping": "按住以停用吸附",
|
||||
"prefs.icons_only": "仅图标",
|
||||
"prefs.imported_file": "已导入文件",
|
||||
@@ -1543,6 +1560,7 @@
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
"prefs.toggle_item": "添加项目,或取消选择",
|
||||
"prefs.toolbar": "工具栏",
|
||||
"prefs.top": "顶部",
|
||||
"prefs.translucent": "半透明",
|
||||
"prefs.under_browser": "浏览器下方",
|
||||
"prefs.under_cursor": "光标下方",
|
||||
@@ -2477,7 +2495,7 @@
|
||||
"ui.agent_chat_pop_out": "在独立窗口中打开(可移到另一个屏幕)",
|
||||
"ui.agent_chat_previous_picture": "上一张图片",
|
||||
"ui.agent_chat_privacy": "除了你以自己的账户运行的智能体之外,不会向任何地方发送任何内容。",
|
||||
"ui.agent_chat_privacy_note": "SolidCraft 不向任何地方发送任何内容:你运行的智能体以你自己的账户与它自己的服务通信。SolidCraft 从不查看或保存任何密钥或密码。",
|
||||
"ui.agent_chat_privacy_note": "聊天本身不向任何地方发送任何内容:你运行的智能体以你自己的账户与它自己的服务通信。SolidCraft 从不查看或保存任何密钥或密码。",
|
||||
"ui.agent_chat_provider_models": "{count} 个模型",
|
||||
"ui.agent_chat_provider_other": "其他",
|
||||
"ui.agent_chat_provider_tip": "旁边的列表显示此提供商的模型。",
|
||||
@@ -2695,6 +2713,7 @@
|
||||
"ui.command_done": "{command} 完成",
|
||||
"ui.command_palette_hint": "输入命令,例如 {example} — 或按 S 搜索",
|
||||
"ui.concentric": "同心",
|
||||
"ui.cone": "圆锥体",
|
||||
"ui.conic_curve": "圆锥曲线",
|
||||
"ui.constant": "恒定",
|
||||
"ui.constraints_conflict": "约束冲突",
|
||||
@@ -2755,6 +2774,11 @@
|
||||
"ui.draft": "拔模",
|
||||
"ui.drawing": "绘图",
|
||||
"ui.drill_point": "钻尖",
|
||||
"ui.easyprint_consent": "EasyPrint 是 Printables 在浏览器中的切片软件。为了在那里打开模型,SolidCraft 会将模型上传到 Printables 的服务器。链接 30 分钟后失效,并且你需要登录 Printables。要继续吗?",
|
||||
"ui.easyprint_failed": "上传到 EasyPrint 失败:{error}",
|
||||
"ui.easyprint_opened": "已在 EasyPrint 中打开",
|
||||
"ui.easyprint_row": "EasyPrint(Printables,在浏览器中)",
|
||||
"ui.easyprint_uploading": "正在上传到 Printables…",
|
||||
"ui.edit": "编辑",
|
||||
"ui.editJointLimits": "编辑接头限制",
|
||||
"ui.edit_section_analysis": "编辑截面分析",
|
||||
@@ -2817,6 +2841,7 @@
|
||||
"ui.heading_circular_pattern": "圆形阵列",
|
||||
"ui.heading_coil": "螺旋",
|
||||
"ui.heading_combine": "组合",
|
||||
"ui.heading_cone": "圆锥体",
|
||||
"ui.heading_configure": "配置",
|
||||
"ui.heading_constraints": "约束",
|
||||
"ui.heading_construct": "构造",
|
||||
@@ -2897,6 +2922,7 @@
|
||||
"ui.history_names": "名称",
|
||||
"ui.history_play": "播放至末尾",
|
||||
"ui.history_show_names": "显示名称",
|
||||
"ui.history_sketch_listed_under": "列在 {feature} 下",
|
||||
"ui.history_title": "历史",
|
||||
"ui.history_toggle_names": "显示或隐藏特征名称",
|
||||
"ui.hole": "孔",
|
||||
@@ -3085,9 +3111,8 @@
|
||||
"ui.right": "右视",
|
||||
"ui.save": "保存",
|
||||
"ui.save_as": "另存为",
|
||||
"ui.save_mesh_no_slicer": "未找到切片软件:请选择“自定义…”并指定切片软件程序,或关闭“发送到切片软件”",
|
||||
"ui.save_mesh_no_slicer_open": "未找到切片软件:请选择“自定义…”并指定切片软件程序",
|
||||
"ui.save_mesh_pick_bodies": "请选择要保存的实体",
|
||||
"ui.save_mesh_temp_failed": "无法创建 {folder}:{error}",
|
||||
"ui.saved_to": "已保存:{path}",
|
||||
"ui.sbox_pin": "固定到 S 框顶部",
|
||||
"ui.sbox_unpin": "取消固定",
|
||||
@@ -3102,8 +3127,9 @@
|
||||
"ui.selection_shift_add_only": "Shift+点击仅添加、不取消选择",
|
||||
"ui.selection_shift_add_only_tip": "开:Shift 添加,Ctrl/Cmd 切换。关(默认):Shift 与 Ctrl/Cmd 均可切换。",
|
||||
"ui.send": "发送",
|
||||
"ui.send_to_slicer": "发送到切片软件",
|
||||
"ui.send_to_slicer_unavailable": "“发送到切片软件”需要桌面应用:浏览器无法启动程序",
|
||||
"ui.sent_to_slicer": "已发送到 {slicer}",
|
||||
"ui.sent_to_slicer_copy": "已作为 {file} 发送到 {slicer},因为第一个文件仍在其中打开",
|
||||
"ui.sheet_gap": "间隙",
|
||||
"ui.shell": "抽壳",
|
||||
"ui.shortcut_capture_add_key": "请再按一个普通键。按 Esc 取消。",
|
||||
@@ -3138,6 +3164,15 @@
|
||||
"ui.slice": "切片",
|
||||
"ui.slicer": "切片软件",
|
||||
"ui.slicer_delete_tip": "{program}\n按 Delete 键从列表中移除",
|
||||
"ui.slicer_lost": "{slicer} 已启动,但无法再次找到它来发送文件。文件已保存到 {path}:请从该位置打开。",
|
||||
"ui.slicer_no_file": "{slicer} 已就绪,但没有可发送给它的文件。",
|
||||
"ui.slicer_no_response": "{slicer} 未在规定时间内响应。文件已保存到 {path}:切片软件启动后请再次发送。",
|
||||
"ui.slicer_resend_hint": "再次发送会替换切片软件中已打开的文件。",
|
||||
"ui.slicer_single_instance": "在已运行的切片软件中打开文件",
|
||||
"ui.slicer_single_instance_tip": "PrusaSlicer、SuperSlicer 和 Cura 会在已打开的窗口中加载文件,而不是再启动一个。其他切片软件按原样启动。PrusaSlicer 3.0 始终如此:其启动器无法以其他方式接收文件。",
|
||||
"ui.slicer_starting": "正在启动 {slicer}…",
|
||||
"ui.slicer_still_starting": "仍在等待 {slicer} 启动。没有发送其他内容。",
|
||||
"ui.slicer_stl_fallback": "无法写入 3MF,因此发送的是 STL。",
|
||||
"ui.slot": "槽",
|
||||
"ui.smooth": "平滑",
|
||||
"ui.smooth_wheel_zoom": "平滑滚轮缩放",
|
||||
@@ -3205,6 +3240,7 @@
|
||||
"ui.untitled": "无标题",
|
||||
"ui.up_axis_note": "视图中朝上的轴(轴视图、主视图、注视)。设计本身不变。",
|
||||
"ui.updateToCurrentView": "更新为当前视图",
|
||||
"ui.upload": "上传",
|
||||
"ui.vertical_constraint": "垂直约束",
|
||||
"ui.view_centre": "视图中心",
|
||||
"ui.viewport_theme_follow": "跟随主题",
|
||||
|
||||
@@ -37,6 +37,15 @@ fn clipped(p: vec3<f32>) -> bool {
|
||||
return length(u.clip.xyz) > 0.5 && dot(u.clip.xyz, p) > u.clip.w;
|
||||
}
|
||||
@group(0) @binding(0) var<uniform> u: U;
|
||||
// The scene's colours, 2048 entries to a row: an R32Uint texture rather than a storage buffer,
|
||||
// so the viewport draws on WebGL2 (which has no storage buffers) as well.
|
||||
@group(0) @binding(1) var pal: texture_2d<u32>;
|
||||
|
||||
/// The colour at palette entry `i`, as byte/255 (the old Unorm8x4 conversion, bit for bit).
|
||||
fn palette(i: u32) -> vec4<f32> {
|
||||
let v = textureLoad(pal, vec2<i32>(i32(i & 2047u), i32(i >> 11u)), 0).x;
|
||||
return vec4<f32>(f32(v & 255u), f32((v >> 8u) & 255u), f32((v >> 16u) & 255u), f32((v >> 24u) & 255u)) / 255.0;
|
||||
}
|
||||
|
||||
fn to_linear(c: vec3<f32>) -> vec3<f32> {
|
||||
return select(c, pow((c + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4)), c > vec3<f32>(0.04045));
|
||||
@@ -57,11 +66,11 @@ struct TOut {
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_tri(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: vec4<f32>) -> TOut {
|
||||
fn vs_tri(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: u32) -> TOut {
|
||||
var o: TOut;
|
||||
o.pos = u.vp * vec4<f32>(p, 1.0);
|
||||
o.n = n;
|
||||
o.c = c;
|
||||
o.c = palette(c);
|
||||
o.wp = p;
|
||||
return o;
|
||||
}
|
||||
@@ -76,14 +85,14 @@ fn vs_tri_inst(
|
||||
@location(3) r0: vec4<f32>,
|
||||
@location(4) r1: vec4<f32>,
|
||||
@location(5) r2: vec4<f32>,
|
||||
@location(6) c: vec4<f32>,
|
||||
@location(6) c: u32,
|
||||
) -> TOut {
|
||||
let q = vec4<f32>(p, 1.0);
|
||||
let wp = vec3<f32>(dot(r0, q), dot(r1, q), dot(r2, q));
|
||||
var o: TOut;
|
||||
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.c = c;
|
||||
o.c = palette(c);
|
||||
o.wp = wp;
|
||||
return o;
|
||||
}
|
||||
@@ -106,14 +115,14 @@ fn vs_tri_inst_flat(
|
||||
@location(3) r0: vec4<f32>,
|
||||
@location(4) r1: vec4<f32>,
|
||||
@location(5) r2: vec4<f32>,
|
||||
@location(6) c: vec4<f32>,
|
||||
@location(6) c: u32,
|
||||
) -> FOut {
|
||||
let q = vec4<f32>(p, 1.0);
|
||||
let wp = vec3<f32>(dot(r0, q), dot(r1, q), dot(r2, q));
|
||||
var o: FOut;
|
||||
o.pos = u.vp * vec4<f32>(wp, 1.0);
|
||||
o.rp = wp - u.eye.xyz;
|
||||
o.c = c;
|
||||
o.c = palette(c);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -546,7 +555,7 @@ fn lifted_z(p: vec3<f32>, c: vec4<f32>, px: f32, half: vec2<f32>) -> f32 {
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3<f32>, @location(1) b: vec3<f32>, @location(2) c: vec4<f32>, @location(3) wbits: u32) -> LOut {
|
||||
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 {
|
||||
var pa = a;
|
||||
var pb = b;
|
||||
var ca = u.vp * vec4<f32>(a, 1.0);
|
||||
@@ -596,7 +605,8 @@ fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3<f32>, @location(
|
||||
}
|
||||
var o: LOut;
|
||||
o.pos = vec4<f32>(base.xy + off / half * base.w, z, base.w);
|
||||
o.c = vec4<f32>(c.rgb, c.a * (wa / wd));
|
||||
let col = palette(c);
|
||||
o.c = vec4<f32>(col.rgb, col.a * (wa / wd));
|
||||
o.wp = select(pa, pb, t > 0.5);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,21 @@ pub(crate) fn closest_on_triangle(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Only an appearance change is appearance-only: with any other field changed too, or with
|
||||
/// nothing changed, the model must be evaluated as usual.
|
||||
#[test]
|
||||
fn appearance_only_changes_are_proved_on_the_whole_document() {
|
||||
let before = crate::Document::default();
|
||||
let mut painted = before.clone();
|
||||
painted.appearances.bodies.insert("Body1".into(), Look::custom([255, 0, 0], 0.5));
|
||||
assert!(painted.differs_only_in_appearance(&before));
|
||||
assert!(before.differs_only_in_appearance(&painted), "clearing it too");
|
||||
assert!(!before.differs_only_in_appearance(&before.clone()), "nothing changed");
|
||||
let mut mixed = painted.clone();
|
||||
mixed.materials.insert("Body1".into(), "Steel".into());
|
||||
assert!(!mixed.differs_only_in_appearance(&before), "a material change is not appearance-only");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn library_and_materials() {
|
||||
assert_eq!(library("abs - red").map(|l| l.color), Some([196, 40, 40]));
|
||||
|
||||
@@ -272,6 +272,12 @@ impl Document {
|
||||
Some((c, m))
|
||||
}
|
||||
|
||||
/// World transform of one placement of `component`: the occurrence `path` from the root
|
||||
/// (empty for the root itself). `None` when the path no longer leads to that component.
|
||||
pub fn path_transform(&self, component: u64, path: &[u64]) -> Option<Mat> {
|
||||
self.path_placement(path).and_then(|(c, m)| (c == component).then_some(m))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn placements(&self) -> Vec<(u64, Vec<u64>, Mat)> {
|
||||
@@ -307,9 +313,10 @@ impl Document {
|
||||
pub fn body_frames_with_source(&self, st: &crate::ModelState) -> Vec<(String, String, u64, Vec<u64>, Mat)> {
|
||||
let mut out = Vec::new();
|
||||
let owners = self.model_body_components(st);
|
||||
let names = WorldNames::new(self, st);
|
||||
for (comp, path, m) in self.placements() {
|
||||
for (b, _) in st.bodies.iter().zip(&owners).filter(|(_, owner)| **owner == comp) {
|
||||
let name = self.world_body_name(st, &b.name, comp, &path);
|
||||
let name = names.name(&b.name, comp, &path);
|
||||
let w = match self.model_body_offset(b) {
|
||||
Some(o) => mat_mul(&m, o),
|
||||
None => m,
|
||||
@@ -322,38 +329,10 @@ impl Document {
|
||||
|
||||
/// Computed placement name. Source names retain priority; ambiguous labels use full IDs.
|
||||
/// Resolves label ambiguity with bounded dynamic counts instead of expanding all placements.
|
||||
/// To name many placed bodies, make one [`WorldNames`] and ask it for each.
|
||||
pub fn world_body_name(&self, st: &crate::ModelState, source: &str, component: u64, path: &[u64]) -> String {
|
||||
if path == self.first_path(component) {
|
||||
return source.to_string();
|
||||
}
|
||||
let names: Vec<&str> = path.iter().filter_map(|id| self.occurrences.iter().find(|o| o.id == *id)).map(|o| o.name.as_str()).collect();
|
||||
let label = names.join("/");
|
||||
let candidate = format!("{source} ({label})");
|
||||
let mut counts = std::collections::BTreeMap::from([(0, 1usize)]);
|
||||
for name in &names {
|
||||
let mut next = std::collections::BTreeMap::<u64, usize>::new();
|
||||
for o in self.occurrences.iter().filter(|o| o.name == *name) {
|
||||
if let Some(count) = counts.get(&o.parent) {
|
||||
let n = next.entry(o.component).or_default();
|
||||
*n = (*n + count).min(2);
|
||||
}
|
||||
}
|
||||
counts = next;
|
||||
}
|
||||
let ambiguous = counts.get(&component).copied().unwrap_or(0) > 1;
|
||||
// Slash and reserved-marker names could alias concatenated labels or fallback keys.
|
||||
let reserved =
|
||||
self.occurrences.iter().any(|o| o.name.contains('/') || o.name.contains("@occ[")) || st.bodies.iter().any(|b| b.name.contains("@occ["));
|
||||
if !ambiguous && !reserved && st.body(&candidate).is_none() {
|
||||
return candidate;
|
||||
}
|
||||
let ids = path.iter().map(u64::to_string).collect::<Vec<_>>().join("/");
|
||||
let encoded = serde_json::to_string(source).unwrap_or_else(|_| source.to_string());
|
||||
let mut key = format!("{encoded}@occ[{ids}]");
|
||||
while st.body(&key).is_some() {
|
||||
key.push('#');
|
||||
}
|
||||
key
|
||||
// One name: scanning the design once is cheaper than indexing it first.
|
||||
placed_name(&Scan { doc: self, st }, source, component, path)
|
||||
}
|
||||
|
||||
/// The occurrence path of a component's first placement.
|
||||
@@ -389,12 +368,36 @@ impl Document {
|
||||
false
|
||||
}
|
||||
|
||||
/// Would putting a placement of `component` into `parent` put it inside itself: is `parent`
|
||||
/// the component, or inside it, created in it or placed in it at any depth? Placements can sit
|
||||
/// apart from where their component was created ([`Self::component_within`] follows only
|
||||
/// that), so both are followed.
|
||||
pub fn inside_component(&self, parent: u64, component: u64) -> bool {
|
||||
if self.component_within(parent, component) {
|
||||
return true;
|
||||
}
|
||||
let mut stack = vec![component];
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
while let Some(c) = stack.pop() {
|
||||
if !seen.insert(c) || seen.len() > MAX_PLACEMENTS {
|
||||
continue;
|
||||
}
|
||||
for o in self.occurrences.iter().filter(|o| o.parent == c) {
|
||||
if o.component == parent {
|
||||
return true;
|
||||
}
|
||||
stack.push(o.component);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Add an occurrence of an existing component into `parent`.
|
||||
pub fn add_occurrence(&mut self, component: u64, parent: u64, transform: Mat) -> Result<u64> {
|
||||
if component == 0 || !self.components.iter().any(|c| c.id == component) {
|
||||
return Err(DocError::Unknown(format!("component {component}")));
|
||||
}
|
||||
if self.component_within(parent, component) {
|
||||
if self.inside_component(parent, component) {
|
||||
return Err(DocError::Invalid("a component can't contain itself".into()));
|
||||
}
|
||||
if self.occurrences.len() >= 100_000 {
|
||||
@@ -408,3 +411,140 @@ impl Document {
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// The names of a design's placed bodies ([`Document::world_body_name`]), with what naming one
|
||||
/// needs from the whole design worked out once: the occurrences by id, by name and by
|
||||
/// component, the model's body names, and whether any name could alias a label or fallback key.
|
||||
/// Naming each body of an assembly is then as cheap as its occurrence path, not a scan of the
|
||||
/// whole design.
|
||||
pub struct WorldNames<'a> {
|
||||
/// The first occurrence with each id.
|
||||
by_id: std::collections::HashMap<u64, &'a crate::Occurrence>,
|
||||
/// The occurrences with each name, in document order.
|
||||
by_name: std::collections::HashMap<&'a str, Vec<&'a crate::Occurrence>>,
|
||||
/// Each component's first occurrence ([`Document::occurrence_of`]).
|
||||
first_of: std::collections::HashMap<u64, &'a crate::Occurrence>,
|
||||
bodies: std::collections::HashSet<&'a str>,
|
||||
/// Some occurrence or body name has a slash or the fallback key's marker.
|
||||
reserved: bool,
|
||||
}
|
||||
|
||||
impl<'a> WorldNames<'a> {
|
||||
pub fn new(doc: &'a Document, st: &'a crate::ModelState) -> WorldNames<'a> {
|
||||
let mut by_id = std::collections::HashMap::with_capacity(doc.occurrences.len());
|
||||
let mut by_name: std::collections::HashMap<&str, Vec<&crate::Occurrence>> = std::collections::HashMap::new();
|
||||
let mut first_of = std::collections::HashMap::new();
|
||||
for o in &doc.occurrences {
|
||||
by_id.entry(o.id).or_insert(o);
|
||||
by_name.entry(o.name.as_str()).or_default().push(o);
|
||||
first_of.entry(o.component).or_insert(o);
|
||||
}
|
||||
let reserved =
|
||||
doc.occurrences.iter().any(|o| o.name.contains('/') || o.name.contains("@occ[")) || st.bodies.iter().any(|b| b.name.contains("@occ["));
|
||||
let bodies = st.bodies.iter().map(|b| b.name.as_str()).collect();
|
||||
WorldNames { by_id, by_name, first_of, bodies, reserved }
|
||||
}
|
||||
|
||||
/// The world name of the body `source` of `component` placed through `path`.
|
||||
pub fn name(&self, source: &str, component: u64, path: &[u64]) -> String {
|
||||
placed_name(self, source, component, path)
|
||||
}
|
||||
}
|
||||
|
||||
/// What naming a placed body looks up in the design: by scanning it ([`Scan`], for one name)
|
||||
/// or through its indexes ([`WorldNames`], for many).
|
||||
trait NameLookup {
|
||||
fn occurrence(&self, id: u64) -> Option<&crate::Occurrence>;
|
||||
fn each_named(&self, name: &str, f: &mut dyn FnMut(&crate::Occurrence));
|
||||
fn first_of(&self, component: u64) -> Option<&crate::Occurrence>;
|
||||
fn reserved(&self) -> bool;
|
||||
fn has_body(&self, name: &str) -> bool;
|
||||
}
|
||||
|
||||
/// Lookups by scanning the design.
|
||||
struct Scan<'a> {
|
||||
doc: &'a Document,
|
||||
st: &'a crate::ModelState,
|
||||
}
|
||||
|
||||
impl NameLookup for Scan<'_> {
|
||||
fn occurrence(&self, id: u64) -> Option<&crate::Occurrence> {
|
||||
self.doc.occurrences.iter().find(|o| o.id == id)
|
||||
}
|
||||
fn each_named(&self, name: &str, f: &mut dyn FnMut(&crate::Occurrence)) {
|
||||
self.doc.occurrences.iter().filter(|o| o.name == name).for_each(f);
|
||||
}
|
||||
fn first_of(&self, component: u64) -> Option<&crate::Occurrence> {
|
||||
self.doc.occurrence_of(component)
|
||||
}
|
||||
fn reserved(&self) -> bool {
|
||||
self.doc.occurrences.iter().any(|o| o.name.contains('/') || o.name.contains("@occ["))
|
||||
|| self.st.bodies.iter().any(|b| b.name.contains("@occ["))
|
||||
}
|
||||
fn has_body(&self, name: &str) -> bool {
|
||||
self.st.body(name).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl NameLookup for WorldNames<'_> {
|
||||
fn occurrence(&self, id: u64) -> Option<&crate::Occurrence> {
|
||||
self.by_id.get(&id).copied()
|
||||
}
|
||||
fn each_named(&self, name: &str, f: &mut dyn FnMut(&crate::Occurrence)) {
|
||||
self.by_name.get(name).into_iter().flatten().copied().for_each(f);
|
||||
}
|
||||
fn first_of(&self, component: u64) -> Option<&crate::Occurrence> {
|
||||
self.first_of.get(&component).copied()
|
||||
}
|
||||
fn reserved(&self) -> bool {
|
||||
self.reserved
|
||||
}
|
||||
fn has_body(&self, name: &str) -> bool {
|
||||
self.bodies.contains(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// The world name of the body `source` of `component` placed through `path` (see
|
||||
/// [`Document::world_body_name`]).
|
||||
fn placed_name(l: &impl NameLookup, source: &str, component: u64, path: &[u64]) -> String {
|
||||
// The occurrence path of the component's first placement ([`Document::first_path`]).
|
||||
let mut first = Vec::new();
|
||||
let mut c = component;
|
||||
for _ in 0..1000 {
|
||||
if c == 0 {
|
||||
break;
|
||||
}
|
||||
let Some(o) = l.first_of(c) else { break };
|
||||
first.insert(0, o.id);
|
||||
c = o.parent;
|
||||
}
|
||||
if path == first {
|
||||
return source.to_string();
|
||||
}
|
||||
let names: Vec<&str> = path.iter().filter_map(|id| l.occurrence(*id)).map(|o| o.name.as_str()).collect();
|
||||
let label = names.join("/");
|
||||
let candidate = format!("{source} ({label})");
|
||||
let mut counts = std::collections::BTreeMap::from([(0, 1usize)]);
|
||||
for name in &names {
|
||||
let mut next = std::collections::BTreeMap::<u64, usize>::new();
|
||||
l.each_named(name, &mut |o| {
|
||||
if let Some(count) = counts.get(&o.parent) {
|
||||
let n = next.entry(o.component).or_default();
|
||||
*n = (*n + count).min(2);
|
||||
}
|
||||
});
|
||||
counts = next;
|
||||
}
|
||||
let ambiguous = counts.get(&component).copied().unwrap_or(0) > 1;
|
||||
// Slash and reserved-marker names could alias concatenated labels or fallback keys.
|
||||
if !ambiguous && !l.reserved() && !l.has_body(&candidate) {
|
||||
return candidate;
|
||||
}
|
||||
let ids = path.iter().map(u64::to_string).collect::<Vec<_>>().join("/");
|
||||
let encoded = serde_json::to_string(source).unwrap_or_else(|_| source.to_string());
|
||||
let mut key = format!("{encoded}@occ[{ids}]");
|
||||
while l.has_body(&key) {
|
||||
key.push('#');
|
||||
}
|
||||
key
|
||||
}
|
||||
|
||||
@@ -351,7 +351,9 @@ mod clone_tests {
|
||||
use serde_json::json;
|
||||
|
||||
let names = BTreeMap::from([("Old".into(), "New".into()), ("New".into(), "Wrong".into())]);
|
||||
let origin = json!({"type": "plane", "plane": {"type": "construction", "name": "Old"}});
|
||||
// Construction plane references are keyed by id (#1134): a copy remap moves the id map,
|
||||
// not this one; the axis and point names below are what a rename and a copy rewrite.
|
||||
let origin = json!({"type": "plane", "plane": {"type": "construction", "feature": 7}});
|
||||
for kind in [
|
||||
json!({"type": "construction_axis", "def": {"type": "two_points", "a": {"type": "construct_point", "name": "Old"}, "b": {"type": "point", "p": [1, 2, 3]}}}),
|
||||
json!({"type": "construction_point", "def": {"type": "edge_and_plane", "edge": {"type": "axis", "name": "Old"}, "plane": origin}}),
|
||||
|
||||
@@ -5,7 +5,7 @@ use solidcraft_sketch::Sketch;
|
||||
|
||||
use crate::construct::{AxisDef, GeoRef, PointDef};
|
||||
use crate::expr::{self, Expr, Kind};
|
||||
use crate::names::{BodyName, FeatureName, SketchId};
|
||||
use crate::names::{BodyName, FeatureId, FeatureName, SketchId};
|
||||
use crate::points::{Dir, FacePick, Position};
|
||||
use crate::{DocError, Result};
|
||||
|
||||
@@ -83,8 +83,8 @@ pub enum PlaneRef {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
line: Option<Box<GeoRef>>,
|
||||
},
|
||||
/// A construction plane feature (by name).
|
||||
Construction { name: FeatureName },
|
||||
/// A construction plane feature (by id).
|
||||
Construction { feature: FeatureId },
|
||||
/// `base` seen from its other side: the normal reversed by a half turn about the base's
|
||||
/// own x axis (y reversed), or about its y axis (x reversed) when `about_y`. A sketch
|
||||
/// started from behind a plane faces the viewer this way.
|
||||
@@ -714,6 +714,23 @@ pub enum FeatureKind {
|
||||
#[serde(default)]
|
||||
operation: Operation,
|
||||
},
|
||||
/// A cone or a truncated cone (#5641): the base circle's centre `base`, its `radius`, the
|
||||
/// radius of the top circle `height` along `axis` (`top_radius`, 0 for a full cone).
|
||||
Cone {
|
||||
#[serde(with = "point_expr")]
|
||||
#[walk(placement)]
|
||||
base: PointExpr,
|
||||
#[serde(default = "z_axis")]
|
||||
axis: Dir,
|
||||
#[walk(length = "Radius")]
|
||||
radius: Expr,
|
||||
#[walk(length = "Top radius")]
|
||||
top_radius: Expr,
|
||||
#[walk(length = "Height")]
|
||||
height: Expr,
|
||||
#[serde(default)]
|
||||
operation: Operation,
|
||||
},
|
||||
Combine {
|
||||
target: BodyName,
|
||||
tools: Vec<BodyName>,
|
||||
@@ -722,16 +739,18 @@ pub enum FeatureKind {
|
||||
keep_tools: bool,
|
||||
},
|
||||
/// Copies of other features' results, or of bodies (rectangular, circular or along a path).
|
||||
/// The copied features are named by id (#1134).
|
||||
Pattern {
|
||||
features: Vec<FeatureName>,
|
||||
features: Vec<FeatureId>,
|
||||
pattern: PatternKind,
|
||||
/// Copy these bodies (as new bodies) instead of replaying features.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
bodies: Vec<BodyName>,
|
||||
},
|
||||
/// Mirror copies of other features' results in a plane.
|
||||
/// Mirror copies of other features' results in a plane. The copied features are named by
|
||||
/// id (#1134).
|
||||
Mirror {
|
||||
features: Vec<FeatureName>,
|
||||
features: Vec<FeatureId>,
|
||||
plane: PlaneRef,
|
||||
/// Mirror these bodies instead of replaying features.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
@@ -2313,6 +2332,7 @@ impl FeatureKind {
|
||||
FeatureKind::Cylinder { .. } => "CylinderFeature",
|
||||
FeatureKind::Sphere { .. } => "SphereFeature",
|
||||
FeatureKind::Torus { .. } => "TorusFeature",
|
||||
FeatureKind::Cone { .. } => "ConeFeature",
|
||||
FeatureKind::Combine { .. } => "CombineFeature",
|
||||
FeatureKind::Pattern { pattern: PatternKind::Rectangular { .. }, .. } => "RectangularPatternFeature",
|
||||
FeatureKind::Pattern { pattern: PatternKind::Path { .. }, .. } => "PathPatternFeature",
|
||||
@@ -2395,6 +2415,7 @@ impl FeatureKind {
|
||||
FeatureKind::Cylinder { .. } => "Cylinder",
|
||||
FeatureKind::Sphere { .. } => "Sphere",
|
||||
FeatureKind::Torus { .. } => "Torus",
|
||||
FeatureKind::Cone { .. } => "Cone",
|
||||
FeatureKind::Combine { .. } => "Combine",
|
||||
FeatureKind::Pattern { pattern: PatternKind::Rectangular { .. }, .. } => "RectangularPattern",
|
||||
FeatureKind::Pattern { pattern: PatternKind::Path { .. }, .. } => "PathPattern",
|
||||
@@ -3083,6 +3104,18 @@ impl Document {
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Only appearances (body, face and component looks) changed: evaluation never reads them, so
|
||||
/// the model and its placed world stay valid. The same whole-Document proof as
|
||||
/// [`Document::differs_only_in_render`].
|
||||
pub fn differs_only_in_appearance(&self, previous: &Document) -> bool {
|
||||
if self.appearances == previous.appearances {
|
||||
return false;
|
||||
}
|
||||
let mut normalized = previous.clone();
|
||||
normalized.appearances = self.appearances.clone();
|
||||
*self == normalized
|
||||
}
|
||||
|
||||
/// The colour a body is shown and exported in (sRGB as 0–1 fractions): its appearance,
|
||||
/// else its component's, else its material's look, else the colour it was imported with.
|
||||
/// (Faces may have their own: `face_looks`.)
|
||||
@@ -3311,6 +3344,17 @@ impl Document {
|
||||
self.features.iter().find(|f| f.name.eq_ignore_ascii_case(r))
|
||||
}
|
||||
|
||||
/// The feature a pattern or mirror seed names, resolved the way `replay()` resolved seed
|
||||
/// names before seeds were keyed by id (#1134): an exact name match on a feature that is
|
||||
/// not a sketch or a construction plane wins — a sketch and its extrude often share a
|
||||
/// name, and the feature that makes geometry is the one to copy — else [`find_feature`].
|
||||
pub fn seed_feature(&self, name: &str) -> Option<&Feature> {
|
||||
self.features
|
||||
.iter()
|
||||
.find(|x| x.name == name && !matches!(x.kind, FeatureKind::Sketch { .. } | FeatureKind::ConstructionPlane { .. }))
|
||||
.or_else(|| self.find_feature(name))
|
||||
}
|
||||
|
||||
fn unique_name(&self, base: &str) -> String {
|
||||
// The numbers taken after `base`, gathered once: the thousandth cylinder of a design
|
||||
// tried Cylinder1, Cylinder2 … each against every feature's name.
|
||||
@@ -3745,16 +3789,21 @@ impl Document {
|
||||
Plane::new(o, rot(b.x), rot(b.y)).ok_or_else(|| DocError::Invalid("degenerate plane".into()))
|
||||
}
|
||||
PlaneRef::Flipped { base, about_y } => Ok(self.resolve_plane_in(vals, base, depth + 1, st)?.flipped(*about_y)),
|
||||
PlaneRef::Construction { name } => {
|
||||
PlaneRef::Construction { feature } => {
|
||||
// Evaluated already: as it came out.
|
||||
if let Some(c) = st.and_then(|st| st.construct.iter().find(|c| &c.name == name))
|
||||
if let Some(c) = st.and_then(|st| st.construct.iter().find(|c| c.feature == feature.0))
|
||||
&& let crate::construct::ConstructGeom::Plane(pl) = &c.geom
|
||||
{
|
||||
return Ok(*pl);
|
||||
}
|
||||
match self.find_feature(name).map(|f| &f.kind) {
|
||||
match self.feature(feature.0).map(|f| &f.kind) {
|
||||
Some(FeatureKind::ConstructionPlane { plane }) => self.resolve_plane_in(vals, plane, depth + 1, st),
|
||||
_ => Err(DocError::Unknown(format!("construction plane `{name}`"))),
|
||||
// A name that could not be resolved when an older design opened keeps id 0,
|
||||
// which no feature has (#1134): the reference dangles, as it always did.
|
||||
_ => {
|
||||
let shown = self.feature(feature.0).map(|f| f.name.clone()).unwrap_or_else(|| feature.to_string());
|
||||
Err(DocError::Unknown(format!("construction plane `{shown}`")))
|
||||
}
|
||||
}
|
||||
}
|
||||
PlaneRef::Midplane { .. }
|
||||
|
||||
@@ -15,7 +15,7 @@ use solidcraft_sketch::{CurveKind, Profile, SolveReport, find_profiles};
|
||||
|
||||
use crate::expr::{self, Kind, Value};
|
||||
use crate::lineage::Traced;
|
||||
use crate::{AxisRef, DocError, Document, Feature, FeatureKind, HoleKind, Operation, PlaneRef, ProfileSel, Result};
|
||||
use crate::{AxisRef, DocError, Document, Feature, FeatureId, FeatureKind, HoleKind, Operation, PlaneRef, ProfileSel, Result};
|
||||
|
||||
type JointFaces = (Body, kernel::Result<Arc<[kernel::FaceInfo]>>);
|
||||
|
||||
@@ -1490,6 +1490,13 @@ fn adopt_solution(stored: &mut solidcraft_sketch::Sketch, solved: &solidcraft_sk
|
||||
true
|
||||
}
|
||||
|
||||
impl Document {
|
||||
/// The values features are evaluated with: the parameters, and the design's length unit.
|
||||
pub fn evaluation_values(&self) -> BTreeMap<String, Value> {
|
||||
evaluation_values(self).0
|
||||
}
|
||||
}
|
||||
|
||||
/// The values features are evaluated with: the parameters, and the design's length unit.
|
||||
fn evaluation_values(doc: &Document) -> (BTreeMap<String, Value>, BTreeMap<String, String>) {
|
||||
let (mut vals, perr) = doc.param_values();
|
||||
@@ -1561,20 +1568,20 @@ fn chain_fingerprint(
|
||||
|
||||
/// The features a pattern or mirror of `features` replays: each source, and the sources of one
|
||||
/// that is itself a pattern or mirror of features (see `copy_plan`), each once.
|
||||
fn replayed_sources<'a>(doc: &'a Document, features: &[crate::FeatureName]) -> Vec<&'a Feature> {
|
||||
fn replayed_sources<'a>(doc: &'a Document, features: &[crate::FeatureId]) -> Vec<&'a Feature> {
|
||||
let mut out: Vec<&Feature> = Vec::new();
|
||||
let mut todo: Vec<String> = crate::plain(features);
|
||||
let mut todo: Vec<crate::FeatureId> = features.to_vec();
|
||||
// A bound on the walk, as on the copy plan's nesting: a design can't make it run on.
|
||||
for _ in 0..1024 {
|
||||
let Some(name) = todo.pop() else { break };
|
||||
let Ok(g) = source_feature(doc, &name) else { continue };
|
||||
let Some(id) = todo.pop() else { break };
|
||||
let Ok(g) = source_feature(doc, id) else { continue };
|
||||
if out.iter().any(|x| x.id == g.id) {
|
||||
continue;
|
||||
}
|
||||
if let FeatureKind::Pattern { features, bodies, .. } | FeatureKind::Mirror { features, bodies, .. } = &g.kind
|
||||
&& bodies.is_empty()
|
||||
{
|
||||
todo.extend(crate::plain(features));
|
||||
todo.extend(features.iter().copied());
|
||||
}
|
||||
out.push(g);
|
||||
}
|
||||
@@ -2280,7 +2287,7 @@ impl Model {
|
||||
// Copies made through a pattern or mirror among the sources come after (#395).
|
||||
let mats = match &f.kind {
|
||||
FeatureKind::Pattern { features, bodies, .. } | FeatureKind::Mirror { features, bodies, .. } if bodies.is_empty() => {
|
||||
copy_instances(doc, &vals, &state, &crate::plain(features), &mats).unwrap_or(mats)
|
||||
copy_instances(doc, &vals, &state, features, &mats).unwrap_or(mats)
|
||||
}
|
||||
_ => mats,
|
||||
};
|
||||
@@ -2665,6 +2672,7 @@ fn note_join_apart(before: &ModelState, after: &ModelState, f: &Feature, warning
|
||||
| FeatureKind::Cylinder { operation, .. }
|
||||
| FeatureKind::Sphere { operation, .. }
|
||||
| FeatureKind::Torus { operation, .. }
|
||||
| FeatureKind::Cone { operation, .. }
|
||||
| FeatureKind::Loft { operation, .. }
|
||||
| FeatureKind::Sweep { operation, .. }
|
||||
| FeatureKind::Pipe { operation, .. }
|
||||
@@ -2906,13 +2914,27 @@ fn empty_boolean_targets(doc: &Document, state: &ModelState, f: &Feature, tools:
|
||||
|
||||
/// [`apply_op`] with tools whose lineage is known: the bodies it makes or changes keep theirs.
|
||||
fn apply_traced(doc: &Document, state: &mut ModelState, f: &Feature, tools: Vec<Traced>, op: Operation, targets: &[String]) -> Result<()> {
|
||||
apply_traced_with(doc, state, f, tools, op, targets, &[])
|
||||
}
|
||||
|
||||
/// [`apply_traced`] with bodies that take part in the boolean besides the feature's targets:
|
||||
/// the body a Join extrude runs to (#6424), which joins with the tool.
|
||||
fn apply_traced_with(
|
||||
doc: &Document,
|
||||
state: &mut ModelState,
|
||||
f: &Feature,
|
||||
tools: Vec<Traced>,
|
||||
op: Operation,
|
||||
targets: &[String],
|
||||
extra: &[String],
|
||||
) -> Result<()> {
|
||||
let tools = if op == Operation::Intersect { intersection_tool_union(tools)? } else { tools };
|
||||
// Participants: the feature's own targets, else its explicit participants, else the bodies
|
||||
// of its component (and what it made itself): other components are left alone.
|
||||
let targets: &[String] = if targets.is_empty() { &f.participants } else { targets };
|
||||
let all = targets.len() == 1 && targets.first().is_some_and(|t| t == "*");
|
||||
let named = !all && !targets.is_empty();
|
||||
let target_idx: Vec<usize> = if all {
|
||||
let mut target_idx: Vec<usize> = if all {
|
||||
(0..state.bodies.len()).collect()
|
||||
} else if targets.is_empty() {
|
||||
(0..state.bodies.len()).filter(|i| state.bodies.get(*i).is_some_and(|b| b.component == f.component || b.feature == f.id)).collect()
|
||||
@@ -2923,6 +2945,15 @@ fn apply_traced(doc: &Document, state: &mut ModelState, f: &Feature, tools: Vec<
|
||||
}
|
||||
v
|
||||
};
|
||||
// Bodies that take part besides the feature's own targets: the body a Join extrude runs to
|
||||
// joins with the tool (#6424).
|
||||
for name in extra {
|
||||
if let Some(i) = state.bodies.iter().position(|b| &b.name == name)
|
||||
&& !target_idx.contains(&i)
|
||||
{
|
||||
target_idx.push(i);
|
||||
}
|
||||
}
|
||||
if op == Operation::NewBody || target_idx.is_empty() {
|
||||
if op == Operation::Cut || op == Operation::Intersect {
|
||||
return Err(empty_boolean_targets(doc, state, f, &tools, targets.is_empty()));
|
||||
@@ -4982,13 +5013,16 @@ pub(crate) fn thin_regions(vals: &BTreeMap<String, Value>, ss: &SolvedSketch, th
|
||||
const APART_GAP: f64 = 1e-3;
|
||||
|
||||
/// The tool bodies a feature adds or removes (extrude, revolve, primitives), in the current state.
|
||||
/// `to_body` is the body a To Object extent runs to (an extrude), when it found one.
|
||||
fn feature_tools_with_constructor(
|
||||
doc: &Document,
|
||||
vals: &BTreeMap<String, Value>,
|
||||
f: &Feature,
|
||||
st: &ModelState,
|
||||
constructor: &mut Option<ExtrudeConstructor>,
|
||||
to_body: &mut Option<String>,
|
||||
) -> Result<Vec<Traced>> {
|
||||
*to_body = None;
|
||||
let unnamed = |bodies: Vec<Body>| -> Vec<Traced> { bodies.into_iter().map(Traced::unnamed).collect() };
|
||||
match &f.kind {
|
||||
FeatureKind::Extrude { sketch, profiles, extent, operation, targets, thin } => {
|
||||
@@ -5022,6 +5056,8 @@ fn feature_tools_with_constructor(
|
||||
let name = |k: usize| f.face_names.get(k).map(String::as_str);
|
||||
let (to_name, from_name) = if extent.to.is_some() { (name(0), name(1)) } else { (None, name(0)) };
|
||||
let to_face = extent.to.and_then(|p| object_face(&tst, to_name, p.0, Some(n)));
|
||||
// The body the extent runs to: a Join to it joins that body too (#6424).
|
||||
*to_body = to_face.and_then(|(bi, _)| tst.bodies.get(bi)).map(|b| b.name.clone());
|
||||
let centre = {
|
||||
let pts: Vec<Vec2> = regions.iter().flat_map(|r| r.outer.polyline(1e-2)).collect();
|
||||
let c = pts.iter().fold(Vec2::default(), |a, p| a + *p) * (1.0 / pts.len().max(1) as f64);
|
||||
@@ -5720,6 +5756,13 @@ fn feature_tools_with_constructor(
|
||||
FeatureKind::Torus { center, major, minor, .. } => {
|
||||
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(
|
||||
point(vals, base)?,
|
||||
axis.0,
|
||||
val(vals, radius, Kind::Length)?,
|
||||
val(vals, top_radius, Kind::Length)?,
|
||||
val(vals, height, Kind::Length)?,
|
||||
)?])),
|
||||
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))),
|
||||
}
|
||||
@@ -6014,7 +6057,8 @@ fn feature_op(f: &Feature) -> (Operation, Vec<String>) {
|
||||
FeatureKind::Box { operation, .. }
|
||||
| FeatureKind::Cylinder { operation, .. }
|
||||
| FeatureKind::Sphere { operation, .. }
|
||||
| FeatureKind::Torus { operation, .. } => (*operation, Vec::new()),
|
||||
| FeatureKind::Torus { operation, .. }
|
||||
| FeatureKind::Cone { operation, .. } => (*operation, Vec::new()),
|
||||
FeatureKind::Hole { .. } => (Operation::Cut, Vec::new()),
|
||||
FeatureKind::Pipe { operation, targets, .. } => (*operation, crate::plain(targets)),
|
||||
FeatureKind::Loft { operation, targets, .. } | FeatureKind::Sweep { operation, targets, .. } => (*operation, crate::plain(targets)),
|
||||
@@ -6312,27 +6356,28 @@ fn copy_misses(st: &ModelState, f: &Feature, targets: &[String], tools: &[Traced
|
||||
/// The deepest a pattern or mirror reaches through patterns and mirrors of features (#395).
|
||||
const MAX_NESTING: usize = 8;
|
||||
|
||||
/// The feature a pattern or mirror names as a source. A sketch and its extrude often share a
|
||||
/// name; the feature that makes geometry wins.
|
||||
fn source_feature<'a>(doc: &'a Document, name: &str) -> Result<&'a Feature> {
|
||||
doc.features
|
||||
.iter()
|
||||
.find(|x| x.name == name && !matches!(x.kind, FeatureKind::Sketch { .. } | FeatureKind::ConstructionPlane { .. }))
|
||||
.or_else(|| doc.find_feature(name))
|
||||
.ok_or_else(|| DocError::Unknown(format!("feature `{name}`")))
|
||||
/// The feature a pattern or mirror names as a source. Seeds are keyed by id (#1134): a
|
||||
/// reference that names no feature dangles, as a missing name did before.
|
||||
fn source_feature(doc: &Document, id: FeatureId) -> Result<&Feature> {
|
||||
doc.feature(id.0).ok_or_else(|| DocError::Unknown(format!("feature `{id}`")))
|
||||
}
|
||||
|
||||
/// How a source feature shows in a warning: its name, or its id when it names no feature.
|
||||
fn source_label(doc: &Document, id: FeatureId) -> String {
|
||||
doc.feature(id.0).map(|f| f.name.clone()).unwrap_or_else(|| id.to_string())
|
||||
}
|
||||
|
||||
/// What a pattern or mirror of features copies, when it is the source of another one (#395): its
|
||||
/// own sources, and the transforms of its copies. `None` for any other feature, and for a pattern
|
||||
/// or mirror of bodies (its copies are bodies, which a pattern or mirror of features can't take).
|
||||
fn copied_features(doc: &Document, vals: &BTreeMap<String, Value>, st: &ModelState, src: &Feature) -> Result<Option<(Vec<String>, Vec<Mat>)>> {
|
||||
fn copied_features(doc: &Document, vals: &BTreeMap<String, Value>, st: &ModelState, src: &Feature) -> Result<Option<(Vec<FeatureId>, Vec<Mat>)>> {
|
||||
match &src.kind {
|
||||
FeatureKind::Pattern { features, pattern, bodies } if bodies.is_empty() => {
|
||||
Ok(Some((crate::plain(features), pattern_transforms(doc, vals, st, src.component, pattern)?)))
|
||||
Ok(Some((features.clone(), pattern_transforms(doc, vals, st, src.component, pattern)?)))
|
||||
}
|
||||
FeatureKind::Mirror { features, plane, bodies, .. } if bodies.is_empty() => {
|
||||
let pl = doc.resolve_plane_in(vals, plane, 0, Some(st))?;
|
||||
Ok(Some((crate::plain(features), vec![mirror_matrix(&pl)])))
|
||||
Ok(Some((features.clone(), vec![mirror_matrix(&pl)])))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
@@ -6396,7 +6441,7 @@ fn copy_plan<'a>(
|
||||
doc: &'a Document,
|
||||
vals: &BTreeMap<String, Value>,
|
||||
st: &ModelState,
|
||||
features: &[String],
|
||||
features: &[FeatureId],
|
||||
mats: &[Mat],
|
||||
) -> Result<Vec<(&'a Feature, Vec<Mat>, usize)>> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -6404,18 +6449,18 @@ fn copy_plan<'a>(
|
||||
doc: &'a Document,
|
||||
vals: &BTreeMap<String, Value>,
|
||||
st: &ModelState,
|
||||
features: &[String],
|
||||
features: &[FeatureId],
|
||||
mats: &[Mat],
|
||||
first: usize,
|
||||
next: &mut usize,
|
||||
depth: usize,
|
||||
out: &mut Vec<(&'a Feature, Vec<Mat>, usize)>,
|
||||
) -> Result<()> {
|
||||
for name in features {
|
||||
if doc.find_feature(name).is_some_and(|x| x.suppressed) {
|
||||
for id in features {
|
||||
if doc.feature(id.0).is_some_and(|x| x.suppressed) {
|
||||
continue;
|
||||
}
|
||||
let src = source_feature(doc, name)?;
|
||||
let src = source_feature(doc, *id)?;
|
||||
let Some((inner, inner_mats)) = copied_features(doc, vals, st, src)? else {
|
||||
out.push((src, mats.to_vec(), first));
|
||||
continue;
|
||||
@@ -6442,7 +6487,7 @@ fn copy_plan<'a>(
|
||||
/// The transforms of a pattern's or mirror's copies in their instance order (instance `k + 1`
|
||||
/// is the `k`-th): `mats`, then those of the copies made through patterns and mirrors among
|
||||
/// `features` ([`copy_plan`]). Naming finds a copy's faces through them.
|
||||
fn copy_instances(doc: &Document, vals: &BTreeMap<String, Value>, st: &ModelState, features: &[String], mats: &[Mat]) -> Result<Vec<Mat>> {
|
||||
fn copy_instances(doc: &Document, vals: &BTreeMap<String, Value>, st: &ModelState, features: &[FeatureId], mats: &[Mat]) -> Result<Vec<Mat>> {
|
||||
let plan = copy_plan(doc, vals, st, features, mats)?;
|
||||
let len = plan.iter().map(|(_, ms, first)| first + ms.len()).max().unwrap_or(0).max(mats.len());
|
||||
let mut all = vec![crate::IDENTITY; len];
|
||||
@@ -6465,7 +6510,7 @@ fn replay(
|
||||
vals: &BTreeMap<String, Value>,
|
||||
f: &Feature,
|
||||
st: &mut ModelState,
|
||||
features: &[String],
|
||||
features: &[FeatureId],
|
||||
mats: &[Mat],
|
||||
warning: &mut Option<String>,
|
||||
) -> Result<()> {
|
||||
@@ -6473,9 +6518,9 @@ fn replay(
|
||||
return Err(DocError::Invalid("no features selected".into()));
|
||||
}
|
||||
// A suppressed feature has nothing to copy: its copies go with it.
|
||||
let suppressed: Vec<&String> = features.iter().filter(|n| doc.find_feature(n).is_some_and(|x| x.suppressed)).collect();
|
||||
let suppressed: Vec<FeatureId> = features.iter().copied().filter(|n| doc.feature(n.0).is_some_and(|x| x.suppressed)).collect();
|
||||
if !suppressed.is_empty() {
|
||||
let list = suppressed.iter().map(|n| format!("`{n}`")).collect::<Vec<_>>().join(", ");
|
||||
let list = suppressed.iter().map(|n| format!("`{}`", source_label(doc, *n))).collect::<Vec<_>>().join(", ");
|
||||
*warning = Some(if suppressed.len() == features.len() {
|
||||
format!("nothing to copy: {list} suppressed")
|
||||
} else {
|
||||
@@ -6484,8 +6529,8 @@ fn replay(
|
||||
}
|
||||
// The copies are made in timeline order, whatever order the features were picked in: a
|
||||
// chamfer picked before the hole it blends would otherwise find no edge to copy.
|
||||
let mut kept: Vec<String> = features.iter().filter(|n| !suppressed.contains(n)).cloned().collect();
|
||||
let at = |name: &String| source_feature(doc, name).ok().and_then(|f| doc.features.iter().position(|x| std::ptr::eq(x, f))).unwrap_or(usize::MAX);
|
||||
let mut kept: Vec<FeatureId> = features.iter().copied().filter(|n| !suppressed.contains(n)).collect();
|
||||
let at = |id: &FeatureId| doc.features.iter().position(|x| x.id == id.0).unwrap_or(usize::MAX);
|
||||
kept.sort_by_key(at);
|
||||
kept.dedup();
|
||||
let (mut missed, mut total) = (0usize, 0usize);
|
||||
@@ -6582,7 +6627,7 @@ fn replay(
|
||||
// points follow their named faces, as the feature's own did (#2121): a To Object
|
||||
// extrude's copies reach the face where it is now, not where it was picked.
|
||||
let moved = with_named_face_points(doc, st, src).map(|(g, _)| g);
|
||||
let tools = feature_tools_with_constructor(doc, vals, moved.as_ref().unwrap_or(src), st, &mut None)?;
|
||||
let tools = feature_tools_with_constructor(doc, vals, moved.as_ref().unwrap_or(src), st, &mut None, &mut None)?;
|
||||
for (k, m) in mats.iter().enumerate() {
|
||||
let start = copies.len();
|
||||
for t in &tools {
|
||||
@@ -7107,7 +7152,8 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
| FeatureKind::Coil { operation, targets, .. }
|
||||
| FeatureKind::Sweep { operation, targets, .. } => {
|
||||
let mut constructor = None;
|
||||
let tools = feature_tools_with_constructor(doc, vals, f, st, &mut constructor)?;
|
||||
let mut to_body = None;
|
||||
let tools = feature_tools_with_constructor(doc, vals, f, st, &mut constructor, &mut to_body)?;
|
||||
// Cuts through sheet metal become cut-outs of its flat pattern.
|
||||
if matches!(operation, Operation::Cut | Operation::Intersect) && matches!(f.kind, FeatureKind::Extrude { .. }) && !st.sheets.is_empty() {
|
||||
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
|
||||
@@ -7116,7 +7162,10 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
return apply_beside_sheet_cut(doc, st, f, tools, *operation, &crate::plain(targets), &handled);
|
||||
}
|
||||
}
|
||||
apply_traced(doc, st, f, tools, *operation, &crate::plain(targets))
|
||||
// A Join extrude to an object joins that object too (#6424): the tool and the body
|
||||
// it runs to are one body, not only the body the profile was sketched on.
|
||||
let extra: Vec<String> = if *operation == Operation::Join { to_body.into_iter().collect() } else { Vec::new() };
|
||||
apply_traced_with(doc, st, f, tools, *operation, &crate::plain(targets), &extra)
|
||||
}
|
||||
FeatureKind::Hole { thread, thread_depth, diameter, handedness, .. } => {
|
||||
let thread_depth = thread_depth.as_ref().map(|d| val(vals, d, Kind::Length)).transpose()?;
|
||||
@@ -7435,17 +7484,40 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
FeatureKind::Pattern { features, pattern, bodies } => {
|
||||
let mats = pattern_transforms(doc, vals, st, f.component, pattern)?;
|
||||
if bodies.is_empty() {
|
||||
return replay(doc, vals, f, st, &crate::plain(features), &mats, warning);
|
||||
return replay(doc, vals, f, st, features, &mats, warning);
|
||||
}
|
||||
// Bodies: each copy is a new body named after its source.
|
||||
let here = doc.component_transform(f.component);
|
||||
let mut output = 0;
|
||||
for n in bodies {
|
||||
let mb = st.body(n).cloned().ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
|
||||
// A body of another component is held in that component's frame, the pattern's
|
||||
// transforms in this feature's: each copy is moved as the pattern moves it in the
|
||||
// world and kept beside its source, in its component (#4763). Taken as they were,
|
||||
// the copies landed where the source sits before its component is placed.
|
||||
let owner = (mb.component != f.component).then(|| {
|
||||
let frame = doc.component_transform(mb.component);
|
||||
let singular = || DocError::Invalid(format!("the component holding `{n}` has a singular placement"));
|
||||
Ok::<_, DocError>((frame, crate::mat_inverse(&frame).ok_or_else(singular)?, crate::mat_inverse(&here).ok_or_else(singular)?))
|
||||
});
|
||||
let owner = owner.transpose()?;
|
||||
for (k, m) in mats.iter().enumerate() {
|
||||
let copy = moved_copy(&mb, kernel::transform_matrix(&mb.body, *m)?, k + 1);
|
||||
let local = match &owner {
|
||||
Some((frame, frame_inv, here_inv)) => {
|
||||
crate::mat_mul(&crate::mat_mul(frame_inv, &here), &crate::mat_mul(&crate::mat_mul(m, here_inv), frame))
|
||||
}
|
||||
None => *m,
|
||||
};
|
||||
let copy = moved_copy(&mb, kernel::transform_matrix(&mb.body, local)?, k + 1);
|
||||
let name = pattern_copy_name(st, f, output, &mb.name);
|
||||
copy_sheet(st, &mb.name, &name, m);
|
||||
st.bodies.push(ModelBody::traced_new(name, copy, f.id));
|
||||
copy_sheet(st, &mb.name, &name, &local);
|
||||
let mut body = ModelBody::traced_new(name, copy, f.id);
|
||||
if owner.is_some() {
|
||||
// Kept in that component as a moved body is (and through later features).
|
||||
body.component = mb.component;
|
||||
body.transferred = true;
|
||||
}
|
||||
st.bodies.push(body);
|
||||
output += 1;
|
||||
}
|
||||
}
|
||||
@@ -7454,7 +7526,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
FeatureKind::Mirror { features, plane, bodies, combine, placements } => {
|
||||
let pl = doc.resolve_plane_in(vals, plane, 0, Some(st))?;
|
||||
if bodies.is_empty() {
|
||||
return replay(doc, vals, f, st, &crate::plain(features), &[mirror_matrix(&pl)], warning);
|
||||
return replay(doc, vals, f, st, features, &[mirror_matrix(&pl)], warning);
|
||||
}
|
||||
let m = mirror_matrix(&pl);
|
||||
let mut output = 0;
|
||||
@@ -7597,6 +7669,12 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
let b = kernel::torus(point(vals, center)?, val(vals, major, Kind::Length)?, val(vals, minor, Kind::Length)?)?;
|
||||
apply_op(doc, st, f, vec![b], *operation, &[])
|
||||
}
|
||||
FeatureKind::Cone { base, axis, radius, top_radius, height, operation } => {
|
||||
let (radius, top_radius, height) =
|
||||
(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)?;
|
||||
apply_op(doc, st, f, vec![b], *operation, &[])
|
||||
}
|
||||
FeatureKind::Combine { target, tools, operation, keep_tools } => {
|
||||
if tools.iter().any(|t| t == target) {
|
||||
return Err(DocError::Invalid("a body cannot be combined with itself".into()));
|
||||
@@ -7932,6 +8010,7 @@ fn drawn_tools_change_bodies(kind: &FeatureKind) -> bool {
|
||||
| FeatureKind::Cylinder { operation, .. }
|
||||
| FeatureKind::Sphere { operation, .. }
|
||||
| FeatureKind::Torus { operation, .. }
|
||||
| FeatureKind::Cone { operation, .. }
|
||||
| FeatureKind::Extrude { operation, .. }
|
||||
| FeatureKind::Revolve { operation, .. }
|
||||
| FeatureKind::Loft { operation, .. }
|
||||
@@ -8286,12 +8365,13 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
|
||||
// places hundreds of parts, each a B-rep transform.
|
||||
let mut jobs = Vec::new();
|
||||
let owners = doc.model_body_components(st);
|
||||
let names = crate::WorldNames::new(doc, st);
|
||||
let bodies_of = |comp: u64| st.bodies.iter().zip(&owners).filter(move |(_, owner)| **owner == comp).map(|(b, _)| b);
|
||||
for (comp, path, m) in &placements {
|
||||
for b in bodies_of(*comp) {
|
||||
let bm = body_matrix(b, m);
|
||||
if !crate::is_identity(&bm) {
|
||||
jobs.push((b, bm, doc.world_body_name(st, &b.name, *comp, path)));
|
||||
jobs.push((b, bm, names.name(&b.name, *comp, path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8310,14 +8390,14 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
|
||||
// A changed placement keeps a distinct mesh, at that placement's coordinates.
|
||||
let mut placed = if crate::is_identity(&bm) {
|
||||
let mut placed = b.clone();
|
||||
placed.name = doc.world_body_name(st, &b.name, comp, &path);
|
||||
placed.name = names.name(&b.name, comp, &path);
|
||||
placed
|
||||
} else {
|
||||
// The same order as the jobs: this body's, made above (again here if it failed).
|
||||
let placed = match moved.next() {
|
||||
Some((Some(placed), _)) => placed,
|
||||
Some((None, (_, _, name))) => placed_before.placed(b, &bm, name.clone()),
|
||||
None => placed_before.placed(b, &bm, doc.world_body_name(st, &b.name, comp, &path)),
|
||||
None => placed_before.placed(b, &bm, names.name(&b.name, comp, &path)),
|
||||
};
|
||||
placed_now.by_source.insert(placed_key(b, &bm), (b.clone(), placed.clone()));
|
||||
placed
|
||||
@@ -8372,7 +8452,7 @@ pub fn world_state_reusing(doc: &Document, st: &ModelState, placed_before: &mut
|
||||
.filter(|t| st.body(&t.body).map(|body| doc.model_body_component(body)).unwrap_or_else(|| comp_of_feature(t.feature)) == comp)
|
||||
{
|
||||
let mut t2 = t.clone();
|
||||
t2.body = doc.world_body_name(st, &t.body, comp, &path);
|
||||
t2.body = names.name(&t.body, comp, &path);
|
||||
let frame = st.body(&t.body).and_then(|body| doc.model_body_offset(body)).map(|offset| crate::mat_mul(&m, offset)).unwrap_or(m);
|
||||
t2.axis_point = crate::apply_point(&frame, t.axis_point);
|
||||
t2.axis = crate::apply_vector(&frame, *t.axis).unit().unwrap_or(t.axis);
|
||||
|
||||
@@ -184,11 +184,10 @@ fn a_sketch_on_a_fixed_plane_costs_the_same_in_a_large_placed_design() {
|
||||
let plane = doc.add_feature(FeatureKind::ConstructionPlane { plane: PlaneRef::Origin { name: "XZ".into() } }, Some("Datum")).unwrap();
|
||||
assert_eq!(counted(|| model.evaluate(&doc)), Counts::default(), "construction plane");
|
||||
assert!(model.result(plane).unwrap().error.is_none());
|
||||
let datum = doc.feature(plane).unwrap().name.clone();
|
||||
let planes = [
|
||||
PlaneRef::Origin { name: "XY".into() },
|
||||
PlaneRef::Offset { base: Box::new(PlaneRef::Origin { name: "YZ".into() }), distance: "5 mm".into() },
|
||||
PlaneRef::Construction { name: datum.into() },
|
||||
PlaneRef::Construction { feature: plane.into() },
|
||||
];
|
||||
for (k, plane) in planes.into_iter().enumerate() {
|
||||
let id = doc.add_feature(line_sketch(plane, Vec2::new(10.0, 1.0 + k as f64)), None).unwrap();
|
||||
|
||||
@@ -1168,6 +1168,57 @@ pub(super) fn sheet_drill_cuts(
|
||||
Ok(Some(handled))
|
||||
}
|
||||
|
||||
/// The largest size a cone takes (mm): beyond it the tessellation and booleans lose precision.
|
||||
const MAX_CONE_SIZE: f64 = 1e7;
|
||||
|
||||
/// A cone or truncated cone (#5641): the base circle of `radius` centred on `base`, the top circle
|
||||
/// 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
|
||||
/// 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> {
|
||||
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)) {
|
||||
return Err(DocError::Invalid("the cone's base is out of range".into()));
|
||||
}
|
||||
if !(in_range(radius) && in_range(top_radius) && in_range(height)) {
|
||||
return Err(DocError::Invalid(format!("cone sizes must be finite and within {MAX_CONE_SIZE} mm")));
|
||||
}
|
||||
if !(radius > 1e-6) {
|
||||
return Err(DocError::Invalid("the cone's radius must be positive".into()));
|
||||
}
|
||||
if top_radius < 0.0 {
|
||||
return Err(DocError::Invalid("the cone's top radius must be 0 or more".into()));
|
||||
}
|
||||
if !(height.abs() > 1e-6) {
|
||||
return Err(DocError::Invalid("the cone's height must not be zero".into()));
|
||||
}
|
||||
let dir = axis.normalized().ok_or_else(|| DocError::Invalid("the cone's axis has no direction".into()))?;
|
||||
let (dir, h) = if height < 0.0 { (-dir, -height) } else { (dir, height) };
|
||||
// The half section in the XZ plane, the axis along +Z through the origin, revolved there
|
||||
// (where an apex on the axis comes out exact; built in a slanted frame directly, its side
|
||||
// surface measured too large) and then moved into place.
|
||||
let section = if top_radius > 1e-6 {
|
||||
vec![Vec2::ZERO, Vec2::new(radius, 0.0), Vec2::new(top_radius, h), Vec2::new(0.0, h)]
|
||||
} else {
|
||||
vec![Vec2::ZERO, Vec2::new(radius, 0.0), Vec2::new(0.0, h)]
|
||||
};
|
||||
let plane = Plane { origin: Vec3::ZERO, x: Vec3::X, y: Vec3::Z };
|
||||
let region = Region2 { outer: Loop2::polygon(§ion), holes: vec![] };
|
||||
let mut bodies = kernel::revolve(&plane, &[region], Vec2::ZERO, Vec2::Y, std::f64::consts::TAU)?;
|
||||
let body = match (bodies.pop(), bodies.is_empty()) {
|
||||
(Some(b), true) => b,
|
||||
_ => return Err(DocError::Invalid("the cone could not be built".into())),
|
||||
};
|
||||
if (dir - Vec3::Z).len() < 1e-12 && base.len() == 0.0 {
|
||||
return Ok(body);
|
||||
}
|
||||
// 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 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]];
|
||||
Ok(kernel::transform_matrix(&body, m)?)
|
||||
}
|
||||
|
||||
/// A primitive tool's exact geometry, for [`sheet_primitive_cut`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) enum PrimitivePrism {
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
//! References from a feature to the rest of the design: the sketches it is built from (by id),
|
||||
//! the features it names (patterned and mirrored features, the construction plane a plane is
|
||||
//! built on) and the bodies it works on (by name). The walk (`crate::walk`) is behind
|
||||
//! the features it names (patterned and mirrored features and the construction plane a plane is
|
||||
//! built on, by id since #1134; construction axes and points, still by name) and the bodies it
|
||||
//! works on (by name). The walk (`crate::walk`) is behind
|
||||
//! `input_sketches`, `remap_refs` and `remap_bodies`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::walk::Walk;
|
||||
use crate::{BodyName, FeatureKind, FeatureName, SketchId};
|
||||
use crate::{BodyName, FeatureId, FeatureKind, FeatureName, SketchId};
|
||||
|
||||
/// A reference a feature holds: `S` is `&SketchId` or `&mut SketchId`, and so on.
|
||||
pub(crate) enum Ref<S, F, B> {
|
||||
/// A reference a feature holds: `S` is `&SketchId` or `&mut SketchId`, and so on. `Feature`
|
||||
/// carries a feature id (pattern and mirror seeds, construction planes); `FeatureName` carries
|
||||
/// a feature name, for the references that are still keyed by name (construction axes and
|
||||
/// points).
|
||||
pub(crate) enum Ref<S, F, N, B> {
|
||||
Sketch(S),
|
||||
Feature(F),
|
||||
FeatureName(N),
|
||||
Body(B),
|
||||
}
|
||||
|
||||
@@ -28,28 +33,34 @@ impl FeatureKind {
|
||||
v
|
||||
}
|
||||
|
||||
/// Whether the feature names the feature `name` (the construction plane a plane is built on,
|
||||
/// a patterned or mirrored feature).
|
||||
pub fn names_feature(&self, name: &str) -> bool {
|
||||
/// Whether the feature refers to the feature `id` (by id: a patterned or mirrored feature,
|
||||
/// the construction plane a plane is built on) or names it (`name`: a construction axis or
|
||||
/// point it is built from). Used to refuse a feature built on itself (#3567).
|
||||
pub fn names_feature(&self, id: u64, name: &str) -> bool {
|
||||
let mut found = false;
|
||||
self.walk_refs(&mut |r| {
|
||||
if let Ref::Feature(n) = r {
|
||||
found |= n.as_ref() == name;
|
||||
}
|
||||
self.walk_refs(&mut |r| match r {
|
||||
Ref::Feature(x) => found |= x.0 == id,
|
||||
Ref::FeatureName(n) => found |= n.as_ref() == name,
|
||||
_ => {}
|
||||
});
|
||||
found
|
||||
}
|
||||
|
||||
/// Point references to sketches (by id) and features (by name) at their copies, or at a
|
||||
/// feature's new name (`timeline.rename`).
|
||||
pub fn remap_refs(&mut self, sketches: &BTreeMap<u64, u64>, names: &BTreeMap<String, String>) {
|
||||
/// Point references to sketches and features (both by id) at their copies, and references
|
||||
/// that are still keyed by name at a feature's new name (`timeline.rename`).
|
||||
pub fn remap_refs(&mut self, ids: &BTreeMap<u64, u64>, names: &BTreeMap<String, String>) {
|
||||
self.walk_refs_mut(&mut |r| match r {
|
||||
Ref::Sketch(id) => {
|
||||
if let Some(n) = sketches.get(&id.0) {
|
||||
if let Some(n) = ids.get(&id.0) {
|
||||
*id = SketchId(*n);
|
||||
}
|
||||
}
|
||||
Ref::Feature(name) => {
|
||||
Ref::Feature(id) => {
|
||||
if let Some(n) = ids.get(&id.0) {
|
||||
*id = FeatureId(*n);
|
||||
}
|
||||
}
|
||||
Ref::FeatureName(name) => {
|
||||
if let Some(n) = names.get(name.as_ref()) {
|
||||
*name = FeatureName(n.clone());
|
||||
}
|
||||
@@ -72,8 +83,9 @@ impl FeatureKind {
|
||||
|
||||
impl crate::Document {
|
||||
/// Rename feature `old` to `new` where the design names it outside the features' own
|
||||
/// references (`remap_refs`): sketch projections of a construction plane and the canvases
|
||||
/// placed on it (#4353). (The features are the rename command's to change.)
|
||||
/// references (`remap_refs`): sketch projections of a construction plane (#4353). The
|
||||
/// features are the rename command's to change; feature references inside features are keyed
|
||||
/// by id since #1134 and follow a rename on their own.
|
||||
pub fn rename_feature_keys(&mut self, old: &str, new: &str) {
|
||||
let names = BTreeMap::from([(old.to_string(), new.to_string())]);
|
||||
let (ids, bodies) = (BTreeMap::new(), BTreeMap::new());
|
||||
@@ -84,14 +96,5 @@ impl crate::Document {
|
||||
}
|
||||
}
|
||||
}
|
||||
for c in &mut self.canvases {
|
||||
c.plane.walk_refs_mut(&mut |r| {
|
||||
if let Ref::Feature(name) = r
|
||||
&& name.as_ref() == old
|
||||
{
|
||||
*name = FeatureName(new.to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,8 @@ fn construction_names_follow_renames_in_every_definition() {
|
||||
|
||||
let axis = json!({"type": "axis", "name": "Old"});
|
||||
let point = json!({"type": "construct_point", "name": "Old"});
|
||||
let plane = json!({"type": "plane", "plane": {"type": "construction", "name": "Old"}});
|
||||
// Construction plane references are keyed by id (#1134): a rename leaves them alone.
|
||||
let plane = json!({"type": "plane", "plane": {"type": "construction", "feature": 7}});
|
||||
let definitions = [
|
||||
json!({"type": "construction_axis", "def": {"type": "through_face", "face": axis}}),
|
||||
json!({"type": "construction_axis", "def": {"type": "normal_to_face", "face": plane, "point": point}}),
|
||||
@@ -217,7 +218,7 @@ fn construction_names_follow_renames_in_every_definition() {
|
||||
let expected_count = definition.to_string().matches("\"Old\"").count();
|
||||
let mut seen = Vec::new();
|
||||
kind.walk_refs(&mut |r| {
|
||||
if let Ref::Feature(name) = r {
|
||||
if let Ref::FeatureName(name) = r {
|
||||
seen.push(name.to_string());
|
||||
}
|
||||
});
|
||||
@@ -233,13 +234,13 @@ fn construction_reference_walk_keeps_its_depth_limit_across_geometry() {
|
||||
use crate::construct::GeoRef;
|
||||
use crate::walk::{Ref, Walk};
|
||||
|
||||
let mut plane = crate::PlaneRef::Construction { name: "TooDeep".into() };
|
||||
let mut plane = crate::PlaneRef::Construction { feature: crate::FeatureId(7) };
|
||||
for _ in 0..80 {
|
||||
plane = crate::PlaneRef::Perpendicular { plane: Box::new(GeoRef::Plane { plane }), line: Box::new(GeoRef::Axis { name: "Side".into() }) };
|
||||
}
|
||||
let mut seen = Vec::new();
|
||||
plane.walk_refs(&mut |r| {
|
||||
if let Ref::Feature(name) = r {
|
||||
if let Ref::FeatureName(name) = r {
|
||||
seen.push(name.to_string());
|
||||
}
|
||||
});
|
||||
@@ -247,7 +248,7 @@ fn construction_reference_walk_keeps_its_depth_limit_across_geometry() {
|
||||
assert!(seen.iter().all(|name| name == "Side"));
|
||||
let mut changed = 0;
|
||||
plane.walk_refs_mut(&mut |r| {
|
||||
if let Ref::Feature(name) = r {
|
||||
if let Ref::FeatureName(name) = r {
|
||||
assert_eq!(name.as_ref(), "Side");
|
||||
*name = "Renamed".into();
|
||||
changed += 1;
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde_json::{Value, json};
|
||||
|
||||
fn plane(chain: &str) -> Value {
|
||||
json!({"type": "at_angle", "angle": format!("{chain}.angle"), "axis_origin": [0, 0, 0], "axis_dir": [1, 0, 0],
|
||||
"base": {"type": "offset", "distance": format!("{chain}.offset"), "base": {"type": "construction", "name": "CP"}}})
|
||||
"base": {"type": "offset", "distance": format!("{chain}.offset"), "base": {"type": "construction", "feature": 8}}})
|
||||
}
|
||||
|
||||
fn geo_plane(chain: &str) -> Value {
|
||||
@@ -25,7 +25,7 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
|
||||
"taper": format!("{n}.taper"), "to_offset": format!("{n}.to_offset")})
|
||||
};
|
||||
let fillet = |style: Value| json!({"type": "fillet", "edges": [o], "radius": "fillet.radius", "style": style});
|
||||
let pattern = |p: Value| json!({"type": "pattern", "features": ["F"], "pattern": p});
|
||||
let pattern = |p: Value| json!({"type": "pattern", "features": [7], "pattern": p});
|
||||
let hole =
|
||||
|h: Value| json!({"type": "hole", "position": o, "direction": [0, 0, 1], "diameter": "hole.diameter", "depth": "hole.depth", "hole": h});
|
||||
vec![
|
||||
@@ -58,6 +58,10 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
|
||||
("cylinder", json!({"type": "cylinder", "base": ["cyl.x", "cyl.y", "cyl.z"], "radius": "cyl.radius", "height": "cyl.height"})),
|
||||
("sphere", json!({"type": "sphere", "center": ["sph.x", "sph.y", "sph.z"], "radius": "sph.radius"})),
|
||||
("torus", json!({"type": "torus", "center": ["tor.x", "tor.y", "tor.z"], "major": "tor.major", "minor": "tor.minor"})),
|
||||
(
|
||||
"cone",
|
||||
json!({"type": "cone", "base": ["cone.x", "cone.y", "cone.z"], "radius": "cone.radius", "top_radius": "cone.top_radius", "height": "cone.height"}),
|
||||
),
|
||||
("combine", json!({"type": "combine", "target": "A", "tools": ["B"], "operation": "join"})),
|
||||
(
|
||||
"pattern_rectangular",
|
||||
@@ -67,7 +71,7 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
|
||||
),
|
||||
("pattern_circular", pattern(json!({"type": "circular", "origin": o, "axis": [0, 0, 1], "count": "circ.count", "angle": "circ.angle"}))),
|
||||
("pattern_path", pattern(json!({"type": "path", "path_sketch": 1, "path": ["l1"], "count": "path.count", "spacing": "path.spacing"}))),
|
||||
("mirror", json!({"type": "mirror", "features": ["F"], "plane": plane("mirror")})),
|
||||
("mirror", json!({"type": "mirror", "features": [7], "plane": plane("mirror")})),
|
||||
("hole_simple", hole(json!({"type": "simple"}))),
|
||||
("hole_drilled", hole(json!({"type": "drilled", "tip_angle": "hole.tip_angle"}))),
|
||||
("hole_counterbore", hole(json!({"type": "counterbore", "cb_diameter": "hole.cb_diameter", "cb_depth": "hole.cb_depth"}))),
|
||||
@@ -232,7 +236,7 @@ pub(super) fn fixtures() -> Vec<(&'static str, Value)> {
|
||||
|
||||
/// The reference fields a fixture leaves out (they don't change its expressions), set here so
|
||||
/// every sketch, feature and body reference is exercised: sketches 1–8, feature `F`,
|
||||
/// construction plane `CP`, body `B`.
|
||||
/// construction plane feature 8, body `B`.
|
||||
pub(super) fn with_refs(name: &str, mut v: Value) -> Value {
|
||||
let o = [0, 0, 0];
|
||||
let tool = json!({"body": "B", "point": o});
|
||||
|
||||
@@ -82,6 +82,12 @@ torus
|
||||
Major radius = tor.major (Length)
|
||||
Minor radius = tor.minor (Length)
|
||||
expressions: tor.major, tor.minor, tor.x, tor.y, tor.z
|
||||
cone
|
||||
inputs:
|
||||
Radius = cone.radius (Length)
|
||||
Top radius = cone.top_radius (Length)
|
||||
Height = cone.height (Length)
|
||||
expressions: cone.radius, cone.top_radius, cone.height, cone.x, cone.y, cone.z
|
||||
combine
|
||||
pattern_rectangular
|
||||
inputs:
|
||||
|
||||
@@ -36,6 +36,9 @@ sphere
|
||||
torus
|
||||
moves: center[0], center[1], center[2]
|
||||
placement: tor.x, tor.y, tor.z
|
||||
cone
|
||||
moves: axis[0], axis[2], base[0], base[1], base[2]
|
||||
placement: cone.x, cone.y, cone.z
|
||||
combine
|
||||
pattern_rectangular
|
||||
moves: pattern.dir1[0], pattern.dir1[1], pattern.dir2[1], pattern.dir2[2]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
body_transfer
|
||||
remap_bodies: bodies[0]
|
||||
sketch
|
||||
remap_refs: plane.base.base.name
|
||||
remap_refs: plane.base.base.feature
|
||||
sketch_along_path
|
||||
extrude
|
||||
sketches: [1]
|
||||
@@ -31,6 +31,7 @@ box
|
||||
cylinder
|
||||
sphere
|
||||
torus
|
||||
cone
|
||||
combine
|
||||
remap_bodies: target, tools[0]
|
||||
pattern_rectangular
|
||||
@@ -44,7 +45,7 @@ pattern_path
|
||||
remap_refs: features[0], pattern.path_sketch
|
||||
remap_bodies: bodies[0]
|
||||
mirror
|
||||
remap_refs: features[0], plane.base.base.name
|
||||
remap_refs: features[0], plane.base.base.feature
|
||||
remap_bodies: bodies[0]
|
||||
hole_simple
|
||||
sketches: [3]
|
||||
@@ -75,25 +76,25 @@ sweep_options
|
||||
shell
|
||||
remap_bodies: body
|
||||
draft
|
||||
remap_refs: neutral.base.base.name
|
||||
remap_refs: neutral.base.base.feature
|
||||
remap_bodies: body
|
||||
construction_plane
|
||||
remap_refs: plane.base.base.name
|
||||
remap_refs: plane.base.base.feature
|
||||
split
|
||||
remap_refs: plane.base.base.name
|
||||
remap_refs: plane.base.base.feature
|
||||
remap_bodies: body, tool.body
|
||||
split_line
|
||||
sketches: [6]
|
||||
remap_refs: plane.base.base.name, sketch
|
||||
remap_refs: plane.base.base.feature, sketch
|
||||
remap_bodies: body
|
||||
construction_axis
|
||||
remap_refs: def.a.plane.base.base.name, def.b.plane.base.base.name
|
||||
remap_refs: def.a.plane.base.base.feature, def.b.plane.base.base.feature
|
||||
construction_point_planes
|
||||
remap_refs: def.a.plane.base.base.name, def.b.plane.base.base.name, def.c.plane.base.base.name
|
||||
remap_refs: def.a.plane.base.base.feature, def.b.plane.base.base.feature, def.c.plane.base.base.feature
|
||||
construction_point_along
|
||||
split_face
|
||||
sketches: [6]
|
||||
remap_refs: plane.base.base.name, sketch
|
||||
remap_refs: plane.base.base.feature, sketch
|
||||
remap_bodies: body, tool.body
|
||||
scale
|
||||
remap_bodies: bodies[0]
|
||||
@@ -121,7 +122,7 @@ rib
|
||||
sketches: [1]
|
||||
remap_refs: sketch
|
||||
replace_face
|
||||
remap_refs: target.base.base.name
|
||||
remap_refs: target.base.base.feature
|
||||
remap_bodies: body
|
||||
align
|
||||
remap_bodies: bodies[0]
|
||||
@@ -215,21 +216,21 @@ surface_untrim
|
||||
surface_extend
|
||||
remap_bodies: body
|
||||
surface_extend_to_plane
|
||||
remap_refs: up_to.plane.base.base.name
|
||||
remap_refs: up_to.plane.base.base.feature
|
||||
remap_bodies: body
|
||||
surface_extend_to_face
|
||||
remap_bodies: body, up_to.face.body
|
||||
surface_ruled
|
||||
remap_bodies: body
|
||||
surface_trim
|
||||
remap_refs: plane.base.base.name
|
||||
remap_refs: plane.base.base.feature
|
||||
remap_bodies: body, tool.body
|
||||
surface_trim_curves
|
||||
sketches: [1]
|
||||
remap_refs: curves.sketch, plane.base.base.name
|
||||
remap_refs: curves.sketch, plane.base.base.feature
|
||||
remap_bodies: body
|
||||
surface_trim_mutual
|
||||
remap_refs: plane.base.base.name
|
||||
remap_refs: plane.base.base.feature
|
||||
remap_bodies: body, with
|
||||
move
|
||||
remap_bodies: bodies[0]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//! A design can hold cones (#5641): a `cone` feature with the centre of its base circle (`base`,
|
||||
//! numbers in mm or length expressions), its `axis` (default +Z), the base `radius`, the
|
||||
//! `top_radius` (0: a full cone ending in an apex) and the `height` along the axis, and its
|
||||
//! `operation` (new body, join, cut or intersect) like the other primitives. Older designs need no
|
||||
//! transformation: they have no such feature. The last release refuses a design saved with this
|
||||
//! (the newer format number).
|
||||
@@ -0,0 +1,180 @@
|
||||
//! Pattern and mirror seeds (`Pattern`/`Mirror` `features`) and construction plane references
|
||||
//! (`PlaneRef::Construction`) are saved as feature ids (#1134), like every other feature
|
||||
//! reference. An older design has its names resolved to ids once as it opens, exactly the way
|
||||
//! `replay()` resolved them: an exact name match on a feature that is not a sketch or a
|
||||
//! construction plane, else a feature id written as a number, else a case-insensitive name
|
||||
//! match; a name that resolves to nothing keeps id 0, which no feature has, so it dangles at
|
||||
//! evaluation with the same warning or error it always gave. New saves write ids; a rename no
|
||||
//! longer has to rewrite anything. The last release refuses a design that has them (it refuses
|
||||
//! the newer format number).
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// A design's features as `(id, name, type)`, for resolving names the way `replay()` did.
|
||||
type Features<'a> = &'a [(u64, String, String)];
|
||||
|
||||
/// `Document::find_feature` at the JSON level: an id written as a number, then a
|
||||
/// case-insensitive name match.
|
||||
fn find_feature(feats: Features, name: &str) -> Option<u64> {
|
||||
name.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|id| feats.iter().find(|(i, ..)| *i == id))
|
||||
.or_else(|| feats.iter().find(|(_, n, _)| n.eq_ignore_ascii_case(name)))
|
||||
.map(|(i, ..)| *i)
|
||||
}
|
||||
|
||||
/// The feature a pattern or mirror seed named: an exact name match on a feature that is not a
|
||||
/// sketch or a construction plane (a sketch and its extrude often share a name; the feature
|
||||
/// that makes geometry wins), else [`find_feature`]; nothing is id 0, which dangles at
|
||||
/// evaluation as the missing name did.
|
||||
fn resolve_seed(feats: Features, name: &str) -> u64 {
|
||||
feats
|
||||
.iter()
|
||||
.find(|(_, n, t)| n == name && !matches!(t.as_str(), "sketch" | "construction_plane"))
|
||||
.map(|(i, ..)| *i)
|
||||
.or_else(|| find_feature(feats, name))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// The feature a construction plane reference named ([`find_feature`]); nothing is id 0.
|
||||
fn resolve_plane(feats: Features, name: &str) -> u64 {
|
||||
find_feature(feats, name).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Rewrite every `{"type": "construction", "name": …}` plane reference in `v` to carry the
|
||||
/// feature's id. `skip_axis` leaves a revolve's `axis` alone: an `AxisRef::Construction` has
|
||||
/// the same shape but names a construction axis, which is still keyed by name.
|
||||
fn rewrite(v: &mut Value, feats: Features, skip_axis: bool) {
|
||||
match v {
|
||||
Value::Object(o) => {
|
||||
if o.get("type").and_then(Value::as_str) == Some("construction") {
|
||||
if let Some(Value::String(name)) = o.get("name") {
|
||||
let id = resolve_plane(feats, name);
|
||||
o.remove("name");
|
||||
o.insert("feature".into(), Value::from(id));
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (key, x) in o.iter_mut() {
|
||||
if !(skip_axis && key == "axis") {
|
||||
rewrite(x, feats, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Array(a) => a.iter_mut().for_each(|x| rewrite(x, feats, false)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(v: &mut Value) -> Result<()> {
|
||||
let feats: Vec<(u64, String, String)> = v
|
||||
.get("features")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(Value::as_object)
|
||||
.filter_map(|f| {
|
||||
let id = f.get("id")?.as_u64()?;
|
||||
let name = f.get("name")?.as_str()?.to_string();
|
||||
let kind = f.get("type")?.as_str()?.to_string();
|
||||
Some((id, name, kind))
|
||||
})
|
||||
.collect();
|
||||
if let Some(Value::Array(features)) = v.get_mut("features") {
|
||||
for f in features.iter_mut() {
|
||||
let kind = f.get("type").and_then(Value::as_str).unwrap_or("").to_string();
|
||||
// A pattern's or mirror's seeds: each name becomes the feature's id.
|
||||
if matches!(kind.as_str(), "pattern" | "mirror")
|
||||
&& let Some(Value::Array(seeds)) = f.get_mut("features")
|
||||
{
|
||||
for s in seeds.iter_mut() {
|
||||
if let Value::String(name) = s {
|
||||
let id = resolve_seed(&feats, name);
|
||||
*s = Value::from(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Construction plane references, everywhere but a revolve's axis.
|
||||
let skip_axis = matches!(kind.as_str(), "revolve" | "surface_revolve");
|
||||
rewrite(f, &feats, skip_axis);
|
||||
}
|
||||
}
|
||||
// References outside the features: canvases placed on a construction plane and the like.
|
||||
if let Some(o) = v.as_object_mut() {
|
||||
for (key, x) in o.iter_mut() {
|
||||
if key != "features" {
|
||||
rewrite(x, &feats, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::step;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// A design's feature list: a sketch and an extrude sharing the name `Boss` (the sketch
|
||||
/// first, as designs write them), a construction plane, a construction axis and a lower-
|
||||
/// cased extrude `boss`.
|
||||
fn features() -> Vec<Value> {
|
||||
vec![
|
||||
json!({"id": 1, "name": "Boss", "type": "sketch"}),
|
||||
json!({"id": 2, "name": "Boss", "type": "extrude"}),
|
||||
json!({"id": 3, "name": "Mid", "type": "construction_plane"}),
|
||||
json!({"id": 4, "name": "Axis1", "type": "construction_axis"}),
|
||||
json!({"id": 5, "name": "boss", "type": "extrude"}),
|
||||
]
|
||||
}
|
||||
|
||||
fn design(features: Vec<Value>) -> Value {
|
||||
json!({"format": "solidcraft/36", "features": features})
|
||||
}
|
||||
|
||||
fn migrated(mut v: Value) -> Value {
|
||||
step(&mut v).unwrap();
|
||||
v
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seeds_resolve_exactly_as_replay_resolved_them() {
|
||||
let mut feats = features();
|
||||
feats.push(json!({"id": 6, "name": "Copies", "type": "pattern", "features": ["Boss", "mid", "5", "Gone"], "pattern": {"type": "circular"}}));
|
||||
let out = migrated(design(feats));
|
||||
// `Boss`: the exact name on a feature that is not a sketch wins over the sketch and
|
||||
// over the case-insensitive `boss`. `mid`: no exact match, so the case-insensitive
|
||||
// find_feature fallback. `5`: an id written as a number. `Gone`: dangling, id 0.
|
||||
assert_eq!(out["features"][5]["features"], json!([2, 3, 5, 0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construction_planes_resolve_by_find_feature_and_dangle_as_zero() {
|
||||
let mut feats = features();
|
||||
feats.push(json!({"id": 7, "name": "On Mid", "type": "sketch", "plane": {"type": "construction", "name": "MID"}}));
|
||||
feats.push(json!({"id": 8, "name": "Lost", "type": "sketch", "plane": {"type": "offset", "distance": "5", "base": {"type": "construction", "name": "Gone"}}}));
|
||||
let out = migrated(design(feats));
|
||||
assert_eq!(out["features"][5]["plane"], json!({"type": "construction", "feature": 3}));
|
||||
assert_eq!(out["features"][6]["plane"]["base"], json!({"type": "construction", "feature": 0}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_revolve_axis_naming_a_construction_axis_keeps_its_name() {
|
||||
let mut feats = features();
|
||||
feats.push(json!({"id": 9, "name": "Turn", "type": "revolve", "axis": {"type": "construction", "name": "Axis1"}}));
|
||||
feats.push(json!({"id": 10, "name": "Surface Turn", "type": "surface_revolve", "axis": {"type": "construction", "name": "Axis1"}}));
|
||||
let out = migrated(design(feats));
|
||||
assert_eq!(out["features"][5]["axis"], json!({"type": "construction", "name": "Axis1"}));
|
||||
assert_eq!(out["features"][6]["axis"], json!({"type": "construction", "name": "Axis1"}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn references_outside_the_features_are_migrated_too() {
|
||||
let mut d = design(features());
|
||||
d["canvases"] = json!([{"id": 1, "plane": {"type": "construction", "name": "Mid"}}]);
|
||||
let out = migrated(d);
|
||||
assert_eq!(out["canvases"][0]["plane"], json!({"type": "construction", "feature": 3}));
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,8 @@ pub mod sheet;
|
||||
pub mod text_params;
|
||||
|
||||
pub use assembly::{
|
||||
IDENTITY, MAX_PLACEMENT_DEPTH, MAX_PLACEMENTS, Mat, Occurrence, apply_point, apply_vector, is_identity, map_point_expr, mat_inverse, mat_mul,
|
||||
rigid,
|
||||
IDENTITY, MAX_PLACEMENT_DEPTH, MAX_PLACEMENTS, Mat, Occurrence, WorldNames, apply_point, apply_vector, is_identity, map_point_expr, mat_inverse,
|
||||
mat_mul, rigid,
|
||||
};
|
||||
pub use document::*;
|
||||
pub use eval::KeptResults;
|
||||
@@ -82,7 +82,7 @@ pub use eval::{
|
||||
world_state_reusing,
|
||||
};
|
||||
pub use expr::Expr;
|
||||
pub use names::{BodyName, FeatureName, SketchId};
|
||||
pub use names::{BodyName, FeatureId, FeatureName, SketchId, copy_name};
|
||||
pub use params::{ParamRow, ParamUser, cycles};
|
||||
pub use points::{Dir, FacePick, Position, plain};
|
||||
pub use solidcraft_kernel as kernel;
|
||||
|
||||
@@ -103,6 +103,55 @@ name_type!(
|
||||
FeatureName
|
||||
);
|
||||
|
||||
/// A feature, by its id. Pattern and mirror seeds and construction planes name features by id
|
||||
/// (#1134); a reference that resolves to nothing keeps id 0, which no feature ever has.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct FeatureId(pub u64);
|
||||
|
||||
impl FeatureId {
|
||||
/// A reference that names no feature: a name that could not be resolved when an older
|
||||
/// design was migrated (#1134). Evaluation reports it like today's dangling name.
|
||||
pub const DANGLING: FeatureId = FeatureId(0);
|
||||
}
|
||||
|
||||
impl Deref for FeatureId {
|
||||
type Target = u64;
|
||||
fn deref(&self) -> &u64 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for FeatureId {
|
||||
fn from(id: u64) -> Self {
|
||||
FeatureId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FeatureId> for u64 {
|
||||
fn from(id: FeatureId) -> u64 {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u64> for FeatureId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<FeatureId> for u64 {
|
||||
fn eq(&self, other: &FeatureId) -> bool {
|
||||
*self == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FeatureId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A sketch, by its feature id.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
@@ -144,3 +193,28 @@ impl std::fmt::Display for SketchId {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of a copy of `name` (a pasted body, feature or component): the name and the next
|
||||
/// number in brackets that `taken` doesn't hold — `Cube` → `Cube (2)`, another copy of `Cube` →
|
||||
/// `Cube (3)`. A name that already ends in brackets takes the number right after them: a copy of
|
||||
/// `Cube (2)` → `Cube (2)(2)`.
|
||||
pub fn copy_name(name: &str, taken: impl Fn(&str) -> bool) -> String {
|
||||
let name = name.trim();
|
||||
let gap = if name.ends_with(')') { "" } else { " " };
|
||||
(2..100_002u64).map(|i| format!("{name}{gap}({i})")).find(|c| !taken(c)).unwrap_or_else(|| format!("{name}{gap}(2)"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod copy_name_tests {
|
||||
use super::copy_name;
|
||||
|
||||
#[test]
|
||||
fn a_copy_takes_the_next_free_number() {
|
||||
let taken = |names: &'static [&'static str]| move |n: &str| names.contains(&n);
|
||||
assert_eq!(copy_name("Cube", taken(&["Cube"])), "Cube (2)");
|
||||
assert_eq!(copy_name("Cube", taken(&["Cube", "Cube (2)"])), "Cube (3)");
|
||||
assert_eq!(copy_name("Cube (2)", taken(&["Cube", "Cube (2)", "Cube (3)"])), "Cube (2)(2)");
|
||||
assert_eq!(copy_name("Cube (2)", taken(&["Cube (2)", "Cube (2)(2)"])), "Cube (2)(3)");
|
||||
assert_eq!(copy_name("Sketch1", taken(&["Sketch1"])), "Sketch1 (2)");
|
||||
}
|
||||
}
|
||||
|
||||