Compare commits
503 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72ad6383ef | |||
| 2cf05388f1 | |||
| 093571cfe4 | |||
| a1b9ca13e3 | |||
| 699f526429 | |||
| 16a5254e97 | |||
| e5613ebfdf | |||
| dfde1e032d | |||
| 10da6b9efc | |||
| 5a5a98ee80 | |||
| 4e2270517a | |||
| 0312808b92 | |||
| 9d45fa6b34 | |||
| e4f11c8df6 | |||
| bbc3aa4301 | |||
| 611d4eb6b8 | |||
| c50f5364c6 | |||
| beb38b44aa | |||
| a49698ee3d | |||
| 919f54a23e | |||
| 1d0e371e64 | |||
| 43565cd33b | |||
| ab9d9ac50f | |||
| 157c0d68d0 | |||
| 38d7ea4198 | |||
| 7c2b2f1559 | |||
| cc1e654690 | |||
| 7af97e3729 | |||
| 138bb0261c | |||
| dbe7dfa4bc | |||
| 1ceab2ed4c | |||
| 743accca7a | |||
| 128692da0c | |||
| 13b4f6e98c | |||
| ac53fc1e6f | |||
| 8d63a9f4cb | |||
| a150822d9b | |||
| a9fd4b1b05 | |||
| face5e2d31 | |||
| efd0e70f7d | |||
| 3f4c6e4571 | |||
| abc39685b1 | |||
| f93913b3d3 | |||
| 765c5fd143 | |||
| 6494f8fe18 | |||
| 1623034973 | |||
| 74004841b4 | |||
| be08517de2 | |||
| 24ed21998b | |||
| 3592031d5e | |||
| b8d9db7440 | |||
| 2b620338df | |||
| a5b6408587 | |||
| 78de687902 | |||
| 2741cd9a8b | |||
| 86f5500914 | |||
| 7804f9938a | |||
| ab03aa5bc5 | |||
| 2e3f27f06e | |||
| bda32419bd | |||
| c242c484a8 | |||
| 5d37cb01d5 | |||
| 7313fd8684 | |||
| 6f340ade69 | |||
| e260437846 | |||
| f54ad0f12f | |||
| 43f21e8fec | |||
| 3e1f1a89f8 | |||
| 82253c6f35 | |||
| f4d937d8a2 | |||
| 75fb613e25 | |||
| ce614044a7 | |||
| 91549d29fb | |||
| b6a5a9166a | |||
| 0063954286 | |||
| 6f6b64d484 | |||
| 52e496d017 | |||
| d557c09c3b | |||
| 7046f7f0e7 | |||
| 16286249ed | |||
| 19e2800472 | |||
| a5f7f5a921 | |||
| a402c5e502 | |||
| cdde7d62b2 | |||
| 3f6b9fe89f | |||
| b4acd83c78 | |||
| f7233c4418 | |||
| 5573ae06fc | |||
| 89a4a96630 | |||
| 34d1ac0376 | |||
| 0e9e175e79 | |||
| f11ad0ce28 | |||
| 9ad96fb81b | |||
| b671265faa | |||
| 832ff941d1 | |||
| 9869997733 | |||
| 745a24bbc8 | |||
| 26340882c0 | |||
| 8e2b8f2ace | |||
| 34e75979b2 | |||
| d5eca6cc78 | |||
| 77c77829e3 | |||
| f01ef3c6aa | |||
| f5a6c8fa51 | |||
| 865575e410 | |||
| 992c93fe5b | |||
| 0343960bd6 | |||
| 1d48707d0c | |||
| 443baa41f0 | |||
| d366b8922a | |||
| 692265980b | |||
| 28fe5fdb43 | |||
| 26c9d1c445 | |||
| f157f5489f | |||
| d1ec8dfd5a | |||
| 08d094db39 | |||
| 5fa6361ff2 | |||
| 1f93b6d39c | |||
| 0477e0ba4e | |||
| ae7176cc86 | |||
| eef9a68809 | |||
| f06875128b | |||
| 9d226b3535 | |||
| 7a1f903216 | |||
| b88673f366 | |||
| 67adf5abb5 | |||
| adbd2adf89 | |||
| 96303873fe | |||
| b925a1af3d | |||
| 05d43d3542 | |||
| c0640cb95e | |||
| 2ff17c0fcb | |||
| ecf57d131a | |||
| 32ad6e0832 | |||
| 13c4a62a71 | |||
| bba1953623 | |||
| 0537623a75 | |||
| 2a0096fe4a | |||
| 4b119dd471 | |||
| 6224cbf526 | |||
| e89a8f56c1 | |||
| 4cd1a51f48 | |||
| 88f079bd8c | |||
| dbdeeec2e9 | |||
| 1dbb13cad2 | |||
| 0b91ff8a34 | |||
| 1e81e65e7e | |||
| 9227685483 | |||
| 582e6097be | |||
| 22190e703c | |||
| f7b08ab8c4 | |||
| ae0b1edeac | |||
| cf1b0d67cc | |||
| b702ce8c7d | |||
| 60105172b2 | |||
| e2b8bf5a05 | |||
| 9e065aa0d5 | |||
| 2fd7b6f08d | |||
| 59795872f6 | |||
| 37b7b0bd3e | |||
| 7e24f0e35a | |||
| 152efb5774 | |||
| e726f780d1 | |||
| b201c283c7 | |||
| a2ae3ffbe0 | |||
| 96661111f9 | |||
| cc9d76cd86 | |||
| c61ffb54a8 | |||
| ab889a8fff | |||
| fcc3753a4c | |||
| 2c9d0a2847 | |||
| 99fd05afeb | |||
| a8051760f7 | |||
| c795a3f581 | |||
| 2cab9dc142 | |||
| 6d8d53aac8 | |||
| 4e6939b5cf | |||
| f6ff2ff139 | |||
| 048c1ae8a8 | |||
| b00a1e87f4 | |||
| 8cbde001ef | |||
| ad8b2f91bb | |||
| 9919825ea3 | |||
| 97e0ff9050 | |||
| c48c1a09d3 | |||
| ee3d41210d | |||
| be685d064f | |||
| 32e613aa8c | |||
| 58a9d33f02 | |||
| a8e6154c0d | |||
| d61dff69a6 | |||
| 8c853fff3e | |||
| daec1091f4 | |||
| 0520910bfd | |||
| 46f24f2394 | |||
| 1236863b17 | |||
| 4f4b1fc2d2 | |||
| d5a79a77e6 | |||
| 5d47946e4d | |||
| 69cfd89ae8 | |||
| 9e6e0b8df1 | |||
| b7b25f9e2b | |||
| a633a67e63 | |||
| 5ca7c2fb95 | |||
| 592f6a6fee | |||
| 4612c5e6a9 | |||
| c0da9fe861 | |||
| 19db763899 | |||
| 9a737bd63c | |||
| 8b65d41b31 | |||
| 4dc30c092d | |||
| e67ae66f3e | |||
| 5e0e7a4c6a | |||
| 807e367bdb | |||
| 400e06c202 | |||
| 1fa0371c75 | |||
| fc3e3b6c8d | |||
| d1a6262861 | |||
| 612d9d08dd | |||
| d2eb8cf639 | |||
| 1f6a2d4daf | |||
| 6c6ffe4802 | |||
| 46fca160e1 | |||
| 16727f331c | |||
| 01220d3eb5 | |||
| 89c1f02850 | |||
| 45edd5baf5 | |||
| 7dd5baac5c | |||
| 65d288227e | |||
| 2e5924ed5e | |||
| a199a2cc25 | |||
| fb374b543b | |||
| 0dffb206b1 | |||
| 30b01e8d79 | |||
| 1ed394f8d6 | |||
| d5a046c3d3 | |||
| 4353f34bd1 | |||
| fedb3bcdb1 | |||
| a51dfc9e1f | |||
| 9aaa8016e5 | |||
| 71f7a8e697 | |||
| 632ec4157c | |||
| 6eaf723041 | |||
| c3ed8b1c07 | |||
| 8301d1c48e | |||
| 2a02eb71ac | |||
| 097b8b7550 | |||
| f120285c96 | |||
| af7ca3a8a2 | |||
| b31fddb98e | |||
| bb155d446a | |||
| 27ddb725c7 | |||
| 2bf3d4e11f | |||
| 9852a2e758 | |||
| 98c4f335e8 | |||
| 4bdeaacde6 | |||
| e5dffc7052 | |||
| 43fa25458d | |||
| 9b8fa3d357 | |||
| 755ad9af07 | |||
| 46aa3d5d47 | |||
| 125b8d5387 | |||
| b5edd98bc9 | |||
| 647a52d0fb | |||
| 8c977131d9 | |||
| 30a1fe74ca | |||
| fa2c1c31f6 | |||
| 7e472bc4da | |||
| 145c6ad60d | |||
| e1bf784014 | |||
| 1bbc5ab0f7 | |||
| c1e9d2f057 | |||
| 68abb15d7f | |||
| 945e6261cc | |||
| d6b1608f9d | |||
| 2d54cb31bc | |||
| 3acb81964a | |||
| 4e7bf2b63a | |||
| 3093ce1d87 | |||
| ca9dd02201 | |||
| 1d9bdb1ca3 | |||
| d78494e016 | |||
| d18cc0df4e | |||
| 04dc68d988 | |||
| efb45d7411 | |||
| 426f8ddd18 | |||
| 27d2fe6ec0 | |||
| 6fb72c7401 | |||
| 14ccc71291 | |||
| ec56df3ffd | |||
| 8565f3f1b3 | |||
| 12c823ffdd | |||
| 5d92ad1e23 | |||
| e8a6a5d3ff | |||
| 605d32ceb0 | |||
| b79c03d227 | |||
| 17bac141c8 | |||
| 4b42ef50c7 | |||
| fdde3f9da9 | |||
| 74ea81519b | |||
| 769dfff1a5 | |||
| 3ef68c708f | |||
| 3ed1829cc7 | |||
| b982ccd8c4 | |||
| 2586b09f5a | |||
| a5f9ff45dd | |||
| 61360c3b0d | |||
| eec6da060f | |||
| f422bb1263 | |||
| 96ce31153c | |||
| a9c3c8bb8a | |||
| a147a7c083 | |||
| 89a11d75c0 | |||
| 84cad43154 | |||
| 5c280b9a94 | |||
| 1a953736ff | |||
| 4106332738 | |||
| 9559ad398a | |||
| bddb52c96a | |||
| a0fd39a7be | |||
| be87303683 | |||
| bd0cc4bb51 | |||
| 4c8ae5d078 | |||
| e33c371e99 | |||
| e104e241a3 | |||
| 3bca787213 | |||
| 21257c39c2 | |||
| ed6e123d54 | |||
| dad28abecf | |||
| d1f5d6e223 | |||
| 1b215b521d | |||
| 36a354e3a7 | |||
| a05a0f1053 | |||
| 60efbe39e3 | |||
| 9e20a373ca | |||
| 2f55a39c9c | |||
| 80fd2fbbf1 | |||
| c3d1ce8237 | |||
| 567bc50723 | |||
| 1a5fcd05d6 | |||
| 3a6153d18f | |||
| d273c45de6 | |||
| da46c660df | |||
| 92d1331978 | |||
| 01bbc0aa9f | |||
| dbbad6d4ee | |||
| 4f69e6540c | |||
| 55608665ef | |||
| 0fb4a3e868 | |||
| 2dc31a30d4 | |||
| 8b68eb7eac | |||
| 96679f3e2b | |||
| 3ba17bb517 | |||
| ae3b36ac88 | |||
| 5367ef4367 | |||
| caa1e6e196 | |||
| 8bd17cea42 | |||
| 4256775274 | |||
| 5962f56f87 | |||
| b6a45b8278 | |||
| 8ae90f9667 | |||
| 17f0d6bc68 | |||
| 8b1a722f06 | |||
| 63d81cc096 | |||
| 46a8f86e80 | |||
| 94279fe059 | |||
| 867df396ea | |||
| 858e5995d6 | |||
| edf57cec29 | |||
| 0c72ad56d2 | |||
| 92d3a2fde5 | |||
| b0048a6104 | |||
| 0db2abbeca | |||
| 9761d75699 | |||
| a06bdf5cfc | |||
| 030187452a | |||
| 198fbeb814 | |||
| 186096cbe6 | |||
| 3b982ca371 | |||
| aaa110218b | |||
| 46f80b769f | |||
| 26ca82735b | |||
| 408d30ea56 | |||
| 75261cd797 | |||
| 6f5d0038ab | |||
| 1814adb06a | |||
| 971b28c982 | |||
| f79cbc216c | |||
| 4dc1828138 | |||
| f843a3f83c | |||
| f0fd5c10b7 | |||
| 981d23d1d2 | |||
| 01648da72d | |||
| 3d3dd27439 | |||
| 6c60f1a9ed | |||
| 33fdcdb3a0 | |||
| 5899e5b6c5 | |||
| e23a5bf243 | |||
| 4b23bb3a03 | |||
| 920398ac53 | |||
| 688124937d | |||
| b49557c294 | |||
| dfd58293a6 | |||
| ba4f7864ff | |||
| b289dc2421 | |||
| 2daf22923c | |||
| 2fdc105d87 | |||
| 95b23f28d6 | |||
| 14b27f323a | |||
| 4ab3a85323 | |||
| c54b711345 | |||
| 84ec512d7a | |||
| 17ece2edc0 | |||
| 63f210b49f | |||
| 9acff62422 | |||
| 915ee8a178 | |||
| ac1768e08b | |||
| e1daf0a8b6 | |||
| 57c3e94df6 | |||
| 5e05d925e0 | |||
| 50313d147f | |||
| c07ab5f8f9 | |||
| 728f753b80 | |||
| f22a026c27 | |||
| 4b37c046e4 | |||
| 54072b7014 | |||
| cb3cabeda1 | |||
| 775e5155bb | |||
| e570c0b4eb | |||
| d9059d8da5 | |||
| 463cea0615 | |||
| 0225e4d2a5 | |||
| fea32f8aac | |||
| 034aa60d47 | |||
| 971b223246 | |||
| 8c861f9f46 | |||
| e04a016e7c | |||
| 93a2922c5e | |||
| ff8b09c225 | |||
| 975a08c992 | |||
| 55587f4ac4 | |||
| e94900933d | |||
| 4927214976 | |||
| 57bcc40838 | |||
| 9974413a5b | |||
| 818af2b856 | |||
| 80238a7cfb | |||
| bd65a561c8 | |||
| 776541b081 | |||
| 79a843c963 | |||
| 1bcc3602b4 | |||
| 93684a4728 | |||
| 9db39f3d7d | |||
| 29369a0bb5 | |||
| 5a9e771cc9 | |||
| 63cb48a4ee | |||
| 36c732ddb7 | |||
| fdec948abc | |||
| 7803b3968f | |||
| cbac4eab3f | |||
| 08eff4f9dd | |||
| 9b80163421 | |||
| 164554edba | |||
| 453c6bcb14 | |||
| f44ff18ca4 | |||
| 98cd67f029 | |||
| d9d6af51c6 | |||
| 38c710e72e | |||
| 5b5b051e02 | |||
| 686576e368 | |||
| 68e4186080 | |||
| b66ad0a9c9 | |||
| 2eacbc37f4 | |||
| f2f9c6fe27 | |||
| d21e2b1afd | |||
| bdd2fb1a5c | |||
| e64f1e0427 | |||
| ac362945cc | |||
| 382680d8e8 | |||
| 316b49cd84 | |||
| b0932a487e | |||
| edad8078ba | |||
| 26c0c19ef7 | |||
| 2f43ad5c5b | |||
| 6a8604c55d | |||
| 4098ff9dc8 | |||
| 97383454c6 | |||
| 10d7bd045e | |||
| 6e87fb1a25 | |||
| 30e0db1e05 | |||
| 2b1cb2c4e1 | |||
| f815491936 | |||
| a595805fc8 | |||
| 0379ee76c6 | |||
| 4f7ff8c4d8 | |||
| d2188fb7ab | |||
| 438abbcf82 | |||
| 079a600481 | |||
| 94bd8719aa | |||
| fc3dedfd5c | |||
| 41a62d210c | |||
| 5638259737 | |||
| 090119a5c5 |
@@ -1,6 +1,5 @@
|
||||
# cargo-nextest settings for CI (`cargo xtask test` does not use nextest).
|
||||
# CI builds the tests once and runs them in shards: 2 for a pull request, 4 for main and the nightly
|
||||
# (.github/workflows/ci.yml).
|
||||
# CI builds the tests once and runs them in one job on a 32 vCPU runner (.github/workflows/ci.yml).
|
||||
|
||||
[profile.ci]
|
||||
# Heavy tests (one to twelve minutes each on a CI runner) that pull requests skip. They run on
|
||||
@@ -34,13 +33,14 @@ slow-timeout = { period = "60s" }
|
||||
[profile.ci.junit]
|
||||
path = "junit.xml"
|
||||
|
||||
# The action matrix sample runs its designs in parallel worker processes, one per CPU. Run with
|
||||
# other tests beside it (the heavy ones on main), a pi-case hole that takes 8 s alone (#952 makes
|
||||
# it a real hole) went past the matrix's 90 s action limit. It runs on its own.
|
||||
# The action matrix sample runs its designs in parallel workers (ACTION_MATRIX_JOBS, set to match in
|
||||
# ci.yml). Its workers reserve that many of nextest's threads, so the cores are not oversubscribed
|
||||
# (a pi-case hole that takes 8 s alone went past the matrix's 90 s action limit when they were),
|
||||
# and it starts first, beside the other slow tests.
|
||||
[[profile.ci.overrides]]
|
||||
filter = "binary(action_matrix) & test(=action_matrix_sample)"
|
||||
threads-required = "num-cpus"
|
||||
|
||||
threads-required = 12
|
||||
priority = 100
|
||||
|
||||
# The slowest tests start first, so a shard does not end with one of them running alone on a
|
||||
# core while the others sit idle (seed_81 alone takes about 20 minutes under CI load). Medians
|
||||
@@ -68,3 +68,60 @@ filter = """
|
||||
))
|
||||
"""
|
||||
priority = 100
|
||||
|
||||
# Each nextest test has its own process/device. Concurrent cold lavapipe path
|
||||
# compiles contend for CPU and can exhaust the 600 s pipeline setup budget.
|
||||
# Serialize this module per CI shard; other tests retain their normal parallelism.
|
||||
[test-groups.gpu-path]
|
||||
max-threads = 1
|
||||
|
||||
[[profile.ci.overrides]]
|
||||
filter = "package(=solidcraft-gpu) & test(/^path::/)"
|
||||
test-group = "gpu-path"
|
||||
|
||||
# Full local validation opts into its own scheduling policy. Keep GitHub CI's
|
||||
# worker reservation and slow-test priorities above independent of this runner.
|
||||
[profile.local-ci]
|
||||
inherits = "ci"
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = "binary(action_matrix) & test(=action_matrix_sample)"
|
||||
threads-required = "num-cpus"
|
||||
priority = 100
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = "(package(solidcraft-engine) & test(=tests::robustness_goldens::seed_81)) | (package(solidcraft-kernel) & test(=tspline::tests::hostile_tsplines_never_panic))"
|
||||
priority = 99
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = """
|
||||
(package(solidcraft-engine) & (
|
||||
test(/^tests::robustness_goldens::seed_(81|45|20|24|95|02|57|40|18|48|32|46|85|61)$/)
|
||||
| test(/^tests::robustness::/)
|
||||
| test(=tests::hostile_params_never_panic)
|
||||
| test(=cmd::clearance::tests::distances_are_exact_on_edges_and_curved_faces)
|
||||
| test(=cmd::clipboard::tests::a_pasted_coil_joins_the_pasted_body)
|
||||
| test(=tests::pleat_join::the_pleats_join_at_every_wall_from_2_4_to_2_6_mm)
|
||||
| test(=tests::flush_join_loft::a_flange_flush_with_the_end_of_a_bent_tapered_tube_joins)
|
||||
| test(=cmd::sheet::tests::perforated_sheet_takes_a_thousand_round_holes)
|
||||
| test(=boolean_regression_tests::joined_loft_frames_plan_their_display_at_every_quality)
|
||||
))
|
||||
| (package(solidcraft-engine) & (binary(atomic_commands) | binary(step_export_regressions)))
|
||||
| (package(solidcraft-kernel) & (
|
||||
test(=curveblend::tests::rolling_ball_turns_about_a_rounded_corner_of_its_radius)
|
||||
| test(=tests::fillet_tabbed_plate_outer_loop_and_partial_selection_after_hole)
|
||||
| test(=tspline::tests::hostile_tsplines_never_panic)
|
||||
| test(=fill::tests::regular_rims_of_three_to_eight_sides_fill_with_one_face_and_close_the_cup)
|
||||
| test(/^issue_788_tests::equal_bores_crossing_/)
|
||||
))
|
||||
"""
|
||||
priority = 98
|
||||
|
||||
# Native Windows shader/device creation must not overlap within the local run.
|
||||
[test-groups.windows-gpu]
|
||||
max-threads = 1
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
platform = "cfg(windows)"
|
||||
filter = "package(solidcraft-gpu) | package(solidcraft) | (package(solidcraft-ui-egui) & test(/(offscreen|gpu)/))"
|
||||
test-group = "windows-gpu"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Preserve canonical source receipts and the runtime icon-discovery snapshot.
|
||||
.gitattributes text eol=lf
|
||||
/assets/icons/ui/design/requested_icons.txt text eol=lf
|
||||
@@ -7,4 +7,5 @@ self-hosted-runner:
|
||||
- blacksmith-4vcpu-ubuntu-2404
|
||||
- blacksmith-8vcpu-ubuntu-2404
|
||||
- blacksmith-16vcpu-ubuntu-2404
|
||||
- blacksmith-32vcpu-ubuntu-2404
|
||||
- blacksmith-8vcpu-windows-2025
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
Describe the problem and resulting behavior. Link the issue when applicable.
|
||||
|
||||
Changes a default: no
|
||||
|
||||
<!-- Required line, replace "no" if this changes what a user gets out of the box (a default value, key binding, setting, tool behaviour):
|
||||
Changes a default: yes — <what>, old behaviour kept behind preference <name>
|
||||
Docs-only and bot PRs are exempt. -->
|
||||
|
||||
## Validation
|
||||
|
||||
What did you run, and what were the results? Include expected and measured values for geometry changes.
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
# whole workspace, as do pushes to main that change code, the nightly run and Run workflow.
|
||||
# Documentation, packaging, the other workflows and the GitHub bots' scripts select nothing:
|
||||
# no native job runs for them, on pull requests or on main.
|
||||
# Tests are built once and run in shards. A few heavy tests (see .config/nextest.toml) are
|
||||
# Tests are built and run in one job on a big runner, slowest first, starting beside the repository
|
||||
# checks (no archive to upload and download). A few heavy tests (see .config/nextest.toml) are
|
||||
# skipped on pull requests and run on pushes to main and nightly. The Windows jobs (D3D12 shaders
|
||||
# and app startup), the reference tests and the doc tests run on main and nightly only, not on
|
||||
# pull requests (the fast lane, in Select checks); a break there is fixed forward on main. Only
|
||||
# main saves the build caches; pull requests restore them. On pull requests the test build and the
|
||||
# shards start only after the format and repository checks pass.
|
||||
# main saves the build caches; pull requests restore them. sccache is on a Blacksmith sticky disk that
|
||||
# only main commits.
|
||||
# A push to main runs only the repository checks when a passing merge-queue run tested that very
|
||||
# commit, or when nothing a build reads changed since the last push to main whose run passed.
|
||||
# Otherwise it runs everything.
|
||||
@@ -29,14 +30,13 @@
|
||||
# reads `ci` from the newest run of a commit, so a run without it would block a PR that passed.
|
||||
# Cross-compilation is opt-in through Run workflow; release builds remain in release.yml.
|
||||
# Every Linux job runs on Blacksmith's Ubuntu 24.04 runners (blacksmith-<N>vcpu-ubuntu-2404), sized
|
||||
# per job: 16 vCPU for Build tests, Clippy and the merge queue's test shards, 8 for compiles, the
|
||||
# other test shards, doc and reference tests, 4 for the light checks, 2 for Select checks and ci.
|
||||
# per job: 32 vCPU for the tests, 16 for Clippy, the archive build (nightly) and the merge queue's, 8 for compiles, doc and reference tests, 4 for the light checks, 2 for Select checks and ci.
|
||||
# The Windows shader job runs on Blacksmith's Windows Server 2025 runners (blacksmith-8vcpu-windows-2025);
|
||||
# the Windows app startup smoke stays on windows-latest, which has a desktop session that presents frames.
|
||||
# Pull requests, forks included, the merge queue, pushes to main and the nightly run all use them:
|
||||
# the runners are ephemeral, fork runs get no secrets, and main saves the build caches that every
|
||||
# other run restores read-only. The tests are built once (Build tests) into an archive the shards
|
||||
# run; Blacksmith's image matches ubuntu-24.04, so the archive and the recorded goldens behave the
|
||||
# other run restores read-only. Nightly and by hand, Build tests also makes an archive the matrix
|
||||
# jobs run; Blacksmith's image matches ubuntu-24.04, so the archive and the recorded goldens behave the
|
||||
# same.
|
||||
name: CI
|
||||
|
||||
@@ -178,9 +178,12 @@ jobs:
|
||||
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/ci-paths"
|
||||
while IFS= read -r -d '' path; do
|
||||
case "$path" in crates/kernel/*|vendor/truck-*) kernel=true ;; esac
|
||||
# What can change a robustness golden: the kernel and the crates a design is built with.
|
||||
# What a pull request re-checks the goldens for: the kernel, truck and the goldens
|
||||
# themselves (a re-record is checked before it is queued). The goldens are frozen, so
|
||||
# on any other change they would only re-check unchanged results; the merge queue,
|
||||
# main and the nightly run every seed before anything lands.
|
||||
case "$path" in
|
||||
crates/kernel/*|crates/engine/*|crates/doc/*|crates/sketch/*|crates/geom/*|vendor/truck-*|tests/robustness/*) goldens=true ;;
|
||||
crates/kernel/*|vendor/truck-*|tests/robustness/*) goldens=true ;;
|
||||
esac
|
||||
# What the Windows D3D12 jobs exercise: the GPU crate and its shaders, the app and
|
||||
# its startup, the viewport's wgpu glue, the vendored egui-wgpu, their scripts.
|
||||
@@ -277,6 +280,10 @@ jobs:
|
||||
run: cargo xtask licences --check
|
||||
- name: Dependency layers
|
||||
run: cargo xtask layers
|
||||
- name: Universal UI containers
|
||||
run: |
|
||||
cargo xtask ui-containers
|
||||
cargo test -p xtask ui_containers --locked
|
||||
- name: Sorted lists
|
||||
run: cargo xtask lists
|
||||
- name: Design format pending changes
|
||||
@@ -287,8 +294,8 @@ jobs:
|
||||
run: python3 -I scripts/ci/test_affected.py
|
||||
- name: Merge-of-main detection self-test
|
||||
run: python3 -I scripts/ci/test_main_merge.py
|
||||
- name: Claim and triage bot tests
|
||||
run: node --test scripts/claim/*.test.js scripts/triage/*.test.js
|
||||
- name: Claim, triage and kernel-paused bot tests
|
||||
run: node --test scripts/claim/*.test.js scripts/triage/*.test.js scripts/kernel-paused/*.test.js scripts/pr-checks/*.test.js
|
||||
|
||||
# Fast feedback for a pull request that has not been approved for the full CI yet.
|
||||
quick-compile:
|
||||
@@ -337,25 +344,33 @@ jobs:
|
||||
|
||||
build-tests:
|
||||
name: Build tests
|
||||
# Fail fast on pull requests: the test archive (and so the shards) waits for the repository
|
||||
# checks, the most common cheap failure; heavy jobs used to run on to the end beside a failed
|
||||
# format check. Main, nightly and Run workflow build regardless, so the action matrix and the
|
||||
# property tests that hang off this job keep their signal.
|
||||
needs: [changes, checks]
|
||||
if: ${{ !cancelled() && needs.changes.result == 'success' && needs.changes.outputs.native == 'true' && (github.event_name != 'pull_request' || needs.checks.result == 'success') }}
|
||||
# The archive is for the action matrix and the property tests, which run only nightly and by
|
||||
# hand. Pull requests, the merge queue and main build and run their tests in the `tests` job.
|
||||
needs: changes
|
||||
if: ${{ !cancelled() && needs.changes.result == 'success' && needs.changes.outputs.native == 'true' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
# Fork builds start from main's cache, read-only; give them room to finish and upload.
|
||||
timeout-minutes: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 60 || 45 }}
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
|
||||
SCCACHE_DIR: /home/runner/.cache/sccache
|
||||
SCCACHE_CACHE_SIZE: 20G
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Size parallel compiles to the job's memory
|
||||
run: scripts/ci/build-jobs.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# sccache lives on a Blacksmith sticky disk (an ext4 volume cloned from the last snapshot,
|
||||
# no 10 GB limit and no download). Only main commits it; pull requests, the merge queue and
|
||||
# forks mount a clone and never write back, so nothing they build can reach main's cache.
|
||||
- name: Mount the compiler cache
|
||||
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: ${{ github.repository }}-sccache-archive
|
||||
path: /home/runner/.cache/sccache
|
||||
commit: ${{ github.ref == 'refs/heads/main' && 'on-change' || 'false' }}
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: native-tests
|
||||
@@ -387,63 +402,94 @@ jobs:
|
||||
run: sccache --show-stats
|
||||
|
||||
tests:
|
||||
name: Native Linux tests (${{ matrix.shard }}/4)
|
||||
needs: [changes, checks, build-tests]
|
||||
if: ${{ !cancelled() && needs.changes.outputs.native == 'true' && (github.event_name != 'pull_request' || needs.checks.result == 'success') && needs.build-tests.result == 'success' }}
|
||||
runs-on: ${{ github.event_name == 'merge_group' && 'blacksmith-16vcpu-ubuntu-2404' || 'blacksmith-8vcpu-ubuntu-2404' }}
|
||||
name: Native Linux tests
|
||||
needs: changes
|
||||
if: ${{ !cancelled() && needs.changes.result == 'success' && needs.changes.outputs.native == 'true' }}
|
||||
# Builds the tests and runs them in one job on a big runner: there is no archive to upload
|
||||
# and download (3 GB on a full run), and the job starts beside the repository checks, not
|
||||
# after them. nextest hands tests to its threads as they free up, the slowest first (priority
|
||||
# in .config/nextest.toml), so they start at once and no core idles at the end.
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Pull requests test their affected crates in 2 shards; main and the nightly use 4.
|
||||
shard: ${{ fromJSON(github.event_name == 'pull_request' && '[1, 2]' || '[1, 2, 3, 4]') }}
|
||||
env:
|
||||
# Pictures (snapshots, screenshots, ui.render, the viewport's offscreen tests) are drawn on
|
||||
# the GPU. The runner has none: Mesa's lavapipe (software Vulkan) draws them, and a test that
|
||||
# would skip for want of an adapter fails instead.
|
||||
SOLIDCRAFT_GPU: software
|
||||
SOLIDCRAFT_REQUIRE_GPU: "1"
|
||||
ACTION_MATRIX_JOBS: "12"
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_DIR: /home/runner/.cache/sccache
|
||||
SCCACHE_CACHE_SIZE: 20G
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install a software Vulkan driver (lavapipe)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends mesa-vulkan-drivers libvulkan1
|
||||
- name: Size parallel compiles to the job's memory
|
||||
run: scripts/ci/build-jobs.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# sccache lives on a Blacksmith sticky disk (an ext4 volume cloned from the last snapshot,
|
||||
# no 10 GB limit and no download). Only main commits it; pull requests, the merge queue and
|
||||
# forks mount a clone and never write back, so nothing they build can reach main's cache.
|
||||
- name: Mount the compiler cache
|
||||
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: ${{ github.repository }}-sccache-tests
|
||||
path: /home/runner/.cache/sccache
|
||||
commit: ${{ github.ref == 'refs/heads/main' && 'on-change' || 'false' }}
|
||||
# Main saves the cache; every other run restores it, read-only.
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: native-tests
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
version: v0.16.0
|
||||
- uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
# Each shard runs the archive the Build tests job made.
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: nextest-archive
|
||||
- name: Run tests
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
GOLDENS: ${{ needs.changes.outputs.goldens }}
|
||||
SHARD: ${{ matrix.shard }}
|
||||
SHARDS: ${{ github.event_name == 'pull_request' && 2 || 4 }}
|
||||
FULL: ${{ needs.changes.outputs.full }}
|
||||
PACKAGES: ${{ needs.changes.outputs.packages }}
|
||||
# The robustness golden seeds a pull request skips: each takes one to twenty minutes under
|
||||
# CI load, together about four fifths of the goldens' time. Pushes to main, the merge
|
||||
# queue and the nightly run every seed.
|
||||
SLOW_SEEDS: 81|45|20|24|95|02|57|40|18|48|32|46|85|61|77|34|14
|
||||
run: |
|
||||
# Pull requests skip the heavy tests (default-filter in .config/nextest.toml).
|
||||
filter=()
|
||||
# Pull requests skip the heavy tests (default-filter in .config/nextest.toml); `default()`
|
||||
# below is that filter, so a pull request still runs only what it ran before.
|
||||
base='default()'
|
||||
if [ "$EVENT_NAME" = pull_request ]; then
|
||||
# The action matrix sample (about 6 minutes on all cores) runs in the merge queue, on
|
||||
# main and nightly, not on every pull request push.
|
||||
base='default() & not (binary(action_matrix) & test(=action_matrix_sample))'
|
||||
fi
|
||||
if [ "$EVENT_NAME" != pull_request ]; then
|
||||
# The boolean property tests draw new random inputs every run; they run only in the
|
||||
# nightly property-tests job, so a known kernel bug cannot turn main red at random.
|
||||
filter=(--ignore-default-filter -E 'not (package(solidcraft-kernel) & binary(boolean_props))')
|
||||
base='not (package(solidcraft-kernel) & binary(boolean_props))'
|
||||
elif [ "$GOLDENS" = true ]; then
|
||||
# A pull request that can change a robustness golden (tests/robustness/goldens.json)
|
||||
# also checks every seed but the slow ones, in the same run so they share its cores.
|
||||
filter=(--ignore-default-filter -E "default() | (package(solidcraft-engine) & test(/^tests::robustness_goldens::/) & not test(/^tests::robustness_goldens::seed_($SLOW_SEEDS)\$/))")
|
||||
base="(default() & not (binary(action_matrix) & test(=action_matrix_sample))) | (package(solidcraft-engine) & test(/^tests::robustness_goldens::/) & not test(/^tests::robustness_goldens::seed_($SLOW_SEEDS)\$/))"
|
||||
fi
|
||||
junit=${CARGO_TARGET_DIR:-target}/nextest/ci/junit.xml
|
||||
rm -f "$junit"
|
||||
# Extract into ./target: tests that run a built binary (env!("CARGO_BIN_EXE_*")) have
|
||||
# the build job's path, which is the same checkout path here.
|
||||
run=(cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite)
|
||||
if ! "${run[@]}" --profile ci --partition "slice:$SHARD/$SHARDS" --no-tests=pass "${filter[@]}"; then
|
||||
# Pull requests test the affected packages (scripts/ci/affected.py).
|
||||
scope=(--workspace)
|
||||
if [ "$FULL" != true ]; then scope=(); for p in $PACKAGES; do scope+=(-p "$p"); done; fi
|
||||
status=0
|
||||
cargo nextest run "${scope[@]}" --cargo-profile ci --locked --profile ci --no-tests=pass \
|
||||
--ignore-default-filter -E "$base" || status=1
|
||||
if [ "$status" != 0 ]; then
|
||||
# A failed golden gets the hint to re-record it (junit.xml lists the failures).
|
||||
if [ -f "$junit" ] && tr '\n' ' ' < "$junit" | grep -q '<testcase name="tests::robustness_goldens::[^"]*"[^>]*>[[:space:]]*<failure'; then
|
||||
echo "::error::A robustness golden changed. If the new result is intended, re-record it in this pull request and say why:"
|
||||
@@ -451,11 +497,14 @@ jobs:
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
- name: Compiler cache statistics
|
||||
if: always()
|
||||
run: sccache --show-stats
|
||||
- name: Upload test timings
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: junit-${{ matrix.shard }}
|
||||
name: junit
|
||||
path: ${{ env.CARGO_TARGET_DIR || 'target' }}/nextest/ci/junit.xml
|
||||
retention-days: 7
|
||||
|
||||
@@ -560,13 +609,23 @@ jobs:
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
|
||||
SCCACHE_DIR: /home/runner/.cache/sccache
|
||||
SCCACHE_CACHE_SIZE: 20G
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Size parallel compiles to the job's memory
|
||||
run: scripts/ci/build-jobs.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# sccache lives on a Blacksmith sticky disk (an ext4 volume cloned from the last snapshot,
|
||||
# no 10 GB limit and no download). Only main commits it; pull requests, the merge queue and
|
||||
# forks mount a clone and never write back, so nothing they build can reach main's cache.
|
||||
- name: Mount the compiler cache
|
||||
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: ${{ github.repository }}-sccache-doc
|
||||
path: /home/runner/.cache/sccache
|
||||
commit: ${{ github.ref == 'refs/heads/main' && 'on-change' || 'false' }}
|
||||
# Main saves the cache; every other run restores it, read-only.
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
@@ -608,13 +667,23 @@ jobs:
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
|
||||
SCCACHE_DIR: /home/runner/.cache/sccache
|
||||
SCCACHE_CACHE_SIZE: 20G
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Size parallel compiles to the job's memory
|
||||
run: scripts/ci/build-jobs.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# sccache lives on a Blacksmith sticky disk (an ext4 volume cloned from the last snapshot,
|
||||
# no 10 GB limit and no download). Only main commits it; pull requests, the merge queue and
|
||||
# forks mount a clone and never write back, so nothing they build can reach main's cache.
|
||||
- name: Mount the compiler cache
|
||||
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: ${{ github.repository }}-sccache-oracle
|
||||
path: /home/runner/.cache/sccache
|
||||
commit: ${{ github.ref == 'refs/heads/main' && 'on-change' || 'false' }}
|
||||
# Main saves the cache; every other run restores it, read-only.
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
@@ -746,6 +815,7 @@ jobs:
|
||||
DOCTESTS: ${{ needs.changes.outputs.doctests }}
|
||||
CLIPPY: ${{ needs.clippy.result }}
|
||||
BUILD: ${{ needs.build-tests.result }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
TESTS: ${{ needs.tests.result }}
|
||||
DOCS: ${{ needs.doc-tests.result }}
|
||||
ORACLE: ${{ needs.oracle.result }}
|
||||
@@ -760,7 +830,10 @@ jobs:
|
||||
fi
|
||||
if [ "$NATIVE" = true ]; then
|
||||
test "$CLIPPY" = success
|
||||
test "$BUILD" = success
|
||||
# The archive for the action matrix and property tests exists nightly and by hand only.
|
||||
if [ "$EVENT_NAME" = schedule ] || [ "$EVENT_NAME" = workflow_dispatch ]; then
|
||||
test "$BUILD" = success
|
||||
fi
|
||||
test "$TESTS" = success
|
||||
fi
|
||||
if [ "$DOCTESTS" = true ]; then
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Files generated from the code are rewritten here, on main, after each merge, so pull requests
|
||||
# never edit them (two pull requests that each regenerated one would always conflict):
|
||||
# THIRD-PARTY-LICENSES.txt (`cargo xtask licences`) and docs/features.md (`cargo xtask features`).
|
||||
# Pending files (assets/locales/pending, attribution/pending) are folded into the catalogs and
|
||||
# ATTRIBUTION.md by `cargo xtask fold-pending`.
|
||||
name: Generated files
|
||||
|
||||
on:
|
||||
@@ -17,6 +19,8 @@ on:
|
||||
- "crates/engine/**"
|
||||
- "apps/solidcraft-cli/**"
|
||||
- "xtask/**"
|
||||
- "assets/locales/pending/*.json"
|
||||
- "attribution/pending/*.md"
|
||||
- ".github/workflows/generated.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -48,13 +52,15 @@ jobs:
|
||||
run: cargo xtask licences
|
||||
- name: docs/features.md
|
||||
run: cargo xtask features
|
||||
- name: Pending locale strings and attribution rows
|
||||
run: cargo xtask fold-pending
|
||||
- name: Commit if changed
|
||||
run: |
|
||||
if git diff --quiet -- THIRD-PARTY-LICENSES.txt docs/features.md; then echo "unchanged"; exit 0; fi
|
||||
if [ -z "$(git status --porcelain)" ]; then echo "unchanged"; exit 0; fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add THIRD-PARTY-LICENSES.txt docs/features.md
|
||||
git commit -m "Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]"
|
||||
git add -A THIRD-PARTY-LICENSES.txt docs/features.md assets/locales ATTRIBUTION.md attribution
|
||||
git commit -m "Regenerate generated files and fold pending files [skip ci]"
|
||||
for _ in 1 2 3; do
|
||||
git pull --rebase origin main && git push origin HEAD:main && exit 0
|
||||
sleep 10
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# While a new geometry kernel is evaluated, new issues, pull requests and discussions about kernel
|
||||
# work are labelled `kernel-paused` for a maintainer to answer (CONTRIBUTING.md, "Kernel work is
|
||||
# paused"). The logic is in scripts/kernel-paused/ (tested in ci.yml).
|
||||
name: Kernel paused
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
# pull_request_target so PRs from forks can be labelled. It runs the base branch's
|
||||
# scripts/kernel-paused and only lists the PR's changed paths; the PR's code is never checked out.
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
discussion:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
contents: read # to check out scripts/kernel-paused
|
||||
issues: write
|
||||
pull-requests: read # to list a PR's changed paths
|
||||
discussions: write
|
||||
|
||||
concurrency:
|
||||
group: kernel-paused-${{ github.event.issue.number || github.event.pull_request.number || github.event.discussion.number }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
paused:
|
||||
name: Kernel paused bot
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
sparse-checkout: scripts/kernel-paused
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const paused = require('./scripts/kernel-paused/kernel-paused.js');
|
||||
const done = context.eventName === 'discussion'
|
||||
? await paused.handleDiscussion({ github, context })
|
||||
: await paused.handleOpened({ github, context });
|
||||
core.info(done);
|
||||
@@ -39,6 +39,8 @@ jobs:
|
||||
run: python3 packaging/tests/nightly_test.py
|
||||
- name: Windows executable icon validation regression
|
||||
run: python3 packaging/tests/windows_icon_test.py
|
||||
- name: AppImage CLI wrapper regression
|
||||
run: python3 packaging/tests/apprun_test.py
|
||||
- name: Flatpak desktop smoke validation regression
|
||||
run: python3 packaging/tests/flatpak_test.py
|
||||
- name: PowerShell
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# Two quick checks on every pull request (CONTRIBUTING.md, "Sending a change"): no second open PR
|
||||
# for an issue a lower-numbered one already closes, and the "Changes a default:" line is filled in.
|
||||
# Neither is a required status check. The logic is in scripts/pr-checks/pr-checks.js (tested in ci.yml).
|
||||
name: PR checks
|
||||
|
||||
on:
|
||||
# pull_request, never pull_request_target: nothing here checks out or runs the PR's code, and the
|
||||
# token is read-only for fork PRs. Only scripts/pr-checks from the PR's own merge commit is run.
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize, reopened, ready_for_review, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: pr-checks-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
duplicate:
|
||||
name: Duplicate PR check
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
sparse-checkout: scripts/pr-checks
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const checks = require('./scripts/pr-checks/pr-checks.js');
|
||||
const r = await checks.checkDuplicate({ github, context, core });
|
||||
core.info(JSON.stringify(r));
|
||||
|
||||
defaults:
|
||||
name: Defaults line check
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
sparse-checkout: scripts/pr-checks
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const checks = require('./scripts/pr-checks/pr-checks.js');
|
||||
const r = await checks.checkDefaults({ github, context, core });
|
||||
core.info(JSON.stringify(r));
|
||||
|
||||
# Labelling needs write access, which a fork PR's token never has, so it is its own job and best
|
||||
# effort: a fork PR just doesn't get the `changes-default` label from here.
|
||||
label:
|
||||
name: Label changes-default
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
sparse-checkout: scripts/pr-checks
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const checks = require('./scripts/pr-checks/pr-checks.js');
|
||||
await checks.syncDefaultLabel({ github, context, core });
|
||||
@@ -4,11 +4,12 @@
|
||||
# comes from [workspace.package] in Cargo.toml. An agent verifies the artifacts and publishes
|
||||
# the draft separately; see docs/releasing.md. Tags and pushes to main do not trigger releases.
|
||||
# At midnight Eastern, changed main commits build and publish a nightly pre-release.
|
||||
# A version with a suffix (0.2.0-rc.1) is a pre-release. Nothing is signed yet.
|
||||
# A version with a suffix (0.2.0-rc.1) is a pre-release.
|
||||
# The macOS app is signed and notarized when the APPLE_* secrets exist, nothing else is signed yet.
|
||||
#
|
||||
# Linux x86_64, aarch64 AppImage (+ .zsync), .deb, .rpm, .flatpak, .snap, .tar.gz packaging/linux
|
||||
# FreeBSD x86_64 .tar.gz (built in a FreeBSD VM) packaging/freebsd
|
||||
# macOS universal .dmg, and the CLI as a .zip packaging/macos
|
||||
# macOS universal .dmg, and the CLI as a .zip (both archs built in parallel) packaging/macos
|
||||
# Windows x64, x86, arm64 .msi and a portable .zip; x64 also the plain .exe packaging/windows
|
||||
# Web a .zip of the static site packaging/web
|
||||
#
|
||||
@@ -203,6 +204,8 @@ jobs:
|
||||
name: Snap ${{ matrix.arch }}
|
||||
needs: [version, linux]
|
||||
if: needs.version.outputs.build == 'true'
|
||||
# The Snap Store hasn't accepted SolidCraft yet, so a snap failure never holds up a release.
|
||||
continue-on-error: true
|
||||
# Repackages the Linux job's tarball, like the Flatpak. On 24.04 because the core24 base is
|
||||
# Ubuntu 24.04, which lets snapcraft build on the runner itself (--destructive-mode).
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@@ -293,11 +296,19 @@ jobs:
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
macos:
|
||||
name: macOS universal
|
||||
# The two halves of the universal binary build in parallel on their own runners (each alone
|
||||
# took about half of the old single job's 45 minutes); `macos` below joins and packages them.
|
||||
macos-build:
|
||||
name: macOS build ${{ matrix.arch }}
|
||||
needs: version
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: aarch64, target: aarch64-apple-darwin }
|
||||
- { arch: x86_64, target: x86_64-apple-darwin }
|
||||
env:
|
||||
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
@@ -305,12 +316,58 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-macos-universal
|
||||
- name: Build and package (dmg, CLI zip; the dmg is mounted and checked)
|
||||
run: packaging/macos/package.sh --arch universal
|
||||
key: release-macos-${{ matrix.arch }}
|
||||
- name: Build
|
||||
run: packaging/macos/package.sh --build-only ${{ matrix.target }}
|
||||
# Not named release-*: the release job collects every release-* artifact as an asset.
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: macos-bin-${{ matrix.arch }}
|
||||
path: |
|
||||
target/${{ matrix.target }}/release/solidcraft
|
||||
target/${{ matrix.target }}/release/solidcraft-cli
|
||||
target/${{ matrix.target }}/release/solvecraft-cli
|
||||
if-no-files-found: error
|
||||
|
||||
macos:
|
||||
name: macOS universal
|
||||
needs: [version, macos-build]
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: macos-14
|
||||
env:
|
||||
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: macos-bin-aarch64
|
||||
path: target/aarch64-apple-darwin/release
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: macos-bin-x86_64
|
||||
path: target/x86_64-apple-darwin/release
|
||||
- name: Set up Developer ID signing and notarization
|
||||
# Optional until the APPLE_* secrets exist (docs/releasing.md, "macOS signing"). Without
|
||||
# them (forks, dry runs) the app keeps its ad-hoc signature and is not notarized.
|
||||
env:
|
||||
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
|
||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
run: packaging/macos/signing.sh --setup
|
||||
- name: Join, sign, package and check (dmg, CLI zip; the dmg is mounted and checked)
|
||||
run: |
|
||||
# Artifacts don't keep the executable bit.
|
||||
chmod +x target/*-apple-darwin/release/solidcraft target/*-apple-darwin/release/solidcraft-cli target/*-apple-darwin/release/solvecraft-cli
|
||||
packaging/macos/package.sh --arch universal --skip-build ${MACOS_SIGN_IDENTITY:+--sign}
|
||||
- name: Remove the signing keychain and API key
|
||||
if: always()
|
||||
run: packaging/macos/signing.sh --cleanup
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: release-macos
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
/.claude/worktrees/
|
||||
/target-snapshot
|
||||
__pycache__/
|
||||
.vscode/
|
||||
|
||||
# cargo-mutants output (docs/mutation-testing.md)
|
||||
mutants.out*/
|
||||
|
||||
@@ -33,9 +33,12 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
|
||||
and calls through its function pointers), two functions in
|
||||
`apps/solidcraft/src/open_documents.rs` (macOS hands Finder files only to an Objective-C
|
||||
method), the Windows-only `apps/solidcraft/src/native_crash.rs` (the exception filter that
|
||||
records crashes inside native code such as graphics drivers) and the Windows-only
|
||||
records crashes inside native code such as graphics drivers), the Windows-only
|
||||
`apps/solidcraft/src/native_frame.rs` (the window procedure that answers the resize-border hit
|
||||
test of the window without OS decorations).
|
||||
test of the window without OS decorations, and gives its class the icon the taskbar falls back
|
||||
on) and the Windows-only
|
||||
`apps/solidcraft/src/space_mouse/windows_hid.rs` (asks Plug and Play which HID interfaces are 3D
|
||||
mice, so no other device is sent anything).
|
||||
- **Errors are `Result<T, E>`** through the crate's error type and `?`. An unfinished feature
|
||||
returns an error ("not supported yet: …"); it never panics.
|
||||
- **Input-derived numbers are hostile** (command parameters, design files, control-channel
|
||||
@@ -88,6 +91,14 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
|
||||
egui/eframe/winit/wgpu/rfd. **The UI crate is swappable.** Only `crates/kernel` may use `truck`.
|
||||
- **The UI is thin**: panels read engine state and act through `app.run(id, params)` /
|
||||
`app.start(id)`. Colours come from `theme::Tokens`.
|
||||
- **One outer UI container**: all panels, floating sheets, menus, overlays and platform UI
|
||||
windows use `ui_kit::Container` (directly or through `Panel`, `Window`, `Confirm`, `Menu`).
|
||||
Declare the semantic `Surface`; workspace panels use its shared layout policy and chrome.
|
||||
System/report windows use `ui_kit::Window`; its low-level host builder is kit-private.
|
||||
Do not construct raw egui hosts, outer popup/window frames or shadows in feature modules.
|
||||
`cargo xtask ui-containers` recursively checks source/examples in all current and future crates
|
||||
and apps. It runs in the fast repository CI job and `cargo xtask ci`, with no caller allowances.
|
||||
See `docs/ui-container-audit.md` for the inventory and legitimate content/native exceptions.
|
||||
- **One set of camera controls**: every page or canvas with a camera (viewport, Render
|
||||
preview, future flat pattern or drawing pages) calls `navigation::handle` with its `NavCaps`
|
||||
(orbit off for flat 2D pages). New canvases must use `navigation::handle`; never hand-roll
|
||||
@@ -145,6 +156,9 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
|
||||
- If someone already has it, comment on their PR or issue instead of opening a second one. Two
|
||||
PRs for one change can't both land, and one of them is wasted work and CI.
|
||||
|
||||
- Keep the `Changes a default:` line in the PR description filled in, and don't open a second PR for
|
||||
an issue a lower-numbered open PR already closes; `pr-checks.yml` fails both (CONTRIBUTING.md, "Sending a change").
|
||||
|
||||
## Claim = a draft PR
|
||||
- Before working on issue #N, check nobody has it: `gh pr list -R tangency-labs/solidcraft --state open --search "#N"`.
|
||||
- If none, branch from the latest `origin/main`, `git commit --allow-empty -m "claim: #N"`, push,
|
||||
@@ -204,3 +218,5 @@ workflow; never regenerate them in a PR.
|
||||
Keep them conflict-free (CONTRIBUTING.md, "Lists many changes add to"): message catalogs sorted
|
||||
(`cargo xtask lists --fix`), `mod` lines in their sorted group, the language list English first
|
||||
then by id, generated files left to `main`, and a design-format change as one new file.
|
||||
New UI strings go in a new `assets/locales/pending/<slug>.json` and new ATTRIBUTION.md rows in a new
|
||||
`attribution/pending/<slug>.md`, never into the catalogs or the table (see the README in each folder).
|
||||
|
||||
@@ -14,8 +14,33 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/render/catalog/capabilities.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/capabilities.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
|
||||
| assets/render/catalog/environments.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/environments.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
|
||||
| assets/render/catalog/lighting.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/lighting.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
|
||||
| assets/render/catalog/materials.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/materials.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
|
||||
| assets/render/catalog/quality-presets.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/quality-presets.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
|
||||
| assets/render/catalog/scene-presets.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/scene-presets.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
|
||||
| examples/chebyshev-motion-trace.json | SolidCraft contributors | Original parametric Chebyshev lambda mechanism and associative-trace demo, constructed from analytic circle intersections; classical link proportions documented at https://doi.org/10.1002/rcs.70095; no external CAD geometry or artwork | MIT OR Apache-2.0 |
|
||||
| examples/material-card.json | SolidCraft contributors | original analytic box and explicitly synthetic engineering card; invented illustrative constants, no external material dataset | MIT OR Apache-2.0 |
|
||||
| examples/studio/author_sweep.py | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/author_scene.py | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/materials.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-exploded-sweep.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-exploded-render.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-exploded.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/stage1-sweep.json | SolidCraft contributors | original tangent circular-arc CAD cyclorama; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/stage1.json | SolidCraft contributors | original editable CAD studio scene; no imported geometry or external image assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/stage1-render.json | SolidCraft contributors | original studio camera and lighting parameters | MIT OR Apache-2.0 |
|
||||
| examples/render-studio.json | SolidCraft contributors | original procedural box/cylinder enclosure script for the integrated Render workspace | MIT OR Apache-2.0 |
|
||||
| examples/render-opaque-studio.json | SolidCraft contributors | original procedural sphere study varying opaque metal fraction and roughness independently; no external assets or engineering data | MIT OR Apache-2.0 |
|
||||
| crates/gpu/src/path/denoise.wgsl | SolidCraft contributors | original bounded edge-aware a-trous spatial filter; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
|
||||
| crates/gpu/src/path/denoise_guides.wgsl | SolidCraft contributors | original deterministic centre-ray material and geometry guide pass; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
|
||||
| crates/gpu/src/path/shadows.wgsl | SolidCraft contributors | original bounded area, sun and environment visibility transport; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
|
||||
| crates/gpu/src/path/textures.wgsl | SolidCraft contributors | original planar colour and normal map sampling; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
|
||||
| assets/appearances/starter_pack.rs | SolidCraft contributors | original illustrative optical recipes, authored scalar values and future procedural resource descriptors; no measured or external material dataset | MIT OR Apache-2.0 |
|
||||
| examples/material-finishes.json | SolidCraft contributors | original procedural sphere study of six illustrative opaque finishes with generic material densities; no external assets or measured optical data | MIT OR Apache-2.0 |
|
||||
| examples/render-path-studio.json | SolidCraft contributors | original procedural opaque enclosure, teal insert and metal fasteners for progressive rendering; no imported models | MIT OR Apache-2.0 |
|
||||
| crates/gpu/src/path/path.wgsl | SolidCraft contributors | original software BVH traversal and path integration using the existing SolidCraft surface/studio equations; no external shader source | MIT OR Apache-2.0 |
|
||||
| crates/gpu/src/pbr.rs | SolidCraft contributors | original WGSL translation of SolidCraft's opaque surface and fixed studio reference, embedded as Rust source; no external shader code | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/macos/source/SolidCraft.icon/icon.json | SolidCraft contributors | approved Icon Composer composition and appearance settings for the original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/macos/source/SolidCraft.icon/Assets/02-grid.svg | SolidCraft contributors | original perspective grid with elliptical radial alpha, exported from VectorCraft | MIT OR Apache-2.0 |
|
||||
@@ -24,6 +49,8 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/app/macos/exports/Assets.car | SolidCraft contributors | Apple actool compilation of assets/icons/app/macos/source/SolidCraft.icon for macOS; contains original artwork only | MIT OR Apache-2.0 |
|
||||
| assets/archive/icons/macos-v1/source/icon.vectorcraft | SolidCraft contributors | retained editable historical VectorCraft source, with named face and background layers; redundant exports retired | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/macos/exports/icon.icns | SolidCraft contributors | macOS fallback compiled by Apple's actool from the approved assets/icons/app/macos/source/SolidCraft.icon composition | MIT OR Apache-2.0 |
|
||||
| assets/models/test_boat.bin | 3DBenchy.com | 3DBenchy.com | Public Domain |
|
||||
| assets/models/make_test_boat.py | SolidCraft contributors | original script that packs a model into the compact file above | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/class-a-blend.png | SolidCraft contributors | screenshot of SolidCraft itself: two boxes bridged by `surface.blend` (a 40x10 sheet, `solidcraft-cli snapshot` of plan/evidence script ev_blend) | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/class-a-crown.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch crowned with `surface.patch crown=0.3` over a saddle rim (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/class-a-boundary.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch through two sketch lines built by `surface.boundary` with `crown: 0.4` (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
|
||||
@@ -57,6 +84,24 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| docs/book/src/generated/tool-preview.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/tool-preview.json`; gitignored) | MIT OR Apache-2.0 |
|
||||
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
|
||||
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
|
||||
| tests/step-conformance/cone_point.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cone_point_vertex.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cylinder.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cylinder_inch.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cylinder_nurbs.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cylinder_placed.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cylinder_seam.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/cylinder_twice.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/dome.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/dome_nurbs.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/far_cylinder.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/grooved_shaft.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/plate_100_holes.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/sheet_disc.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/tiny_pin.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/torus_ring_nurbs.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/washer.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-regressions/rod_doubled_edge.step | SolidCraft contributors | written by SolidCraft from the commands of #4598 (a bored rod moved 100 mm and exported); import regression fixture | MIT OR Apache-2.0 |
|
||||
| tests/oracle/17-loft/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/17-loft/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/23-bracket-multifeature/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/23-bracket-multifeature/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/28-blind-pocket-fillets/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/28-blind-pocket-fillets/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
@@ -77,8 +122,8 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| tests/oracle/61-shaft-keyway/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/61-shaft-keyway/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/63-hinge-knuckle/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/63-hinge-knuckle/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/65-sketch-gear-tooth/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/65-sketch-gear-tooth/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
|
||||
| assets/appearances/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/appearances/opaque_finishes.rs | SolidCraft contributors | original illustrative opaque finish values; no external dataset, textures or measured engineering data | MIT OR Apache-2.0 |
|
||||
| assets/fonts/biz-udp-gothic/OFL.txt | The BIZ UDGothic Project Authors | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
| assets/fonts/ibm-plex-mono/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
| assets/fonts/ibm-plex-sans/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
@@ -91,7 +136,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/app/linux/exports/hicolor/48x48/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/512x512/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/64x64/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/shared/source/app_icon.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/shared/source/app_icon.rs | SolidCraft contributors | Original SolidCraft drawing code: the borderless mark, drawn from the faces and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/shared/source/windows_ico.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/windows/exports/solidcraft-executable.ico | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/windows/exports/solidcraft.ico | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
@@ -102,6 +147,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/locales/cs.json | potuzai | Czech translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||
| assets/locales/da.json | SolidCraft contributors | Original Danish translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/locales/de.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/locales/en-GB.json | SolidCraft contributors | English (UK): the English catalog's messages whose spelling differs, in UK spelling | MIT OR Apache-2.0 |
|
||||
| assets/locales/en.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/locales/es.json | jawler | Spanish (Spain) translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||
| assets/locales/fr.json | rlartot | French translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||
@@ -139,6 +185,20 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/styles/ui/themes/gruvbox-light.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/gruvbox-light-hard.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/gruvbox-light-soft.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/solidcraft-95.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/platinum-9.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/cupertino.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/green-phosphor.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/amber-phosphor.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/blueprint.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/one-dark.json | SolidCraft contributors | original SolidCraft theme file with colours adapted from the One Dark palette (Atom One Dark by GitHub Atom, https://github.com/atom/atom, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/tokyo-night.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Tokyo Night by enkia, https://github.com/enkia/tokyo-night-vscode-theme, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/solarized-light.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/nord.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Nord by Sven Greb and the Nord contributors, https://github.com/nordtheme/nord, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/high-contrast.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/graphite.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/nosferatu.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Dracula by Zeno Rocha and the Dracula Theme contributors, https://github.com/dracula/dracula-theme, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/catppuccin-mocha.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Catppuccin by the Catppuccin organisation, https://github.com/catppuccin/catppuccin, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/web/loading-logo.svg | SolidCraft contributors | Animation of the SolidCraft mark, built from the paths and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg; original animation | MIT OR Apache-2.0 |
|
||||
| assets/styles/web/loading.css | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| crates/io/src/native_cad/fixtures/block.FCStd | SolidCraft contributors | original 10 × 20 × 30 mm test box saved by FreeCAD 1.1.4 run as an external program (`make_block_fcstd.py` beside it); no third-party geometry | MIT OR Apache-2.0 |
|
||||
@@ -165,6 +225,11 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/flat/redo.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/save.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings_keyboard.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings_monitor.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings_mouse.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings_palette.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings_plug.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/spin.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/style.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/undo.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -302,6 +367,11 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/model/offset_symmetric.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/import_tray.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/export_tray.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/send.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/stop.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| 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/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 |
|
||||
@@ -443,6 +513,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
|
||||
| docs/book/src/og.png | SolidCraft contributors | copy of assets/branding/web/source/og.png staged by cargo xtask book (gitignored) | MIT OR Apache-2.0 |
|
||||
| docs/sponsors/blacksmith-ci-powered.png | Blacksmith (blacksmith.sh) | "CI powered by Blacksmith" sponsor banner supplied by Blacksmith for the sponsorship credit; used only to credit the sponsor, not part of the application | © Blacksmith, used with permission |
|
||||
| assets/icons/ui/design/svg/model/path_flange.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
|
||||
## Code adapted from sibling projects
|
||||
|
||||
@@ -485,9 +556,9 @@ crates/engine/src/tests/gltf_import.rs); no third-party models are committed.
|
||||
## FreeCAD and Rhino import library
|
||||
|
||||
FreeCAD `.FCStd` and Rhino `.3dm` files are read with [cadmpeg](https://github.com/cadmpeg/cadmpeg)
|
||||
0.6.1 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
|
||||
0.6.3 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
|
||||
`cadmpeg-container`, `cadmpeg-codec-freecad`, `cadmpeg-codec-rhino` and `cadmpeg-codec-step`
|
||||
in native builds (`crates/io/src/native_cad.rs`). Only those open-format codecs are built: no
|
||||
in native and browser builds (`crates/io/src/native_cad.rs`). Only those open-format codecs are built: no
|
||||
default features, so cadmpeg's codecs for commercial CAD formats and its ShapeManager/ACIS
|
||||
codec are not compiled in. Its licence text is in THIRD-PARTY-LICENSES.txt; no code from it is
|
||||
copied into SolidCraft.
|
||||
@@ -520,3 +591,453 @@ A frame whose meshes the GPU refuses to stage (device lost or out of memory) is
|
||||
skipped with a warning instead of panicking (#1469).
|
||||
Paint callbacks are prepared only after a drawable is acquired, so a skipped
|
||||
surface frame cannot strand cached meshes without their GPU initialization.
|
||||
|
||||
## Runtime studio lighting mathematics
|
||||
|
||||
`crates/gpu/src/path/path.wgsl` implements the power-heuristic estimator from the
|
||||
mathematical description in [PBRT fourth edition, section 13.4](https://pbr-book.org/4ed/Light_Transport_I_Surface_Reflection/A_Better_Path_Tracer).
|
||||
No PBRT code or assets were copied. `crates/render/src/job/lighting.rs` evaluates
|
||||
[Krzysztof Narkowicz's published 2016 ACES rational fit](https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/)
|
||||
using its published coefficients (author licenses the fit under CC0/MIT).
|
||||
These original implementations are under SolidCraft's MIT OR Apache-2.0 licence.
|
||||
|
||||
## Original coffee framing variant
|
||||
|
||||
| File | Author | Provenance | Licence |
|
||||
|---|---|---|---|
|
||||
| examples/studio/author_coffee_framing.py | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-framing.json | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-framing-sweep.json | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-framing-render.json | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-framing.md | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
|
||||
|
||||
## Original cylindrical coffee housing variant
|
||||
|
||||
| File | Author | Provenance | Licence |
|
||||
|---|---|---|---|
|
||||
| examples/studio/author_coffee_cylinder.py | SolidCraft contributors | original parametric cylindrical CAD construction and editable command recipes; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-cylinder.json | SolidCraft contributors | original cylindrical housing on the original exploded coffee composition; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-cylinder-sweep.json | SolidCraft contributors | original cylindrical housing and continuous CAD cyclorama; no external assets | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-cylinder-render.json | SolidCraft contributors | original camera, studio lighting and optical settings retained from the framing variant | MIT OR Apache-2.0 |
|
||||
| examples/studio/coffee-cylinder.md | SolidCraft contributors | original dimensional recipe, provenance and validation limitations | MIT OR Apache-2.0 |
|
||||
|
||||
## Runtime environment sampling
|
||||
|
||||
`crates/render/src/lighting/environment.rs` and the environment/sun portions of
|
||||
`crates/gpu/src/path/path.wgsl` are original SolidCraft implementations of
|
||||
solid-angle importance sampling and MIS, informed by the mathematical description
|
||||
in [PBRT fourth edition, sections 12.5](https://pbr-book.org/4ed/Light_Sources/Infinite_Area_Lights)
|
||||
and [13.4](https://pbr-book.org/4ed/Light_Transport_I_Surface_Reflection/A_Better_Path_Tracer).
|
||||
The procedural gradient and inline RGBE test fixtures are original mathematical
|
||||
colours, not downloaded imagery. No third-party environment images or shader code
|
||||
are included. Licence: MIT OR Apache-2.0.
|
||||
|
||||
## Original tumbler showcase assets
|
||||
|
||||
The CAD room, tumblers, window opening, wood/weave patterns and RGBE sky below
|
||||
are original SolidCraft contributor work. No third-party scene, photograph, HDRI,
|
||||
texture, or AI-generated artwork was used. Python authoring sources are included.
|
||||
All listed files use the repository MIT OR Apache-2.0 licence.
|
||||
|
||||
| File | Source / author | Use | License |
|
||||
| --- | --- | --- | --- |
|
||||
| examples/studio/author_showcase.py | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/author_tumblers.py | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/textures/ATTRIBUTION.md | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/textures/author_textures.py | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/textures/environment-manifest.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/textures/manifest.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-hdr-textures-blocked-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-hdr-textures-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-materials.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-sun-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-sun-textures-blocked-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-sun-textures-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-sun.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-textures-blocked-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers-textures-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
| examples/studio/tumblers.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
|
||||
|
||||
The original studio generators produce local ignored PNG maps and an RGBE gradient.
|
||||
Generated image bytes are not source assets; their authorship, licences and expected
|
||||
hashes are documented under `examples/studio/textures`. Rendered evidence belongs
|
||||
on the results branch. No photographs, scans or external image assets are included.
|
||||
|
||||
## Additional render and showcase resources
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/render/starter/tools/check_all.py | SolidCraft contributors | original read-only authored/native resource validation orchestration and evidence receipts | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/test_check_all.py | SolidCraft contributors | original validation-runner regression fixtures and subprocess tests | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/requirements.txt | SolidCraft contributors | pinned offline authoring tool requirements; no application dependency changes | MIT OR Apache-2.0 |
|
||||
| crates/render/examples/validate_starter.rs | SolidCraft contributors | original native resource conformance checker using public SolidCraft decoding, normalization, sampling and mip APIs | MIT OR Apache-2.0 |
|
||||
| .gitattributes | SolidCraft contributors | original canonical line-ending policy for the existing runtime icon-discovery manifest; no artwork or icon-assignment changes | MIT OR Apache-2.0 |
|
||||
| assets/render/.gitattributes | SolidCraft contributors | original canonical line-ending policy for SHA-256-pinned renderer authoring resources | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/.gitattributes | SolidCraft contributors | original canonical line-ending policy for SHA-256-pinned renderer authoring resources | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/assembly-bench-soft.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/automotive-turntable.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/black-metal-strips.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/cnc-overhead.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/esd-bench-neutral.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/fdm-neutral-inspection.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/garage-door.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/hangar-skylight.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/home-workbench.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/makerspace-worklights.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/mechanical-bay.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/neutral-comparison.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/neutral-side-softbox.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/paint-inspection-bands.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/printed-part-photo.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/rework-offset-strips.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/robot-led-editorial.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/showroom-ribbons.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/spacecraft-clean-bay.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/sun-free-overcast.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/vacuum-black.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/warm-table-window.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/white-enveloping-studio.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/exports/woodshop-window.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/manifest.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/environments/source/recipes.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/lighting/exports/rigs.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/lighting/recipe.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-001/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-001/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-001/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-001/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-001/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-002/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-002/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-002/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-002/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-002/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-003/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-003/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-003/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-003/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-003/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-004/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-004/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-004/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-004/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-004/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-005/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-005/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-005/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-005/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-005/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-006/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-006/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-006/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-006/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-006/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-007/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-007/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-007/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-007/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-007/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-008/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-008/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-008/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-008/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-008/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-009/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-009/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-009/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-009/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-009/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-010/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-010/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-010/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-010/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-010/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-011/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-011/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-011/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-011/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-011/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-012/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-012/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-012/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-012/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-012/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-013/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-013/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-013/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-013/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-013/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-014/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-014/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-014/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-014/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-014/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-015/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-015/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-015/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-015/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-015/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-016/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-016/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-016/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-016/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-016/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-017/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-017/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-017/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-017/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-017/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-018/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-018/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-018/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-018/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-018/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-019/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-019/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-019/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-019/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-019/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-020/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-020/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-020/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-020/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-020/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-021/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-021/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-021/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-021/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-021/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-022/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-022/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-022/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-022/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-022/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-023/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-023/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-023/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-023/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-023/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-024/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-024/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-024/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-024/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-024/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-025/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-025/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-025/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-025/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-025/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-026/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-026/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-026/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-026/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-026/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-027/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-027/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-027/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-027/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-027/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-028/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-028/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-028/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-028/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-028/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-029/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-029/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-029/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-029/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-029/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-030/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-030/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-030/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-030/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-030/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-031/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-031/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-031/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-031/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-031/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-032/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-032/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-032/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-032/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-032/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-033/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-033/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-033/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-033/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-033/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-034/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-034/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-034/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-034/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-034/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-035/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-035/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-035/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-035/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-035/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-036/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-036/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-036/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-036/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-036/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-037/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-037/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-037/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-037/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-037/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-038/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-038/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-038/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-038/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-038/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-039/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-039/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-039/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-039/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-039/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-040/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-040/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-040/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-040/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-040/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-041/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-041/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-041/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-041/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-041/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-042/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-042/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-042/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-042/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-042/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-043/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-043/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-043/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-043/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-043/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-044/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-044/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-044/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-044/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-044/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-045/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-045/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-045/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-045/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-045/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-046/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-046/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-046/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-046/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-046/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-047/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-047/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-047/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-047/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-047/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-048/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-048/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-048/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-048/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/exports/SURF-048/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/manifest.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/materials/source/recipes.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/pack.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/environments.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/lighting.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/materials.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/test_environments.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/test_lighting.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/test_materials.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/starter/tools/validate_pack.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/licenses/UFL.txt | Canonical Ltd. | exact Ubuntu Font Licence 1.0 notice retained from https://github.com/0xlillith/solidcraft-rover/blob/b32437915372d32b71419dc8f5143fe22ff1e869/UFL.txt for the modelled Ubuntu Light lettering | Ubuntu Font Licence 1.0 |
|
||||
| assets/render/selene/materials/appearance-pack.json | SolidCraft contributors | original clean-room English appearance authoring data: 48 illustrative opaque PBR projections with separate advanced target specifications; no measured dataset or external assets | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/materials/appearance-swatches.json | SolidCraft contributors | original analytic command script for 48 opaque box swatches and a support base, derived only from the adjacent original appearance pack; no external artwork or engineering properties | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/models/SELENE-functional.solidcraft | SolidCraft contributors (native enhancements); 0xlillith (original rover); SychPL / Mateusz Sych (supplied showcase assembly); Canonical Ltd. (lettering outlines) | native-command derivative of the unchanged author-supplied source, with original bearing, suspension, gripper, electronics and presentation work; source and final hashes in assets/render/selene/SOURCE.md and VALIDATION.md | Model and showcase contributions: MIT OR Apache-2.0 (explicit SychPL grant https://github.com/tangency-labs/solidcraft/pull/5494#issuecomment-6375788360); lettering: Ubuntu Font Licence 1.0, notice retained in assets/render/selene/licenses/UFL.txt |
|
||||
| assets/render/selene/motion/interference-pairs.json | SolidCraft contributors | original authored exact-body pair selection for bounded native clearance checks on the functional SELENE derivative; not a generated collision result | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/motion/mechanism-acceptance.json | SolidCraft contributors | original kinematic acceptance criteria, inspected source-container bindings and analytic planning checks from cited public SolidCraft documentation and the admitted native source; derivative runtime acceptance is recorded separately | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/motion/preflight-commands.json | SolidCraft contributors | original read-only inventory script using existing project commands; no source-model selectors or state mutations | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/motion/sample-sequence.json | SolidCraft contributors | original future lunar sampling and sealed-vial dynamics state specification; no simulation or physical calibration result | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/scenes/roles.json | SolidCraft contributors | original semantic role and candidate-material authoring specification for the prepared SELENE demo; no native geometry or external assets bundled | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/scenes/shots.json | SolidCraft contributors | original shot, framing and staged-sequence authoring specification for the prepared SELENE demo; no render or simulation result claimed | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/scenes/stages.json | SolidCraft contributors | original stage composition and light-ownership authoring specification derived from the project render catalog; scientific context linked in scenes/README.md | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/source-lock.json | SolidCraft contributors | original source-admission metadata and public-reference inventory pinning the supplied improved SELENE native file; original-model and font notices preserved in SOURCE.md | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/source/SELENE-showcase.solidcraft | 0xlillith (original rover); SychPL / Mateusz Sych (showcase assembly and helpers); SolidCraft contributors (emblem); Canonical Ltd. (lettering outlines) | unchanged author-published native file from https://github.com/SychPL/solidcraft/blob/32ec1720447e3fcc6bb6cc53a63fb5b39cfa21b9/SELENE-showcase.solidcraft; source hash and license evidence in assets/render/selene/SOURCE.md | Model and showcase contributions: MIT OR Apache-2.0 (explicit SychPL grant https://github.com/tangency-labs/solidcraft/pull/5494#issuecomment-6375788360); lettering: Ubuntu Font Licence 1.0, notice retained in assets/render/selene/licenses/UFL.txt |
|
||||
| assets/render/selene/tools/bearings.py | SolidCraft contributors | original native-command authoring of grooved bearings, rolling-element kinematics, clamp hubs and arm/head clearances; analytic geometry, no copied CAD source | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/build.py | SolidCraft contributors | original Python development tool for pinned-source native command replay, staged checkpoints, command-trace and geometry health checks; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/electronics.py | SolidCraft contributors | original native-command Raspberry Pi 5 visual reconstruction using public manufacturer mechanical dimensions; original package envelopes and connector internals, no manufacturer CAD or artwork copied | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/export_views.py | SolidCraft contributors | original native MCP preview export tool for three reusable showcase views; changes only transient visibility and cameras and verifies the source master hash | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/finishes.py | SolidCraft contributors | original native physical-material proxy and opaque PBR assignment generator preserving the supplied rover palette | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/mechanisms.py | SolidCraft contributors | original native-command gripper sleeves, pads, wheel ownership, joint limits and inspection studies with independent analytic suspension relation | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/prepare.py | SolidCraft contributors | original Python development tool for bounded native-container inspection and reviewed opaque body-assignment preparation; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/presentation.py | SolidCraft contributors | original native-command browser organization, camera/render setups and closed sample-vial glass preparation | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/suspension.py | SolidCraft contributors | original native-command trailing-arm suspension, telescoping coilover loops, plain bushings, carrier gussets and local motion clearances derived from the admitted rover geometry; independent analytic pin-distance relation, no copied external CAD | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/validate_native.py | SolidCraft contributors | original bounded native MCP verification tool for sampled SELENE kinematics, independent analytic follower and world-coordinate checks, optional exact-name interference pairs and unchanged input hashes; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/validate_prepared.py | SolidCraft contributors | original Python development validator for prepared SELENE resources, source status, references, hashes and command projections; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/tools/yaw_supports.py | SolidCraft contributors | original native-command plain yaw journals, flanged brass thrust bushings, recessed axial retaining pins and mounting-head clearances derived from the admitted rover geometry; no copied external CAD | MIT OR Apache-2.0 |
|
||||
| assets/render/selene/ui/demo-flow.json | SolidCraft contributors | original progressive-disclosure UI integration specification for the existing Render workspace; no copied UI artwork or runtime registration | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/baseline-setup-commands.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/capabilities.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/environment-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/environments.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/lighting.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/lunar-physics-fixtures.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/materials.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/provenance.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/quality-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/roadmap.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/scene-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/selene-bindings.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/simulation-requirements.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/studio-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/ui-integration.json | SolidCraft contributors | original English UI integration specification and exact source-revision mappings from SolidCraft's own implemented shell, UI kit and accepted icons; no copied artwork or runtime registration | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/ux.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
|
||||
| docs/render-materials/validate_catalog.py | SolidCraft contributors | original Python development validator for catalog references, preset composition and analytic specification fixtures; no application runtime dependency | MIT OR Apache-2.0 |
|
||||
|
||||
### Built-in render resource adapters (personal integration batch, 2026-10-22)
|
||||
|
||||
The original adapters in `crates/engine/src/cmd/render/library.rs`, the generated lookup `library_data.rs` and its generator `tools/render-library/generate.py` project the already attributed starter and SELENE resources without replacing their bytes or authoring manifests. The added scalar texture channels, explicit color/V conventions and immutable-source/GPU-payload sharing are original contributor work under MIT OR Apache-2.0. No external code, commercial CAD data or new third-party imagery was imported. Source metadata and the original authoring attribution remain authoritative; pending runtime acceptance is not a photometric or engineering qualification.
|
||||
|
||||
## Physical reference resource additions
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/render/physical/bindings.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/exports/coverage.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/exports/native-cards.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/profiles.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/quantities.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/sources.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/tools/check_physical.py | SolidCraft contributors | original offline physical-reference validation and projection tool | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/tools/physical.py | SolidCraft contributors | original offline physical-reference validation and projection tool | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/tools/test_physical.py | SolidCraft contributors | original offline physical-reference validation and projection tool | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/tools/export_library.py | SolidCraft contributors | original explicit reference-library planner and native no-loss export verifier; no external code | MIT OR Apache-2.0 |
|
||||
| assets/render/physical/tools/test_export_library.py | SolidCraft contributors | original regressions for native capability refusal, source identity and physical-card preservation | MIT OR Apache-2.0 |
|
||||
|
||||
| tools/render-library/selene_studio.py | SolidCraft contributors | original bounded native MCP studio orchestration with pinned editable master, explicit GPU policy and request/export receipts; no generated model or image substitutes | MIT OR Apache-2.0 |
|
||||
|
||||
| tools/render-library/selene_recipe.py | SolidCraft contributors | original full-detail SELENE material/light composition and analytic camera fit; unchanged pinned native master, no external imagery or measured optical dataset | MIT OR Apache-2.0 |
|
||||
| tools/render-library/test_selene_recipe.py | SolidCraft contributors | original independent projection, scaling, nonmutation and input-refusal fixtures for the SELENE studio recipe | MIT OR Apache-2.0 |
|
||||
|
||||
| assets/render/selene/scenes/studio-hero.json | SolidCraft contributors | original executable render intent and verified provenance for the selected native SELENE studio showcase; authored RGB appearance and relative light units, not measured optics or simulation results | MIT OR Apache-2.0 |
|
||||
| tools/render-library/publish_selene.py | SolidCraft contributors | original bounded native-frame publication checks for explicit visual review, source identity, resource hashes, PNG CRCs and scanline limits; copies completed native bytes without image synthesis or resampling | MIT OR Apache-2.0 |
|
||||
| tools/render-library/test_publish_selene.py | SolidCraft contributors | original tiny analytic PNG fixtures for source-size, CRC, expansion, dimensions and explicit-publication-consent regressions | MIT OR Apache-2.0 |
|
||||
|
||||
| tools/render-library/package_selene.py | SolidCraft contributors | original byte-preserving packaging of the reviewed native SELENE PNG, public provenance and checksums; bounded resource/PNG verification, no fonts, executables or new render execution | MIT OR Apache-2.0 |
|
||||
| .github/workflows/selene-showcase.yml | SolidCraft contributors | original read-only artifact-delivery workflow for the reviewed native SELENE export; existing pinned repository actions, no compilation, package installation, secret use or renderer claims | MIT OR Apache-2.0 |
|
||||
|
||||
| tools/render-library/verify_linear_master.py | SolidCraft contributors | original independent validation driver for native linear EXR output using the official OpenEXR Python reader as an external test oracle; no codec implementation copied or production dependency added | MIT OR Apache-2.0 |
|
||||
|
||||
## Aging, effects and backgrounds showcase, 2026-10-23
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| tools/render-showcase/instrument.py | SolidCraft contributors | original native-scene authoring, checksum-verified editorial assembly or bounded delivery tool; no third-party source or font binary copied | MIT OR Apache-2.0 |
|
||||
| tools/render-showcase/compose.py | SolidCraft contributors | original native-scene authoring, checksum-verified editorial assembly or bounded delivery tool; no third-party source or font binary copied | MIT OR Apache-2.0 |
|
||||
| tools/render-showcase/package.py | SolidCraft contributors | original native-scene authoring, checksum-verified editorial assembly or bounded delivery tool; no third-party source or font binary copied | MIT OR Apache-2.0 |
|
||||
|
||||
| distribution/SolidCraft-Render-Showcase.zip | SolidCraft contributors and the explicitly credited original model/UI/resource authors | byte-verified selected collaborator package of reviewed native media, editable sample, recipes and public provenance; contained attribution and existing SELENE lettering notices retained; no fonts or executable binaries | MIT OR Apache-2.0 for new contributions; referenced original notices retained |
|
||||
| distribution/SolidCraft-Render-Showcase.json | SolidCraft contributors | original verified delivery archive manifest with exact SHA-256, member count and bounded scope | MIT OR Apache-2.0 |
|
||||
|
||||
@@ -15,6 +15,15 @@ Before choosing a task, check the [command inventory](docs/features.md) and
|
||||
|
||||
Questions, ideas or want to say what you're working on? Join the [Discord](https://discord.gg/FtU4NQNVDH).
|
||||
|
||||
## Kernel work is paused
|
||||
|
||||
We're evaluating a new geometry kernel. Until that's decided, please don't open issues, pull
|
||||
requests or discussions about kernel work: booleans, fillets, chamfers, shell, offsets, sweeps and
|
||||
lofts, threads and holes, STEP/IGES import and export, tessellation, or anything else that changes
|
||||
`crates/kernel/`. The pinned issue on the tracker explains it. New ones get the `kernel-paused`
|
||||
label (`.github/workflows/kernel-paused.yml`) and a maintainer answers them. Crashes and data loss
|
||||
are still taken as issues, and everything outside the kernel carries on as normal.
|
||||
|
||||
## Get it running (about 10 minutes on a fast machine)
|
||||
|
||||
You need stable Rust 1.95 or newer (required by egui/eframe 0.36). For the web
|
||||
@@ -177,8 +186,14 @@ no LTO, and debug assertions off (truck has debug-only assertions that reject va
|
||||
builds several times faster than `--release`. If a test fails only in a plain debug build, say so
|
||||
in the PR. `cargo xtask test` builds the tests with it and then runs every test binary at once
|
||||
(`cargo test` runs them one after another); arguments after `--` go to each binary, e.g.
|
||||
`cargo xtask test -- shell`. Tests run beside each other on a busy machine, so a test must not
|
||||
depend on wall-clock timing.
|
||||
`cargo xtask test -- shell`. Set `SOLIDCRAFT_TEST_JOBS` to a positive integer to opt into a
|
||||
shared bound on top-level test/side commands for `cargo xtask test` and `cargo xtask ci`.
|
||||
For example, `SOLIDCRAFT_TEST_JOBS=2` allows two commands at once; the sequential wasm/doc
|
||||
worker consumes one of those slots. Every binary and side check still runs after failures.
|
||||
Without the variable the existing concurrency is unchanged. `RUST_TEST_THREADS`,
|
||||
`ACTION_MATRIX_JOBS` and Cargo's build-job setting remain independent caller overrides;
|
||||
this limit does not choose their values or change test filters, timeouts or baselines.
|
||||
Tests run beside each other on a busy machine, so a test must not depend on wall-clock timing.
|
||||
|
||||
**Ready pull requests test what they touch.** CI maps changed files to workspace packages and
|
||||
runs Clippy, the test build and tests for those and their dependents. The
|
||||
@@ -199,7 +214,7 @@ Check that validation after merging. They are listed in
|
||||
`.config/nextest.toml` and the [testing page](docs/book/src/dev/testing.md):
|
||||
|
||||
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness` and
|
||||
the seeds of `tests::robustness_goldens` (`seed_00` … `seed_95`; pull requests matching the workflow's geometry paths run all but the slowest, `SLOW_SEEDS` in `ci.yml`),
|
||||
the seeds of `tests::robustness_goldens` (`seed_00` … `seed_95`; pull requests that change the kernel, truck or the goldens run all but the slowest, `SLOW_SEEDS` in `ci.yml`),
|
||||
`pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen`,
|
||||
`pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs`,
|
||||
`cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall`, the `atomic_commands` contract tests
|
||||
@@ -265,6 +280,9 @@ and a fresh CI run:
|
||||
after each merge by the Generated files workflow (`cargo xtask licences`, `cargo xtask
|
||||
features`). Don't regenerate or edit them in a pull request.
|
||||
- **Design format**: one file per change, never an edit to a shared number (above).
|
||||
- **New UI strings and attribution rows**: one new file per change, `assets/locales/pending/<slug>.json`
|
||||
and `attribution/pending/<slug>.md`, instead of editing the catalogs or the ATTRIBUTION.md table;
|
||||
`cargo xtask fold-pending` folds them on `main` (see the README in each folder).
|
||||
|
||||
## Sending a change
|
||||
|
||||
@@ -331,6 +349,10 @@ owning one area, with a human setting direction and reporting bugs. Their workin
|
||||
[AGENTS.md](AGENTS.md). Human contributors are just as welcome. Use whatever tools you like, as
|
||||
long as the change meets the rules above.
|
||||
|
||||
Two quick checks run on every pull request (`.github/workflows/pr-checks.yml`, not required): it
|
||||
fails when a lower-numbered open PR by someone else already closes the same issue (the label
|
||||
`not-a-duplicate` skips it), or when the description lacks the `Changes a default: no` / `Changes a default: yes — <what>, old behaviour kept behind preference <name>` line (docs-only and bot PRs are exempt).
|
||||
|
||||
## Labels and triage
|
||||
|
||||
The triage bot labels every new issue and PR (`.github/workflows/triage.yml`). It only adds labels;
|
||||
|
||||
@@ -590,9 +590,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-freecad"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a5257265c2549cf59a8457b3c7aadcc96c95abe20205232553766993a808812"
|
||||
checksum = "3c4a8fa61748cbb3ce80838b00a8cebfe553460583941f5aba6e2b97c16b4d37"
|
||||
dependencies = [
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
@@ -601,14 +601,14 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"zip 8.6.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-rhino"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d88b42fb91c7e5af4c75c0433029771f7e3233a0b72ec3d0b2e5ff2c72bed07"
|
||||
checksum = "d7af41440d4c1d69e88e612de02cc3c9bad79d03429531e640ac924fef53c6bf"
|
||||
dependencies = [
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
@@ -625,53 +625,53 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-step"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83a52258c129a683f77fb360ca30af54ae34339625ad890be12621128a5811c"
|
||||
checksum = "35e1b6d17cf26b31f59f2edab8ef5c29d2a33216c581fd913f012ad4869bd559"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
"cadmpeg-ir",
|
||||
"encoding_rs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
"zip 8.6.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-container"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c110508cd9155e905f52906de505dfd42dc70a0321f5e90b5f68c28ba207686e"
|
||||
checksum = "5ab7bcef48ac7480b4917820272d85cd2c9533e17904e15b0e4d22f19a6045bd"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"zip 8.6.0",
|
||||
"zstd 0.14.2",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-core"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b1147e51a74e7c52da5b393a0344597238cdc622fac6443eb2fd526759f6fa"
|
||||
checksum = "e86bb7a40521832504bb1f5953a1c706b2c52aad73bf6d84e1b35e5cb0beae3f"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"memchr",
|
||||
"roxmltree",
|
||||
"ruzstd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
"zstd 0.14.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-ir"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ff19f019f013d97e353bb802c5d2e253c2859aaa5422009aa933dc2f2e7dbc"
|
||||
checksum = "b570a1a8c579e407a9fd7bb10fa1900f8827f1d578b8fcb4ea2a056b5f323c72"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cadmpeg-core",
|
||||
@@ -684,9 +684,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-registry"
|
||||
version = "0.6.1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66046f1a694926707bea8944f861e699964b04c68972708588f97e14ccee3c5d"
|
||||
checksum = "b2be23469a9bca0b684f1811118fffb581ebd32ded08e6cc8b0716a41f8c1377"
|
||||
dependencies = [
|
||||
"cadmpeg-codec-freecad",
|
||||
"cadmpeg-codec-rhino",
|
||||
@@ -996,6 +996,15 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.8"
|
||||
@@ -2271,6 +2280,16 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
@@ -2480,6 +2499,24 @@ dependencies = [
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muda"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"keyboard-types",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"png",
|
||||
"thiserror 2.0.21",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiversion_no_op"
|
||||
version = "1.0.0"
|
||||
@@ -3749,6 +3786,15 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "ruzstd"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e4ce4a039f42bf25920b95667e4d789e98749ab11bbb8d2c2936f788421bb5"
|
||||
dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -4162,7 +4208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"eframe",
|
||||
@@ -4172,6 +4218,7 @@ dependencies = [
|
||||
"hidapi",
|
||||
"keyring",
|
||||
"log",
|
||||
"muda",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-foundation 0.3.2",
|
||||
@@ -4193,13 +4240,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-3dx-client"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-cli"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
@@ -4208,7 +4256,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-derive"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4217,7 +4265,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-doc"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -4234,7 +4282,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-engine"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"schemars",
|
||||
@@ -4248,12 +4296,13 @@ dependencies = [
|
||||
"solidcraft-render",
|
||||
"solidcraft-sketch",
|
||||
"thiserror 2.0.21",
|
||||
"toml_edit",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-geom"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4262,15 +4311,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-gpu"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"naga",
|
||||
"pollster 0.4.0",
|
||||
"robust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-render",
|
||||
"thiserror 2.0.21",
|
||||
@@ -4279,13 +4331,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-io"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"cadmpeg-ir",
|
||||
"cadmpeg-registry",
|
||||
"chardetng",
|
||||
"encoding_rs",
|
||||
"gethostname",
|
||||
"gltf",
|
||||
"log",
|
||||
"quick-xml",
|
||||
@@ -4297,12 +4350,12 @@ dependencies = [
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
"thiserror 2.0.21",
|
||||
"zip 9.0.4",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-kernel"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"proptest",
|
||||
@@ -4322,7 +4375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-mcp"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
@@ -4331,7 +4384,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-render"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"moxcms",
|
||||
@@ -4345,7 +4398,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-sketch"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4356,14 +4409,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-space-mouse"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-egui"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"egui",
|
||||
@@ -4371,7 +4424,9 @@ dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"nix 0.31.3",
|
||||
"pollster 0.4.0",
|
||||
"proc-macro2",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4385,12 +4440,12 @@ dependencies = [
|
||||
"ttf-parser",
|
||||
"web-time",
|
||||
"winreg",
|
||||
"zip 9.0.4",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -4645,6 +4700,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -4839,6 +4895,12 @@ version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2"
|
||||
|
||||
[[package]]
|
||||
name = "type-map"
|
||||
version = "0.5.1"
|
||||
@@ -5920,10 +5982,12 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6211,20 +6275,6 @@ dependencies = [
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
"typed-path",
|
||||
"zstd 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "9.0.4"
|
||||
@@ -6264,52 +6314,6 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe 7.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "057cfd910cfac363a0ada849592624b4c9ff2e10bef504c3433810d78ed96f93"
|
||||
dependencies = [
|
||||
"zstd-safe 8.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "8.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdd44c6a7284e91f3717755b24315a302edd9153a01f753c3cba3d765e8eafac"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.1.1+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeec9eaf2dffbbd09201e23bd0ffcbaa33bb8e9266a10734fd7ed90a85eca078"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.3"
|
||||
|
||||
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
exclude = ["vendor"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.18.1"
|
||||
version = "0.22.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
@@ -13,6 +13,7 @@ authors = ["SolidCraft contributors"]
|
||||
repository = "https://github.com/bherbruck/solidcraft"
|
||||
|
||||
[workspace.dependencies]
|
||||
robust = "1.2"
|
||||
# internal
|
||||
solidcraft-geom = { path = "crates/geom" }
|
||||
solidcraft-sketch = { path = "crates/sketch" }
|
||||
@@ -66,18 +67,24 @@ encoding_rs = "0.8"
|
||||
# glTF 2.0 import (MIT OR Apache-2.0): the document model and GLB container only; buffers are
|
||||
# read by crates/io itself, so no image decoding or file access comes with it.
|
||||
gltf = { version = "1.4", default-features = false, features = ["names"] }
|
||||
# The computer's name in a design's "open elsewhere" lock file (MIT OR Apache-2.0; already in the
|
||||
# tree through x11rb).
|
||||
gethostname = "1"
|
||||
|
||||
# FreeCAD and Rhino import (Apache-2.0, pure Rust apart from zstd; native builds only).
|
||||
# FreeCAD and Rhino import (Apache-2.0, pure Rust; native and browser builds).
|
||||
# Pinned: its IR and codec APIs change between minor versions. Open formats only: no default
|
||||
# features, so cadmpeg's codecs for commercial formats (f3d, sldprt, inventor, catia, creo, nx)
|
||||
# and its ShapeManager/ACIS `sat` codec are not compiled in. `step` is the writer we restate through.
|
||||
cadmpeg-core = "=0.6.1"
|
||||
cadmpeg-ir = "=0.6.1"
|
||||
cadmpeg-registry = { version = "=0.6.1", default-features = false, features = ["fcstd", "rhino", "step"] }
|
||||
cadmpeg-core = "=0.6.3"
|
||||
cadmpeg-ir = "=0.6.3"
|
||||
cadmpeg-registry = { version = "=0.6.3", default-features = false, features = ["fcstd", "rhino", "step"] }
|
||||
|
||||
# fonts (sketch text outlines; MIT OR Apache-2.0, pure Rust)
|
||||
ttf-parser = { version = "0.25", default-features = false, features = ["std"] }
|
||||
|
||||
# Ends a process group with the system call (MIT; already in the tree through zip).
|
||||
nix = { version = "0.31", default-features = false, features = ["signal"] }
|
||||
|
||||
# images
|
||||
image = { version = "0.25", default-features = false, features = ["png", "hdr"] }
|
||||
png = "0.18.1"
|
||||
|
||||
@@ -51,26 +51,37 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
|
||||
|---|---|---|---|
|
||||
| M0 | Vertical slice | done | — |
|
||||
| M1 | Sketch depth: command coverage implemented — projection/intersect/include as linked geometry (auto-project on faces, lost-reference warnings, break link), full SKETCH create/modify set (slots, polygons, tangent arcs/circles, ellipse, fit/control splines, conic, text, trim/extend/break, fillet/chamfer, offset, mirror, patterns, move/scale, blend curve, centerline), constraints incl. curvature and polygon with over-constraint refusal, Shift-picked line midpoints in Horizontal/Vertical using persisted points and ordinary constraints (#2087), driven/arc-length/linear-diameter dimensions, AutoConstrain, DXF/SVG insert, 3D sketch curves (Project To Surface, Intersection Curve, Isoparametric Curve, Spun Profile), exact free-form profiles (B-spline/NURBS edges, any spline degree, curves cut by crossings), sweeps and pipes along 3D sketch curves, canvases and decals, curvature combs. Exact free-form Extend and certified associative offsets now preserve geometry and IDs through edits, deletion and reopen. Occurrence-frame 3D wires now store explicit coordinate spaces and retain points and analytic fits through moves, refresh, undo and reopen. Constructive sketch solving (Owen 1991, Fudos & Hoffmann 1997) ahead of the numerical solver: points and rigid clusters are placed on the branch the sketch had, so dimension edits and drags keep their solution; edits that would turn a sketch over are refused; drags re-solve only the sketch per frame. Remaining workflow repairs: mixed offset junctions, legacy natural-spline seams, and damaged legacy wire links lacking frame provenance | in progress | 4 |
|
||||
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces, at distances from reference edges, and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
|
||||
| M1 | Sketch depth: command coverage implemented — projection/intersect/include as linked geometry (auto-project on faces, lost-reference warnings, break link), full SKETCH create/modify set (slots, polygons, tangent arcs/circles, ellipse, fit/control splines, conic, text, trim/extend/break, fillet/chamfer, offset, mirror, patterns, move/scale, blend curve, centerline), constraints incl. curvature and polygon with over-constraint refusal, Shift-picked line midpoints in Horizontal/Vertical using persisted points and ordinary constraints (#2087), drawing snaps to where two lines, circles or arcs cross (#4967), driven/arc-length/linear-diameter dimensions, AutoConstrain, DXF/SVG insert, 3D sketch curves (Project To Surface, Intersection Curve, Isoparametric Curve, Spun Profile), exact free-form profiles (B-spline/NURBS edges, any spline degree, curves cut by crossings), sweeps and pipes along 3D sketch curves, canvases and decals, curvature combs. Exact free-form Extend and certified associative offsets now preserve geometry and IDs through edits, deletion and reopen. Occurrence-frame 3D wires now store explicit coordinate spaces and retain points and analytic fits through moves, refresh, undo and reopen. Constructive sketch solving (Owen 1991, Fudos & Hoffmann 1997) ahead of the numerical solver: points and rigid clusters are placed on the branch the sketch had, so dimension edits and drags keep their solution; edits that would turn a sketch over are refused; drags re-solve only the sketch per frame. Remaining workflow repairs: mixed offset junctions, legacy natural-spline seams, and damaged legacy wire links lacking frame provenance | in progress | 4 |
|
||||
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
|
||||
| M3 | General fillets and chamfers (curved edges, chains, vertex blends); saddle rolling-ball surfaces now use continuous seam sections and arc-length fitting with shared contact boundaries and bounded accuracy checks; tiny rim edges remain selected instead of nearby wall seams when filleting (#2498) | in progress | 30 |
|
||||
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), FreeCAD/Rhino solids through cadmpeg (done, desktop and CLI), 3MF export and 3MF/STL import as mesh bodies (done), 3MF with B-rep face tags ([docs/formats/3mf-face-tags.md](docs/formats/3mf-face-tags.md), done), constant-section parametric mesh reconstruction, DXF sketches, OBJ import | in progress | 6 |
|
||||
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), FreeCAD/Rhino solids through cadmpeg (done, desktop, browser and CLI), 3MF export and 3MF/STL import as mesh bodies (done), 3MF with B-rep face tags ([docs/formats/3mf-face-tags.md](docs/formats/3mf-face-tags.md), done), constant-section parametric mesh reconstruction, DXF sketches, OBJ import | in progress | 6 |
|
||||
| M5 | Persistent naming of faces and edges (docs/book/src/dev/naming.md): faces named from their exact kernel-reported history (sketch curve sides, caps, revolve sections, primitive sides, blends, shell inner walls, pattern/mirror copies; split pieces told apart by the faces around them, lumps of a cut body named after their faces), edges by their faces; fillets/chamfers, sketches on faces, shell/draft/offset/replace faces, hole placements, joint origins and face appearances resolve by name first and warn when the entity split or vanished: done. sheet metal edges by base sketch line and flange side, shell inner faces, sweep and loft roles: done. kernel-reported face history: done (merged faces answer to every name; operations without history yet fall back to geometric names, flagged unverified; format 14 keeps older designs' geometric names). Left: history through sheet metal, plastic, surface and emboss features; FaceSource certificates through history | in progress | 20 |
|
||||
| M6 | UI depth: selection feedback and origin (done), timeline editing (done: hover and selection highlight modifier faces, including fillets and chamfers; edit feature, history marker drag, dependency-checked single and multi-item reorder, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names; optional compact named left feature history with rollback bar, multi-selection and persistent folders above the parts browser), right-docked dialogs (done), live previews for every feature dialog (done), keyboard-first commands with on-canvas value boxes and sketch inline dimensions (done), drag manipulators with automatic join/cut (done: extrude incl. two sides, fillet, shell, hole depth, move, pattern spacing, revolve rotator, extrude taper), dark theme (done), right-click menu with Repeat (done), measure tool (done), section analysis (done), selection filter (done), drag-solve of sketch points (done); appearance controls now preserve body/face/component targeting, named transparent looks, reuse/search and override removal; preferences | in progress | 6 |
|
||||
| M6 | UI depth: selection feedback and origin (done), timeline editing (done: hover and selection highlight modifier faces, including fillets and chamfers; edit feature, history marker drag, dependency-checked single and multi-item reorder, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names; responsive feature history with width-driven icons/names/title, snapping width resize, bottom/left/browser-stack docking, shared-column collapse, rollback bar, multi-selection and persistent folders), right-docked dialogs (done), live previews for every feature dialog (done), keyboard-first commands with on-canvas value boxes and sketch inline dimensions (done), drag manipulators with automatic join/cut (done: extrude incl. two sides, fillet, shell, hole depth, move, pattern spacing, revolve rotator, extrude taper), dark theme (done), right-click menu with Repeat (done), measure tool (done), section analysis (done), selection filter (done), drag-solve of sketch points (done); appearance controls now preserve body/face/component targeting, named transparent looks, reuse/search and override removal; Browser Expand All / Collapse All (`browser.expand_all`, `browser.collapse_all`) on the design and component menus; preferences | in progress | 6 |
|
||||
| M7 | Thin extrude (walls along open or closed curves: centre, inside, outside or two sides; mitred corners, crossing walls joined; #721), sweep, loft (incl. to a point), rib and web (to next / depth), emboss (planar faces and exact radial emboss/deboss of bounded orthogonal profiles on complete exterior cylinders/sleeves), thread (cosmetic, and modelled M3–M20 outside and inside: the ISO profile screwed in as a faceted cut), coil (smooth B-spline helical walls), pipe, pattern on path, patterns of bodies and components, replace face (parallel), align, remove, feature copy/paste: done. Unique eligible cylindrical targets are inferred for new Emboss features. Left: general curved-face emboss and exact (unfaceted) modelled threads | in progress | 12 |
|
||||
| M8 | Surface workspace (#772). Done: Patch (sketch regions; edge loops filled G0, plus `crown`), Stitch, Thicken (planes and cylinders), Extend (straight edges of planar surfaces), Trim (by a plane, a face or sketch curves swept onto the surface, keeping a side or the picked regions; mutual trim of two surfaces, extended to meet and stitched, #767), Delete Face (healing), Boundary Fill, zebra/draft/curvature-map analysis, Surface Extrude/Revolve/Sweep/Loft from open and closed curves, Offset, Unstitch and Reverse Normal; the continuity-driven core (`kernel/src/bridge.rs`, G0–G3 Hermite rows) with **Blend** (`surface.blend`, #764: edge chains along one or several faces, closed rims paired seam to seam or from picked seams, a dialog), **Match** (`surface.match`), **Boundary** (`surface.boundary`, #763 — two sketch curves exact with G0–G3, three or four as the flat patch of their loop, and curve networks: Coons and Gordon surfaces through sketch, 3D-sketch or edge curves, a point for a three-sided side, G1/G2 against the faces along one direction's edges, joined into their body on request; G1/G2 on two sides that meet at a corner waits for the Gregory-type corner), **Fair** (`surface.fair`, CV fairing), **continuity analysis** (`inspect.continuity`, #770; a toolbar tool with edge markers and a legend, and curvature combs along face isocurves) and a multi-section **loft** with per-end continuity and crown, and guide rails on solid and surface lofts (one or two, scaling or stretching the sections between; #201). Also in the Class-A pass: G1…G4 blend curves in the sketch, chord and variable-radius fillets on curved edges (with honest refusals where the fit cannot hold), and a G2 corner patch where filleted edges meet (kernel; the command refuses until the patch's sections can be sewn to the blends' contacts). Left: bias/form-factor fillet sections (design recorded), a Gregory-type corner, surface Fillet between separate surface bodies (#769), Ruled (#762), Untrim (#761), curved-face Extend (#760), and every remaining #757–#772 item | deferred | — |
|
||||
| M9 | Components and assemblies: New Component / Create Components from Bodies, active component, per-component origin/bodies/sketches/timeline entries, occurrences with transforms, Copy/Paste vs Paste New, nested Browser tree with visibility and isolate; Ground, Move/Copy of occurrences, joints (rigid, revolute, slider, cylindrical, pin-slot, planar, ball) by joint origins with limits and Drive Joints, closed kinematic loops (four-bar, slider-crank: the loop's other joints follow the driven one on its branch, and driving stops where a loop can no longer close), cams (`joint.cam`: a revolute cam drives a slider or revolute follower along a closed sketch profile or the cam's edges, knife, roller or flat followers, worked out again when the profile changes; drives, studies, closed loops, contact sets), As-built Joint, Rigid Group, Contact Sets (driving and motion studies stop where bodies in a set would pass through each other), Motion Study (joint values keyed along a timeline, playback, export of occurrence positions as JSON/CSV), motion analysis (joint values and a point's path charted over a study as value, velocity and acceleration; the parts that collide along a study, with the steps where they do), Exploded views (automatic or by hand, shown without changing placements); interference, per-component physical properties; STEP with assembly structure; URDF export for robot simulators (links with meshes, mass and inertia; joint chains whose values match Drive Joints; docs/urdf.md). Physical properties now measure material-aware component/occurrence placements, descendants and repeated instances, including pending moves and body reassignment offsets; contact geometry and automatic exploded centres respect those offsets. Groundwork done: component tree, occurrences with transforms (instances, Paste New), world placement, active component as authoring frame, ground, Capture/Revert Position, moving bodies and sketches. UI: ASSEMBLE panel dialogs for New Component, Joint (snaps on faces, edges, circles and vertices; animated motion preview; offsets, flip, limits), As-built Joint, Joint Origin, Rigid Group, Drive Joints (live slider), Motion Link, Cam and Interference (overlaps shown red); joints in the timeline and the Browser | in progress | 32 |
|
||||
| M9 | Components and assemblies: New Component / Create Components from Bodies, active component, per-component origin/bodies/sketches/timeline entries, occurrences with transforms, Copy/Paste vs Paste New, nested Browser tree with visibility and isolate; Ground, Move/Copy of occurrences, joints (rigid, revolute, slider, cylindrical, pin-slot, planar, ball) by joint origins with limits and Drive Joints, closed kinematic loops (four-bar, slider-crank: the loop's other joints follow the driven one on its branch, and driving stops where a loop can no longer close), cams (`joint.cam`: a revolute cam drives a slider or revolute follower along a closed sketch profile or the cam's edges, knife, roller or flat followers, worked out again when the profile changes; drives, studies, closed loops, contact sets), As-built Joint, Rigid Group, Contact Sets (driving and motion studies stop where bodies in a set would pass through each other), Motion Study (joint values keyed along a timeline, playback, export of occurrence positions as JSON/CSV), motion analysis (joint values and a point's path charted over a study as value, velocity and acceleration; the parts that collide along a study, with the steps where they do), Exploded views (automatic or by hand, shown without changing placements, and exported as an animation coming apart and back together); interference, per-component physical properties; STEP with assembly structure; URDF export for robot simulators (links with meshes, mass and inertia; joint chains whose values match Drive Joints; docs/urdf.md). Physical properties now measure material-aware component/occurrence placements, descendants and repeated instances, including pending moves and body reassignment offsets; contact geometry and automatic exploded centres respect those offsets. Groundwork done: component tree, occurrences with transforms (instances, Paste New), world placement, active component as authoring frame, ground, Capture/Revert Position, moving bodies and sketches. UI: ASSEMBLE panel dialogs for New Component, Joint (snaps on faces, edges, circles and vertices; animated motion preview; offsets, flip, limits), As-built Joint, Joint Origin, Rigid Group, Drive Joints (live slider), Motion Link, Cam and Interference (overlaps shown red); joints in the timeline and the Browser | in progress | 32 |
|
||||
| M10 | Sheet metal: flange (base, edge, contour), hem, unfold/refold, convert, flat pattern + DXF, rules: done. PLASTIC enclosure subset: Boss, Rib, Web, Lip/Groove, Snap Fit, Rest, Plastic Rules (library, edit, assign; rule draft and clearance as feature defaults): done, tested against hand-computed geometry. UI: SHEET METAL and PLASTIC tabs with dialogs (live preview, Edit Feature, pre-selection) for Flange, Hem, Unfold/Refold, Convert, Sheet Metal Rules, Create Flat Pattern (flat view with bend lines, bend table CSV/JSON and DXF export with exact circle/arc/line cutouts), Boss, Lip/Groove, Snap Fit, Rest, Manage and Assign Plastic Rules; Fold (bend along a sketch line); flanges meeting at a corner closed at any angle with the rule's gap; edge flanges with their own angle and height per edge in one feature; corner reliefs (notch, round, rectangular, tear) chosen by the rule; Convert to Sheet Metal from a solid or shelled body by its bend edges (flat faces, sharp bends) | in progress | 36 |
|
||||
| M11 | 2D drawings | deferred | — |
|
||||
| M12 | Release: cross-platform packages, verification and deliberate publication implemented; original current-UI demo refreshed. Trusted distribution signing and broader performance work remain | in progress | 16 |
|
||||
| M12 | Release: cross-platform packages, verification and deliberate publication implemented; original current-UI demo refreshed. macOS Developer ID signing and notarization are in the release workflow (#5749; they switch on when the Apple secrets exist). Windows installer signing and broader performance work remain | in progress | 16 |
|
||||
| M14 | Configurations: a table of rows (parameter expressions and feature suppressions per row) switched with config.activate (undoable, all or nothing), kept through parameter renames and feature deletes (config.configure, config.table, config.column/row/activate). Per-row body materials and appearances (library looks, custom colour/opacity and clearing overrides), undoable activation and save/reopen: done. Ordered typed configuration rules, atomic validation and structured rule editing: done. Component appearance and certified semantic face style columns, atomic preview/apply and independent-copy remapping: done. Exact constructed sketch-side and immutable imported-face lineage now preserve legacy names and reject retargeting. Left: ambiguous shifted/two-sided caps | in progress | 4 |
|
||||
| M13 | Never lose work: atomic saves (temporary file, flush, rename; a crash at any moment leaves the old file or the new one) with the replaced version kept as `<file>.bak`; autosave of unsaved changes to the recovery folder every 5 minutes (preference) and after big operations, only when something changed; "Recover unsaved design?" on the next launch (`doc.recovery_list`, `doc.recover`, `doc.recovery_discard`); a kill-mid-save test (process killed while saving a 0.5 MB design 25 times). File format: `"format": "solidcraft/12"`, older formats upgraded step by step (`doc::format`), newer ones refused with a message, out-of-range numbers and broken placements refused; a corpus of designs saved by M0, components-era, joints-era and current builds (apps/solidcraft-cli/tests/corpus) that must open and evaluate as saved; damaged and hostile variants (truncation, bit flips, hostile values, removed keys, repeated features, deep nesting) never panic | done | 6 |
|
||||
| M15 | Frames and weldments (#4003): structural members along 2D and 3D sketch lines, butt and mitre corners, trim to faces and bodies, end caps, gussets, cosmetic welds and a cut list. Done: the profile library (`frame.profile.list`, `frame.profile.properties`, `frame.profile.insert`): bar, round bar, rectangular and round tube, angle, channel and I-beam, each SolidCraft's own parametric definition with exact fillet arcs and exact section properties (area, centroid, second moments), tested against hand-computed values; members (`frame.member`): one body per straight line of a 2D or 3D frame sketch, following parameter edits, with a built-in profile (its dimensions parameter expressions) or a profile sketch (its drawn material by alternating nested loops), anchor, angle and mirror; each member's name kept with its line. Left: corners, trims, end caps, gussets, welds, cut list, FRAME tab and dialogs | in progress | 60 |
|
||||
|
||||
M6 navigation preferences include **Touchpad**: Shift + pointer movement pans, Alt/Option +
|
||||
pointer movement orbits without a mouse button, and vertical scrolling zooms. Other presets
|
||||
retain their saved two-finger scroll preference; camera motion is available through
|
||||
`view.navigate` and modifier-aware `ui.move` automation.
|
||||
With the **Fusion** preset and two-finger pan enabled, two fingers pan and Shift + two fingers
|
||||
orbit freely, with pinch zoom and mouse bindings unchanged. The shared navigation handler
|
||||
applies the same gestures to model and Render views and blocks orbit on flat pages.
|
||||
|
||||
M11 precursor: File → Export Current View as DXF exports shown placed bodies from the current
|
||||
UI redesign (#2016): the File/start view (#2027) uses shared light/dark navigation,
|
||||
action tiles, recent rows and sample cards, with recovery review and Save As/Export
|
||||
entry points. Document loading, sample caches and recovery safety retain their
|
||||
existing command paths.
|
||||
|
||||
M11 precursor: File → Export Current View as DXF exports shown placed bodies and sketches from the current
|
||||
camera as a 2D millimetre drawing, with hidden edges omitted or dashed. Vector occlusion splits
|
||||
partly hidden edges and includes curved silhouettes. Repeated part meshes and triangle
|
||||
hierarchies are shared; bounded desktop preparation and occlusion use up to eight workers.
|
||||
@@ -134,6 +145,12 @@ completed drawings publish atomically. The browser still exports synchronously.
|
||||
|
||||
## Render foundation
|
||||
|
||||
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
|
||||
|
||||
- Bounded linear film accumulation (#1696) supplies caller-admitted mean/variance storage and complete-pass views; actual progressive consumers remain separate. See [the film contract](docs/render-film.md).
|
||||
|
||||
- Definition-local visual mapping (#1214) supplies a bounded numerical frame, physical repeat coordinates and a deterministic one-time grain-axis suggestion. It adds no saved record or texture consumer; see [the mapping contract](docs/render-visual-mapping.md).
|
||||
|
||||
- Explicit texture normalization (#1218) produces completed float RGBA with
|
||||
role-aware linear color/data semantics, bounded matrix/TRC ICC conversion and
|
||||
opt-in EXIF orientation. It retains high-precision raw samples and records lossy
|
||||
@@ -189,6 +206,11 @@ completed drawings publish atomically. The browser still exports synchronously.
|
||||
|
||||
## Dimensioning and error reporting
|
||||
|
||||
- Operation length fields share design-unit defaults and input evaluation, including secondary
|
||||
distances, thin walls, blend controls, sheet/plastic rules, joints and motion keys. Canvas
|
||||
handles and sketch previews use the same lengths as committed geometry. Existing stored
|
||||
millimetre values retain their size when edited in a design using another unit (#3271).
|
||||
|
||||
- MCP `execute` accepts `verbose: false`, sharing the compact reply helper used by
|
||||
`batch`: body changes and created sketch IDs stay, while repeated lists are omitted.
|
||||
Both tools retain full replies by default.
|
||||
@@ -232,6 +254,9 @@ completed drawings publish atomically. The browser still exports synchronously.
|
||||
- Sketch Dimension takes a line and a circle or arc (#1803): it measures from the line to the
|
||||
circle's edge (the gap, 0 when tangent, or how far the edge reaches past a line that cuts
|
||||
it), drives it like any other dimension and keeps the circle on its side of the line.
|
||||
- Sketch Dimension takes a point and a circle or arc, or two circles or arcs (#4064): it
|
||||
measures to the circle's edge (from the point, or between the two edges: the gap, 0 when
|
||||
they touch, how far they overlap, or the narrowest gap between nested circles).
|
||||
- Sketch annotations, live drawing lengths and Measure use document units. Inches show
|
||||
three decimals and retain a meaningful fourth digit. Bare length dimension entry captures
|
||||
the current design unit; changing units leaves existing geometry and expressions intact.
|
||||
@@ -329,8 +354,25 @@ completed drawings publish atomically. The browser still exports synchronously.
|
||||
- Saved projections from a removed strip retain their geometry and report the lost
|
||||
reference. Explicitly reselect the wall to repair that projection.
|
||||
|
||||
## Workspace layouts (#2029)
|
||||
|
||||
- General preferences save Floating or Compact using the universal UI containers.
|
||||
Compact joins the workspace selector and ribbon, docks the content-hugging Browser
|
||||
flush beneath them with a square join, and opens tool sheets below the compass.
|
||||
Sheets remain draggable, rounding all corners away from their right dock; footer
|
||||
backgrounds follow the same corners. A separate preference hides ribbon icon labels
|
||||
for a shorter ribbon in either layout.
|
||||
Assistant keeps the outermost edge and History keeps its chosen dock. Switching
|
||||
retains the active tool, camera, selection and design; system windows and menus
|
||||
keep their existing behavior.
|
||||
|
||||
## View navigation
|
||||
|
||||
- Navigation has an opt-in Free orbit preference for mouse and 3D-mouse rotation through
|
||||
both poles with roll. It defaults off, preserving existing preset snapping and pole limits.
|
||||
The SolidWorks face menu offers Normal to Selection (`view.normal_to`), shared with Look At
|
||||
and Ctrl+8, including repeat-to-flip, preserving scale and projection.
|
||||
|
||||
- Desktop Ctrl/Cmd +/- and 0 change or reset the saved UI scale. The scale slider keeps
|
||||
its layout stable during a drag and applies the selected scale on release.
|
||||
|
||||
@@ -561,6 +603,10 @@ completed drawings publish atomically. The browser still exports synchronously.
|
||||
continuous collision detection; local thin walls and small details remain limitations.
|
||||
See [contact-set limits and recovery](docs/contact-sets.md).
|
||||
|
||||
## Assembly dragging
|
||||
|
||||
The design's first part is grounded when it is made, and a left-button drag repositions a free part (`occurrence.drag`: one undo step, exact occurrence paths including repeated jointed definitions, any floating-chain member carries the chain, grounded parts and joints hold where present, Escape cancels).
|
||||
|
||||
## 3D sketch bridges
|
||||
|
||||
3D sketch drawing snaps to points on other visible sketch planes without flattening them onto
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Third-party software in SolidCraft
|
||||
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 560 crates below.
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 559 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.
|
||||
|
||||
@@ -60,13 +60,13 @@ bytemuck_derive 1.12.1 (Zlib OR Apache-2.0 OR MIT)
|
||||
byteorder 1.5.0 (Unlicense OR MIT)
|
||||
byteorder-lite 0.1.0 (Unlicense OR MIT)
|
||||
bytes 1.12.1 (MIT)
|
||||
cadmpeg-codec-freecad 0.6.1 (Apache-2.0)
|
||||
cadmpeg-codec-rhino 0.6.1 (Apache-2.0)
|
||||
cadmpeg-codec-step 0.6.1 (Apache-2.0)
|
||||
cadmpeg-container 0.6.1 (Apache-2.0)
|
||||
cadmpeg-core 0.6.1 (Apache-2.0)
|
||||
cadmpeg-ir 0.6.1 (Apache-2.0)
|
||||
cadmpeg-registry 0.6.1 (Apache-2.0)
|
||||
cadmpeg-codec-freecad 0.6.3 (Apache-2.0)
|
||||
cadmpeg-codec-rhino 0.6.3 (Apache-2.0)
|
||||
cadmpeg-codec-step 0.6.3 (Apache-2.0)
|
||||
cadmpeg-container 0.6.3 (Apache-2.0)
|
||||
cadmpeg-core 0.6.3 (Apache-2.0)
|
||||
cadmpeg-ir 0.6.3 (Apache-2.0)
|
||||
cadmpeg-registry 0.6.3 (Apache-2.0)
|
||||
calloop 0.13.0 (MIT)
|
||||
calloop 0.14.5 (MIT)
|
||||
calloop-wayland-source 0.3.0 (MIT)
|
||||
@@ -95,6 +95,7 @@ core_detect 1.0.0 (MIT/Apache-2.0)
|
||||
cpufeatures 0.2.17 (MIT OR Apache-2.0)
|
||||
cpufeatures 0.3.1 (MIT OR Apache-2.0)
|
||||
crc32fast 1.5.2 (MIT OR Apache-2.0)
|
||||
crossbeam-channel 0.5.17 (MIT OR Apache-2.0)
|
||||
crossbeam-deque 0.8.8 (MIT OR Apache-2.0)
|
||||
crossbeam-epoch 0.9.21 (MIT OR Apache-2.0)
|
||||
crossbeam-utils 0.8.23 (MIT OR Apache-2.0)
|
||||
@@ -210,6 +211,7 @@ jni-sys 0.4.1 (MIT OR Apache-2.0)
|
||||
jni-sys-macros 0.4.1 (MIT OR Apache-2.0)
|
||||
js-sys 0.3.106 (MIT OR Apache-2.0)
|
||||
katexit 0.1.5 (MIT OR Apache-2.0)
|
||||
keyboard-types 0.8.3 (MIT OR Apache-2.0)
|
||||
keyring 3.6.3 (MIT OR Apache-2.0)
|
||||
khronos-egl 6.0.0 (MIT/Apache-2.0)
|
||||
kurbo 0.13.1 (Apache-2.0 OR MIT)
|
||||
@@ -234,6 +236,7 @@ minisign-verify 0.3.0 (MIT)
|
||||
miniz_oxide 0.8.9 (MIT OR Zlib OR Apache-2.0)
|
||||
miniz_oxide 0.9.1 (MIT OR Zlib OR Apache-2.0)
|
||||
moxcms 0.8.1 (BSD-3-Clause OR Apache-2.0)
|
||||
muda 0.21.2 (Apache-2.0 OR MIT)
|
||||
multiversion_no_op 1.0.0 (Apache-2.0 OR MIT)
|
||||
naga 30.0.1 (MIT OR Apache-2.0)
|
||||
naga-types 30.0.1 (MIT OR Apache-2.0)
|
||||
@@ -352,6 +355,7 @@ rustls-platform-verifier 0.7.1 (MIT OR Apache-2.0)
|
||||
rustls-platform-verifier-android 0.2.0 (MIT OR Apache-2.0)
|
||||
rustls-webpki 0.103.15 (ISC)
|
||||
rustversion 1.0.23 (MIT OR Apache-2.0)
|
||||
ruzstd 0.9.1 (MIT)
|
||||
schannel 0.1.29 (MIT)
|
||||
schemars 1.2.3 (MIT)
|
||||
schemars_derive 1.2.3 (MIT)
|
||||
@@ -429,6 +433,7 @@ truck-shapeops 0.4.0 (Apache-2.0)
|
||||
truck-stepio 0.3.0 (Apache-2.0)
|
||||
truck-topology 0.6.0 (Apache-2.0)
|
||||
ttf-parser 0.25.1 (MIT OR Apache-2.0)
|
||||
twox-hash 2.1.5 (MIT)
|
||||
type-map 0.5.1 (MIT/Apache-2.0)
|
||||
typed-path 0.12.3 (MIT OR Apache-2.0)
|
||||
typeid 1.0.3 (MIT OR Apache-2.0)
|
||||
@@ -546,16 +551,10 @@ zeroize_derive 1.5.0 (Apache-2.0 OR MIT)
|
||||
zerotrie 0.2.5 (Unicode-3.0)
|
||||
zerovec 0.11.8 (Unicode-3.0)
|
||||
zerovec-derive 0.11.6 (Unicode-3.0)
|
||||
zip 8.6.0 (MIT)
|
||||
zip 9.0.4 (MIT)
|
||||
zlib-rs 0.6.8 (Zlib)
|
||||
zmij 1.0.23 (MIT)
|
||||
zopfli 0.8.3 (Apache-2.0)
|
||||
zstd 0.13.3 (MIT)
|
||||
zstd 0.14.2 (BSD-3-Clause)
|
||||
zstd-safe 7.3.0 (BSD-3-Clause)
|
||||
zstd-safe 8.1.0 (BSD-3-Clause)
|
||||
zstd-sys 2.1.1+zstd.1.5.7 (BSD-3-Clause)
|
||||
zune-core 0.5.3 (MIT OR Apache-2.0 OR Zlib)
|
||||
zune-jpeg 0.5.15 (MIT OR Apache-2.0 OR Zlib)
|
||||
zvariant 4.2.0 (MIT)
|
||||
@@ -1108,26 +1107,27 @@ ahash 0.8.12, arrayvec 0.7.8, async-channel 2.5.0, async-executor 1.14.0, async-
|
||||
4.7.1, atomic-waker 1.1.2, base64 0.23.1, bitflags 1.3.2, bitflags 2.13.2, blocking 1.7.0, bumpalo
|
||||
3.20.3, cfg-if 1.0.5, cgl 0.3.2, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation
|
||||
0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, core_detect
|
||||
1.0.0, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23, dbus-secret-service
|
||||
4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14, euclid 0.22.14,
|
||||
event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10, form_urlencoded
|
||||
1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow 0.17.0, gltf 1.4.1, gltf-derive
|
||||
1.4.1, gltf-json 1.4.1, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, httparse 1.10.1, idna
|
||||
1.1.0, idna_adapter 1.2.2, indexmap 2.14.2, itertools 0.13.0, itertools 0.15.0, js-sys 0.3.106,
|
||||
khronos-egl 6.0.0, lazy_static 1.5.1, linux-raw-sys 0.12.1, linux-raw-sys 0.4.15, lock_api 0.4.14,
|
||||
log 0.4.34, num 0.4.3, num-bigint 0.4.8, num-complex 0.4.6, num-integer 0.1.47, num-iter 0.1.46,
|
||||
num-rational 0.4.2, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0,
|
||||
parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5,
|
||||
plain 0.2.3, png 0.18.1, polling 3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core
|
||||
1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, ron 0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix
|
||||
0.38.44, rustix 1.1.5, rustls 0.23.46, 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, 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
|
||||
1.0.0, crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils
|
||||
0.8.23, dbus-secret-service 4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14,
|
||||
euclid 0.22.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10,
|
||||
form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow 0.17.0, gltf 1.4.1,
|
||||
gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, httparse
|
||||
1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.2, itertools 0.13.0, itertools 0.15.0, js-sys
|
||||
0.3.106, keyboard-types 0.8.3, khronos-egl 6.0.0, lazy_static 1.5.1, linux-raw-sys 0.12.1,
|
||||
linux-raw-sys 0.4.15, lock_api 0.4.14, log 0.4.34, muda 0.21.2, num 0.4.3, num-bigint 0.4.8,
|
||||
num-complex 0.4.6, num-integer 0.1.47, num-iter 0.1.46, num-rational 0.4.2, num-traits 0.2.19,
|
||||
once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5,
|
||||
parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, plain 0.2.3, png 0.18.1, polling
|
||||
3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core 1.13.0, renderdoc-sys 1.1.0, robust
|
||||
1.2.0, ron 0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix 0.38.44, rustix 1.1.5, rustls 0.23.46,
|
||||
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,
|
||||
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
|
||||
================================================================================
|
||||
|
||||
Apache License
|
||||
@@ -4030,7 +4030,605 @@ Apache License
|
||||
limitations under the License.
|
||||
|
||||
================================================================================
|
||||
crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23
|
||||
crossbeam-channel 0.5.17
|
||||
================================================================================
|
||||
|
||||
===============================================================================
|
||||
|
||||
matching.go
|
||||
https://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
Attribution 3.0 Unported
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||
DAMAGES RESULTING FROM ITS USE.
|
||||
|
||||
License
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||
CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||
other pre-existing works, such as a translation, adaptation,
|
||||
derivative work, arrangement of music or other alterations of a
|
||||
literary or artistic work, or phonogram or performance and includes
|
||||
cinematographic adaptations or any other form in which the Work may be
|
||||
recast, transformed, or adapted including in any form recognizably
|
||||
derived from the original, except that a work that constitutes a
|
||||
Collection will not be considered an Adaptation for the purpose of
|
||||
this License. For the avoidance of doubt, where the Work is a musical
|
||||
work, performance or phonogram, the synchronization of the Work in
|
||||
timed-relation with a moving image ("synching") will be considered an
|
||||
Adaptation for the purpose of this License.
|
||||
b. "Collection" means a collection of literary or artistic works, such as
|
||||
encyclopedias and anthologies, or performances, phonograms or
|
||||
broadcasts, or other works or subject matter other than works listed
|
||||
in Section 1(f) below, which, by reason of the selection and
|
||||
arrangement of their contents, constitute intellectual creations, in
|
||||
which the Work is included in its entirety in unmodified form along
|
||||
with one or more other contributions, each constituting separate and
|
||||
independent works in themselves, which together are assembled into a
|
||||
collective whole. A work that constitutes a Collection will not be
|
||||
considered an Adaptation (as defined above) for the purposes of this
|
||||
License.
|
||||
c. "Distribute" means to make available to the public the original and
|
||||
copies of the Work or Adaptation, as appropriate, through sale or
|
||||
other transfer of ownership.
|
||||
d. "Licensor" means the individual, individuals, entity or entities that
|
||||
offer(s) the Work under the terms of this License.
|
||||
e. "Original Author" means, in the case of a literary or artistic work,
|
||||
the individual, individuals, entity or entities who created the Work
|
||||
or if no individual or entity can be identified, the publisher; and in
|
||||
addition (i) in the case of a performance the actors, singers,
|
||||
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||
play in, interpret or otherwise perform literary or artistic works or
|
||||
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||
being the person or legal entity who first fixes the sounds of a
|
||||
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||
organization that transmits the broadcast.
|
||||
f. "Work" means the literary and/or artistic work offered under the terms
|
||||
of this License including without limitation any production in the
|
||||
literary, scientific and artistic domain, whatever may be the mode or
|
||||
form of its expression including digital form, such as a book,
|
||||
pamphlet and other writing; a lecture, address, sermon or other work
|
||||
of the same nature; a dramatic or dramatico-musical work; a
|
||||
choreographic work or entertainment in dumb show; a musical
|
||||
composition with or without words; a cinematographic work to which are
|
||||
assimilated works expressed by a process analogous to cinematography;
|
||||
a work of drawing, painting, architecture, sculpture, engraving or
|
||||
lithography; a photographic work to which are assimilated works
|
||||
expressed by a process analogous to photography; a work of applied
|
||||
art; an illustration, map, plan, sketch or three-dimensional work
|
||||
relative to geography, topography, architecture or science; a
|
||||
performance; a broadcast; a phonogram; a compilation of data to the
|
||||
extent it is protected as a copyrightable work; or a work performed by
|
||||
a variety or circus performer to the extent it is not otherwise
|
||||
considered a literary or artistic work.
|
||||
g. "You" means an individual or entity exercising rights under this
|
||||
License who has not previously violated the terms of this License with
|
||||
respect to the Work, or who has received express permission from the
|
||||
Licensor to exercise rights under this License despite a previous
|
||||
violation.
|
||||
h. "Publicly Perform" means to perform public recitations of the Work and
|
||||
to communicate to the public those public recitations, by any means or
|
||||
process, including by wire or wireless means or public digital
|
||||
performances; to make available to the public Works in such a way that
|
||||
members of the public may access these Works from a place and at a
|
||||
place individually chosen by them; to perform the Work to the public
|
||||
by any means or process and the communication to the public of the
|
||||
performances of the Work, including by public digital performance; to
|
||||
broadcast and rebroadcast the Work by any means including signs,
|
||||
sounds or images.
|
||||
i. "Reproduce" means to make copies of the Work by any means including
|
||||
without limitation by sound or visual recordings and the right of
|
||||
fixation and reproducing fixations of the Work, including storage of a
|
||||
protected performance or phonogram in digital form or other electronic
|
||||
medium.
|
||||
|
||||
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||
limit, or restrict any uses free from copyright or rights arising from
|
||||
limitations or exceptions that are provided for in connection with the
|
||||
copyright protection under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License,
|
||||
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||
perpetual (for the duration of the applicable copyright) license to
|
||||
exercise the rights in the Work as stated below:
|
||||
|
||||
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||
Collections, and to Reproduce the Work as incorporated in the
|
||||
Collections;
|
||||
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||
including any translation in any medium, takes reasonable steps to
|
||||
clearly label, demarcate or otherwise identify that changes were made
|
||||
to the original Work. For example, a translation could be marked "The
|
||||
original work was translated from English to Spanish," or a
|
||||
modification could indicate "The original work has been modified.";
|
||||
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||
in Collections; and,
|
||||
d. to Distribute and Publicly Perform Adaptations.
|
||||
e. For the avoidance of doubt:
|
||||
|
||||
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme cannot be waived, the Licensor
|
||||
reserves the exclusive right to collect such royalties for any
|
||||
exercise by You of the rights granted under this License;
|
||||
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme can be waived, the Licensor waives the
|
||||
exclusive right to collect such royalties for any exercise by You
|
||||
of the rights granted under this License; and,
|
||||
iii. Voluntary License Schemes. The Licensor waives the right to
|
||||
collect royalties, whether individually or, in the event that the
|
||||
Licensor is a member of a collecting society that administers
|
||||
voluntary licensing schemes, via that society, from any exercise
|
||||
by You of the rights granted under this License.
|
||||
|
||||
The above rights may be exercised in all media and formats whether now
|
||||
known or hereafter devised. The above rights include the right to make
|
||||
such modifications as are technically necessary to exercise the rights in
|
||||
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||
granted by Licensor are hereby reserved.
|
||||
|
||||
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||
subject to and limited by the following restrictions:
|
||||
|
||||
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||
of this License. You must include a copy of, or the Uniform Resource
|
||||
Identifier (URI) for, this License with every copy of the Work You
|
||||
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||
on the Work that restrict the terms of this License or the ability of
|
||||
the recipient of the Work to exercise the rights granted to that
|
||||
recipient under the terms of the License. You may not sublicense the
|
||||
Work. You must keep intact all notices that refer to this License and
|
||||
to the disclaimer of warranties with every copy of the Work You
|
||||
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||
Perform the Work, You may not impose any effective technological
|
||||
measures on the Work that restrict the ability of a recipient of the
|
||||
Work from You to exercise the rights granted to that recipient under
|
||||
the terms of the License. This Section 4(a) applies to the Work as
|
||||
incorporated in a Collection, but this does not require the Collection
|
||||
apart from the Work itself to be made subject to the terms of this
|
||||
License. If You create a Collection, upon notice from any Licensor You
|
||||
must, to the extent practicable, remove from the Collection any credit
|
||||
as required by Section 4(b), as requested. If You create an
|
||||
Adaptation, upon notice from any Licensor You must, to the extent
|
||||
practicable, remove from the Adaptation any credit as required by
|
||||
Section 4(b), as requested.
|
||||
b. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||
Collections, You must, unless a request has been made pursuant to
|
||||
Section 4(a), keep intact all copyright notices for the Work and
|
||||
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||
and/or if the Original Author and/or Licensor designate another party
|
||||
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||
terms of service or by other reasonable means, the name of such party
|
||||
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||
extent reasonably practicable, the URI, if any, that Licensor
|
||||
specifies to be associated with the Work, unless such URI does not
|
||||
refer to the copyright notice or licensing information for the Work;
|
||||
and (iv) , consistent with Section 3(b), in the case of an Adaptation,
|
||||
a credit identifying the use of the Work in the Adaptation (e.g.,
|
||||
"French translation of the Work by Original Author," or "Screenplay
|
||||
based on original Work by Original Author"). The credit required by
|
||||
this Section 4 (b) may be implemented in any reasonable manner;
|
||||
provided, however, that in the case of a Adaptation or Collection, at
|
||||
a minimum such credit will appear, if a credit for all contributing
|
||||
authors of the Adaptation or Collection appears, then as part of these
|
||||
credits and in a manner at least as prominent as the credits for the
|
||||
other contributing authors. For the avoidance of doubt, You may only
|
||||
use the credit required by this Section for the purpose of attribution
|
||||
in the manner set out above and, by exercising Your rights under this
|
||||
License, You may not implicitly or explicitly assert or imply any
|
||||
connection with, sponsorship or endorsement by the Original Author,
|
||||
Licensor and/or Attribution Parties, as appropriate, of You or Your
|
||||
use of the Work, without the separate, express prior written
|
||||
permission of the Original Author, Licensor and/or Attribution
|
||||
Parties.
|
||||
c. Except as otherwise agreed in writing by the Licensor or as may be
|
||||
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||
Publicly Perform the Work either by itself or as part of any
|
||||
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||
take other derogatory action in relation to the Work which would be
|
||||
prejudicial to the Original Author's honor or reputation. Licensor
|
||||
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||
of the right granted in Section 3(b) of this License (the right to
|
||||
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||
modification or other derogatory action prejudicial to the Original
|
||||
Author's honor and reputation, the Licensor will waive or not assert,
|
||||
as appropriate, this Section, to the fullest extent permitted by the
|
||||
applicable national law, to enable You to reasonably exercise Your
|
||||
right under Section 3(b) of this License (right to make Adaptations)
|
||||
but not otherwise.
|
||||
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
a. This License and the rights granted hereunder will terminate
|
||||
automatically upon any breach by You of the terms of this License.
|
||||
Individuals or entities who have received Adaptations or Collections
|
||||
from You under this License, however, will not have their licenses
|
||||
terminated provided such individuals or entities remain in full
|
||||
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||
survive any termination of this License.
|
||||
b. Subject to the above terms and conditions, the license granted here is
|
||||
perpetual (for the duration of the applicable copyright in the Work).
|
||||
Notwithstanding the above, Licensor reserves the right to release the
|
||||
Work under different license terms or to stop distributing the Work at
|
||||
any time; provided, however that any such election will not serve to
|
||||
withdraw this License (or any other license that has been, or is
|
||||
required to be, granted under the terms of this License), and this
|
||||
License will continue in full force and effect unless terminated as
|
||||
stated above.
|
||||
|
||||
8. Miscellaneous
|
||||
|
||||
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||
the Licensor offers to the recipient a license to the Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||
offers to the recipient a license to the original Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
c. If any provision of this License is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of
|
||||
the remainder of the terms of this License, and without further action
|
||||
by the parties to this agreement, such provision shall be reformed to
|
||||
the minimum extent necessary to make such provision valid and
|
||||
enforceable.
|
||||
d. No term or provision of this License shall be deemed waived and no
|
||||
breach consented to unless such waiver or consent shall be in writing
|
||||
and signed by the party to be charged with such waiver or consent.
|
||||
e. This License constitutes the entire agreement between the parties with
|
||||
respect to the Work licensed here. There are no understandings,
|
||||
agreements or representations with respect to the Work not specified
|
||||
here. Licensor shall not be bound by any additional provisions that
|
||||
may appear in any communication from You. This License may not be
|
||||
modified without the mutual written agreement of the Licensor and You.
|
||||
f. The rights granted under, and the subject matter referenced, in this
|
||||
License were drafted utilizing the terminology of the Berne Convention
|
||||
for the Protection of Literary and Artistic Works (as amended on
|
||||
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||
These rights and subject matter take effect in the relevant
|
||||
jurisdiction in which the License terms are sought to be enforced
|
||||
according to the corresponding provisions of the implementation of
|
||||
those treaty provisions in the applicable national law. If the
|
||||
standard suite of rights granted under applicable copyright law
|
||||
includes additional rights not granted under this License, such
|
||||
additional rights are deemed to be included in the License; this
|
||||
License is not intended to restrict the license of any rights under
|
||||
applicable law.
|
||||
|
||||
|
||||
Creative Commons Notice
|
||||
|
||||
Creative Commons is not a party to this License, and makes no warranty
|
||||
whatsoever in connection with the Work. Creative Commons will not be
|
||||
liable to You or any party on any legal theory for any damages
|
||||
whatsoever, including without limitation any general, special,
|
||||
incidental or consequential damages arising in connection to this
|
||||
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||
Commons has expressly identified itself as the Licensor hereunder, it
|
||||
shall have all rights and obligations of Licensor.
|
||||
|
||||
Except for the limited purpose of indicating to the public that the
|
||||
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||
the use by either party of the trademark "Creative Commons" or any
|
||||
related trademark or logo of Creative Commons without the prior
|
||||
written consent of Creative Commons. Any permitted use will be in
|
||||
compliance with Creative Commons' then-current trademark usage
|
||||
guidelines, as may be published on its website or otherwise made
|
||||
available upon request from time to time. For the avoidance of doubt,
|
||||
this trademark restriction does not form part of this License.
|
||||
|
||||
Creative Commons may be contacted at https://creativecommons.org/.
|
||||
|
||||
===============================================================================
|
||||
|
||||
The Go Programming Language
|
||||
https://golang.org/LICENSE
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
===============================================================================
|
||||
|
||||
The Rust Programming Language
|
||||
https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
===============================================================================
|
||||
|
||||
The Rust Programming Language
|
||||
https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================================
|
||||
crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23
|
||||
================================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
@@ -7716,6 +8314,30 @@ 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.
|
||||
|
||||
================================================================================
|
||||
keyboard-types 0.8.3
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2017 Pyfisch
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
keyring 3.6.3
|
||||
================================================================================
|
||||
@@ -9452,6 +10074,56 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
muda 0.21.2
|
||||
================================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
muda 0.21.2
|
||||
================================================================================
|
||||
|
||||
SPDXVersion: SPDX-2.1
|
||||
DataLicense: CC0-1.0
|
||||
PackageName: muda
|
||||
DataFormat: SPDXRef-1
|
||||
PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
|
||||
PackageHomePage: https://tauri.app
|
||||
PackageLicenseDeclared: Apache-2.0
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: 2020-2022, The Tauri Programme in the Commons Conservancy
|
||||
PackageSummary: <text>Menu Utilities for Desktop Applications.
|
||||
</text>
|
||||
PackageComment: <text>The package includes the following libraries; see
|
||||
Relationship information.
|
||||
</text>
|
||||
Created: 2022-12-05T09:00:00Z
|
||||
PackageDownloadLocation: git://github.com/tauri-apps/muda
|
||||
PackageDownloadLocation: git+https://github.com/tauri-apps/muda.git
|
||||
PackageDownloadLocation: git+ssh://github.com/tauri-apps/muda.git
|
||||
Creator: Person: Daniel Thompson-Yvetot
|
||||
|
||||
================================================================================
|
||||
multiversion_no_op 1.0.0
|
||||
================================================================================
|
||||
@@ -11781,6 +12453,32 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
The files under third-party/chromium are licensed as described in
|
||||
third-party/chromium/LICENSE.
|
||||
|
||||
================================================================================
|
||||
ruzstd 0.9.1
|
||||
================================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Moritz Borcherding
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
schannel 0.1.29
|
||||
================================================================================
|
||||
@@ -13042,6 +13740,32 @@ 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.
|
||||
|
||||
================================================================================
|
||||
twox-hash 2.1.5
|
||||
================================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Jake Goulding
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
type-map 0.5.1
|
||||
================================================================================
|
||||
@@ -15663,7 +16387,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
zip 8.6.0, zip 9.0.4
|
||||
zip 9.0.4
|
||||
================================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
@@ -15918,89 +16642,6 @@ Apache License
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================================
|
||||
zstd 0.13.3
|
||||
================================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2016 Alexandre Bury
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
zstd 0.14.2, zstd-safe 7.3.0, zstd-safe 8.1.0, zstd-sys 2.1.1+zstd.1.5.7
|
||||
================================================================================
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2026, Alexandre Bury
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
zstd-sys 2.1.1+zstd.1.5.7
|
||||
================================================================================
|
||||
|
||||
The auto-generated bindings are under the 3-clause BSD license:
|
||||
|
||||
BSD License
|
||||
|
||||
For Zstandard software
|
||||
|
||||
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook, nor Meta, nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
zune-core 0.5.3, zune-jpeg 0.5.15
|
||||
================================================================================
|
||||
@@ -16068,13 +16709,13 @@ accesskit_winit 0.32.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
array-macro 2.1.8: MIT OR Apache-2.0 (https://codeberg.org/xfix/array-macro)
|
||||
block2 0.5.1: MIT (https://github.com/madsmtm/objc2)
|
||||
block2 0.6.2: MIT (https://github.com/madsmtm/objc2)
|
||||
cadmpeg-codec-freecad 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-rhino 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-step 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-container 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-core 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-ir 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-registry 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-freecad 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-rhino 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-step 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-container 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-core 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-ir 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-registry 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
clipboard-win 5.4.1: BSL-1.0 (https://github.com/DoumanAsh/clipboard-win)
|
||||
convert_case 0.4.0: MIT (https://github.com/rutrum/convert-case)
|
||||
dispatch 0.2.0: MIT (http://github.com/SSheldon/rust-dispatch)
|
||||
|
||||
@@ -19,10 +19,16 @@ path = "src/main.rs"
|
||||
name = "solvecraft-cli"
|
||||
path = "src/alias.rs"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
|
||||
[dependencies]
|
||||
solidcraft-engine = { workspace = true }
|
||||
solidcraft-gpu = { workspace = true }
|
||||
solidcraft-mcp = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
//! Reproducible evidence through the production command/job interfaces.
|
||||
//! Usage: studio_evidence RECIPE.json RENDER-PARAMS.json OUTPUT.png
|
||||
//! Writes the editable design, command timings and actual adapter/job metadata beside PNG.
|
||||
//! Accepts runtime requests as well as inline or named saved render jobs.
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::{
|
||||
Session,
|
||||
doc::render_settings::{RenderJob, RenderSetup},
|
||||
render::job::{CpuHost, ErrorCode, JobId, Phase},
|
||||
};
|
||||
use std::{
|
||||
io::Read,
|
||||
path::Path,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn read_json(path: &str) -> Result<Value, String> {
|
||||
let mut bytes = Vec::new();
|
||||
std::fs::File::open(path).map_err(|e| format!("{path}: {e}"))?.take(16_777_217).read_to_end(&mut bytes).map_err(|e| e.to_string())?;
|
||||
if bytes.len() > 16_777_216 {
|
||||
return Err("evidence input exceeds 16 MiB".into());
|
||||
}
|
||||
serde_json::from_slice(&bytes).map_err(|e| format!("{path}: {e}"))
|
||||
}
|
||||
|
||||
// Only decide which optional host must be installed. The production command
|
||||
// still validates and resolves the actual request without a second wire format.
|
||||
fn needs_gpu(session: &Session, params: &Value) -> Result<bool, String> {
|
||||
let inline = params.get("settings").filter(|value| !value.is_null());
|
||||
let named = params.get("setup").filter(|value| !value.is_null());
|
||||
if inline.is_some() && named.is_some() {
|
||||
return Err("choose either setup or settings".into());
|
||||
}
|
||||
let inline_setup = inline
|
||||
.filter(|value| value.get("job").is_some_and(|job| !job.is_null()))
|
||||
.map(|value| serde_json::from_value::<RenderSetup>(value.clone()).map_err(|e| e.to_string()))
|
||||
.transpose()?;
|
||||
let setup = if let Some(name) = named.and_then(Value::as_str) {
|
||||
Some(session.doc.render.setups.get(name).ok_or("no saved render setup with that name")?)
|
||||
} else {
|
||||
inline_setup.as_ref()
|
||||
};
|
||||
if let Some(setup) = setup
|
||||
&& let Some(job) = &setup.job
|
||||
{
|
||||
if params.as_object().is_some_and(|object| object.keys().any(|key| !matches!(key.as_str(), "setup" | "settings" | "replace"))) {
|
||||
return Err("saved render jobs cannot be mixed with runtime controls".into());
|
||||
}
|
||||
setup.validate().map_err(|e| e.to_string())?;
|
||||
return Ok(matches!(job, RenderJob::GpuPath { .. }));
|
||||
}
|
||||
Ok(params.get("progressive").and_then(Value::as_bool) == Some(true))
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let [recipe_path, params_path, output_path] = args.as_slice() else {
|
||||
return Err("usage: studio_evidence RECIPE.json RENDER-PARAMS.json OUTPUT.png".into());
|
||||
};
|
||||
let output = Path::new(output_path);
|
||||
if !output.extension().is_some_and(|e| e.eq_ignore_ascii_case("png")) {
|
||||
return Err("output must have a .png extension".into());
|
||||
}
|
||||
let recipe = read_json(recipe_path)?;
|
||||
let params = read_json(params_path)?;
|
||||
let mut session = Session::default();
|
||||
eprintln!("Generating original CAD scene from {recipe_path}");
|
||||
let generation_start = Instant::now();
|
||||
let (commands, command_timings) = session.run_script_timed(&recipe, false).map_err(|e| e.to_string())?;
|
||||
let generation_seconds = generation_start.elapsed().as_secs_f64();
|
||||
let design_path = output.with_extension("solidcraft");
|
||||
session.execute("file.save_as", &json!({"path":design_path})).map_err(|e| e.to_string())?;
|
||||
let scene = solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?;
|
||||
let bodies: Vec<Value> = scene
|
||||
.meshes
|
||||
.iter()
|
||||
.map(|mesh| json!({"name":mesh.name,"vertices":mesh.mesh.positions.len(),"triangles":mesh.mesh.triangles.len()}))
|
||||
.collect();
|
||||
drop(scene);
|
||||
|
||||
let cpu_host_start = Instant::now();
|
||||
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
|
||||
let cpu_host_seconds = cpu_host_start.elapsed().as_secs_f64();
|
||||
// Match runtime and saved-job dispatch. CPU evidence must not require a
|
||||
// Vulkan device, parse GPU policy or initialize a GPU host.
|
||||
let (adapter_report, gpu_host_seconds) = if needs_gpu(&session, ¶ms)? {
|
||||
let gpu_host_start = Instant::now();
|
||||
let policy = solidcraft_gpu::select::Policy::from_env().map_err(|e| e.to_string())?;
|
||||
let (adapter, report) =
|
||||
solidcraft_gpu::select::pick_vulkan(policy, |_| true).ok_or("no Vulkan adapter satisfies the requested GPU policy")?;
|
||||
session.register_render_job_host(solidcraft_gpu::path::host_on_adapter(&adapter).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
|
||||
(Some(report), Some(gpu_host_start.elapsed().as_secs_f64()))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let preflight = session.execute("render.preflight", ¶ms).map_err(|e| e.to_string())?;
|
||||
let start = Instant::now();
|
||||
let started = session.execute("render.start", ¶ms).map_err(|e| e.to_string())?;
|
||||
let executor = started
|
||||
.get("resolved")
|
||||
.and_then(|resolved| resolved.get("executor"))
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("started job did not report the resolved image executor")?
|
||||
.to_owned();
|
||||
let id: JobId = serde_json::from_value(started.get("job").cloned().ok_or("missing job id")?).map_err(|e| e.to_string())?;
|
||||
let handle = session.render_job_handle(id).map_err(|e| e.to_string())?;
|
||||
eprintln!("Rendering through production {executor} executor");
|
||||
if let Some(adapter) = &adapter_report {
|
||||
eprintln!("GPU adapter: {adapter:?}");
|
||||
}
|
||||
let status = loop {
|
||||
match handle.wait_timeout(Duration::from_secs(10)) {
|
||||
Ok(status) => break status,
|
||||
Err(error) if error.code == ErrorCode::TimedOut && start.elapsed() < Duration::from_secs(3600) => {
|
||||
let status = handle.status().map_err(|e| e.to_string())?;
|
||||
eprintln!(
|
||||
"Render progress: {:?} {}/{} after {:.1}s",
|
||||
status.progress.phase,
|
||||
status.progress.done,
|
||||
status.progress.total,
|
||||
start.elapsed().as_secs_f64()
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
let _ = handle.cancel();
|
||||
return Err(error.to_string());
|
||||
}
|
||||
}
|
||||
};
|
||||
let render_and_encode_seconds = start.elapsed().as_secs_f64();
|
||||
if status.progress.phase != Phase::Completed {
|
||||
return Err(status.error.map_or_else(|| "render did not complete".into(), |e| e.to_string()));
|
||||
}
|
||||
let exported = session.execute("render.export", &json!({"job":id,"path":output_path,"overwrite":true})).map_err(|e| e.to_string())?;
|
||||
let report = json!({
|
||||
"recipe":recipe_path,"render_params":params,"executor":executor,"adapter":adapter_report,
|
||||
"environment":{"SOLIDCRAFT_GPU":std::env::var("SOLIDCRAFT_GPU").ok(),"WGPU_BACKEND":std::env::var("WGPU_BACKEND").ok()},
|
||||
"cad_generation_seconds":generation_seconds,"cpu_host_setup_seconds":cpu_host_seconds,"gpu_host_setup_seconds":gpu_host_seconds,
|
||||
"render_and_encode_seconds":render_and_encode_seconds,
|
||||
"timing_scope":"wall time from render.start through completed PNG encoding; includes job preparation and readback, excludes CAD generation, preflight and initial CPU/GPU host setup; GPU setup timing includes adapter selection and pipelines when requested",
|
||||
"bodies":bodies,"commands":commands,"command_timings":command_timings,
|
||||
"preflight":preflight,"export":exported,"editable_design":design_path
|
||||
});
|
||||
std::fs::write(output.with_extension("json"), serde_json::to_vec_pretty(&report).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
|
||||
println!("{}", serde_json::to_string_pretty(&report).map_err(|e| e.to_string())?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
run()
|
||||
}
|
||||
@@ -36,8 +36,9 @@ impl Script {
|
||||
);
|
||||
}
|
||||
|
||||
/// A part placed after it is built: free, even as the design's first part (#2804).
|
||||
fn component(&mut self, name: &str) {
|
||||
self.c("component.create", json!({"name": name}));
|
||||
self.c("component.create", json!({"name": name, "grounded": false}));
|
||||
}
|
||||
|
||||
fn root(&mut self) {
|
||||
|
||||
@@ -0,0 +1,701 @@
|
||||
//! `bench-assistant`: the standard Assistant model test (v1, #4851), run against a desktop app.
|
||||
//!
|
||||
//! The app is already running with `--control PORT`; everything goes through its control channel
|
||||
//! (`docs/control-protocol.md`), so the command needs no window code of its own. For each task it
|
||||
//! starts a new design, runs the task's prescribed setup (a box, for some), adds the endpoint as an
|
||||
//! API agent with All permissions, starts a new chat, sends the prompt once and waits for the turn
|
||||
//! to end. The result is checked on the design (`document.inspect` with measurements), never on
|
||||
//! what the model says, except for the one question whose answer is the reply. A failed task is
|
||||
//! put in one of the issue's failure categories from the conversation's items.
|
||||
//!
|
||||
//! ```text
|
||||
//! solidcraft-cli bench-assistant --connect 127.0.0.1:PORT --base-url URL --model NAME
|
||||
//! [--token T] [--tasks 1,2,5] [--repeat N] [--timeout-s 300]
|
||||
//! [--json out.json] [--markdown out.md]
|
||||
//! ```
|
||||
//! prints a table of the results; `--markdown` writes the issue's comment template filled in,
|
||||
//! `--json` every task's rules, conversation and design before and after.
|
||||
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// Standard test v1, as #4851 states it. Changing a prompt or a rule makes it another test.
|
||||
const TASKS: &str = r#"[
|
||||
{"id": "01-cube", "prompt": "Make a 20 mm cube.", "check": {"bodies": 1, "volume": 8000, "bbox_size": [20, 20, 20]}},
|
||||
{"id": "02-cylinder", "prompt": "Make a cylinder 30 mm in diameter and 50 mm tall.", "check": {"bodies": 1, "volume": 35342.92, "bbox_size": [30, 30, 50]}},
|
||||
{"id": "03-block", "prompt": "Create a block 60 mm long, 40 mm wide and 10 mm thick.", "check": {"bodies": 1, "volume": 24000, "bbox_size": [10, 40, 60]}},
|
||||
{"id": "04-sketch-extrude", "prompt": "Sketch a 50 by 30 mm rectangle on the XY plane and extrude it 15 mm.", "check": {"bodies": 1, "volume": 22500, "bbox_size": [15, 30, 50], "feature_kinds": ["Extrude"]}},
|
||||
{"id": "05-plate-hole", "prompt": "Make a plate 80 x 60 x 10 mm with a 20 mm diameter hole straight through the middle.", "check": {"bodies": 1, "volume": 44858.41, "bbox_size": [10, 60, 80], "note": "80*60*10 - pi*10^2*10"}},
|
||||
{"id": "06-fillet-box", "prompt": "Round all the edges of the box with a 3 mm radius.", "setup": [{"command": "solid.box", "params": {"length": 40, "width": 40, "height": 40}}], "check": {"bodies": 1, "volume": 63108.53, "tol": 0.003, "feature_kinds": ["Fillet"], "note": "all 12 edges of a 40 mm cube rounded r=3"}},
|
||||
{"id": "07-move-body", "prompt": "Move the box 50 mm along the X axis.", "setup": [{"command": "solid.box", "params": {"length": 20, "width": 20, "height": 20}}], "check": {"bodies": 1, "volume": 8000, "bbox_min": [50, 0, 0]}},
|
||||
{"id": "08-rename-body", "prompt": "Rename the body to Bracket.", "setup": [{"command": "solid.box", "params": {"length": 30, "width": 20, "height": 10}}], "check": {"bodies": 1, "volume": 6000, "body_name": "Bracket"}},
|
||||
{"id": "09-volume-question", "prompt": "What is the volume of the part?", "setup": [{"command": "solid.box", "params": {"length": 30, "width": 20, "height": 10}}], "check": {"bodies": 1, "volume": 6000, "unchanged": true, "reply_number": "6[ ,. ]?000(?![0-9])|6(\\.0+)? ?cm"}},
|
||||
{"id": "10-two-holes", "prompt": "Make a 100 x 50 x 5 mm plate and drill two 10 mm holes through it, each 15 mm in from the short ends and centred across the width.", "check": {"bodies": 1, "volume": 24214.6, "bbox_size": [5, 50, 100], "note": "100*50*5 - 2*pi*5^2*5"}},
|
||||
{"id": "11-any-change", "prompt": "can you use the software to make a change to the current shape, any change at all", "setup": [{"command": "solid.box", "params": {"length": 30, "width": 20, "height": 10}}], "check": {"changed": true, "note": "verbatim user prompt from #4846; pass = the design changed"}}
|
||||
]"#;
|
||||
|
||||
/// The agent the test adds. A fixed id replaces it on every run instead of adding another.
|
||||
const AGENT_ID: &str = "api-bench-assistant";
|
||||
const AGENT_NAME: &str = "bench";
|
||||
/// Longest control reply read (the channel's own request limit).
|
||||
const MAX_REPLY: usize = 16 << 20;
|
||||
/// Most result data kept for `--json`.
|
||||
const MAX_RESULTS: usize = 64 << 20;
|
||||
/// Deadline of the requests that only set up or read (each is answered at once by the app).
|
||||
const REQUEST_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
/// How often a running turn is polled.
|
||||
const POLL: Duration = Duration::from_millis(250);
|
||||
/// After this long, an idle chat with no reply yet counts as a turn that ended without one.
|
||||
const START_GRACE: Duration = Duration::from_secs(30);
|
||||
/// Absolute tolerance of the lowest corner, in millimetres.
|
||||
const MIN_CORNER_TOL: f64 = 0.05;
|
||||
|
||||
/// A JSON-lines connection to the app. Requests are sent once; a lost reply is never retried,
|
||||
/// so nothing runs twice.
|
||||
struct Control {
|
||||
addr: SocketAddr,
|
||||
token: String,
|
||||
reader: BufReader<TcpStream>,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
impl Control {
|
||||
fn connect(addr: &str, token: Option<&str>) -> Result<Self, String> {
|
||||
let addr: SocketAddr = addr.parse().map_err(|_| format!("--connect: `{addr}` is not an IP address and port"))?;
|
||||
if !addr.ip().is_loopback() || addr.port() == 0 {
|
||||
return Err("--connect requires a loopback address and a nonzero port (the app listens on 127.0.0.1)".into());
|
||||
}
|
||||
let token = solidcraft_engine::control_auth::client_token(addr.port(), token)
|
||||
.map(|t| t.token)
|
||||
.ok_or("no control token: pass --token, set SOLIDCRAFT_CONTROL_TOKEN, or run on the app's computer (its token file)")?;
|
||||
if token.len() > 4096 {
|
||||
return Err("the control token is longer than 4096 bytes".into());
|
||||
}
|
||||
Self::open(addr, token)
|
||||
}
|
||||
|
||||
fn open(addr: SocketAddr, token: String) -> Result<Self, String> {
|
||||
let stream = TcpStream::connect_timeout(&addr, Duration::from_secs(5)).map_err(|e| format!("connecting to {addr}: {e}"))?;
|
||||
Ok(Self { addr, token, reader: BufReader::new(stream), id: 0 })
|
||||
}
|
||||
|
||||
/// A new connection to the same app: a request that outlived its deadline keeps the old one
|
||||
/// busy until it is answered.
|
||||
fn reopen(&self) -> Result<Self, String> {
|
||||
Self::open(self.addr, self.token.clone())
|
||||
}
|
||||
|
||||
fn call(&mut self, method: &str, params: Value, deadline: Instant) -> Result<Value, String> {
|
||||
let left = || deadline.checked_duration_since(Instant::now()).filter(|d| !d.is_zero()).ok_or_else(|| "timeout".to_string());
|
||||
let io = |e: std::io::Error| match e.kind() {
|
||||
std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock => "timeout".to_string(),
|
||||
_ => format!("control channel: {e}"),
|
||||
};
|
||||
self.reader.get_ref().set_write_timeout(Some(left()?)).map_err(io)?;
|
||||
self.id += 1;
|
||||
// The first request on a connection carries the token; sending it on every one is allowed.
|
||||
let request = json!({"id": self.id, "method": method, "params": params, "token": self.token});
|
||||
writeln!(self.reader.get_mut(), "{request}").map_err(io)?;
|
||||
let mut line = Vec::new();
|
||||
loop {
|
||||
self.reader.get_ref().set_read_timeout(Some(left()?)).map_err(io)?;
|
||||
let bytes = self.reader.fill_buf().map_err(io)?;
|
||||
if bytes.is_empty() {
|
||||
return Err("the app closed the control connection".into());
|
||||
}
|
||||
let newline = bytes.iter().position(|b| *b == b'\n');
|
||||
let used = newline.map_or(bytes.len(), |n| n + 1);
|
||||
if line.len() + used > MAX_REPLY {
|
||||
return Err("a control reply is larger than 16 MiB".into());
|
||||
}
|
||||
line.extend_from_slice(bytes.get(..used).unwrap_or_default());
|
||||
self.reader.consume(used);
|
||||
if newline.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let reply: Value = serde_json::from_slice(&line).map_err(|e| format!("control reply: {e}"))?;
|
||||
if reply["id"].as_u64() != Some(self.id) {
|
||||
return Err("control reply for another request".into());
|
||||
}
|
||||
if reply["ok"] != true {
|
||||
return Err(format!("{method}: {}", reply["error"].as_str().unwrap_or("failed")));
|
||||
}
|
||||
reply.get("result").cloned().ok_or_else(|| format!("{method}: reply without a result"))
|
||||
}
|
||||
|
||||
fn execute(&mut self, command: &str, params: Value) -> Result<Value, String> {
|
||||
self.call("engine.execute", json!({"command": command, "params": params, "animate": false}), Instant::now() + REQUEST_TIMEOUT)
|
||||
}
|
||||
|
||||
fn chat(&mut self, params: Value) -> Result<Value, String> {
|
||||
self.call("ui.agent_chat", params, Instant::now() + REQUEST_TIMEOUT)
|
||||
}
|
||||
|
||||
fn inspect(&mut self) -> Result<Value, String> {
|
||||
let design = self.call("document.inspect", json!({"measure": true}), Instant::now() + REQUEST_TIMEOUT)?;
|
||||
if design.is_object() { Ok(design) } else { Err("document.inspect returned no design".into()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn tasks() -> Result<Vec<Value>, String> {
|
||||
serde_json::from_str(TASKS).map_err(|e| format!("embedded tasks: {e}"))
|
||||
}
|
||||
|
||||
fn triple(value: &Value) -> Option<[f64; 3]> {
|
||||
match value.as_array()?.as_slice() {
|
||||
[x, y, z] => {
|
||||
let n = |v: &Value| v.as_f64().filter(|n| n.is_finite());
|
||||
Some([n(x)?, n(y)?, n(z)?])
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// What the model said (thoughts left out).
|
||||
fn reply(items: &[Value]) -> String {
|
||||
items.iter().filter(|i| i["thought"] != true).filter_map(|i| i["agent"].as_str()).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
/// v1's reply pattern `6[ ,. ]?000(?![0-9])|6(\.0+)? ?cm`: "6000", "6 000", "6,000", "6.000" not
|
||||
/// followed by a digit, or "6 cm", "6.0cm", … (a narrow no-break space also separates thousands).
|
||||
fn volume_reply(text: &str) -> bool {
|
||||
text.match_indices('6').any(|(at, _)| {
|
||||
let tail = text.get(at + 1..).unwrap_or_default();
|
||||
let thousands = |t: &str| t.strip_prefix("000").is_some_and(|rest| !rest.starts_with(|c: char| c.is_ascii_digit()));
|
||||
let separated = tail.strip_prefix([' ', ',', '.', '\u{202f}']).is_some_and(thousands);
|
||||
let cm = {
|
||||
let t = tail.strip_prefix(".0").map_or(tail, |t| t.trim_start_matches('0'));
|
||||
t.strip_prefix(' ').unwrap_or(t).starts_with("cm")
|
||||
};
|
||||
thousands(tail) || separated || cm
|
||||
})
|
||||
}
|
||||
|
||||
/// The design as the user sees it, without what reading or saving changes (path, saved state,
|
||||
/// revision, selection, undo depth, evaluation timings).
|
||||
fn content(design: &Value) -> Value {
|
||||
let mut v = design.clone();
|
||||
if let Some(o) = v.as_object_mut() {
|
||||
for key in ["path", "dirty", "revision", "selection", "undo", "redo"] {
|
||||
o.remove(key);
|
||||
}
|
||||
}
|
||||
for feature in v.get_mut("timeline").and_then(Value::as_array_mut).into_iter().flatten() {
|
||||
if let Some(o) = feature.as_object_mut() {
|
||||
o.remove("ms");
|
||||
o.remove("boolean_diagnostics");
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// Whether the design changed: its content, or a step on the undo stack (a colour, say, that the
|
||||
/// inspection does not list).
|
||||
fn changed(before: &Value, after: &Value) -> bool {
|
||||
content(before) != content(after) || after["undo"].as_u64() > before["undo"].as_u64()
|
||||
}
|
||||
|
||||
/// The rules a task's result breaks (none: it passed). `before` and `after` are
|
||||
/// `document.inspect {measure: true}` before the prompt and after the turn.
|
||||
pub(super) fn check(rule: &Value, before: &Value, after: &Value, items: &[Value]) -> Vec<String> {
|
||||
let Some(all) = after["bodies"].as_array() else { return vec!["the design could not be read".into()] };
|
||||
// Bodies the user sees; a hidden one (a tool body, a hidden original) is not the result.
|
||||
let bodies: Vec<&Value> = all.iter().filter(|b| b["visible"] != false).collect();
|
||||
let mut errors = Vec::new();
|
||||
if let Some(n) = rule["bodies"].as_u64()
|
||||
&& bodies.len() as u64 != n
|
||||
{
|
||||
errors.push(format!("{} bodies, expected {n}", bodies.len()));
|
||||
}
|
||||
if let Some(expected) = rule["volume"].as_f64() {
|
||||
let tol = rule["tol"].as_f64().unwrap_or(0.01);
|
||||
let volumes: Vec<f64> = bodies.iter().filter_map(|b| b["volume_mm3"].as_f64()).filter(|v| v.is_finite()).collect();
|
||||
let total: f64 = volumes.iter().sum();
|
||||
if volumes.len() != bodies.len() || (total - expected).abs() > expected * tol {
|
||||
errors.push(format!("volume {total:.1} mm³, expected {expected} mm³ ±{}%", tol * 100.0));
|
||||
}
|
||||
}
|
||||
let first = bodies.first().copied().unwrap_or(&Value::Null);
|
||||
let min = triple(&first["bbox"]["min"]);
|
||||
if let Some(mut expected) = triple(&rule["bbox_size"]) {
|
||||
expected.sort_by(f64::total_cmp);
|
||||
let size = min.zip(triple(&first["bbox"]["max"])).map(|(lo, hi)| {
|
||||
let mut size = [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]];
|
||||
size.sort_by(f64::total_cmp);
|
||||
size
|
||||
});
|
||||
if !size.is_some_and(|s| s.iter().zip(expected).all(|(a, e)| (a - e).abs() <= 0.01 * e.max(1.0))) {
|
||||
errors.push(format!("size {size:?}, expected {expected:?} in any orientation"));
|
||||
}
|
||||
}
|
||||
if let Some(expected) = triple(&rule["bbox_min"])
|
||||
&& !min.is_some_and(|m| m.iter().zip(expected).all(|(a, e)| (a - e).abs() <= MIN_CORNER_TOL))
|
||||
{
|
||||
errors.push(format!("lowest corner {min:?}, expected {expected:?}"));
|
||||
}
|
||||
if let Some(name) = rule["body_name"].as_str()
|
||||
&& !bodies.iter().any(|b| b["name"] == name)
|
||||
{
|
||||
errors.push(format!("no body named {name}"));
|
||||
}
|
||||
for kind in rule["feature_kinds"].as_array().into_iter().flatten().filter_map(Value::as_str) {
|
||||
let live = |f: &&Value| f["suppressed"] != true && f["rolled_back"] != true && f["error"].is_null();
|
||||
if !after["timeline"].as_array().into_iter().flatten().filter(live).any(|f| f["type"].as_str().is_some_and(|t| t.contains(kind))) {
|
||||
errors.push(format!("no {kind} in the timeline"));
|
||||
}
|
||||
}
|
||||
if rule["changed"] == true || rule["unchanged"] == true {
|
||||
let changed = before.is_object() && changed(before, after);
|
||||
if rule["changed"] == true && !changed {
|
||||
errors.push("the design did not change".into());
|
||||
}
|
||||
if rule["unchanged"] == true && (changed || !before.is_object()) {
|
||||
errors.push("the design changed".into());
|
||||
}
|
||||
}
|
||||
if rule["reply_number"].is_string() && !volume_reply(&reply(items)) {
|
||||
errors.push("the reply does not give the volume".into());
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
||||
/// A tool call written into the reply as JSON: `{"name": …`, `{"command": …`, `{"function": …`,
|
||||
/// `{"tool": …`.
|
||||
fn call_as_text(text: &str) -> bool {
|
||||
text.match_indices('{').any(|(at, _)| {
|
||||
let rest = text.get(at + 1..).unwrap_or_default().trim_start();
|
||||
["\"name\"", "\"command\"", "\"function\"", "\"tool\""]
|
||||
.iter()
|
||||
.any(|key| rest.strip_prefix(key).is_some_and(|r| r.trim_start().starts_with(':')))
|
||||
})
|
||||
}
|
||||
|
||||
/// How a failed task failed, in the issue's words, from the conversation's items.
|
||||
pub(super) fn classify(items: &[Value], timed_out: bool) -> &'static str {
|
||||
if timed_out {
|
||||
return "timeout";
|
||||
}
|
||||
let tools: Vec<&Value> = items.iter().filter(|i| i.get("tool").is_some()).collect();
|
||||
// A failed call, or a batch whose answer reports a failed step (`"ok": false`).
|
||||
let failed: Vec<String> = tools
|
||||
.iter()
|
||||
.map(|t| t["output"].as_str().unwrap_or_default())
|
||||
.zip(&tools)
|
||||
.filter(|(output, t)| t["status"] == "Failed" || serde_json::from_str::<Value>(output).is_ok_and(|v| v["ok"] == false))
|
||||
.map(|(output, _)| output.to_lowercase())
|
||||
.collect();
|
||||
let notices: Vec<String> = items.iter().filter_map(|i| i["notice"].as_str()).map(str::to_lowercase).collect();
|
||||
let answer = reply(items);
|
||||
if notices.iter().any(|n| n.contains("http 400")) {
|
||||
return "the endpoint refused tools (HTTP 400)";
|
||||
}
|
||||
if notices.iter().any(|n| n.contains("max_turn_requests")) || answer.contains("requests for one message") {
|
||||
return "hit the request limit";
|
||||
}
|
||||
if call_as_text(&answer) {
|
||||
return "wrote the call as text";
|
||||
}
|
||||
if failed.iter().any(|o| o.contains("unknown command") || o.starts_with("unknown tool")) {
|
||||
return "wrong or unknown command";
|
||||
}
|
||||
if failed.iter().any(|o| o.contains("invalid parameters") || o.starts_with("not run:") || o.contains("not valid json")) {
|
||||
return "wrong arguments";
|
||||
}
|
||||
if !failed.is_empty() || notices.iter().any(|n| n.starts_with("http ")) || items.iter().any(|i| i["error"] == true) {
|
||||
return "gave up after errors";
|
||||
}
|
||||
if tools.is_empty() {
|
||||
return if answer.trim().is_empty() { "an empty reply" } else { "only talked" };
|
||||
}
|
||||
"wrong result"
|
||||
}
|
||||
|
||||
/// One run's results in the issue's comment template (`run`: which repetition).
|
||||
pub(super) fn markdown(model: &str, version: &str, results: &[Value], run: u64) -> String {
|
||||
let clean = |s: &str| s.replace(['\n', '\r', '|'], " ");
|
||||
let rows: Vec<&Value> = results.iter().filter(|r| r["repeat"] == run).collect();
|
||||
let passed = rows.iter().filter(|r| r["passed"] == true).count();
|
||||
let mut out = format!(
|
||||
"### {} (<quant>, <download size>)\nRuntime: <Ollama/LM Studio/... version>, context <num_ctx if changed>\nSolidCraft: {}\nResult: {passed}/11\n| # | pass/fail | how it failed |\n|---|---|---|\n",
|
||||
clean(model),
|
||||
clean(version)
|
||||
);
|
||||
for n in 1..=11 {
|
||||
match rows.iter().find(|r| r["task"] == n) {
|
||||
Some(r) if r["passed"] == true => out.push_str(&format!("| {n} | pass | |\n")),
|
||||
Some(r) => out.push_str(&format!("| {n} | fail | {} |\n", clean(r["failure"].as_str().unwrap_or_default()))),
|
||||
None => out.push_str(&format!("| {n} | | not run |\n")),
|
||||
}
|
||||
}
|
||||
out.push_str(if rows.len() == 11 {
|
||||
"Notes: <anything odd, e.g. very slow, needed a retry>\n"
|
||||
} else {
|
||||
"Notes: partial run; the tasks marked \"not run\" were left out.\n"
|
||||
});
|
||||
out
|
||||
}
|
||||
|
||||
fn write(path: &str, text: &str) -> Result<(), String> {
|
||||
solidcraft_engine::io::vfs::write(path, text.as_bytes()).map_err(|e| format!("{path}: {e}"))
|
||||
}
|
||||
|
||||
struct Options<'a> {
|
||||
connect: &'a str,
|
||||
token: Option<&'a str>,
|
||||
base_url: &'a str,
|
||||
model: &'a str,
|
||||
tasks: Vec<usize>,
|
||||
repeat: u64,
|
||||
timeout: Duration,
|
||||
json: Option<&'a str>,
|
||||
markdown: Option<&'a str>,
|
||||
}
|
||||
|
||||
fn options(args: &[String]) -> Result<Options<'_>, String> {
|
||||
const FLAGS: [&str; 9] = ["--connect", "--token", "--base-url", "--model", "--tasks", "--repeat", "--timeout-s", "--json", "--markdown"];
|
||||
let mut given = std::collections::BTreeMap::new();
|
||||
let mut args = args.iter();
|
||||
while let Some(flag) = args.next() {
|
||||
let Some(flag) = FLAGS.iter().find(|f| **f == flag.as_str()) else { return Err(format!("unknown bench-assistant option: {flag}")) };
|
||||
let value = args
|
||||
.next()
|
||||
.filter(|v| !v.starts_with("--") && !v.is_empty() && v.len() <= 4096)
|
||||
.ok_or_else(|| format!("{flag} needs a value (at most 4096 bytes)"))?;
|
||||
if given.insert(*flag, value.as_str()).is_some() {
|
||||
return Err(format!("{flag} given twice"));
|
||||
}
|
||||
}
|
||||
let required = |key: &str| given.get(key).copied().ok_or_else(|| format!("bench-assistant needs {key}"));
|
||||
let number = |key: &str, default: u64, max: u64| -> Result<u64, String> {
|
||||
given
|
||||
.get(key)
|
||||
.map_or(Ok(default), |s| s.parse::<u64>().ok().filter(|n| (1..=max).contains(n)).ok_or_else(|| format!("{key} must be 1 to {max}")))
|
||||
};
|
||||
let tasks = match given.get("--tasks") {
|
||||
None => (1..=11).collect(),
|
||||
Some(list) => {
|
||||
let mut tasks = Vec::new();
|
||||
for n in list.split(',') {
|
||||
let n = n
|
||||
.trim()
|
||||
.parse::<usize>()
|
||||
.ok()
|
||||
.filter(|n| (1..=11).contains(n))
|
||||
.ok_or("--tasks takes task numbers 1 to 11, separated by commas")?;
|
||||
if tasks.contains(&n) {
|
||||
return Err(format!("--tasks lists task {n} twice"));
|
||||
}
|
||||
tasks.push(n);
|
||||
}
|
||||
tasks
|
||||
}
|
||||
};
|
||||
Ok(Options {
|
||||
connect: required("--connect")?,
|
||||
token: given.get("--token").copied(),
|
||||
base_url: required("--base-url")?,
|
||||
model: required("--model")?,
|
||||
tasks,
|
||||
repeat: number("--repeat", 1, 100)?,
|
||||
timeout: Duration::from_secs(number("--timeout-s", 300, 3600)?),
|
||||
json: given.get("--json").copied(),
|
||||
markdown: given.get("--markdown").copied(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Waits for the turn the prompt started to end. Returns the chat's last state and whether the
|
||||
/// deadline passed first.
|
||||
fn wait_for_turn(control: &mut Control, mut chat: Value, began: Instant, end: Instant) -> Result<(Value, bool), String> {
|
||||
let mut seen_busy = false;
|
||||
loop {
|
||||
let state = chat["state"].as_str().unwrap_or_default();
|
||||
let turn = !chat["turn"].is_null();
|
||||
let queued = chat["pending"].as_array().is_some_and(|p| !p.is_empty());
|
||||
seen_busy |= state == "busy" || turn;
|
||||
let idle = !matches!(state, "busy" | "starting") && !turn && !queued;
|
||||
// A turn can start and end between two polls: then the conversation has its answer.
|
||||
let answered = chat["items"].as_array().into_iter().flatten().any(|i| i.get("user").is_none());
|
||||
if idle && (seen_busy || answered || matches!(state, "exited" | "auth" | "off") || began.elapsed() >= START_GRACE) {
|
||||
return Ok((chat, false));
|
||||
}
|
||||
let Some(left) = end.checked_duration_since(Instant::now()).filter(|d| !d.is_zero()) else { return Ok((chat, true)) };
|
||||
std::thread::sleep(POLL.min(left));
|
||||
match control.call("ui.agent_chat", json!({}), end) {
|
||||
Ok(next) => chat = next,
|
||||
Err(e) if e == "timeout" => return Ok((chat, true)),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), String> {
|
||||
let o = options(args)?;
|
||||
let tasks = tasks()?;
|
||||
let mut control = Control::connect(o.connect, o.token)?;
|
||||
let help = control.call("ui.help", json!({}), Instant::now() + REQUEST_TIMEOUT)?;
|
||||
let version = help["version"].as_str().filter(|s| !s.is_empty() && s.len() <= 256).ok_or("the app reported no version")?.to_string();
|
||||
let config = control.chat(json!({
|
||||
"features": true,
|
||||
"add_agent": {"id": AGENT_ID, "name": AGENT_NAME, "kind": "api", "base_url": o.base_url, "model": o.model},
|
||||
}))?;
|
||||
if !config["agents"].as_array().into_iter().flatten().any(|a| a["id"] == AGENT_ID) {
|
||||
return Err("the app did not add the benchmark's agent".into());
|
||||
}
|
||||
// Put the user's permission setting back afterwards.
|
||||
let permissions = config["permissions"].as_str().map(str::to_string);
|
||||
let result = run_tasks(&mut control, &o, &tasks, &version);
|
||||
let _ = control.chat(json!({"stop": true}));
|
||||
if let Some(p) = permissions {
|
||||
let _ = control.chat(json!({"permissions": p}));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn run_tasks(control: &mut Control, o: &Options, tasks: &[Value], version: &str) -> Result<(), String> {
|
||||
let mut results: Vec<Value> = Vec::new();
|
||||
let mut kept = 0;
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
let mut say = |line: String| writeln!(stdout, "{line}").map_err(|e| format!("stdout: {e}"));
|
||||
say(format!("bench-assistant v1: {} on SolidCraft {version}", o.model))?;
|
||||
say("| run | # | task | pass/fail | how it failed | seconds |\n|---|---|---|---|---|---|".into())?;
|
||||
for run in 1..=o.repeat {
|
||||
for &n in &o.tasks {
|
||||
let task = tasks.get(n - 1).ok_or("missing embedded task")?;
|
||||
// A fresh design and a fresh chat, with nothing of the last task still running.
|
||||
control.chat(json!({"stop": true}))?;
|
||||
control.execute("file.new", json!({"name": "Benchmark"}))?;
|
||||
for step in task["setup"].as_array().into_iter().flatten() {
|
||||
control.execute(step["command"].as_str().ok_or("embedded setup without a command")?, step["params"].clone())?;
|
||||
}
|
||||
control.chat(json!({"agent": AGENT_ID, "permissions": "all", "new_chat": true, "attach_view": false}))?;
|
||||
let before = control.inspect()?;
|
||||
let began = Instant::now();
|
||||
let end = began + o.timeout;
|
||||
let (chat, timed_out) = match control.call("ui.agent_chat", json!({"send": task["prompt"]}), end) {
|
||||
Ok(chat) => wait_for_turn(control, chat, began, end)?,
|
||||
Err(e) if e == "timeout" => (json!({}), true),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
if timed_out {
|
||||
// The late reply would arrive on the old connection; stop the model on a new one
|
||||
// before reading the design.
|
||||
*control = control.reopen()?;
|
||||
}
|
||||
// Stop the agent before reading the result, so nothing changes the design meanwhile.
|
||||
let stopped = control.chat(json!({"stop": true}))?;
|
||||
let items = stopped["items"].as_array().or(chat["items"].as_array()).cloned().unwrap_or_default();
|
||||
let after = control.inspect()?;
|
||||
let failed_rules = check(&task["check"], &before, &after, &items);
|
||||
let passed = failed_rules.is_empty();
|
||||
let failure = if passed { "" } else { classify(&items, timed_out) };
|
||||
let seconds = began.elapsed().as_secs_f64();
|
||||
say(format!(
|
||||
"| {run} | {n} | {} | {} | {failure} | {seconds:.0} |",
|
||||
task["id"].as_str().unwrap_or_default(),
|
||||
if passed { "pass" } else { "fail" }
|
||||
))?;
|
||||
let result = json!({
|
||||
"task": n, "id": task["id"], "prompt": task["prompt"], "repeat": run, "passed": passed, "failure": failure,
|
||||
"failed_rules": failed_rules, "timed_out": timed_out, "seconds": seconds, "check": task["check"],
|
||||
"items": items, "before": before, "after": after,
|
||||
});
|
||||
kept += result.to_string().len();
|
||||
if kept > MAX_RESULTS {
|
||||
return Err("the results passed 64 MiB; run fewer tasks or repetitions".into());
|
||||
}
|
||||
results.push(result);
|
||||
// Written after every task, so an interrupted run keeps what it measured.
|
||||
if let Some(path) = o.json {
|
||||
let all = json!({"test": "v1", "model": o.model, "base_url": o.base_url, "solidcraft": version, "results": results});
|
||||
write(path, &serde_json::to_string_pretty(&all).map_err(|e| e.to_string())?)?;
|
||||
}
|
||||
if let Some(path) = o.markdown {
|
||||
write(path, &(1..=run).map(|r| markdown(o.model, version, &results, r)).collect::<Vec<_>>().join("\n"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
for run in 1..=o.repeat {
|
||||
let rows: Vec<&Value> = results.iter().filter(|r| r["repeat"] == run).collect();
|
||||
say(format!("run {run}: {}/{} passed", rows.iter().filter(|r| r["passed"] == true).count(), rows.len()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn design(bodies: Value, timeline: Value) -> Value {
|
||||
json!({"name": "Benchmark", "params": [], "timeline": timeline, "bodies": bodies, "sketches": [], "undo": 1, "revision": 3, "dirty": true})
|
||||
}
|
||||
|
||||
fn body(name: &str, volume: f64, min: [f64; 3], max: [f64; 3]) -> Value {
|
||||
json!({"name": name, "volume_mm3": volume, "bbox": {"min": min, "max": max}, "visible": true})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_embedded_tasks_are_v1() {
|
||||
let tasks = tasks().unwrap();
|
||||
assert_eq!(tasks.len(), 11);
|
||||
assert_eq!(tasks[0]["prompt"], "Make a 20 mm cube.");
|
||||
assert_eq!(tasks[10]["prompt"], "can you use the software to make a change to the current shape, any change at all");
|
||||
assert_eq!(tasks[5]["check"]["tol"], 0.003);
|
||||
assert_eq!(tasks[6]["check"]["bbox_min"], json!([50, 0, 0]));
|
||||
let boxes: Vec<_> = tasks.iter().filter(|t| t["setup"].is_array()).map(|t| t["id"].as_str().unwrap()).collect();
|
||||
assert_eq!(boxes, ["06-fillet-box", "07-move-body", "08-rename-body", "09-volume-question", "11-any-change"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_rules() {
|
||||
let tasks = tasks().unwrap();
|
||||
let rule = |n: usize| tasks[n - 1]["check"].clone();
|
||||
let empty = design(json!([]), json!([]));
|
||||
let pass = |n: usize, before: &Value, after: &Value, items: &[Value]| check(&rule(n), before, after, items);
|
||||
// 3: any orientation, within 1%.
|
||||
let block = design(json!([body("Body1", 24_100.0, [0.0; 3], [40.0, 10.0, 60.2])]), json!([{"type": "Box"}]));
|
||||
assert!(pass(3, &empty, &block, &[]).is_empty());
|
||||
let thick = design(json!([body("Body1", 24_000.0, [0.0; 3], [40.0, 12.0, 60.0])]), json!([]));
|
||||
assert_eq!(pass(3, &empty, &thick, &[]).len(), 1);
|
||||
let two = design(json!([body("A", 12_000.0, [0.0; 3], [60.0, 40.0, 5.0]), body("B", 12_000.0, [0.0; 3], [60.0, 40.0, 5.0])]), json!([]));
|
||||
assert!(!pass(3, &empty, &two, &[]).is_empty());
|
||||
// A hidden body is not part of the result.
|
||||
let mut hidden = two.clone();
|
||||
hidden["bodies"][1]["visible"] = json!(false);
|
||||
hidden["bodies"][0]["volume_mm3"] = json!(24_000.0);
|
||||
hidden["bodies"][0]["bbox"]["max"] = json!([60.0, 40.0, 10.0]);
|
||||
assert!(pass(3, &empty, &hidden, &[]).is_empty());
|
||||
// 4: the timeline needs a working Extrude.
|
||||
let plate = |kind: Value| design(json!([body("Body1", 22_500.0, [0.0; 3], [50.0, 30.0, 15.0])]), kind);
|
||||
assert!(pass(4, &empty, &plate(json!([{"type": "Sketch"}, {"type": "Extrude"}])), &[]).is_empty());
|
||||
assert!(!pass(4, &empty, &plate(json!([{"type": "Box"}])), &[]).is_empty());
|
||||
for flag in [json!({"suppressed": true}), json!({"rolled_back": true}), json!({"error": "failed"})] {
|
||||
let mut feature = json!({"type": "Extrude"});
|
||||
feature.as_object_mut().unwrap().extend(flag.as_object().unwrap().clone());
|
||||
assert!(!pass(4, &empty, &plate(json!([feature])), &[]).is_empty(), "{flag}");
|
||||
}
|
||||
// 6: the fillet's tolerance is 0.3%.
|
||||
let start = design(json!([body("Body1", 64_000.0, [0.0; 3], [40.0; 3])]), json!([{"type": "Box"}]));
|
||||
let filleted = |v: f64| design(json!([body("Body1", v, [0.0; 3], [40.0; 3])]), json!([{"type": "Box"}, {"type": "Fillet"}]));
|
||||
assert!(pass(6, &start, &filleted(63_108.53 * 1.002), &[]).is_empty());
|
||||
assert!(!pass(6, &start, &filleted(63_108.53 * 1.004), &[]).is_empty());
|
||||
// 7: the lowest corner.
|
||||
let moved = |x: f64| design(json!([body("Body1", 8000.0, [x, 0.0, 0.0], [x + 20.0, 20.0, 20.0])]), json!([]));
|
||||
assert!(pass(7, &empty, &moved(50.0), &[]).is_empty());
|
||||
assert!(!pass(7, &empty, &moved(0.0), &[]).is_empty());
|
||||
// 8: the name.
|
||||
let named = |name: &str| design(json!([body(name, 6000.0, [0.0; 3], [30.0, 20.0, 10.0])]), json!([]));
|
||||
assert!(pass(8, &empty, &named("Bracket"), &[]).is_empty());
|
||||
assert!(!pass(8, &empty, &named("Body1"), &[]).is_empty());
|
||||
// 9: unchanged, and the reply gives the volume.
|
||||
let start = named("Body1");
|
||||
let said = |text: &str| [json!({"user": "What is the volume of the part?"}), json!({"agent": text, "thought": false})];
|
||||
let mut read = start.clone();
|
||||
read["revision"] = json!(7);
|
||||
read["dirty"] = json!(false);
|
||||
read["selection"] = json!([{"body": "Body1"}]);
|
||||
assert!(pass(9, &start, &read, &said("The volume is 6000 mm³.")).is_empty());
|
||||
assert!(!pass(9, &start, &read, &said("About 60 cm³.")).is_empty());
|
||||
assert!(!pass(9, &start, &named("Bracket"), &said("6 000 mm³")).is_empty());
|
||||
let mut undone = start.clone();
|
||||
undone["undo"] = json!(2);
|
||||
assert!(!pass(9, &start, &undone, &said("6000 mm³")).is_empty());
|
||||
// 11: any change counts; reading does not.
|
||||
assert!(pass(11, &start, &named("Bracket"), &[]).is_empty());
|
||||
assert!(pass(11, &start, &undone, &[]).is_empty());
|
||||
assert!(!pass(11, &start, &read, &[]).is_empty());
|
||||
// An unreadable design passes nothing.
|
||||
assert!(!pass(11, &start, &json!({}), &[]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_volume_reply() {
|
||||
for yes in ["6000", "6 000 mm³", "6,000", "6.000", "6\u{202f}000", "6 cm³", "6.00cm3", "is 6000."] {
|
||||
assert!(volume_reply(yes), "{yes}");
|
||||
}
|
||||
for no in ["60000", "6,0001", "60 cm³", "600", "6.01 cm³", ""] {
|
||||
assert!(!volume_reply(no), "{no}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failure_categories() {
|
||||
for (items, expected) in [
|
||||
(
|
||||
json!([{"user": "Make a 20 mm cube."}, {"agent": "{\"name\": \"execute\", \"arguments\": {}}", "thought": false}]),
|
||||
"wrote the call as text",
|
||||
),
|
||||
(
|
||||
json!([{"notice": "HTTP 400: registry.ollama.ai/library/gemma3:4b does not support tools", "error": true}]),
|
||||
"the endpoint refused tools (HTTP 400)",
|
||||
),
|
||||
(json!([{"user": "Make a 20 mm cube."}, {"agent": "", "thought": false}]), "an empty reply"),
|
||||
(json!([{"agent": "Let me think", "thought": true}]), "an empty reply"),
|
||||
(json!([{"agent": "Sure, here is how to make a cube.", "thought": false}]), "only talked"),
|
||||
(
|
||||
json!([{"tool": "1", "status": "Failed", "output": "unknown command `solid.cube` (did you mean `solid.box`?)"}]),
|
||||
"wrong or unknown command",
|
||||
),
|
||||
(json!([{"tool": "1", "status": "Failed", "output": "unknown tool `make_cube`"}]), "wrong or unknown command"),
|
||||
(json!([{"tool": "1", "status": "Failed", "output": "invalid parameters for `solid.box`: `length` is required"}]), "wrong arguments"),
|
||||
(json!([{"tool": "1", "status": "Failed", "output": "Not run: the tool arguments were not valid JSON"}]), "wrong arguments"),
|
||||
(json!([{"tool": "1", "status": "Failed", "output": "no body `Box`"}]), "gave up after errors"),
|
||||
(
|
||||
json!([{"tool": "1", "status": "Completed", "output": "{\"ok\": false, \"failed\": {\"error\": \"unknown command `solid.delete`\"}}"}]),
|
||||
"wrong or unknown command",
|
||||
),
|
||||
(
|
||||
json!([{"tool": "1", "status": "Completed"}, {"agent": "\n(stopped after 24 requests for one message; send another message to go on)"}]),
|
||||
"hit the request limit",
|
||||
),
|
||||
(json!([{"tool": "1", "status": "Completed", "output": "{}"}]), "wrong result"),
|
||||
] {
|
||||
let items = items.as_array().unwrap();
|
||||
assert_eq!(classify(items, false), expected, "{items:?}");
|
||||
assert_eq!(classify(items, true), "timeout");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn markdown_is_the_comment_template() {
|
||||
let rows: Vec<Value> =
|
||||
(1..=11).map(|n| json!({"task": n, "repeat": 1, "passed": n != 2, "failure": if n == 2 { "an empty reply" } else { "" }})).collect();
|
||||
let report = markdown("qwen2.5:7b", "0.19.0", &rows, 1);
|
||||
assert!(report.starts_with(
|
||||
"### qwen2.5:7b (<quant>, <download size>)\nRuntime: <Ollama/LM Studio/... version>, context <num_ctx if changed>\nSolidCraft: 0.19.0\nResult: 10/11\n| # | pass/fail | how it failed |\n|---|---|---|\n| 1 | pass | |\n| 2 | fail | an empty reply |\n"
|
||||
));
|
||||
assert!(report.ends_with("| 11 | pass | |\nNotes: <anything odd, e.g. very slow, needed a retry>\n"));
|
||||
let partial = markdown("m|x", "v", &rows[..1], 1);
|
||||
assert!(partial.starts_with("### m x ("));
|
||||
assert!(partial.contains("Result: 1/11") && partial.contains("| 2 | | not run |") && partial.contains("partial run"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn options_are_checked_before_connecting() {
|
||||
for extra in [
|
||||
&["--repeat", "0"][..],
|
||||
&["--repeat", "101"],
|
||||
&["--timeout-s", "3601"],
|
||||
&["--tasks", "0"],
|
||||
&["--tasks", "12"],
|
||||
&["--tasks", "1,1"],
|
||||
&["--model", "again"],
|
||||
&["--unexpected", "1"],
|
||||
&["--token"],
|
||||
] {
|
||||
let mut args: Vec<String> =
|
||||
["--connect", "127.0.0.1:9", "--model", "m", "--base-url", "http://127.0.0.1:11434/v1"].iter().map(|s| s.to_string()).collect();
|
||||
args.extend(extra.iter().map(|s| s.to_string()));
|
||||
assert!(options(&args).is_err(), "{extra:?}");
|
||||
}
|
||||
let args: Vec<String> =
|
||||
["--connect", "127.0.0.1:9", "--model", "m", "--base-url", "u", "--tasks", "2, 5"].iter().map(|s| s.to_string()).collect();
|
||||
let o = options(&args).unwrap();
|
||||
assert_eq!((o.tasks, o.repeat, o.timeout), (vec![2, 5], 1, Duration::from_secs(300)));
|
||||
assert!(Control::connect("10.0.0.1:9271", Some("t")).is_err());
|
||||
assert!(Control::connect("localhost", Some("t")).is_err());
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,14 @@ use std::sync::Mutex;
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::crash;
|
||||
|
||||
const USAGE: &str = "usage: solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]";
|
||||
/// The command's usage, from the same lines the top-level usage lists.
|
||||
fn usage() -> String {
|
||||
crate::command_usage("crashes")
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), String> {
|
||||
let dir = crash::default_dir().ok_or("no crash folder on this system")?;
|
||||
let id = || args.get(1).map(String::as_str).ok_or_else(|| USAGE.to_string());
|
||||
let id = || args.get(1).map(String::as_str).ok_or_else(usage);
|
||||
match args.first().map(String::as_str) {
|
||||
None | Some("list") => {
|
||||
let reports = crash::list(&dir);
|
||||
@@ -43,7 +46,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
println!("deleted {} report(s)", crash::clear(&dir, args.get(1).map(String::as_str)));
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(USAGE.to_string()),
|
||||
_ => Err(usage()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,16 @@ fn flag<T: std::str::FromStr>(args: &[String], name: &str, default: T) -> T {
|
||||
args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).and_then(|v| v.parse().ok()).unwrap_or(default)
|
||||
}
|
||||
|
||||
/// The options `fuzz` takes, and the ones that stand alone.
|
||||
const OPTIONS: &[&str] = &["--from", "--count", "--jobs", "--timeout", "--steps", "--out", "--report"];
|
||||
const SWITCHES: &[&str] = &["--minimise"];
|
||||
|
||||
/// One seed: a design and a boolean case, as JSON on stdout (progress on stderr).
|
||||
pub fn one(args: &[String]) -> Result<(), String> {
|
||||
let seed: u64 = args.first().and_then(|s| s.parse().ok()).ok_or("fuzz-one <seed> [--steps N] [--minimise]")?;
|
||||
// The seed is the first argument; the rest are options, and an option `fuzz-one` doesn't
|
||||
// take is refused rather than ignored.
|
||||
crate::known_args("fuzz-one", &args[1..], &["--steps"], SWITCHES, false)?;
|
||||
let steps = flag(args, "--steps", 8usize);
|
||||
let minimise = args.iter().any(|a| a == "--minimise");
|
||||
// Panics caught inside the kernel: harmless natively, fatal in the web build.
|
||||
@@ -64,6 +71,9 @@ struct Job {
|
||||
/// Run seeds `--from`…`--from + --count` in `--jobs` processes, `--timeout` seconds each;
|
||||
/// write failing scripts to `--out` and a summary to `--report`.
|
||||
pub fn run(args: &[String]) -> Result<(), String> {
|
||||
// An option `fuzz` doesn't take is refused before a seed runs: it would otherwise be
|
||||
// ignored, and the run would go on for minutes with the wrong settings.
|
||||
crate::known_args("fuzz", args, OPTIONS, SWITCHES, false)?;
|
||||
let from = flag(args, "--from", 0u64);
|
||||
let count = flag(args, "--count", 100u64);
|
||||
let last = from.checked_add(count.saturating_sub(1)).ok_or("seed range exceeds u64::MAX: reduce --from or --count")?;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//! What the engine logs (`log::info!`, `log::debug!`: why a body has no display plan, why a save
|
||||
//! left its model cache out), written to stderr. Off unless asked for, so the CLI's output and
|
||||
//! the scripts reading it stay as they are: `--debug` shows SolidCraft's own debug messages (and
|
||||
//! other crates' warnings, whose debug output is large), `RUST_LOG=error|warn|info|debug|trace`
|
||||
//! sets one level for every crate.
|
||||
|
||||
use log::{LevelFilter, Log, Metadata, Record};
|
||||
|
||||
struct Stderr {
|
||||
/// The level for SolidCraft's crates.
|
||||
ours: LevelFilter,
|
||||
/// The level for every other crate.
|
||||
others: LevelFilter,
|
||||
}
|
||||
|
||||
impl Log for Stderr {
|
||||
fn enabled(&self, m: &Metadata) -> bool {
|
||||
m.level() <= if m.target().starts_with("solidcraft") { self.ours } else { self.others }
|
||||
}
|
||||
|
||||
fn log(&self, r: &Record) {
|
||||
if self.enabled(r.metadata()) {
|
||||
use std::io::Write as _;
|
||||
// A closed stderr is no reason to stop.
|
||||
let _ = writeln!(std::io::stderr().lock(), "[{} {}] {}", r.level().as_str().to_ascii_lowercase(), r.target(), r.args());
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
/// Removes `--debug` from `args`, wherever it is (every subcommand takes it), and installs the
|
||||
/// logger when it or `RUST_LOG` asks for one.
|
||||
pub fn init(args: &mut Vec<String>) {
|
||||
let before = args.len();
|
||||
args.retain(|a| a != "--debug");
|
||||
let debug = args.len() != before;
|
||||
let env = std::env::var("RUST_LOG").ok().and_then(|v| v.trim().parse::<LevelFilter>().ok());
|
||||
let (ours, others) = match (debug, env) {
|
||||
(false, Some(all)) => (all, all),
|
||||
(true, Some(all)) => (all.max(LevelFilter::Debug), all),
|
||||
(true, None) => (LevelFilter::Debug, LevelFilter::Warn),
|
||||
(false, None) => return,
|
||||
};
|
||||
// Installed once, for the life of the process.
|
||||
if log::set_logger(Box::leak(Box::new(Stderr { ours, others }))).is_ok() {
|
||||
log::set_max_level(ours.max(others));
|
||||
}
|
||||
}
|
||||
@@ -6,24 +6,32 @@
|
||||
//! 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
|
||||
//! solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N]
|
||||
//! [--timeout-s 300] [--json out.json] [--markdown out.md]
|
||||
//! the standard Assistant model test (#4851) on a running app
|
||||
//! solidcraft-cli snapshot <script|design> [--in design|part.step] --out shot.png [--width W] [--height H] [--view iso|front|top|…|NAME | --camera JSON] [--no-grid] [--no-axes]
|
||||
//! [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]
|
||||
//! solidcraft-cli exec <command> [json-params] run one command on an empty design
|
||||
//! solidcraft-cli commands the command registry as JSON
|
||||
//! solidcraft-cli recipe <recipe.json> translate an oracle recipe to a script
|
||||
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
|
||||
//! solidcraft-cli mcp [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
||||
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
||||
//! [--param-check off|warn|strict]
|
||||
//! MCP server on stdio (docs/mcp.md)
|
||||
//! solidcraft-cli mcp setup [--dry-run] [--client NAME] [--live] (alias: mcp install) add it to OpenCode, Claude Desktop, Claude Code, Cursor
|
||||
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
|
||||
//! solidcraft-cli icon --out F.png|F.ico [--size N] the app icon (packaging; drawn, not a file)
|
||||
//!
|
||||
//! --debug (with any command) SolidCraft's debug log on stderr (or RUST_LOG=LEVEL)
|
||||
//! ```
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod bench_agent;
|
||||
mod bench_assistant;
|
||||
mod crashes;
|
||||
mod fuzz;
|
||||
mod logger;
|
||||
mod oracle;
|
||||
mod preview;
|
||||
mod recipe;
|
||||
@@ -39,54 +47,98 @@ mod app_icon;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::view::Guides;
|
||||
|
||||
const USAGE: &str = "usage:
|
||||
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]
|
||||
solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...
|
||||
(a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)
|
||||
(--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;
|
||||
--profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)
|
||||
solidcraft-cli eval <script.json|design.solidcraft|part.step> [--json-out FILE] (alias: inspect; or --in FILE)
|
||||
(--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,
|
||||
as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)
|
||||
solidcraft-cli script <design.solidcraft|script.json> [--out script.json]
|
||||
(the design's history as a command script that eval and run replay)
|
||||
solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]
|
||||
(--ops: after opening, run each command of the script and undo it, timing the command, the
|
||||
evaluations it caused and the view it leaves: placing and meshing what changed)
|
||||
solidcraft-cli bench-agent [--scale F] [--save design.solidcraft] [--script-out script.json]
|
||||
solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]
|
||||
[--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]
|
||||
solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...
|
||||
(as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;
|
||||
--view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;
|
||||
--camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);
|
||||
--no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;
|
||||
--body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside
|
||||
it shows) and color #rrggbb, either or both, once per body;
|
||||
--tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body
|
||||
is visible inside the material it meets; a later --body overrides it;
|
||||
--section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world
|
||||
coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)
|
||||
solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]
|
||||
solidcraft-cli exec <command> [json-params]
|
||||
solidcraft-cli commands
|
||||
solidcraft-cli recipe <recipe.json>
|
||||
solidcraft-cli oracle <case-dir>... [--json]
|
||||
solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]
|
||||
solidcraft-cli mcp [--in design.solidcraft|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
||||
[--param-check off|warn|strict]
|
||||
solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]
|
||||
solidcraft-cli licences
|
||||
solidcraft-cli gpu [--policy auto|hardware|software|cpu]
|
||||
(the GPU adapters a headless render can open, best first, the one the policy picks and the tier it
|
||||
lands on: gpu, gpu-software, or the CPU rasterizer (auto with no adapter, or cpu); the policy defaults
|
||||
to SOLIDCRAFT_GPU, else auto; exits with an error when hardware or software finds no such adapter)
|
||||
solidcraft-cli icon --out icon.png [--size 256] | --out icon.ico
|
||||
";
|
||||
/// One command the CLI takes: the name to type, the usage it prints for `--help`,
|
||||
/// and whether the top-level usage lists it (an alias answers to another command's name).
|
||||
struct Command {
|
||||
name: &'static str,
|
||||
usage: &'static str,
|
||||
listed: bool,
|
||||
}
|
||||
|
||||
/// `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";
|
||||
/// `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.
|
||||
const USAGE_BENCH_OPEN: &str = " solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
|
||||
/// `bench-agent`'s lines in the usage.
|
||||
const USAGE_BENCH_AGENT: &str = " solidcraft-cli bench-agent [--scale F] [--save design.solidcraft] [--script-out script.json]\n";
|
||||
/// `bench-assistant`'s lines in the usage.
|
||||
const USAGE_BENCH_ASSISTANT: &str = " solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N] [--timeout-s 300] [--json out.json] [--markdown out.md]\n";
|
||||
/// `snapshot`'s lines in the usage.
|
||||
const USAGE_SNAPSHOT: &str = " solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
|
||||
/// `render`'s lines in the usage.
|
||||
const USAGE_RENDER: &str = " solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]\n [--progressive] [--samples N] [--max-depth N] [--seed N] [--exposure EV] [--lighting FILE.json] [--environment FILE.json] [--tone-map reinhard|aces_fitted]\n";
|
||||
/// `exec`'s lines in the usage.
|
||||
const USAGE_EXEC: &str = " solidcraft-cli exec <command> [json-params]\n";
|
||||
/// `commands`'s lines in the usage.
|
||||
const USAGE_COMMANDS: &str = " solidcraft-cli commands\n";
|
||||
/// `recipe`'s lines in the usage.
|
||||
const USAGE_RECIPE: &str = " solidcraft-cli recipe <recipe.json>\n";
|
||||
/// `oracle`'s lines in the usage.
|
||||
const USAGE_ORACLE: &str = " solidcraft-cli oracle <case-dir>... [--json]\n";
|
||||
/// `fuzz`'s lines in the usage.
|
||||
const USAGE_FUZZ: &str =
|
||||
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
|
||||
/// `mcp`'s lines in the usage.
|
||||
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.solidcraft|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu]\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
|
||||
/// `crashes`'s lines in the usage.
|
||||
const USAGE_CRASHES: &str = " solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
|
||||
/// `licences`'s lines in the usage.
|
||||
const USAGE_LICENCES: &str = " solidcraft-cli licences\n";
|
||||
/// `gpu`'s lines in the usage.
|
||||
const USAGE_GPU: &str = " solidcraft-cli gpu [--policy auto|hardware|software|cpu]\n (the GPU adapters a headless render can open, best first, the one the policy picks and the tier it\n lands on: gpu, gpu-software, or the CPU rasterizer (auto with no adapter, or cpu); the policy defaults\n to SOLIDCRAFT_GPU, else auto; exits with an error when hardware or software finds no such adapter)\n";
|
||||
/// `icon`'s lines in the usage.
|
||||
const USAGE_ICON: &str = " solidcraft-cli icon --out icon.png [--size 256] | --out icon.ico\n";
|
||||
/// `fuzz-one`'s lines in the usage: a seed and the flags of one fuzz run.
|
||||
const USAGE_FUZZ_ONE: &str = " solidcraft-cli fuzz-one <seed> [--steps N] [--minimise]\n";
|
||||
/// `bench-edit`'s lines in the usage: the parameter a script changes.
|
||||
const USAGE_BENCH_EDIT: &str = " solidcraft-cli bench-edit <script.json|recipe.json> <param> <expr>\n";
|
||||
/// `save-stress`'s lines in the usage: the file saved over and over.
|
||||
const USAGE_SAVE_STRESS: &str = " solidcraft-cli save-stress <path> <rounds>\n";
|
||||
/// The flags every command takes, at the end of the top-level usage.
|
||||
const USAGE_DEBUG: &str = " --debug, with any command: what SolidCraft logs (why a body has no display plan, why a save left its\n model cache out), on stderr; RUST_LOG=error|warn|info|debug|trace sets one level for every crate\n";
|
||||
|
||||
/// The commands the CLI takes, in the order the top-level usage lists them.
|
||||
const COMMANDS: &[Command] = &[
|
||||
Command { name: "run", usage: USAGE_RUN, listed: true },
|
||||
Command { name: "eval", usage: USAGE_EVAL, listed: true },
|
||||
Command { name: "inspect", usage: USAGE_EVAL, listed: false },
|
||||
Command { name: "script", usage: USAGE_SCRIPT, listed: true },
|
||||
Command { name: "bench-open", usage: USAGE_BENCH_OPEN, listed: true },
|
||||
Command { name: "bench-agent", usage: USAGE_BENCH_AGENT, listed: true },
|
||||
Command { name: "bench-assistant", usage: USAGE_BENCH_ASSISTANT, listed: true },
|
||||
Command { name: "snapshot", usage: USAGE_SNAPSHOT, listed: true },
|
||||
Command { name: "render", usage: USAGE_RENDER, listed: true },
|
||||
Command { name: "exec", usage: USAGE_EXEC, listed: true },
|
||||
Command { name: "commands", usage: USAGE_COMMANDS, listed: true },
|
||||
Command { name: "recipe", usage: USAGE_RECIPE, listed: true },
|
||||
Command { name: "oracle", usage: USAGE_ORACLE, listed: true },
|
||||
Command { name: "fuzz", usage: USAGE_FUZZ, listed: true },
|
||||
Command { name: "fuzz-one", usage: USAGE_FUZZ_ONE, listed: false },
|
||||
Command { name: "mcp", usage: USAGE_MCP, listed: true },
|
||||
Command { name: "crashes", usage: USAGE_CRASHES, listed: true },
|
||||
Command { name: "licences", usage: USAGE_LICENCES, listed: true },
|
||||
Command { name: "licenses", usage: USAGE_LICENCES, listed: false },
|
||||
Command { name: "gpu", usage: USAGE_GPU, listed: true },
|
||||
Command { name: "icon", usage: USAGE_ICON, listed: true },
|
||||
Command { name: "bench-edit", usage: USAGE_BENCH_EDIT, listed: false },
|
||||
Command { name: "save-stress", usage: USAGE_SAVE_STRESS, listed: false },
|
||||
];
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let mut args: Vec<String> = std::env::args().skip(1).collect();
|
||||
logger::init(&mut args);
|
||||
let r = match args.first().map(String::as_str) {
|
||||
// Asked-for help is not an error: the command's usage on stdout, exit 0. It is read
|
||||
// before the command runs, so `--help` never starts a run (a fuzz run, a server).
|
||||
Some(cmd) if wants_help(&args[1..]) && is_command(cmd) => {
|
||||
use std::io::Write as _;
|
||||
let _ = std::io::stdout().lock().write_all(command_usage(cmd).as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
Some("run") => cmd_run(&args[1..]),
|
||||
Some("eval" | "inspect") => cmd_eval(&args[1..]),
|
||||
Some("script") => cmd_script(&args[1..]),
|
||||
@@ -107,7 +159,7 @@ fn main() -> ExitCode {
|
||||
}
|
||||
})
|
||||
}
|
||||
Some("recipe") => args.get(1).ok_or_else(|| USAGE.to_string()).and_then(|p| {
|
||||
Some("recipe") => args.get(1).ok_or_else(usage).and_then(|p| {
|
||||
let v = read_json(p)?;
|
||||
recipe::to_script(&v).map(|s| println!("{}", pretty(&s)))
|
||||
}),
|
||||
@@ -130,11 +182,19 @@ fn main() -> ExitCode {
|
||||
Some("bench-open") => cmd_bench_open(&args[1..]),
|
||||
// A ~1,300-body design built command by command, as an agent builds one over MCP.
|
||||
Some("bench-agent") => bench_agent::run(&args[1..]),
|
||||
// The standard Assistant model test (#4851), run against a desktop app over its control channel.
|
||||
Some("bench-assistant") => bench_assistant::run(&args[1..]),
|
||||
Some("--version" | "-V") => {
|
||||
println!("solidcraft-cli {}", env!("CARGO_PKG_VERSION"));
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(USAGE.to_string()),
|
||||
// Asked-for help is not an error: usage on stdout, exit 0.
|
||||
Some("--help" | "-h" | "help") => {
|
||||
use std::io::Write as _;
|
||||
let _ = std::io::stdout().lock().write_all(usage().as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(usage()),
|
||||
};
|
||||
match r {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
@@ -149,6 +209,35 @@ fn pretty(v: &Value) -> String {
|
||||
serde_json::to_string_pretty(v).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The whole usage: every command's lines under one `usage:` line. The lines come from the
|
||||
/// same place a command's own `--help` prints, so the two cannot drift apart.
|
||||
fn usage() -> String {
|
||||
let mut s = String::from("usage:\n");
|
||||
for c in COMMANDS.iter().filter(|c| c.listed) {
|
||||
s.push_str(c.usage);
|
||||
}
|
||||
s.push_str(USAGE_DEBUG);
|
||||
s
|
||||
}
|
||||
|
||||
/// A command's own usage: its lines under a `usage:` line.
|
||||
fn command_usage(cmd: &str) -> String {
|
||||
match COMMANDS.iter().find(|c| c.name == cmd) {
|
||||
Some(c) => format!("usage:\n{}", c.usage),
|
||||
None => usage(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the name is a command the CLI takes.
|
||||
fn is_command(cmd: &str) -> bool {
|
||||
COMMANDS.iter().any(|c| c.name == cmd)
|
||||
}
|
||||
|
||||
/// Whether the arguments ask for help.
|
||||
fn wants_help(args: &[String]) -> bool {
|
||||
args.iter().any(|a| a == "--help" || a == "-h")
|
||||
}
|
||||
|
||||
pub fn read_json(path: &str) -> Result<Value, String> {
|
||||
// Through the engine's file access, which refuses anything that is not a regular file and
|
||||
// reads no more than the cap: a character device or a FIFO would otherwise never end.
|
||||
@@ -254,7 +343,7 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
let base = flag(args, "--in");
|
||||
let path = positional(args);
|
||||
if path.is_none() && base.is_none() {
|
||||
return Err(USAGE.to_string());
|
||||
return Err(usage());
|
||||
}
|
||||
let want_results = args.iter().any(|a| a == "--results");
|
||||
// `--profile` times each command and splits out its kernel time; it implies `--timings`.
|
||||
@@ -314,13 +403,14 @@ fn open(base: Option<&str>, path: Option<&String>) -> Result<Session, String> {
|
||||
}
|
||||
(Some(b), None) => load(b),
|
||||
(None, Some(p)) => load(p),
|
||||
(None, None) => Err(USAGE.to_string()),
|
||||
(None, None) => Err(usage()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Refuses an option `cmd` doesn't take, and one missing its value: a typo, or another
|
||||
/// command's option, would otherwise be ignored without a word.
|
||||
fn known_flags(cmd: &str, args: &[String], valued: &[&str], switches: &[&str]) -> Result<(), String> {
|
||||
/// command's option, would otherwise be ignored without a word. `positionals` says whether the
|
||||
/// command takes a word of its own (a file, a seed): `fuzz` takes none, and would start a run.
|
||||
fn known_args(cmd: &str, args: &[String], valued: &[&str], switches: &[&str], positionals: bool) -> Result<(), String> {
|
||||
let mut i = 0;
|
||||
while let Some(a) = args.get(i) {
|
||||
if valued.contains(&a.as_str()) {
|
||||
@@ -328,7 +418,9 @@ fn known_flags(cmd: &str, args: &[String], valued: &[&str], switches: &[&str]) -
|
||||
Some(v) if !v.starts_with("--") => i += 2,
|
||||
_ => return Err(format!("{cmd}: {a} needs a value")),
|
||||
}
|
||||
} else if a.starts_with("--") && !switches.contains(&a.as_str()) {
|
||||
} else if switches.contains(&a.as_str()) {
|
||||
i += 1;
|
||||
} else if a.starts_with("--") || !positionals {
|
||||
return Err(format!("{cmd} doesn't take {a} (it takes {})", [valued, switches].concat().join(", ")));
|
||||
} else {
|
||||
i += 1;
|
||||
@@ -352,7 +444,7 @@ fn timeline_ok(s: &Session) -> Result<(), String> {
|
||||
|
||||
fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||
let json_out = json_out(args)?;
|
||||
let path = flag(args, "--in").or_else(|| positional(args).map(String::as_str)).ok_or_else(|| USAGE.to_string())?;
|
||||
let path = flag(args, "--in").or_else(|| positional(args).map(String::as_str)).ok_or_else(usage)?;
|
||||
let mut s = load(path)?;
|
||||
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())?;
|
||||
@@ -370,7 +462,7 @@ fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||
/// A design's history as a command script (`timeline.export_script`): printed, or written to
|
||||
/// `--out`; what the script leaves out goes to stderr.
|
||||
fn cmd_script(args: &[String]) -> Result<(), String> {
|
||||
let path = positional(args).ok_or_else(|| USAGE.to_string())?;
|
||||
let path = positional(args).ok_or_else(usage)?;
|
||||
let s = &mut load(path)?;
|
||||
let out = flag(args, "--out");
|
||||
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
|
||||
@@ -431,14 +523,15 @@ fn install_renderer() {
|
||||
|
||||
fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
install_renderer();
|
||||
known_flags(
|
||||
known_args(
|
||||
"snapshot",
|
||||
args,
|
||||
&["--in", "--out", "--width", "--height", "--view", "--camera", "--body", "--tool", "--section"],
|
||||
&["--no-grid", "--no-axes"],
|
||||
true,
|
||||
)?;
|
||||
if positional(args).is_none() && flag(args, "--in").is_none() {
|
||||
return Err(USAGE.to_string());
|
||||
return Err(usage());
|
||||
}
|
||||
let out = flag(args, "--out").ok_or("snapshot needs --out FILE.png")?;
|
||||
let size = |name: &str, default: u32| match flag(args, name) {
|
||||
@@ -522,7 +615,7 @@ fn ico(images: &[(usize, Vec<u8>)]) -> Result<Vec<u8>, String> {
|
||||
|
||||
fn cmd_exec(args: &[String]) -> Result<(), String> {
|
||||
install_renderer();
|
||||
let id = args.first().ok_or_else(|| USAGE.to_string())?;
|
||||
let id = args.first().ok_or_else(usage)?;
|
||||
let p: Value = match args.get(1) {
|
||||
Some(t) => serde_json::from_str(t).map_err(|e| format!("params: {e}"))?,
|
||||
None => json!({}),
|
||||
@@ -533,10 +626,27 @@ fn cmd_exec(args: &[String]) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `mcp setup`: add the server to each installed AI client, with this program's own path.
|
||||
fn cmd_mcp_setup(args: &[String]) -> Result<(), String> {
|
||||
use solidcraft_engine::agent_setup as setup;
|
||||
let (lines, ok) = setup::setup(args, &setup::Env::from_process(), &setup::current_exe()?)?;
|
||||
for l in lines {
|
||||
println!("{l}");
|
||||
}
|
||||
if ok { Ok(()) } else { Err("mcp setup: some clients could not be configured".into()) }
|
||||
}
|
||||
|
||||
/// MCP server on stdio: headless (optionally starting from a design or script) or bridged to
|
||||
/// a running app's control port. stdout carries only protocol messages. Files the agent's tool
|
||||
/// calls reach are confined to `solidcraft_mcp::agent_scope` unless `--allow-any-path` is given.
|
||||
fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
if matches!(args.first().map(String::as_str), Some("setup" | "install")) {
|
||||
return cmd_mcp_setup(&args[1..]);
|
||||
}
|
||||
let render_gpu = args.iter().any(|arg| arg == "--render-gpu");
|
||||
if render_gpu && flag(args, "--connect").is_some() {
|
||||
return Err("mcp: --render-gpu installs a headless image host; a connected application owns its own device".into());
|
||||
}
|
||||
install_renderer();
|
||||
let scope = (!args.iter().any(|a| a == "--allow-any-path"))
|
||||
.then(|| solidcraft_mcp::agent_scope(flags(args, "--allow-path").into_iter().map(std::path::PathBuf::from)));
|
||||
@@ -580,18 +690,49 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
}
|
||||
Box::new(remote)
|
||||
}
|
||||
(None, Some(path)) => Box::new(solidcraft_mcp::Headless::new(load_checked(path, param_check)?)),
|
||||
(None, None) => {
|
||||
let mut headless = solidcraft_mcp::Headless::default();
|
||||
headless.session.param_check = param_check;
|
||||
(None, Some(path)) => {
|
||||
let mut headless = solidcraft_mcp::Headless::new(load_checked(path, param_check)?);
|
||||
if render_gpu {
|
||||
headless.session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
|
||||
}
|
||||
Box::new(headless)
|
||||
}
|
||||
(None, None) => {
|
||||
let auto = !args.iter().any(|a| a == "--headless");
|
||||
if auto && let Some(port) = discover_app() {
|
||||
eprintln!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
|
||||
return serve_mcp(Box::new(solidcraft_mcp::Remote::new(&format!("127.0.0.1:{port}"), flag(args, "--token"))), scope);
|
||||
}
|
||||
let mut headless = solidcraft_mcp::Headless::default();
|
||||
headless.session.param_check = param_check;
|
||||
if render_gpu {
|
||||
headless.session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
|
||||
}
|
||||
if auto {
|
||||
// The app may start after the agent: look for it again until there is work here.
|
||||
Box::new(solidcraft_mcp::Auto::new(headless, Box::new(discover_app), flag(args, "--token")))
|
||||
} else {
|
||||
Box::new(headless)
|
||||
}
|
||||
}
|
||||
};
|
||||
serve_mcp(backend, scope)
|
||||
}
|
||||
|
||||
fn serve_mcp(backend: Box<dyn solidcraft_mcp::Backend>, scope: Option<solidcraft_engine::io::vfs::PathScope>) -> Result<(), String> {
|
||||
let stdin = std::io::stdin();
|
||||
let stdout = std::io::stdout();
|
||||
solidcraft_mcp::Server::new(backend).with_path_scope(scope).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// The port of the running desktop app: the one its discovery file names, if something listens
|
||||
/// there (a file left by an app that crashed names a closed port).
|
||||
fn discover_app() -> Option<u16> {
|
||||
let port = solidcraft_engine::control_auth::read_current_port()?;
|
||||
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
|
||||
std::net::TcpStream::connect_timeout(&addr, std::time::Duration::from_millis(500)).ok().map(|_| port)
|
||||
}
|
||||
|
||||
/// `save-stress <path> <rounds>`: save a design of a few hundred kilobytes `rounds` times, its
|
||||
/// parameter `save_round` set to the round number each time.
|
||||
fn cmd_save_stress(args: &[String]) -> Result<(), String> {
|
||||
@@ -704,9 +845,10 @@ pub(crate) fn cmd_bench_open(args: &[String]) -> Result<(), String> {
|
||||
let t = std::time::Instant::now();
|
||||
let s = Session::opened(doc, saved.as_ref());
|
||||
drop(read);
|
||||
// Taken from the file: nothing evaluated (SOLIDCRAFT_NO_MODEL_CACHE evaluates anyway).
|
||||
// Taken from the file: nothing evaluated (SOLIDCRAFT_NO_MODEL_CACHE evaluates anyway), or
|
||||
// only the features whose state a thinned file left out.
|
||||
let adopted = saved.is_some() && s.model.last_recomputed == 0;
|
||||
println!("{}", json!({"step": "evaluate", "ms": t.elapsed().as_secs_f64() * 1000.0, "adopted": adopted}));
|
||||
println!("{}", json!({"step": "evaluate", "ms": t.elapsed().as_secs_f64() * 1000.0, "adopted": adopted, "recomputed": s.model.last_recomputed}));
|
||||
for r in &s.model.results {
|
||||
println!("{}", json!({"step": "feature", "id": r.id, "name": r.name, "ms": r.ms, "error": r.error}));
|
||||
}
|
||||
|
||||
@@ -1,25 +1,89 @@
|
||||
//! Complete headless image-job workflow through the same engine commands as the UI/MCP.
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::{
|
||||
doc::render_settings::RenderSetup,
|
||||
render::job::{CpuHost, JobId, Phase},
|
||||
Session,
|
||||
doc::render_settings::{RenderJob, RenderSetup},
|
||||
render::job::{CpuHost, JobId, Phase, ToneMap},
|
||||
};
|
||||
|
||||
/// Only explicit runtime CLI flags are stored here. A typed setup owns these
|
||||
/// values even when a supplied flag happens to match the saved value.
|
||||
#[derive(Default)]
|
||||
struct RuntimeOptions {
|
||||
progressive: bool,
|
||||
transparent: bool,
|
||||
samples: Option<u32>,
|
||||
max_depth: Option<u32>,
|
||||
seed: Option<u32>,
|
||||
lighting: Option<String>,
|
||||
tone_map: Option<ToneMap>,
|
||||
environment: Option<String>,
|
||||
}
|
||||
|
||||
impl RuntimeOptions {
|
||||
fn explicit_flag(&self) -> Option<&'static str> {
|
||||
[
|
||||
(self.progressive, "--progressive"),
|
||||
(self.transparent, "--transparent"),
|
||||
(self.samples.is_some(), "--samples"),
|
||||
(self.max_depth.is_some(), "--max-depth"),
|
||||
(self.seed.is_some(), "--seed"),
|
||||
(self.lighting.is_some(), "--lighting"),
|
||||
(self.tone_map.is_some(), "--tone-map"),
|
||||
(self.environment.is_some(), "--environment"),
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|(present, flag)| present.then_some(flag))
|
||||
}
|
||||
|
||||
fn request(&self, session: &Session, settings: RenderSetup) -> Result<(Value, bool), String> {
|
||||
if let Some(job) = &settings.job {
|
||||
if let Some(flag) = self.explicit_flag() {
|
||||
return Err(format!("{flag} cannot accompany a typed saved render setup; change the saved job options instead"));
|
||||
}
|
||||
settings.validate().map_err(|e| e.to_string())?;
|
||||
session.doc.render.validate_resources(&settings).map_err(|e| e.to_string())?;
|
||||
let needs_gpu = matches!(job, RenderJob::GpuPath { .. });
|
||||
return Ok((json!({"settings":settings}), needs_gpu));
|
||||
}
|
||||
// Legacy setups and unnamed requests retain their runtime wire contract.
|
||||
settings.validate().map_err(|e| e.to_string())?;
|
||||
let mut params = json!({"settings":settings,"transparent_background":self.transparent,
|
||||
"progressive":self.progressive,"samples":self.samples,"max_depth":self.max_depth,"seed":self.seed});
|
||||
if let Some(path) = &self.lighting {
|
||||
let text = solidcraft_engine::io::vfs::read_to_string(path, 65536).map_err(|e| format!("could not read lighting: {e}"))?;
|
||||
let lighting: solidcraft_engine::render::job::StudioLighting = serde_json::from_str(&text).map_err(|e| e.to_string())?;
|
||||
params["lighting"] = json!(lighting);
|
||||
}
|
||||
if let Some(tone_map) = self.tone_map {
|
||||
params["tone_map"] = json!(tone_map);
|
||||
}
|
||||
if let Some(path) = &self.environment {
|
||||
let text = solidcraft_engine::io::vfs::read_to_string(path, 65536).map_err(|e| format!("could not read environment: {e}"))?;
|
||||
params["environment"] = serde_json::from_str(&text).map_err(|e| e.to_string())?;
|
||||
}
|
||||
Ok((params, self.progressive))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), String> {
|
||||
let input = args.first().filter(|p| !p.starts_with('-')).ok_or("render requires an input design or script")?;
|
||||
let mut out = None;
|
||||
let mut setup = None;
|
||||
let mut width = None;
|
||||
let mut height = None;
|
||||
let mut transparent = false;
|
||||
let mut exposure = None;
|
||||
let mut runtime = RuntimeOptions::default();
|
||||
let mut overwrite = false;
|
||||
let mut timeout = 120u64;
|
||||
let mut i = 1;
|
||||
while let Some(arg) = args.get(i) {
|
||||
match arg.as_str() {
|
||||
"--transparent" => transparent = true,
|
||||
"--progressive" => runtime.progressive = true,
|
||||
"--transparent" => runtime.transparent = true,
|
||||
"--overwrite" => overwrite = true,
|
||||
"--out" | "--setup" | "--width" | "--height" | "--timeout" => {
|
||||
"--out" | "--setup" | "--width" | "--height" | "--timeout" | "--samples" | "--max-depth" | "--seed" | "--exposure" | "--lighting"
|
||||
| "--tone-map" | "--environment" => {
|
||||
i += 1;
|
||||
let value = args.get(i).ok_or_else(|| format!("missing value for {arg}"))?;
|
||||
match arg.as_str() {
|
||||
@@ -27,6 +91,20 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
"--setup" => setup = Some(value.clone()),
|
||||
"--width" => width = Some(value.parse::<u32>().map_err(|_| "invalid width")?),
|
||||
"--height" => height = Some(value.parse::<u32>().map_err(|_| "invalid height")?),
|
||||
"--samples" => runtime.samples = Some(value.parse::<u32>().map_err(|_| "invalid samples")?),
|
||||
"--max-depth" => runtime.max_depth = Some(value.parse::<u32>().map_err(|_| "invalid depth")?),
|
||||
"--seed" => runtime.seed = Some(value.parse::<u32>().map_err(|_| "invalid seed")?),
|
||||
"--exposure" => exposure = Some(value.parse::<f64>().map_err(|_| "invalid exposure")?),
|
||||
// Defer file reads until a saved job has rejected conflicting controls.
|
||||
"--environment" => runtime.environment = Some(value.clone()),
|
||||
"--lighting" => runtime.lighting = Some(value.clone()),
|
||||
"--tone-map" => {
|
||||
runtime.tone_map = Some(match value.as_str() {
|
||||
"reinhard" => ToneMap::Reinhard,
|
||||
"aces_fitted" => ToneMap::AcesFitted,
|
||||
_ => return Err("tone-map must be reinhard or aces_fitted".into()),
|
||||
})
|
||||
}
|
||||
_ => timeout = value.parse::<u64>().map_err(|_| "invalid timeout")?,
|
||||
}
|
||||
}
|
||||
@@ -39,18 +117,10 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
}
|
||||
let out = out.ok_or("render requires --out FILE.png")?;
|
||||
let mut session = crate::load(input)?;
|
||||
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
|
||||
session.execute("render.scene", &json!({})).map_err(|e| e.to_string())?;
|
||||
let mut settings = if let Some(name) = setup {
|
||||
session.doc.render.setups.get(&name).cloned().ok_or("no saved render setup with that name")?
|
||||
let mut settings = if let Some(name) = setup.as_deref() {
|
||||
session.doc.render.setups.get(name).cloned().ok_or("no saved render setup with that name")?
|
||||
} else {
|
||||
let mut settings = RenderSetup::default();
|
||||
if let Some(camera) = session.camera {
|
||||
settings.camera = camera;
|
||||
} else {
|
||||
settings.camera.fit(&solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?.bounds);
|
||||
}
|
||||
settings
|
||||
RenderSetup::default()
|
||||
};
|
||||
if let Some(width) = width {
|
||||
settings.width = width;
|
||||
@@ -58,7 +128,27 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
if let Some(height) = height {
|
||||
settings.height = height;
|
||||
}
|
||||
let params = json!({"settings":settings,"transparent_background":transparent});
|
||||
if let Some(exposure) = exposure {
|
||||
settings.exposure_ev = exposure;
|
||||
}
|
||||
// Resolve saved intent before opening a GPU or explicitly settling geometry.
|
||||
// Width/height/exposure only change this validated inline settings clone.
|
||||
let (mut params, needs_gpu) = runtime.request(&session, settings)?;
|
||||
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
|
||||
if needs_gpu {
|
||||
session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
|
||||
}
|
||||
session.execute("render.scene", &json!({})).map_err(|e| e.to_string())?;
|
||||
if setup.is_none() {
|
||||
let camera = if let Some(camera) = session.camera {
|
||||
camera
|
||||
} else {
|
||||
let mut camera = RenderSetup::default().camera;
|
||||
camera.fit(&solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?.bounds);
|
||||
camera
|
||||
};
|
||||
params["settings"]["camera"] = json!(camera);
|
||||
}
|
||||
let preflight = session.execute("render.preflight", ¶ms).map_err(|e| e.to_string())?;
|
||||
let started = session.execute("render.start", ¶ms).map_err(|e| e.to_string())?;
|
||||
let id: JobId = serde_json::from_value(started["job"].clone()).map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -1412,7 +1412,7 @@ fn a_sketch_on_a_patterned_boss_top() {
|
||||
fn construction_geometry_and_holes_on_a_moved_component() {
|
||||
let moved = || {
|
||||
vec![
|
||||
json!({"command": "component.create", "params": {"name": "Arm"}}),
|
||||
json!({"command": "component.create", "params": {"name": "Arm", "grounded": false}}),
|
||||
json!({"command": "solid.box", "params": {"corner": [20, 0, 0], "length": 10, "width": 4, "height": 4, "body_name": "ArmBody"}}),
|
||||
json!({"command": "component.activate", "params": {"component": 0}}),
|
||||
json!({"command": "occurrence.move", "params": {"occurrence": "Arm:1", "translate": [0, 50, 0]}}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{"commands": [
|
||||
{"command": "solid.box", "params": {"length": 10, "width": 10, "height": 10, "body_name": "Base"}},
|
||||
{"command": "component.create", "params": {"name": "Arm"}},
|
||||
{"command": "component.create", "params": {"name": "Arm", "grounded": false}},
|
||||
{"command": "solid.box", "params": {"corner": [20, 0, 0], "length": 10, "width": 4, "height": 4, "body_name": "ArmBody"}},
|
||||
{"command": "solid.fillet", "params": {"edges": [[25, 4, 4]], "radius": 1}},
|
||||
{"command": "component.activate", "params": {"component": 0}},
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
//! An explicit help request prints usage to stdout and succeeds; an unknown command still fails.
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Every command the CLI takes, with the name its own usage names: `inspect` is `eval`, and
|
||||
/// `licenses` is `licences`.
|
||||
const COMMANDS: &[(&str, &str)] = &[
|
||||
("run", "run"),
|
||||
("eval", "eval"),
|
||||
("inspect", "eval"),
|
||||
("script", "script"),
|
||||
("bench-open", "bench-open"),
|
||||
("bench-agent", "bench-agent"),
|
||||
("snapshot", "snapshot"),
|
||||
("render", "render"),
|
||||
("exec", "exec"),
|
||||
("commands", "commands"),
|
||||
("recipe", "recipe"),
|
||||
("oracle", "oracle"),
|
||||
("fuzz", "fuzz"),
|
||||
("fuzz-one", "fuzz-one"),
|
||||
("mcp", "mcp"),
|
||||
("crashes", "crashes"),
|
||||
("licences", "licences"),
|
||||
("licenses", "licences"),
|
||||
("gpu", "gpu"),
|
||||
("icon", "icon"),
|
||||
("bench-edit", "bench-edit"),
|
||||
("save-stress", "save-stress"),
|
||||
];
|
||||
|
||||
/// The commands the usage lists: the aliases answer to another command's name.
|
||||
const LISTED: &[&str] = &[
|
||||
"run",
|
||||
"eval",
|
||||
"script",
|
||||
"bench-open",
|
||||
"bench-agent",
|
||||
"snapshot",
|
||||
"render",
|
||||
"exec",
|
||||
"commands",
|
||||
"recipe",
|
||||
"oracle",
|
||||
"fuzz",
|
||||
"mcp",
|
||||
"crashes",
|
||||
"licences",
|
||||
"gpu",
|
||||
"icon",
|
||||
];
|
||||
|
||||
/// The command names a text mentions after `solidcraft-cli `, each one only once.
|
||||
fn mentioned(text: &str) -> Vec<&str> {
|
||||
let mut names: Vec<&str> = text.split("solidcraft-cli ").skip(1).filter_map(|rest| rest.split_whitespace().next()).collect();
|
||||
names.sort_unstable();
|
||||
names.dedup();
|
||||
names
|
||||
}
|
||||
|
||||
/// Runs the binary and waits at most `limit` for it to finish. `None` means it was still
|
||||
/// running and was killed: the fuzz run `fuzz --help` used to start goes on for minutes.
|
||||
fn run_within(limit: Duration, args: &[&str]) -> Option<(Option<i32>, String, String)> {
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args(args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
|
||||
let started = Instant::now();
|
||||
loop {
|
||||
match child.try_wait().unwrap() {
|
||||
Some(status) => {
|
||||
let out = child.wait_with_output().unwrap();
|
||||
return Some((status.code(), String::from_utf8_lossy(&out.stdout).into_owned(), String::from_utf8_lossy(&out.stderr).into_owned()));
|
||||
}
|
||||
None if started.elapsed() >= limit => {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
return None;
|
||||
}
|
||||
None => std::thread::sleep(Duration::from_millis(25)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_prints_usage_and_succeeds() {
|
||||
for arg in ["--help", "-h", "help"] {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).arg(arg).output().unwrap();
|
||||
assert!(output.status.success(), "{arg}: {}", String::from_utf8_lossy(&output.stderr));
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(stdout.starts_with("usage:"), "{arg}: {stdout}");
|
||||
assert!(stdout.contains("solidcraft-cli run"), "{arg}: {stdout}");
|
||||
assert!(output.stderr.is_empty(), "{arg}: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_still_fails_with_usage() {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).arg("--not-a-command").output().unwrap();
|
||||
assert!(!output.status.success());
|
||||
assert!(output.stdout.is_empty());
|
||||
assert!(String::from_utf8_lossy(&output.stderr).starts_with("error: usage:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_command_prints_its_own_usage() {
|
||||
for (cmd, named) in COMMANDS {
|
||||
for flag in ["--help", "-h"] {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args([*cmd, flag]).output().unwrap();
|
||||
assert!(output.status.success(), "{cmd} {flag}: {}", String::from_utf8_lossy(&output.stderr));
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(stdout.starts_with("usage:"), "{cmd} {flag}: {stdout}");
|
||||
assert_eq!(mentioned(&stdout), vec![*named], "{cmd} {flag} printed another command's usage: {stdout}");
|
||||
assert!(output.stderr.is_empty(), "{cmd} {flag}: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_top_level_usage_still_lists_every_command() {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).arg("--help").output().unwrap();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for cmd in LISTED {
|
||||
assert!(stdout.contains(&format!("solidcraft-cli {cmd}")), "{cmd}: {stdout}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_help_does_not_start_a_fuzz_run() {
|
||||
let Some((code, stdout, stderr)) = run_within(Duration::from_secs(20), &["fuzz", "--help"]) else {
|
||||
panic!("`fuzz --help` kept running: it started a fuzz run instead of printing the usage");
|
||||
};
|
||||
assert_eq!(code, Some(0), "{stderr}");
|
||||
assert!(stdout.starts_with("usage:"), "{stdout}");
|
||||
assert_eq!(mentioned(&stdout), vec!["fuzz"], "{stdout}");
|
||||
assert!(stderr.is_empty(), "{stderr}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_refuses_an_argument_it_does_not_take() {
|
||||
for arg in ["--not-a-flag", "stray"] {
|
||||
let Some((code, _stdout, stderr)) = run_within(Duration::from_secs(20), &["fuzz", arg]) else {
|
||||
panic!("`fuzz {arg}` kept running: it started a fuzz run instead of refusing the argument");
|
||||
};
|
||||
assert_ne!(code, Some(0), "`fuzz {arg}` succeeded");
|
||||
assert!(stderr.contains(arg), "`fuzz {arg}`: {stderr}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_help_keeps_the_registered_gpu_request_controls() {
|
||||
for args in [vec!["--help"], vec!["render", "--help"]] {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args(&args).output().unwrap();
|
||||
assert!(output.status.success(), "{args:?}: {}", String::from_utf8_lossy(&output.stderr));
|
||||
assert!(output.stderr.is_empty());
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for flag in ["--progressive", "--samples", "--max-depth", "--seed", "--exposure", "--lighting", "--environment", "--tone-map"] {
|
||||
assert!(stdout.contains(flag), "{args:?} lost {flag}: {stdout}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,3 +89,177 @@ fn mcp_connect_without_a_port_is_refused_at_start() {
|
||||
assert!(stderr.contains("HOST:PORT"), "{addr}: {stderr}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `mcp` with no arguments finds the running app through the discovery file and uses that
|
||||
/// launch's token; with no app behind the file it stays a headless session.
|
||||
#[test]
|
||||
fn mcp_without_arguments_finds_the_running_app() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let control_dir = std::env::temp_dir().join(format!("solidcraft-mcp-discover-{}-{port}", std::process::id()));
|
||||
std::fs::create_dir_all(&control_dir).unwrap();
|
||||
let token = "fedcba9876543210".repeat(4);
|
||||
std::fs::write(control_dir.join(format!("{port}.token")), &token).unwrap();
|
||||
std::fs::write(control_dir.join("current.port"), format!("{port}\n")).unwrap();
|
||||
let app = std::thread::spawn(move || {
|
||||
// The discovery probe connects and leaves without a request; the tool call follows.
|
||||
let (stream, line) = loop {
|
||||
let (stream, _) = listener.accept().unwrap();
|
||||
let mut line = String::new();
|
||||
BufReader::new(stream.try_clone().unwrap()).read_line(&mut line).unwrap();
|
||||
if !line.is_empty() {
|
||||
break (stream, line);
|
||||
}
|
||||
};
|
||||
let req: Value = serde_json::from_str(&line).unwrap();
|
||||
let ok = req["token"] == json!(token);
|
||||
let reply = json!({"id": req["id"], "ok": ok, "result": {"bodies": [], "method": req["method"]}, "error": "unauthorized"});
|
||||
writeln!(&stream, "{reply}").unwrap();
|
||||
});
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
||||
.args(["mcp", "--allow-any-path"])
|
||||
.env("SOLIDCRAFT_CONTROL_DIR", &control_dir)
|
||||
.env_remove("SOLIDCRAFT_CONTROL_TOKEN")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
let mut stdout = BufReader::new(child.stdout.take().unwrap());
|
||||
writeln!(stdin, "{}", json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "measure", "arguments": {}}})).unwrap();
|
||||
let mut line = String::new();
|
||||
stdout.read_line(&mut line).unwrap();
|
||||
let r: Value = serde_json::from_str(&line).unwrap_or_else(|e| panic!("{e}: {line:?}"));
|
||||
assert_ne!(r["result"]["isError"], true, "{r}");
|
||||
assert!(r["result"]["content"][0]["text"].as_str().unwrap_or_default().contains("engine.execute"), "{r}");
|
||||
app.join().unwrap();
|
||||
drop(stdin);
|
||||
assert!(child.wait().unwrap().success());
|
||||
|
||||
// The file names a port nothing listens on (the app crashed): a headless session instead.
|
||||
let closed = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let dead = closed.local_addr().unwrap().port();
|
||||
drop(closed);
|
||||
std::fs::write(control_dir.join("current.port"), format!("{dead}\n")).unwrap();
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
||||
.args(["mcp", "--allow-any-path"])
|
||||
.env("SOLIDCRAFT_CONTROL_DIR", &control_dir)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(!String::from_utf8_lossy(&out.stderr).contains("bridged to the running app"), "{}", String::from_utf8_lossy(&out.stderr));
|
||||
std::fs::remove_dir_all(&control_dir).ok();
|
||||
}
|
||||
|
||||
/// `mcp setup` against a temporary home: the client folders it finds are the only ones it writes.
|
||||
mod setup {
|
||||
use serde_json::{Value, json};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn home(name: &str) -> PathBuf {
|
||||
let h = std::env::temp_dir().join(format!("solidcraft-mcp-setup-{name}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&h);
|
||||
std::fs::create_dir_all(&h).unwrap();
|
||||
h
|
||||
}
|
||||
|
||||
fn run(home: &Path, args: &[&str]) -> std::process::Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
||||
.args(["mcp", "setup"])
|
||||
.args(args)
|
||||
.env_clear()
|
||||
.env("HOME", home)
|
||||
.env("USERPROFILE", home)
|
||||
.env("APPDATA", home.join("AppData/Roaming"))
|
||||
.env("LOCALAPPDATA", home.join("AppData/Local"))
|
||||
.env("XDG_CONFIG_HOME", home.join(".config"))
|
||||
.env("PATH", "")
|
||||
.output()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn json_of(p: &Path) -> Value {
|
||||
serde_json::from_str(&std::fs::read_to_string(p).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
fn exe() -> String {
|
||||
let p = Path::new(env!("CARGO_BIN_EXE_solidcraft-cli")).canonicalize().unwrap();
|
||||
p.to_string_lossy().trim_start_matches(r"\\?\").to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merges_into_opencode_and_cursor_keeping_other_servers() {
|
||||
let h = home("merge");
|
||||
let oc = h.join(".config/opencode");
|
||||
std::fs::create_dir_all(&oc).unwrap();
|
||||
let old = json!({"$schema": "https://opencode.ai/config.json", "theme": "x", "mcp": {"other": {"type": "local", "command": ["o"]}}});
|
||||
std::fs::write(oc.join("opencode.json"), old.to_string()).unwrap();
|
||||
std::fs::create_dir_all(h.join(".cursor")).unwrap();
|
||||
std::fs::write(h.join(".cursor/mcp.json"), r#"{"mcpServers": {"other": {"command": "o"}}}"#).unwrap();
|
||||
|
||||
let out = run(&h, &["--live"]);
|
||||
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
|
||||
let v = json_of(&oc.join("opencode.json"));
|
||||
assert_eq!(v["theme"], "x");
|
||||
assert_eq!(v["mcp"]["other"], old["mcp"]["other"]);
|
||||
assert_eq!(v["mcp"]["solidcraft"], json!({"type": "local", "command": [exe(), "mcp"], "enabled": true}));
|
||||
assert_eq!(v["mcp"]["solidcraft-live"]["command"], json!([exe(), "mcp", "--connect", "127.0.0.1:7878"]));
|
||||
assert_eq!(json_of(&oc.join("opencode.json.bak")), old);
|
||||
let c = json_of(&h.join(".cursor/mcp.json"));
|
||||
assert_eq!(c["mcpServers"]["other"], json!({"command": "o"}));
|
||||
assert_eq!(c["mcpServers"]["solidcraft"], json!({"command": exe(), "args": ["mcp"]}));
|
||||
|
||||
// Codex (TOML) and Claude Code (a big file the CLI owns) are edited in place too.
|
||||
std::fs::create_dir_all(h.join(".codex")).unwrap();
|
||||
std::fs::write(h.join(".codex/config.toml"), "# mine\nmodel = \"x\"\n").unwrap();
|
||||
let cc = "{\n \"numStartups\": 3,\n \"projects\": {}\n}\n";
|
||||
std::fs::write(h.join(".claude.json"), cc).unwrap();
|
||||
assert!(run(&h, &[]).status.success());
|
||||
let toml = std::fs::read_to_string(h.join(".codex/config.toml")).unwrap();
|
||||
assert!(toml.starts_with("# mine\nmodel = \"x\"\n") && toml.contains("[mcp_servers.solidcraft]"), "{toml}");
|
||||
let c = json_of(&h.join(".claude.json"));
|
||||
assert_eq!(c["numStartups"], 3);
|
||||
assert_eq!(c["mcpServers"]["solidcraft"], json!({"type": "stdio", "command": exe(), "args": ["mcp"]}));
|
||||
assert_eq!(std::fs::read_to_string(h.join(".claude.json.bak")).unwrap(), cc);
|
||||
|
||||
// Running again updates the same entries and leaves one of each.
|
||||
assert!(run(&h, &[]).status.success());
|
||||
assert_eq!(json_of(&h.join(".cursor/mcp.json"))["mcpServers"].as_object().unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_writes_nothing_and_missing_clients_are_skipped() {
|
||||
let h = home("dry");
|
||||
std::fs::create_dir_all(h.join(".cursor")).unwrap();
|
||||
let out = run(&h, &["--dry-run"]);
|
||||
assert!(out.status.success());
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
assert!(text.contains("would write") && text.contains("solidcraft"), "{text}");
|
||||
assert!(!h.join(".cursor/mcp.json").exists());
|
||||
assert!(!h.join(".config/opencode").exists(), "OpenCode was not installed, so nothing is created for it");
|
||||
let out = run(&h, &["--client", "claude-desktop"]);
|
||||
assert!(String::from_utf8_lossy(&out.stdout).contains("not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_is_not_json_is_left_alone() {
|
||||
let h = home("bad");
|
||||
std::fs::create_dir_all(h.join(".cursor")).unwrap();
|
||||
std::fs::write(h.join(".cursor/mcp.json"), "{ // comment").unwrap();
|
||||
let out = run(&h, &[]);
|
||||
assert!(!out.status.success());
|
||||
assert_eq!(std::fs::read_to_string(h.join(".cursor/mcp.json")).unwrap(), "{ // comment");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_desktop_msix_folder_is_found() {
|
||||
let h = home("msix");
|
||||
let pkg = h.join("AppData/Local/Packages/Claude_abc/LocalCache/Roaming/Claude");
|
||||
std::fs::create_dir_all(&pkg).unwrap();
|
||||
// The MSIX folder is only read through LOCALAPPDATA, set for every platform here.
|
||||
assert!(run(&h, &["--client", "claude-desktop"]).status.success());
|
||||
assert!(json_of(&pkg.join("claude_desktop_config.json"))["mcpServers"]["solidcraft"].is_object());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Explicit native GPU registration must not change bridged MCP ownership.
|
||||
use std::process::Command;
|
||||
#[test]
|
||||
fn connected_application_rejects_headless_gpu_installation_before_connecting() {
|
||||
let result = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args(["mcp", "--render-gpu", "--connect", "127.0.0.1:1"]).output().unwrap();
|
||||
assert!(!result.status.success());
|
||||
let message = String::from_utf8_lossy(&result.stderr);
|
||||
assert!(message.contains("connected application owns its own device"), "{message}");
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use std::sync::Arc;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::{ModelBody, ModelState};
|
||||
use solidcraft_engine::doc::{ModelBody, ModelState, SavedModel};
|
||||
|
||||
fn corpus() -> Vec<PathBuf> {
|
||||
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus");
|
||||
@@ -149,6 +149,87 @@ fn imported_assemblies_open_from_their_model_as_evaluated() {
|
||||
check("imported hinge", s);
|
||||
}
|
||||
|
||||
/// `built` saved with `saved`, a model of it that keeps only some states (as a file without room
|
||||
/// for all of them does, #3220), opens as the design evaluated again: evaluating just the
|
||||
/// features whose state the file left out, then after suppressing a feature at, before or after
|
||||
/// each state it kept, after undoing that, and with the marker moved there and back.
|
||||
fn check_thinned(name: &str, built: &Session, saved: &SavedModel) {
|
||||
let design = built.design();
|
||||
let bytes =
|
||||
solidcraft_engine::io::write_design_container_with_cache(&design, solidcraft_engine::io::DesignCompression::Fast, None, Some(saved)).unwrap();
|
||||
assert!(solidcraft_engine::io::read_design_geometry(&bytes).is_some(), "{name}: the file keeps the final state");
|
||||
let path = temp(&name.replace(' ', "-"));
|
||||
std::fs::write(&path, &bytes).unwrap();
|
||||
let mut cached = open(&path);
|
||||
let mut rebuilt = Session::new(solidcraft_engine::io::read_design(&bytes).unwrap());
|
||||
let features = saved.records["features"].as_array().unwrap();
|
||||
let unkept = features.iter().filter(|f| f["skipped"] == false && f.get("state").is_none()).count();
|
||||
assert!(unkept > 0, "{name}: some states left out");
|
||||
assert_eq!(cached.model.recomputed, unkept, "{name}: evaluated just the features whose state the file left out");
|
||||
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: opened from the file's model");
|
||||
let n = cached.doc.features.len();
|
||||
let mut at: Vec<usize> = features
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, f)| f["skipped"] == false && f.get("state").is_some())
|
||||
.flat_map(|(k, _)| [k.saturating_sub(1), k, k + 1])
|
||||
.filter(|i| *i < n)
|
||||
.collect();
|
||||
at.dedup();
|
||||
for i in at {
|
||||
let edit = json!({"feature": cached.doc.features[i].id, "suppressed": true});
|
||||
let (a, b) = (cached.execute("timeline.suppress", &edit), rebuilt.execute("timeline.suppress", &edit));
|
||||
assert_eq!(a.is_ok(), b.is_ok(), "{name}: suppressing feature {i}");
|
||||
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: feature {i} suppressed");
|
||||
if a.is_ok() {
|
||||
cached.execute("edit.undo", &json!({})).unwrap();
|
||||
rebuilt.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: suppressing feature {i} undone");
|
||||
}
|
||||
for marker in [json!({"position": i}), json!({})] {
|
||||
cached.execute("timeline.rollback", &marker).unwrap();
|
||||
rebuilt.execute("timeline.rollback", &marker).unwrap();
|
||||
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: the marker at {marker}, from feature {i}");
|
||||
}
|
||||
}
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
/// A model too big for its file keeps the final state, then the slowest features' states as fit
|
||||
/// (#3220). Both the smallest such model (the final state, and states that add no body to it)
|
||||
/// and one that keeps more open as the design evaluated again. The placed components' design has
|
||||
/// a cut whose fingerprint reads where the bodies before it lie, which a state left out does not
|
||||
/// say until it is evaluated.
|
||||
#[test]
|
||||
fn thinned_models_open_as_evaluated() {
|
||||
let corpus = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus/components-arm.solidcraft");
|
||||
for sample in ["Sample Plate", "Plastic Enclosure", "components-arm"] {
|
||||
let built = match solidcraft_engine::sample::samples().into_iter().find(|s| s.name == sample) {
|
||||
Some(s) => {
|
||||
let mut built = Session::default();
|
||||
for c in (s.script)()["commands"].as_array().unwrap() {
|
||||
built.execute(c["command"].as_str().unwrap(), &c["params"]).unwrap();
|
||||
}
|
||||
built
|
||||
}
|
||||
None => open(&corpus),
|
||||
};
|
||||
let saved = built.model.saved(&built.design()).unwrap();
|
||||
// Every body a megabyte (the records fit in the room left over): each budget keeps one more
|
||||
// body's worth of states.
|
||||
let body = 1 << 20;
|
||||
let pieces: Vec<Vec<(usize, usize)>> = (0..saved.bodies.len()).map(|i| vec![(i, body)]).collect();
|
||||
let thinned: Vec<SavedModel> =
|
||||
(0..=saved.bodies.len()).filter_map(|n| saved.thinned(&pieces, n * body + body - 1).map(|(m, _, _)| m)).collect();
|
||||
let kept = |m: &SavedModel| m.records["features"].as_array().unwrap().iter().filter(|f| f.get("state").is_some()).count();
|
||||
let smallest = thinned.first().unwrap();
|
||||
assert!(smallest.records["states"].as_array().unwrap().len() < saved.records["states"].as_array().unwrap().len(), "{sample}");
|
||||
let more = thinned.iter().find(|m| kept(m) > kept(smallest) && kept(m) < built.doc.features.len()).unwrap();
|
||||
check_thinned(&format!("{sample} smallest"), &built, smallest);
|
||||
check_thinned(&format!("{sample} thinned"), &built, more);
|
||||
}
|
||||
}
|
||||
|
||||
/// Opened from its model, a design evaluates only from the first feature an edit changes: the
|
||||
/// saved states before it are kept as they were read (the same states, not evaluated again).
|
||||
#[test]
|
||||
|
||||
@@ -59,6 +59,84 @@ fn cli_loads_both_design_encodings_by_contents() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #1369: a pending concept that did not land in the numbered release must not be silently
|
||||
/// dropped on open/resave, in either encoding or before replacing an existing destination.
|
||||
#[test]
|
||||
fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
||||
use solidcraft_engine::{Session, doc, io};
|
||||
|
||||
let dir = Scratch::new();
|
||||
for version in [33, doc::format::FORMAT] {
|
||||
let mut experimental = doc::Document::new("Experimental");
|
||||
experimental.format = doc::format::format_string(version);
|
||||
experimental.format_pending.push("experimental-card".into());
|
||||
for (extension, bytes) in [("json", io::write_design(&experimental)), ("solidcraft", io::write_design_container(&experimental).unwrap())] {
|
||||
let source = dir.0.join(format!("experimental-{version}.{extension}"));
|
||||
std::fs::write(&source, &bytes).unwrap();
|
||||
let mut session = Session::default();
|
||||
session.execute("solid.box", &json!({"length": 2, "width": 3, "height": 4})).unwrap();
|
||||
let original = session.doc.clone();
|
||||
let original_path = session.path.clone();
|
||||
let undo = session.undo.len();
|
||||
let dirty = session.is_dirty();
|
||||
let err = session.execute("doc.open", &json!({"path": source})).unwrap_err().to_string();
|
||||
assert!(err.contains("experimental-card") && err.contains("update SolidCraft"), "{err}");
|
||||
assert_eq!(session.doc, original);
|
||||
assert_eq!(session.path, original_path);
|
||||
assert_eq!(session.undo.len(), undo);
|
||||
assert_eq!(session.is_dirty(), dirty);
|
||||
|
||||
for existing in [false, true] {
|
||||
let destination = dir.0.join(format!("output-{version}-{extension}-{existing}.{extension}"));
|
||||
let backup = io::backup_path(&destination);
|
||||
if existing {
|
||||
std::fs::write(&destination, b"existing design").unwrap();
|
||||
std::fs::write(&backup, b"existing backup").unwrap();
|
||||
}
|
||||
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(error.contains("experimental-card") && error.contains("update SolidCraft"), "{error}");
|
||||
if existing {
|
||||
assert_eq!(std::fs::read(&destination).unwrap(), b"existing design");
|
||||
assert_eq!(std::fs::read(&backup).unwrap(), b"existing backup");
|
||||
} else {
|
||||
assert!(!destination.exists() && !backup.exists());
|
||||
}
|
||||
assert_eq!(std::fs::read(&source).unwrap(), bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_pending_concepts_and_released_files_round_trip_with_inert_metadata() {
|
||||
use solidcraft_engine::{doc, io};
|
||||
|
||||
let dir = Scratch::new();
|
||||
let current = doc::Document::new("Supported");
|
||||
let mut released = current.clone();
|
||||
released.format = doc::format::format_string(33);
|
||||
released.format_pending.clear();
|
||||
let mut experimental = released.clone();
|
||||
experimental.format_pending.push("reserved-body-names".into());
|
||||
for (index, mut document) in [current, released, experimental].into_iter().enumerate() {
|
||||
document.metadata.0.insert("com.example.tool".into(), json!({"format_pending": ["experimental-card"], "physical_card": {"density": 700}}));
|
||||
for (extension, bytes) in [("json", io::write_design(&document)), ("solidcraft", io::write_design_container(&document).unwrap())] {
|
||||
let source = dir.0.join(format!("supported-{index}.{extension}"));
|
||||
let destination = dir.0.join(format!("resaved-{index}.{extension}"));
|
||||
std::fs::write(&source, &bytes).unwrap();
|
||||
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
|
||||
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
let reopened = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
|
||||
assert_eq!(reopened.metadata, document.metadata);
|
||||
assert_eq!(reopened.name, document.name);
|
||||
assert_eq!(reopened.features, document.features);
|
||||
assert_eq!(std::fs::read(&source).unwrap(), bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn results_still_save_and_export_the_script_once() {
|
||||
let dir = Scratch::new();
|
||||
@@ -209,3 +287,175 @@ fn timings_and_profile_report_each_command() {
|
||||
assert!(!output.status.success());
|
||||
assert!(String::from_utf8_lossy(&output.stderr).contains("--timings needs a script"));
|
||||
}
|
||||
|
||||
fn render_setup_script(dir: &Scratch, job: Value) -> PathBuf {
|
||||
let path = dir.0.join("render.json");
|
||||
std::fs::write(
|
||||
&path,
|
||||
json!({"commands":[
|
||||
{"command":"solid.box","params":{"length":10,"width":10,"height":10}},
|
||||
{"command":"render.settings.set","params":{"name":"Portable setup","set":{"width":16,"height":16,"job":job}}}
|
||||
]})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(feature = "raytrace-cpu")]
|
||||
#[test]
|
||||
fn render_uses_typed_cpu_setups_from_scripts_and_saved_designs() {
|
||||
for options in [json!({}), json!({"progressive":{"samples_per_pixel":3,"seed":81},"lights":[]})] {
|
||||
let dir = Scratch::new();
|
||||
let script = render_setup_script(&dir, json!({"method":"cpu_raytrace","options":options}));
|
||||
let image = dir.0.join("script.png");
|
||||
let result = json_output(
|
||||
cli()
|
||||
.arg("render")
|
||||
.arg(&script)
|
||||
.args(["--setup", "Portable setup", "--out"])
|
||||
.arg(&image)
|
||||
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-cpu")
|
||||
.output()
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(result["preflight"]["resolved"]["executor"], "cpu-raytrace");
|
||||
assert_eq!(result["export"]["frame"]["executor"], "cpu-raytrace");
|
||||
assert_eq!(result["preflight"]["request"]["integrator"], "raytrace");
|
||||
assert_eq!(result["preflight"]["request"]["raytrace"].get("progressive"), options.get("progressive"));
|
||||
let png = image::load_from_memory(&std::fs::read(&image).unwrap()).unwrap();
|
||||
assert_eq!((png.width(), png.height()), (16, 16));
|
||||
|
||||
let saved = dir.0.join("portable.solidcraft");
|
||||
json_output(cli().arg("run").arg(&script).arg("--save").arg(&saved).output().unwrap());
|
||||
let original = std::fs::read(&saved).unwrap();
|
||||
let overridden = dir.0.join("overridden.png");
|
||||
let result = json_output(
|
||||
cli()
|
||||
.arg("render")
|
||||
.arg(&saved)
|
||||
.args(["--setup", "Portable setup", "--out"])
|
||||
.arg(&overridden)
|
||||
.args(["--width", "12", "--height", "8", "--exposure", "1.25"])
|
||||
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-cpu")
|
||||
.output()
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(result["preflight"]["request"]["width"], 12);
|
||||
assert_eq!(result["preflight"]["request"]["height"], 8);
|
||||
assert_eq!(result["preflight"]["request"]["raytrace"]["exposure_ev"], 1.25);
|
||||
assert_eq!(result["export"]["frame"]["executor"], "cpu-raytrace");
|
||||
assert_eq!(result["preflight"]["request"]["raytrace"].get("progressive"), options.get("progressive"));
|
||||
let png = image::load_from_memory(&std::fs::read(overridden).unwrap()).unwrap();
|
||||
assert_eq!((png.width(), png.height()), (12, 8));
|
||||
assert_eq!(std::fs::read(saved).unwrap(), original, "CLI overrides must not rewrite saved intent");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_preserves_typed_raster_and_legacy_runtime_controls() {
|
||||
for job in [Value::Null, json!({"method":"raster","transparent_background":true})] {
|
||||
let dir = Scratch::new();
|
||||
let script = render_setup_script(&dir, job.clone());
|
||||
let out = dir.0.join("raster.png");
|
||||
let mut command = cli();
|
||||
command
|
||||
.arg("render")
|
||||
.arg(&script)
|
||||
.args(["--setup", "Portable setup", "--out"])
|
||||
.arg(&out)
|
||||
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-raster");
|
||||
if job.is_null() {
|
||||
command.arg("--transparent");
|
||||
}
|
||||
let result = json_output(command.output().unwrap());
|
||||
assert_eq!(result["preflight"]["resolved"]["executor"], "cpu-product");
|
||||
assert_eq!(result["export"]["frame"]["executor"], "cpu-product");
|
||||
assert_eq!(result["preflight"]["request"]["transparent_background"], true);
|
||||
assert!(std::fs::read(out).unwrap().starts_with(b"\x89PNG\r\n\x1a\n"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_render_rejects_explicit_runtime_flags_before_gpu_or_option_file_reads() {
|
||||
let dir = Scratch::new();
|
||||
let script = render_setup_script(&dir, json!({"method":"gpu_path","options":{"samples":64,"seed":0}}));
|
||||
let out = dir.0.join("existing.png");
|
||||
std::fs::write(&out, b"previous completed image").unwrap();
|
||||
for flags in [
|
||||
&["--progressive"][..],
|
||||
&["--transparent"][..],
|
||||
&["--samples", "64"][..],
|
||||
&["--max-depth", "8"][..],
|
||||
&["--seed", "0"][..],
|
||||
&["--lighting", "deliberately-missing-lighting.json"][..],
|
||||
&["--tone-map", "reinhard"][..],
|
||||
&["--environment", "deliberately-missing-environment.json"][..],
|
||||
] {
|
||||
let output = cli()
|
||||
.arg("render")
|
||||
.arg(&script)
|
||||
.args(["--setup", "Portable setup", "--out"])
|
||||
.arg(&out)
|
||||
.arg("--overwrite")
|
||||
.args(flags)
|
||||
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-conflicts")
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(error.contains(&format!("{} cannot accompany a typed saved render setup", flags[0])), "{error}");
|
||||
assert!(output.stdout.is_empty());
|
||||
assert_eq!(std::fs::read(&out).unwrap(), b"previous completed image");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_gpu_render_selects_its_host_without_a_progressive_runtime_flag() {
|
||||
// A deliberately invalid policy proves that dispatch reached the GPU host
|
||||
// without depending on an adapter or accepting a CPU fallback. The legacy
|
||||
// --progressive path must still reach that same host-selection boundary.
|
||||
for job in [json!({"method":"gpu_path","options":{"samples":2}}), Value::Null] {
|
||||
let dir = Scratch::new();
|
||||
let script = render_setup_script(&dir, job.clone());
|
||||
let out = dir.0.join("gpu.png");
|
||||
let mut command = cli();
|
||||
command.arg("render").arg(&script).args(["--setup", "Portable setup", "--out"]).arg(&out).env("SOLIDCRAFT_GPU", "not-a-gpu-policy");
|
||||
if job.is_null() {
|
||||
command.arg("--progressive");
|
||||
}
|
||||
let output = command.output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(error.contains("SOLIDCRAFT_GPU must be auto, hardware, software or cpu"), "{error}");
|
||||
assert!(!out.exists());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_setup_overrides_obey_shared_validation_before_host_initialization() {
|
||||
let dir = Scratch::new();
|
||||
let script = render_setup_script(&dir, json!({"method":"cpu_raytrace","options":{"progressive":{"samples_per_pixel":64}}}));
|
||||
let out = dir.0.join("invalid.png");
|
||||
// 1024 * 1024 * 64 exceeds the unchanged default Compact camera-sample
|
||||
// budget. Admission must name that envelope before initializing any host.
|
||||
for (flags, message) in [
|
||||
(&["--width", "1024", "--height", "1024"][..], "CPU Compact exceeds its 16777216 camera-sample admission"),
|
||||
(&["--width", "0"][..], "render output must be at least"),
|
||||
(&["--exposure", "NaN"][..], "render exposure must be finite"),
|
||||
] {
|
||||
let output = cli()
|
||||
.arg("render")
|
||||
.arg(&script)
|
||||
.args(["--setup", "Portable setup", "--out"])
|
||||
.arg(&out)
|
||||
.args(flags)
|
||||
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-invalid-settings")
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(error.contains(message), "{error}");
|
||||
assert!(!out.exists());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ workspace = true
|
||||
name = "solidcraft-web"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
|
||||
[dependencies]
|
||||
solidcraft-engine = { workspace = true }
|
||||
solidcraft-ui-egui = { workspace = true }
|
||||
@@ -30,5 +34,5 @@ web-sys = { version = "0.3", features = [
|
||||
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemWritableFileStream",
|
||||
"WritableStream", "File", "FileList", "Blob", "BlobPropertyBag", "Url", "HtmlAnchorElement", "HtmlInputElement", "Storage",
|
||||
"IdbFactory", "IdbDatabase", "IdbObjectStore", "IdbOpenDbRequest", "IdbRequest", "IdbTransaction", "IdbTransactionMode",
|
||||
"BeforeUnloadEvent", "Event", "EventTarget", "Response", "MediaQueryList", "Gamepad", "Performance",
|
||||
"BeforeUnloadEvent", "Event", "EventTarget", "KeyboardEvent", "Response", "MediaQueryList", "Gamepad", "Performance",
|
||||
] }
|
||||
|
||||
@@ -294,7 +294,7 @@ mod web {
|
||||
let options = Object::new();
|
||||
let _ = Reflect::set(&options, &"multiple".into(), &JsValue::FALSE);
|
||||
let extensions = Array::new();
|
||||
for e in [".solidcraft", ".solvecraft", ".json", ".step", ".stp", ".3mf", ".stl", ".obj", ".glb", ".gltf"] {
|
||||
for e in [".solidcraft", ".solvecraft", ".json", ".step", ".stp", ".3mf", ".stl", ".obj", ".glb", ".gltf", ".fcstd", ".3dm"] {
|
||||
extensions.push(&e.into());
|
||||
}
|
||||
let accept = Object::new();
|
||||
|
||||
@@ -499,7 +499,10 @@ fn input_picker(files: &Rc<Files>, intent: Intent) {
|
||||
let Some(doc) = web_sys::window().and_then(|w| w.document()) else { return };
|
||||
let Ok(input) = doc.create_element("input").map(|e| e.unchecked_into::<web_sys::HtmlInputElement>()) else { return };
|
||||
input.set_type("file");
|
||||
input.set_accept(".solidcraft,.solvecraft,.json,.step,.stp,.3mf,.stl,.obj,.glb,.gltf");
|
||||
// Everything the browser build imports, from the format registry: cadmpeg is pure Rust, so
|
||||
// FreeCAD and Rhino files are part of it.
|
||||
let accept = solidcraft_engine::io::formats::open_extensions(true).iter().map(|e| format!(".{e}")).collect::<Vec<_>>().join(",");
|
||||
input.set_accept(&accept);
|
||||
// Attached (hidden) while it is open: some browsers ignore a click on a detached input.
|
||||
let _ = input.set_attribute("style", "display:none");
|
||||
if let Some(body) = doc.body() {
|
||||
|
||||
@@ -17,13 +17,21 @@ mod install;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod space_mouse;
|
||||
|
||||
/// The browser switches tabs with Ctrl+Tab and Ctrl+Shift+Tab, so the page leaves those keys to
|
||||
/// it (#5227); plain Tab and Shift+Tab still move between the app's fields. (Ctrl+PageUp and
|
||||
/// Ctrl+PageDown already reach the browser.)
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
fn browser_switches_tabs(key: &str, ctrl: bool) -> bool {
|
||||
ctrl && key == "Tab"
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web {
|
||||
use std::rc::Rc;
|
||||
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::io::vfs;
|
||||
use solidcraft_ui_egui::{Services, SolveApp};
|
||||
use solidcraft_ui_egui::{Services, SolveApp, ui_kit};
|
||||
use wasm_bindgen::JsCast as _;
|
||||
|
||||
use crate::api::Api;
|
||||
@@ -84,6 +92,20 @@ mod web {
|
||||
on_unload.forget();
|
||||
}
|
||||
|
||||
/// eframe cancels the browser's action for every Tab it gets, so the browser couldn't switch
|
||||
/// tabs while the app has the keyboard. Its tab-switching keys stop here, on the way down to
|
||||
/// the canvas, before eframe sees them; the browser then acts on them as usual.
|
||||
fn leave_tab_switching_to_browser(window: &web_sys::Window) {
|
||||
let on_key = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::KeyboardEvent)>::new(|e: web_sys::KeyboardEvent| {
|
||||
if crate::browser_switches_tabs(&e.key(), e.ctrl_key()) {
|
||||
e.stop_propagation();
|
||||
}
|
||||
});
|
||||
let _ = window.add_event_listener_with_callback_and_bool("keydown", on_key.as_ref().unchecked_ref(), true);
|
||||
// The listener lives as long as the page.
|
||||
on_key.forget();
|
||||
}
|
||||
|
||||
const JAPANESE_FONT_PATH: &str = "./fonts/BIZUDPGothic-Regular.ttf";
|
||||
|
||||
type FontLoadResult = Rc<std::cell::RefCell<Option<Result<Vec<u8>, String>>>>;
|
||||
@@ -109,6 +131,7 @@ mod web {
|
||||
/// The language the fonts were last set up for (a CJK UI leads with the CJK face).
|
||||
font_language: String,
|
||||
space_mouse: SpaceMouse,
|
||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures,
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
@@ -207,13 +230,15 @@ mod web {
|
||||
fn sync_recents(&mut self) {
|
||||
let mut entries = self.files.entries.borrow().clone();
|
||||
entries.sort_by(|a, b| b.modified.total_cmp(&a.modified));
|
||||
let forgotten = self.app.home.forgotten.clone();
|
||||
let recent = &mut self.app.home.recent;
|
||||
let known = self.files.disk_known.borrow().clone();
|
||||
// A design on the computer's disk stays listed while its file is remembered.
|
||||
recent.retain(|p| vfs::exists(p) || disk::parse(p).is_some_and(|(id, _)| known.contains(id)));
|
||||
for e in entries.iter().filter(|e| solidcraft_engine::io::is_design_path(&e.name)) {
|
||||
let p = format!("{}{}", files::OPFS, e.name);
|
||||
if !recent.contains(&p) {
|
||||
// A design the user removed from the list is not added back while it is stored.
|
||||
if !recent.contains(&p) && !forgotten.contains(&p) {
|
||||
recent.push(p);
|
||||
}
|
||||
}
|
||||
@@ -222,6 +247,10 @@ mod web {
|
||||
let recovered = self.files.recovery.borrow().as_ref().and_then(|(_, p)| p.clone());
|
||||
let keep = recent.iter().chain(recovered.as_ref()).filter_map(|p| disk::parse(p)).map(|(id, _)| id.to_string()).collect();
|
||||
disk::prune(keep);
|
||||
// Drop forgotten entries whose stored design is gone, so the set does not grow without bound.
|
||||
let stored: std::collections::BTreeSet<String> =
|
||||
entries.iter().filter(|e| solidcraft_engine::io::is_design_path(&e.name)).map(|e| format!("{}{}", files::OPFS, e.name)).collect();
|
||||
self.app.home.forgotten.retain(|p| stored.contains(p));
|
||||
}
|
||||
|
||||
/// The stored designs: open, insert, rename, delete, download; open from the computer.
|
||||
@@ -229,24 +258,21 @@ mod web {
|
||||
if !self.files.panel.get() {
|
||||
return;
|
||||
}
|
||||
let mut open = true;
|
||||
let mut act: Option<(&'static str, String)> = None;
|
||||
egui::Window::new("Files in this browser")
|
||||
.open(&mut open)
|
||||
.collapsible(false)
|
||||
.resizable(true)
|
||||
.default_width(520.0)
|
||||
.pivot(egui::Align2::CENTER_CENTER)
|
||||
.default_pos(ctx.content_rect().center())
|
||||
.show(ctx, |ui| {
|
||||
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_files"), "folder", "Files in this browser")
|
||||
.cards(3.0)
|
||||
.show_body(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Open from computer…").clicked() {
|
||||
if ui_kit::button(ui, "Open from computer…", ui_kit::Button::Normal).clicked() {
|
||||
files::pick_from_disk(&self.files, Intent::Open);
|
||||
}
|
||||
if ui.button("Insert from computer…").on_hover_text("STEP, 3MF or STL into the open design").clicked() {
|
||||
if ui_kit::button(ui, "Insert from computer…", ui_kit::Button::Normal)
|
||||
.on_hover_text("STEP, 3MF or STL into the open design")
|
||||
.clicked()
|
||||
{
|
||||
files::pick_from_disk(&self.files, Intent::Insert);
|
||||
}
|
||||
if ui.button("Download open design").clicked() {
|
||||
if ui_kit::button(ui, "Download open design", ui_kit::Button::Normal).clicked() {
|
||||
act = Some(("download_open", String::new()));
|
||||
}
|
||||
});
|
||||
@@ -270,7 +296,7 @@ mod web {
|
||||
for e in &entries {
|
||||
match &mut self.renaming {
|
||||
Some((from, to)) if *from == e.name => {
|
||||
let r = ui.add(egui::TextEdit::singleline(to).desired_width(200.0));
|
||||
let r = ui_kit::cell_editor(ui, ui.make_persistent_id(("stored_file_name", from.as_str())), to, "", false);
|
||||
if std::mem::take(&mut self.rename_focus) {
|
||||
r.request_focus();
|
||||
}
|
||||
@@ -288,28 +314,31 @@ mod web {
|
||||
}
|
||||
ui.label(format!("{:.1} KB", e.size as f64 / 1024.0));
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button("Open").clicked() {
|
||||
if ui_kit::button(ui, "Open", ui_kit::Button::Small).clicked() {
|
||||
act = Some(("open", e.name.clone()));
|
||||
}
|
||||
let design = solidcraft_engine::io::is_design_path(&e.name);
|
||||
if !design && ui.small_button("Insert").on_hover_text("Into the open design (STEP, 3MF, STL)").clicked() {
|
||||
if !design
|
||||
&& ui_kit::button(ui, "Insert", ui_kit::Button::Small)
|
||||
.on_hover_text("Into the open design (STEP, 3MF, STL)")
|
||||
.clicked()
|
||||
{
|
||||
act = Some(("insert", e.name.clone()));
|
||||
}
|
||||
if ui.small_button("Rename").clicked() {
|
||||
if ui_kit::button(ui, "Rename", ui_kit::Button::Small).clicked() {
|
||||
self.renaming = Some((e.name.clone(), e.name.clone()));
|
||||
self.rename_focus = true;
|
||||
}
|
||||
if ui
|
||||
.small_button("Save to computer…")
|
||||
if ui_kit::button(ui, "Save to computer…", ui_kit::Button::Small)
|
||||
.on_hover_text("A copy in a file you choose; the stored design stays here")
|
||||
.clicked()
|
||||
{
|
||||
act = Some(("save_copy", e.name.clone()));
|
||||
}
|
||||
if ui.small_button("Download").clicked() {
|
||||
if ui_kit::button(ui, "Download", ui_kit::Button::Small).clicked() {
|
||||
act = Some(("download", e.name.clone()));
|
||||
}
|
||||
if ui.small_button("Delete").clicked() {
|
||||
if ui_kit::button(ui, "Delete", ui_kit::Button::Small).clicked() {
|
||||
act = Some(("delete", e.name.clone()));
|
||||
}
|
||||
});
|
||||
@@ -318,7 +347,7 @@ mod web {
|
||||
});
|
||||
});
|
||||
});
|
||||
if !open {
|
||||
if closed {
|
||||
self.files.panel.set(false);
|
||||
self.renaming = None;
|
||||
}
|
||||
@@ -360,6 +389,8 @@ mod web {
|
||||
"delete" => {
|
||||
files::delete(&self.files, &name);
|
||||
self.app.home.recent.retain(|r| *r != path);
|
||||
// The file is gone, so there is nothing left to keep forgotten.
|
||||
self.app.home.forgotten.remove(&path);
|
||||
}
|
||||
"rename_cancel" => self.renaming = None,
|
||||
"rename" => {
|
||||
@@ -373,6 +404,10 @@ mod web {
|
||||
*r = new.clone();
|
||||
}
|
||||
}
|
||||
// A forgotten design follows its new name.
|
||||
if self.app.home.forgotten.remove(&old) {
|
||||
self.app.home.forgotten.insert(new);
|
||||
}
|
||||
}
|
||||
Err(e) => self.app.set_status(e, true),
|
||||
}
|
||||
@@ -386,20 +421,19 @@ mod web {
|
||||
fn recovery_prompt(&mut self, ctx: &egui::Context) {
|
||||
let Some((name, path)) = self.files.recovery.borrow().clone() else { return };
|
||||
let mut answer = None;
|
||||
egui::Window::new("Recover unsaved work?").collapsible(false).resizable(false).anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]).show(
|
||||
ctx,
|
||||
|ui| {
|
||||
let _ = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_recovery"), "restore", "Recover unsaved work?")
|
||||
.required()
|
||||
.show_body(ctx, |ui| {
|
||||
ui.label(format!("“{name}” had unsaved changes when this page last closed."));
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Recover").clicked() {
|
||||
if ui_kit::button(ui, "Recover", ui_kit::Button::Normal).clicked() {
|
||||
answer = Some(true);
|
||||
}
|
||||
if ui.button("Discard").clicked() {
|
||||
if ui_kit::button(ui, "Discard", ui_kit::Button::Normal).clicked() {
|
||||
answer = Some(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
let Some(recover) = answer else { return };
|
||||
*self.files.recovery.borrow_mut() = None;
|
||||
if recover {
|
||||
@@ -488,6 +522,10 @@ mod web {
|
||||
UNSAVED.with(|u| u.set(solidcraft_ui_egui::documents::any_dirty(&self.app)));
|
||||
}
|
||||
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
let now = raw.time.unwrap_or_else(|| ctx.input(|i| i.time));
|
||||
if let Some(delay) = self.wheel_gestures.update(raw, now) {
|
||||
ctx.request_repaint_after(delay);
|
||||
}
|
||||
files::take_drops(&self.files, raw);
|
||||
// Before the app's own hook, which adds the control channel's events.
|
||||
self.space_mouse.raw_input_hook(ctx, self.app.preferences.space_mouse_enabled, raw);
|
||||
@@ -554,6 +592,7 @@ mod web {
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
let Some(window) = web_sys::window() else { return };
|
||||
ask_before_unload(&window);
|
||||
leave_tab_switching_to_browser(&window);
|
||||
let Some(document) = window.document() else { return };
|
||||
let Some(canvas) = document.get_element_by_id(CANVAS_ID).and_then(|e| e.dyn_into::<web_sys::HtmlCanvasElement>().ok()) else {
|
||||
log::error!("missing <canvas id=\"{CANVAS_ID}\">");
|
||||
@@ -651,6 +690,7 @@ mod web {
|
||||
cjk_bytes: None,
|
||||
font_language,
|
||||
space_mouse: SpaceMouse::default(),
|
||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
|
||||
}))
|
||||
}),
|
||||
)
|
||||
@@ -676,3 +716,17 @@ fn main() {
|
||||
fn main() {
|
||||
eprintln!("solidcraft-web only runs in the browser: build it with `trunk build --release` in apps/solidcraft-web");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn only_ctrl_tab_is_left_to_the_browser() {
|
||||
// Ctrl+Tab and Ctrl+Shift+Tab (the event's key is "Tab" either way) switch browser tabs.
|
||||
assert!(browser_switches_tabs("Tab", true));
|
||||
// Tab and Shift+Tab move between the app's fields; Ctrl with other keys stays the app's.
|
||||
assert!(!browser_switches_tabs("Tab", false));
|
||||
assert!(!browser_switches_tabs("s", true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,11 +246,11 @@ mod tests {
|
||||
let axis =
|
||||
|source, rest, full| solidcraft_ui_egui::space_mouse::AxisCalibration { source, reversed: false, rest, dead_zone: 0, full: [full; 2] };
|
||||
// A short-range device: 15 of its 50 is under the raw dead zone, but 105 of 350 calibrated.
|
||||
let short = Calibration { dead_zone_share: 0.0, axes: std::array::from_fn(|r| axis(r, 0, 50)) };
|
||||
let short = Calibration { dead_zone_share: 0.0, kind: Default::default(), axes: std::array::from_fn(|r| axis(r, 0, 50)) };
|
||||
let push = Axes { translation: [15, 0, 0], rotation: [0; 3] };
|
||||
assert!(!push.moving() && moving(push, Some(short)));
|
||||
// A device resting at 100 on every axis: past the raw dead zone, but at rest calibrated.
|
||||
let offset = Calibration { dead_zone_share: 0.0, axes: std::array::from_fn(|r| axis(r, 100, 350)) };
|
||||
let offset = Calibration { dead_zone_share: 0.0, kind: Default::default(), axes: std::array::from_fn(|r| axis(r, 100, 350)) };
|
||||
let resting = Axes { translation: [100; 3], rotation: [100; 3] };
|
||||
assert!(resting.moving() && !moving(resting, Some(offset)));
|
||||
// Without a calibration, as read.
|
||||
|
||||
@@ -7,9 +7,14 @@ license.workspace = true
|
||||
publish = false
|
||||
default-run = "solidcraft"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "bundled-render-library"]
|
||||
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) can allow it: none has a safe API. Keep the rest in sync with [workspace.lints].
|
||||
# (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"
|
||||
|
||||
@@ -49,9 +54,15 @@ hidapi = { version = "2.6.7", default-features = false, features = ["windows-nat
|
||||
# The main window's handle for src/native_frame.rs (already in the tree through eframe and winit).
|
||||
raw-window-handle = "0.6"
|
||||
# Native crash capture (src/native_crash.rs): the exception filter, the faulting module, a minidump.
|
||||
# The window's resize border and DPI size (src/native_frame.rs): the window procedure, hit test, DPI.
|
||||
# The window's resize border, DPI size and class icon (src/native_frame.rs): the window procedure, hit
|
||||
# test, DPI, the exe's icon.
|
||||
# Finding 3D mice through Plug and Play without asking other devices anything (src/space_mouse/windows_hid.rs).
|
||||
windows-sys = { version = "0.61", features = [
|
||||
"Win32_Devices_DeviceAndDriverInstallation",
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Devices_Properties",
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
@@ -65,15 +76,17 @@ windows-sys = { version = "0.61", features = [
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
muda = { version = "0.21.2", default-features = false }
|
||||
# The 3D mouse through the 3Dconnexion driver, which keeps the device to itself (src/space_mouse.rs).
|
||||
solidcraft-3dx-client = { workspace = true }
|
||||
# The 3D mouse (src/space_mouse.rs), in the shared-device open mode so that a running 3DxWare
|
||||
# driver does not shut the app out.
|
||||
hidapi = { version = "2.6.7", default-features = false, features = ["macos-shared-device"] }
|
||||
# Files opened from Finder and the Dock (src/open_documents.rs); all already come with eframe and rfd.
|
||||
# Files opened from Finder and the Dock (src/open_documents.rs), and the fullscreen title-bar
|
||||
# inset (src/main.rs); all already come with eframe and rfd.
|
||||
objc2 = "0.6"
|
||||
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSResponder"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "block2", "NSArray", "NSNotification", "NSOperation", "NSString", "NSURL"] }
|
||||
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"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::{Receiver, Sender, TryRecvError, channel};
|
||||
use std::time::Duration;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::MAX_LINE;
|
||||
use solidcraft_engine::control_auth;
|
||||
use solidcraft_engine::control_pending;
|
||||
use solidcraft_ui_egui::ControlRequest;
|
||||
|
||||
/// Connections served at once, so a flood must not be able to spawn threads without end.
|
||||
@@ -18,7 +20,7 @@ const MAX_CONNECTIONS: usize = 16;
|
||||
const MAX_BAD_LINES: usize = 8;
|
||||
|
||||
static OPEN: AtomicUsize = AtomicUsize::new(0);
|
||||
/// The token files this launch wrote (its `--control` port, the agent chat's endpoint), removed
|
||||
/// The token files this launch wrote (its control port, the agent chat's endpoint), removed
|
||||
/// when the app ends.
|
||||
static TOKEN_FILES: std::sync::Mutex<Vec<std::path::PathBuf>> = std::sync::Mutex::new(Vec::new());
|
||||
|
||||
@@ -33,17 +35,45 @@ fn new_token() -> Option<String> {
|
||||
pub fn remove_token_file() {
|
||||
if let Ok(files) = TOKEN_FILES.lock() {
|
||||
for p in files.iter() {
|
||||
let _ = std::fs::remove_file(p);
|
||||
control_auth::remove_token_file(p);
|
||||
}
|
||||
}
|
||||
if let Some(port) = APP_PORT.lock().ok().and_then(|p| *p) {
|
||||
control_auth::remove_current_port(port);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
|
||||
/// The port the app's own channel listens on, for removing the discovery file at exit.
|
||||
static APP_PORT: std::sync::Mutex<Option<u16>> = std::sync::Mutex::new(None);
|
||||
|
||||
pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<ControlRequest> {
|
||||
let (tx, rx) = channel::<ControlRequest>();
|
||||
// Every connection must show the token before anything runs (docs/control-protocol.md).
|
||||
match control_auth::env_token().or_else(new_token) {
|
||||
Some(token) => {
|
||||
listen(port, token, tx, ctx, false);
|
||||
let listener = match listen_on {
|
||||
control_auth::Listen::Exact(port) => TcpListener::bind(("127.0.0.1", port)),
|
||||
control_auth::Listen::Default => control_auth::bind_with_fallback(control_auth::DEFAULT_PORT, control_auth::PORT_TRIES),
|
||||
};
|
||||
match listener {
|
||||
Ok(l) => {
|
||||
if let Some(port) = listen_on_listener(l, token, tx, ctx, false) {
|
||||
if matches!(listen_on, control_auth::Listen::Default) {
|
||||
eprintln!("solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)");
|
||||
}
|
||||
// The discovery file: where `solidcraft-cli mcp` finds this app with no arguments.
|
||||
match control_auth::write_current_port(port) {
|
||||
Ok(_) => {
|
||||
if let Ok(mut p) = APP_PORT.lock() {
|
||||
*p = Some(port);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("solidcraft: could not write the control discovery file ({e})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
|
||||
}
|
||||
}
|
||||
None => eprintln!("solidcraft: control server not started: no random token available"),
|
||||
}
|
||||
@@ -59,20 +89,20 @@ pub fn listen_agent(tx: Sender<ControlRequest>, ctx: egui::Context) -> Option<u1
|
||||
eprintln!("solidcraft: agent chat endpoint not started: no random token available");
|
||||
return None;
|
||||
};
|
||||
listen(0, token, tx, ctx, true)
|
||||
}
|
||||
|
||||
/// Listen on `port` (0: a free port the system picks) with `token`; the port it listens on.
|
||||
/// `patient`: every request may wait as long as a batch (a person decides on it).
|
||||
fn listen(port: u16, token: String, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) -> Option<u16> {
|
||||
let listener = match TcpListener::bind(("127.0.0.1", port)) {
|
||||
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("solidcraft: control server failed to bind 127.0.0.1:{port}: {e}");
|
||||
eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
|
||||
listen_on_listener(listener, token, tx, ctx, true)
|
||||
}
|
||||
|
||||
/// Serve `listener` (loopback) with `token`; the port it listens on.
|
||||
/// `patient`: every request may wait as long as a batch (a person decides on it).
|
||||
fn listen_on_listener(listener: TcpListener, token: String, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) -> Option<u16> {
|
||||
let port = listener.local_addr().ok()?.port();
|
||||
match control_auth::token_path(port) {
|
||||
Some(path) => match control_auth::write_token_file(&path, &token) {
|
||||
Ok(()) => {
|
||||
@@ -175,14 +205,171 @@ const WAITS: Waits = Waits { short: Duration::from_secs(60), long: Duration::fro
|
||||
struct Pending {
|
||||
id: Value,
|
||||
method: String,
|
||||
/// The request in messages ("`engine.execute` (solid.fillet)").
|
||||
what: String,
|
||||
reply: Receiver<Value>,
|
||||
}
|
||||
|
||||
fn serve(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) {
|
||||
serve_with(stream, token, tx, ctx, patient, WAITS);
|
||||
/// Every request the app has been sent and not answered yet, from every connection, for
|
||||
/// `control.running` (#3654). Each connection's own thread answers that, so it never waits behind
|
||||
/// a running request.
|
||||
struct Running {
|
||||
list: Mutex<Vec<InFlight>>,
|
||||
connections: AtomicU64,
|
||||
keys: AtomicU64,
|
||||
}
|
||||
|
||||
fn serve_with(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool, waits: Waits) {
|
||||
/// What `control.running` says about a request: never its parameters beyond the command id.
|
||||
struct InFlight {
|
||||
key: u64,
|
||||
connection: u64,
|
||||
id: Value,
|
||||
method: String,
|
||||
command: Option<&'static str>,
|
||||
steps: Option<usize>,
|
||||
since: Instant,
|
||||
started: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
/// The app's requests, from both of its listeners.
|
||||
static RUNNING: Running = Running::new();
|
||||
|
||||
impl Running {
|
||||
const fn new() -> Self {
|
||||
Running { list: Mutex::new(Vec::new()), connections: AtomicU64::new(0), keys: AtomicU64::new(0) }
|
||||
}
|
||||
|
||||
fn lock(&self) -> MutexGuard<'_, Vec<InFlight>> {
|
||||
self.list.lock().unwrap_or_else(PoisonError::into_inner)
|
||||
}
|
||||
|
||||
/// List `req`, sent on `connection`, until the returned ticket is dropped.
|
||||
fn add(&'static self, connection: u64, id: &Value, req: &ControlRequest) -> Ticket {
|
||||
let key = self.keys.fetch_add(1, Ordering::Relaxed);
|
||||
// Ids and methods come from the client: only short ones are repeated to the others.
|
||||
let id = match id {
|
||||
Value::Number(_) => id.clone(),
|
||||
Value::String(s) if s.len() <= 64 => id.clone(),
|
||||
_ => Value::Null,
|
||||
};
|
||||
let p = &req.params;
|
||||
let command = match req.method.as_str() {
|
||||
"engine.execute" | "command" => p.get("command").and_then(Value::as_str).and_then(solidcraft_engine::find_command).map(|c| c.id),
|
||||
_ => None,
|
||||
};
|
||||
let steps = match req.method.as_str() {
|
||||
"engine.batch" | "engine.script" => p.get("commands").and_then(Value::as_array).map(Vec::len),
|
||||
_ => None,
|
||||
};
|
||||
let method = req.method.chars().take(64).collect();
|
||||
let started = req.started.clone();
|
||||
self.lock().push(InFlight { key, connection, id, method, command, steps, since: Instant::now(), started });
|
||||
Ticket { running: self, key }
|
||||
}
|
||||
|
||||
/// The `control.running` result, as `asker`'s connection sees it.
|
||||
fn result(&self, asker: u64) -> Value {
|
||||
let rows: Vec<Value> = self
|
||||
.lock()
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let mut r = json!({
|
||||
"connection": e.connection,
|
||||
"id": e.id,
|
||||
"method": e.method,
|
||||
"running_s": (e.since.elapsed().as_secs_f64() * 10.0).round() / 10.0,
|
||||
"queued": !e.started.load(Ordering::Relaxed),
|
||||
});
|
||||
if let Some(c) = e.command {
|
||||
r["command"] = json!(c);
|
||||
}
|
||||
if let Some(n) = e.steps {
|
||||
r["steps"] = json!(n);
|
||||
}
|
||||
if e.connection == asker {
|
||||
r["self"] = json!(true);
|
||||
}
|
||||
r
|
||||
})
|
||||
.collect();
|
||||
json!({"running": rows})
|
||||
}
|
||||
}
|
||||
|
||||
/// A listed request; dropped when its reply arrives.
|
||||
struct Ticket {
|
||||
running: &'static Running,
|
||||
key: u64,
|
||||
}
|
||||
|
||||
impl Drop for Ticket {
|
||||
fn drop(&mut self) {
|
||||
let key = self.key;
|
||||
self.running.lock().retain(|e| e.key != key);
|
||||
}
|
||||
}
|
||||
|
||||
/// The reply to a timed-out request, received on a thread of its own so the request stays listed
|
||||
/// until the app answers, also once its connection has closed (#3654).
|
||||
fn watch(reply: Receiver<Value>, ticket: Ticket) -> Receiver<Value> {
|
||||
let (late_tx, late_rx) = channel();
|
||||
// A small thread per timed-out request (each connection has at most one). If none can be
|
||||
// made, the reply is lost and `control.pending` says the app ended the request without one.
|
||||
// The reply is passed on before the request leaves the list: once `control.running` no longer
|
||||
// shows it, its connection's `control.pending` has it.
|
||||
let _ = std::thread::Builder::new().stack_size(64 * 1024).spawn(move || {
|
||||
if let Ok(v) = reply.recv() {
|
||||
let _ = late_tx.send(v);
|
||||
}
|
||||
drop(late_tx);
|
||||
drop(ticket);
|
||||
});
|
||||
late_rx
|
||||
}
|
||||
|
||||
/// The reply text for a request that outlasts its wait. A request that only reads changes
|
||||
/// nothing when it finishes; an export or a save still writes its file, and any other request
|
||||
/// still applies, as it was not cancelled.
|
||||
fn timeout_message(what: &str, effect: control_pending::Effect) -> String {
|
||||
match effect {
|
||||
control_pending::Effect::ReadsOnly => {
|
||||
format!("timeout: {what} is still running in the app ({}); ask `control.pending` for its answer", effect.when_done())
|
||||
}
|
||||
control_pending::Effect::WritesFile => format!(
|
||||
"timeout: {what} is still running in the app and will still write the file when it finishes (it was not cancelled: \
|
||||
don't send it again; the design does not change); ask `control.pending` for its result"
|
||||
),
|
||||
control_pending::Effect::Changes => format!(
|
||||
"timeout: {what} is still running in the app and will still apply when it finishes (it was not cancelled: don't send it \
|
||||
again); ask `control.pending` for its result"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// The reply text for a request refused because an earlier one on the connection still runs.
|
||||
fn busy_message(p: &Pending, method: &str) -> String {
|
||||
let method: String = method.chars().take(128).collect();
|
||||
format!(
|
||||
"busy: request {} ({}) is still running, so `{method}` was not run; ask `control.pending` for that request's result, then \
|
||||
send this one again",
|
||||
p.id, p.what
|
||||
)
|
||||
}
|
||||
|
||||
fn serve(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) {
|
||||
serve_with(stream, token, tx, ctx, patient, WAITS, &RUNNING);
|
||||
}
|
||||
|
||||
fn serve_with(
|
||||
stream: TcpStream,
|
||||
token: &str,
|
||||
tx: Sender<ControlRequest>,
|
||||
ctx: egui::Context,
|
||||
patient: bool,
|
||||
waits: Waits,
|
||||
running: &'static Running,
|
||||
) {
|
||||
let connection = running.connections.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
let Ok(read) = stream.try_clone() else { return };
|
||||
let mut out = stream;
|
||||
let mut reader = BufReader::new(read);
|
||||
@@ -216,13 +403,16 @@ fn serve_with(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: e
|
||||
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!({}));
|
||||
// A request that timed out may have finished since (#1618).
|
||||
let late = pending.as_ref().and_then(|p| match p.reply.try_recv() {
|
||||
// A request that timed out may have finished since (#1618). `control.running` only
|
||||
// looks, so it leaves that reply where it is.
|
||||
let late = pending.as_ref().filter(|_| method != "control.running").and_then(|p| match p.reply.try_recv() {
|
||||
Ok(reply) => Some(reply),
|
||||
Err(TryRecvError::Disconnected) => Some(json!({"ok": false, "error": "the app ended the request without a reply"})),
|
||||
Err(TryRecvError::Empty) => None,
|
||||
});
|
||||
let mut r = if method == "control.pending" {
|
||||
let mut r = if method == "control.running" {
|
||||
json!({"ok": true, "result": running.result(connection)})
|
||||
} else if method == "control.pending" {
|
||||
match (pending.take(), late) {
|
||||
(None, _) => json!({"ok": true, "result": {"pending": null}}),
|
||||
(Some(p), Some(reply)) => json!({"ok": true, "result": {"pending": p.id, "method": p.method, "done": true, "reply": reply}}),
|
||||
@@ -234,7 +424,7 @@ fn serve_with(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: e
|
||||
}
|
||||
} else if let Some(p) = pending.as_ref().filter(|_| late.is_none()) {
|
||||
// Nothing more is queued behind it, to run unseen when it ends.
|
||||
json!({"ok": false, "busy": true, "error": format!("busy: request {} (`{}`) is still running; ask `control.pending` for its result", p.id, p.method)})
|
||||
json!({"ok": false, "busy": true, "error": busy_message(p, &method)})
|
||||
} else {
|
||||
if late.is_some() {
|
||||
// Finished, and the client moved on without asking: it is dropped.
|
||||
@@ -242,7 +432,10 @@ fn serve_with(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: e
|
||||
}
|
||||
// A batch replaces many short requests: the whole of it may take long (#743).
|
||||
let wait = if patient || method == "engine.batch" { waits.long } else { waits.short };
|
||||
let what = control_pending::describe(&method, ¶ms);
|
||||
let effect = control_pending::effect(&method, ¶ms);
|
||||
let (req, rrx) = ControlRequest::new(method.clone(), params);
|
||||
let ticket = running.add(connection, &id, &req);
|
||||
if tx.send(req).is_err() {
|
||||
break;
|
||||
}
|
||||
@@ -250,8 +443,8 @@ fn serve_with(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: e
|
||||
match rrx.recv_timeout(wait) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
let r = json!({"ok": false, "running": true, "error": format!("timeout: `{method}` is still running in the app and will still apply; ask `control.pending` for its result")});
|
||||
pending = Some(Pending { id: id.clone(), method, reply: rrx });
|
||||
let r = json!({"ok": false, "running": true, "error": timeout_message(&what, effect)});
|
||||
pending = Some(Pending { id: id.clone(), method, what, reply: watch(rrx, ticket) });
|
||||
r
|
||||
}
|
||||
}
|
||||
@@ -307,9 +500,10 @@ mod tests {
|
||||
}
|
||||
});
|
||||
let waits = Waits { short: Duration::from_millis(100), long: Duration::from_millis(100) };
|
||||
static RUNNING: Running = Running::new();
|
||||
std::thread::spawn(move || {
|
||||
if let Some(stream) = listener.incoming().flatten().next() {
|
||||
serve_with(stream, token, tx, egui::Context::default(), false, waits);
|
||||
serve_with(stream, token, tx, egui::Context::default(), false, waits, &RUNNING);
|
||||
}
|
||||
});
|
||||
let s = TcpStream::connect(addr).unwrap();
|
||||
@@ -349,6 +543,121 @@ mod tests {
|
||||
assert_eq!(*ran.lock().unwrap(), ["slow", "ui.view"], "the busy request never reached the app");
|
||||
}
|
||||
|
||||
/// #3654: `control.running` answers on every connection, at once, with what any connection
|
||||
/// has the app running or queued (command ids, never parameters); a timed-out request stays
|
||||
/// listed until the app answers it, and an idle app lists nothing.
|
||||
#[test]
|
||||
fn every_connection_sees_what_the_app_is_running() {
|
||||
static RUNNING: Running = Running::new();
|
||||
let token = "0123456789abcdef0123456789abcdef";
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let (tx, rx) = channel::<ControlRequest>();
|
||||
// The window: one request at a time, as `drain_control` takes them; the export is held
|
||||
// until the test lets it go.
|
||||
let (gate_tx, gate_rx) = channel::<()>();
|
||||
std::thread::spawn(move || {
|
||||
for req in rx {
|
||||
req.started.store(true, Ordering::Relaxed);
|
||||
if req.method == "engine.execute" {
|
||||
let _ = gate_rx.recv();
|
||||
}
|
||||
let _ = req.reply.send(json!({"ok": true, "result": req.method}));
|
||||
}
|
||||
});
|
||||
let waits = Waits { short: Duration::from_millis(100), long: Duration::from_secs(60) };
|
||||
std::thread::spawn(move || {
|
||||
for stream in listener.incoming().flatten() {
|
||||
let tx = tx.clone();
|
||||
std::thread::spawn(move || serve_with(stream, token, tx, egui::Context::default(), false, waits, &RUNNING));
|
||||
}
|
||||
});
|
||||
let open = || {
|
||||
let s = TcpStream::connect(addr).unwrap();
|
||||
s.set_read_timeout(Some(Duration::from_secs(10))).unwrap();
|
||||
let mut out = s.try_clone().unwrap();
|
||||
let mut lines = BufReader::new(s);
|
||||
move |v: Value| -> Value {
|
||||
writeln!(out, "{v}").unwrap();
|
||||
let mut l = String::new();
|
||||
lines.read_line(&mut l).unwrap();
|
||||
serde_json::from_str(&l).unwrap()
|
||||
}
|
||||
};
|
||||
let running = |ask: &mut dyn FnMut(Value) -> Value, until: &dyn Fn(&[Value]) -> bool| {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
let r = ask(json!({"id": 0, "method": "control.running", "token": token}));
|
||||
let rows = r["result"]["running"].as_array().cloned().unwrap_or_default();
|
||||
if until(&rows) || Instant::now() > deadline {
|
||||
return (r, rows);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
};
|
||||
let mut watcher = open();
|
||||
let (r, rows) = running(&mut watcher, &|_| true);
|
||||
assert!(r["ok"] == true && rows.is_empty(), "idle: {r}");
|
||||
|
||||
let mut export = open();
|
||||
let path = "/private/part.step";
|
||||
let params = json!({"command": "file.export", "params": {"path": path}});
|
||||
let r = export(json!({"id": 7, "method": "engine.execute", "params": params, "token": token}));
|
||||
assert_eq!(r["running"], true, "{r}");
|
||||
// A batch from another connection waits behind the export.
|
||||
let mut batch = open();
|
||||
let queued = std::thread::spawn(move || {
|
||||
let steps = json!([{"command": "solid.box"}, {"command": "solid.box"}]);
|
||||
batch(json!({"id": "b", "method": "engine.batch", "params": {"commands": steps}, "token": token}))
|
||||
});
|
||||
// The window may not have taken the export yet, even though it timed out.
|
||||
let (r, rows) = running(&mut watcher, &|rows| rows.len() == 2 && rows[0]["queued"] == false);
|
||||
assert_eq!(rows.len(), 2, "{r}");
|
||||
let (e, b) = (&rows[0], &rows[1]);
|
||||
assert_eq!(
|
||||
(&e["id"], &e["method"], &e["command"], &e["queued"]),
|
||||
(&json!(7), &json!("engine.execute"), &json!("file.export"), &json!(false)),
|
||||
"{r}"
|
||||
);
|
||||
assert!(e["running_s"].as_f64().is_some_and(|s| s >= 0.1), "{r}");
|
||||
assert_eq!((&b["id"], &b["method"], &b["steps"], &b["queued"]), (&json!("b"), &json!("engine.batch"), &json!(2), &json!(true)), "{r}");
|
||||
assert_ne!(e["connection"], b["connection"], "{r}");
|
||||
assert!(rows.iter().all(|row| row.get("self").is_none()), "{r}");
|
||||
assert!(!r.to_string().contains(path), "parameters stay with their connection: {r}");
|
||||
// The connection whose request timed out may ask too, and sees its own marked.
|
||||
let r = export(json!({"id": 8, "method": "control.running"}));
|
||||
let own: Vec<&Value> = r["result"]["running"].as_array().unwrap().iter().filter(|row| row["self"] == true).collect();
|
||||
assert_eq!(own.len(), 1, "{r}");
|
||||
assert_eq!(own[0]["command"], "file.export", "{r}");
|
||||
|
||||
gate_tx.send(()).unwrap();
|
||||
assert_eq!(queued.join().unwrap()["result"], "engine.batch");
|
||||
let (r, rows) = running(&mut watcher, &|rows| rows.is_empty());
|
||||
assert!(rows.is_empty(), "done: {r}");
|
||||
// Asking `control.running` did not take the export's reply from its connection.
|
||||
let r = export(json!({"id": 9, "method": "control.pending"}));
|
||||
assert_eq!((&r["result"]["pending"], &r["result"]["done"]), (&json!(7), &json!(true)), "{r}");
|
||||
}
|
||||
|
||||
/// A timed-out request that only reads is not said to "still apply", nor is one that only
|
||||
/// writes a file (an export, #1989); a change is, by name.
|
||||
#[test]
|
||||
fn timeout_text_tells_reads_from_changes() {
|
||||
let says = |method: &str, p: Value| timeout_message(&control_pending::describe(method, &p), control_pending::effect(method, &p));
|
||||
let read = says("ui.inspect", json!({}));
|
||||
assert!(read.starts_with("timeout") && read.contains("only reads") && !read.contains("will still apply"), "{read}");
|
||||
let read = says("engine.execute", json!({"command": "sketch.inspect"}));
|
||||
assert!(read.contains("only reads") && read.contains("sketch.inspect"), "{read}");
|
||||
let change = says("engine.execute", json!({"command": "solid.fillet"}));
|
||||
assert!(change.starts_with("timeout") && change.contains("will still apply") && change.contains("solid.fillet"), "{change}");
|
||||
let export = says("engine.execute", json!({"command": "file.export", "params": {"path": "/tmp/wall.step"}}));
|
||||
assert!(export.contains("will still write the file") && !export.contains("apply") && export.contains("file.export"), "{export}");
|
||||
let save = says("engine.execute", json!({"command": "file.save"}));
|
||||
assert!(save.contains("will still write the file") && !save.contains("apply"), "{save}");
|
||||
let render = says("ui.render", json!({"path": "/tmp/a.png"}));
|
||||
assert!(render.contains("will still write the file"), "{render}");
|
||||
}
|
||||
|
||||
/// #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.
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//! The graphics device was lost after the window opened: the driver was reset (on Windows a TDR,
|
||||
//! "Display driver stopped responding and has recovered") or the GPU went away (#4172). A lost
|
||||
//! device can't draw again, so the window can't show anything more, not even a message. The
|
||||
//! painter stops drawing (`egui_wgpu::DeviceLost`); the app then closes, keeping unsaved changes
|
||||
//! for recovery as a forced quit does, and starts again ([`restart`]), and the new start says what
|
||||
//! happened ([`note`]). A device lost again soon after such a restart starts the next one on the
|
||||
//! software renderer, which doesn't depend on the graphics driver.
|
||||
|
||||
use crate::startup;
|
||||
|
||||
/// Set on the start after a lost device: when the device was lost (seconds since 1970).
|
||||
pub const LOST_ENV: &str = "SOLIDCRAFT_DEVICE_LOST";
|
||||
|
||||
/// A loss this recent (seconds) counts for the start after it: its note, and a second loss
|
||||
/// within it starts on the software renderer. Inherited by later starts, an older stamp is
|
||||
/// ignored.
|
||||
const RECENT_SECS: u64 = 600;
|
||||
|
||||
/// Seconds since 1970.
|
||||
pub fn now_secs() -> u64 {
|
||||
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs())
|
||||
}
|
||||
|
||||
/// Whether `stamp` ([`LOST_ENV`]) names a loss at most [`RECENT_SECS`] before `now`.
|
||||
pub fn recent(stamp: Option<&str>, now: u64) -> bool {
|
||||
stamp.and_then(|s| s.trim().parse::<u64>().ok()).is_some_and(|at| at <= now && now - at <= RECENT_SECS)
|
||||
}
|
||||
|
||||
/// Whether the start after a lost device uses the software renderer: when this launch already
|
||||
/// followed a recent loss (the driver keeps losing it), or ran on it anyway.
|
||||
pub fn restart_on_software(after_recent_loss: bool, cpu_renderer: bool) -> bool {
|
||||
after_recent_loss || cpu_renderer
|
||||
}
|
||||
|
||||
/// What the start after a lost device tells the user, `software` when it runs on the software
|
||||
/// renderer.
|
||||
pub fn note(software: bool) -> String {
|
||||
let mut note = String::from(
|
||||
"The graphics device was reset (the graphics driver stopped responding), so SolidCraft closed and started again. Unsaved changes were kept for recovery.",
|
||||
);
|
||||
if software {
|
||||
note.push_str(" This start uses software rendering, which doesn't depend on the graphics driver.");
|
||||
}
|
||||
note
|
||||
}
|
||||
|
||||
/// Start SolidCraft again with the same arguments, the loss stamped at `lost_at`, on the software
|
||||
/// renderer when `software`.
|
||||
pub fn restart(software: bool, lost_at: u64) {
|
||||
let Ok(exe) = std::env::current_exe() else { return };
|
||||
let mut command = std::process::Command::new(exe);
|
||||
command.args(std::env::args_os().skip(1)).env(LOST_ENV, lost_at.to_string());
|
||||
if software {
|
||||
command.env(startup::RENDERER_ENV, "cpu");
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
// Not tied to this process: its own process group, no console.
|
||||
command.creation_flags(0x0000_0200 | 0x0000_0008);
|
||||
}
|
||||
if let Err(e) = command.spawn() {
|
||||
log::error!("graphics: starting again after the device was lost: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// Only a recent, well-formed stamp counts: a later start that inherited it says nothing.
|
||||
#[test]
|
||||
fn only_a_recent_loss_counts() {
|
||||
let now = 1_000_000;
|
||||
assert!(super::recent(Some("999990"), now));
|
||||
assert!(super::recent(Some(" 999400 "), now));
|
||||
assert!(!super::recent(Some("999399"), now), "older than ten minutes");
|
||||
assert!(!super::recent(Some("1000001"), now), "from the future");
|
||||
assert!(!super::recent(Some("soon"), now));
|
||||
assert!(!super::recent(None, now));
|
||||
}
|
||||
|
||||
/// The first loss starts again on the GPU; a second one soon after, on the software renderer.
|
||||
#[test]
|
||||
fn a_second_loss_in_a_row_starts_on_the_software_renderer() {
|
||||
assert!(!super::restart_on_software(false, false));
|
||||
assert!(super::restart_on_software(true, false));
|
||||
assert!(super::restart_on_software(false, true));
|
||||
assert!(super::note(true).contains("software rendering"));
|
||||
assert!(!super::note(false).contains("software rendering"));
|
||||
assert!(super::note(false).contains("kept for recovery"));
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@
|
||||
//! A `.step`/`.stp` file opens as a new design holding the file's bodies (an Import feature); a
|
||||
//! `.3mf`/`.stl`/`.obj`/`.glb`/`.gltf` file as a new design holding mesh bodies.
|
||||
//!
|
||||
//! `SOLIDCRAFT_SETTINGS_DIR` selects an explicit absolute settings directory without legacy adoption.
|
||||
//! `--control <port>` (or `SOLIDCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control
|
||||
//! server: `{"id":1,"method":"engine.execute","params":{"command":"solid.box","params":{…}}}`.
|
||||
//! Without them the channel still opens, on the default port, unless Preferences turns it off.
|
||||
//! See `solidcraft_ui_egui::control` and docs/control-protocol.md.
|
||||
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
@@ -19,42 +21,109 @@
|
||||
#[path = "../../../assets/icons/app/shared/source/app_icon.rs"]
|
||||
mod app_icon;
|
||||
mod control_server;
|
||||
mod device_lost;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod native_crash;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod native_frame;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod native_menu;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod open_documents;
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
mod space_mouse;
|
||||
mod startup;
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
mod window_fit;
|
||||
|
||||
use solidcraft_engine::{Session, io};
|
||||
use solidcraft_ui_egui::{Services, SolveApp};
|
||||
|
||||
/// Windows: put the restored window back inside the monitor's work area if it is out of reach
|
||||
/// (a gone monitor, another scale, a taskbar edge). Sizes are in points, the work area in pixels.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn fit_window_to_work_area(ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
use window_fit::Area;
|
||||
let Some((x, y, w, h)) = native_frame::work_area(frame) else { return };
|
||||
let (outer, inner, scale, maximized) = ctx.input(|i| {
|
||||
let v = i.viewport();
|
||||
(v.outer_rect, v.inner_rect, v.native_pixels_per_point, v.maximized.unwrap_or(false) || v.fullscreen.unwrap_or(false))
|
||||
});
|
||||
let (Some(outer), Some(inner), Some(scale)) = (outer, inner, scale.filter(|s| s.is_finite() && *s > 0.0)) else { return };
|
||||
if maximized {
|
||||
return;
|
||||
}
|
||||
let work = Area::new(x as f32 / scale, y as f32 / scale, w as f32 / scale, h as f32 / scale);
|
||||
let now = Area::new(outer.min.x, outer.min.y, outer.width(), outer.height());
|
||||
let fitted = window_fit::fit(now, work, (960.0, 600.0));
|
||||
if fitted != now {
|
||||
// The frame (title bar, borders) keeps its size; only the inner size follows.
|
||||
let inner_size = egui::vec2((inner.width() - (now.w - fitted.w)).max(1.0), (inner.height() - (now.h - fitted.h)).max(1.0));
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(inner_size));
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(egui::pos2(fitted.x, fitted.y)));
|
||||
}
|
||||
}
|
||||
|
||||
/// `SOLIDCRAFT_PROFILE_STARTUP=1` prints the time since launch at each startup step.
|
||||
fn mark(step: &str) {
|
||||
static START: std::sync::OnceLock<(std::time::Instant, bool)> = std::sync::OnceLock::new();
|
||||
let (start, on) = START.get_or_init(|| (std::time::Instant::now(), solidcraft_engine::geom::env::var_os("PROFILE_STARTUP").is_some()));
|
||||
if *on {
|
||||
eprintln!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
solve: SolveApp,
|
||||
/// The settings folder whose start marker the first frame clears (`startup::started`).
|
||||
starting: Option<std::path::PathBuf>,
|
||||
/// Frames begun; the second one means the first was drawn (startup is over).
|
||||
frames: u32,
|
||||
/// Started over the control channel: the window doesn't ask for the foreground.
|
||||
take_focus_unwanted: bool,
|
||||
/// The graphics device: lost, it can't draw again (`App::watch_device`, #4172).
|
||||
gpu: Option<LostWatch>,
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
space_mouse: space_mouse::SpaceMouse,
|
||||
#[cfg(target_os = "macos")]
|
||||
native_menu: Option<native_menu::NativeMenu>,
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if self.frames < 2 {
|
||||
self.frames += 1;
|
||||
// Before the window is shown: its title bar must be within reach (window_fit.rs).
|
||||
#[cfg(target_os = "windows")]
|
||||
if self.frames == 1 {
|
||||
fit_window_to_work_area(ctx, _frame);
|
||||
}
|
||||
mark(if self.frames == 1 { "first frame begins" } else { "second frame begins (first presented)" });
|
||||
// A second frame right away, which means the first was presented: the app is up.
|
||||
ctx.request_repaint();
|
||||
if self.frames == 2 {
|
||||
// The window is shown only once its first frame is presented, and Windows then
|
||||
// keeps a window of a process started from a shortcut or an installer behind the
|
||||
// others. Ask for the foreground once, now that it is on screen; never again, so
|
||||
// later frames don't take the focus from whatever the user moved to. Automation
|
||||
// over the control channel keeps the focus where it is.
|
||||
if !self.take_focus_unwanted {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Focus);
|
||||
}
|
||||
solidcraft_engine::crash::phase(solidcraft_engine::crash::RUNNING);
|
||||
// A DMG install has no installer step: put solidcraft-cli on PATH, quietly.
|
||||
#[cfg(target_os = "macos")]
|
||||
std::thread::spawn(solidcraft_ui_egui::cli_link::install_on_launch);
|
||||
// Automation runs (a control port was asked for) leave the user's AI apps alone.
|
||||
if !self.take_focus_unwanted {
|
||||
solidcraft_ui_egui::ai_connect::begin(&mut self.solve);
|
||||
}
|
||||
if let Some(dir) = self.starting.take() {
|
||||
startup::started(&dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.watch_device();
|
||||
// The design on screen is the one whose commands a crash report replays.
|
||||
self.solve.session.crash_journal = solidcraft_engine::crash::enabled();
|
||||
// The OS closing the window (Alt+F4, Cmd+Q, the window manager): ask about unsaved
|
||||
@@ -67,6 +136,10 @@ impl eframe::App for App {
|
||||
for f in if self.solve.file_picker_busy() { Vec::new() } else { open_documents::take() } {
|
||||
self.solve.open_path_async(&f);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(menu) = &mut self.native_menu {
|
||||
menu.tick(&mut self.solve, ctx);
|
||||
}
|
||||
self.solve.logic(ctx);
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if !self.solve.file_picker_busy() {
|
||||
@@ -76,14 +149,23 @@ impl eframe::App for App {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
}
|
||||
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(menu) = &mut self.native_menu {
|
||||
menu.raw_input_hook(&mut self.solve, ctx, raw);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
macos_fullscreen_content(ctx, raw);
|
||||
// Before the app's own hook, which adds the control channel's events.
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
self.space_mouse.raw_input_hook(self.solve.preferences.space_mouse_enabled, raw);
|
||||
// The 3D mouse's buttons, before the app reads the frame: Esc and held modifiers join its input.
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
solidcraft_ui_egui::space_mouse::buttons_hook(&mut self.solve, ctx, self.space_mouse.buttons(), raw);
|
||||
self.solve.raw_input_hook(raw);
|
||||
}
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
self.solve.ui(ui);
|
||||
content_ui(&mut self.solve, ui);
|
||||
}
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, self.solve.prefs());
|
||||
@@ -93,8 +175,17 @@ impl eframe::App for App {
|
||||
solidcraft_ui_egui::agent_chat::shutdown(&mut self.solve);
|
||||
// A save still being written ends before the app does.
|
||||
solidcraft_ui_egui::save_job::tick(&mut self.solve, true);
|
||||
// Pictures still being drawn off the window thread (Home cards, file previews) end
|
||||
// before the GPU is torn down: one drawn while the process exits can crash the driver.
|
||||
if !solidcraft_engine::capture::close(std::time::Duration::from_secs(5)) {
|
||||
log::warn!("a picture was still being drawn as the app closed");
|
||||
}
|
||||
solidcraft_ui_egui::recovery_ui::close(&mut self.solve);
|
||||
solidcraft_ui_egui::open_elsewhere::close(&mut self.solve);
|
||||
solidcraft_engine::crash::end_session();
|
||||
if let Some(lost_at) = self.gpu.as_ref().and_then(|g| g.lost_at) {
|
||||
device_lost::restart(self.gpu.as_ref().is_some_and(|g| g.restart_on_software), lost_at);
|
||||
}
|
||||
// An update asked to restart into the new version or to run its installer.
|
||||
if let Some((program, args)) = solidcraft_ui_egui::update::take_launch(&mut self.solve) {
|
||||
let mut command = std::process::Command::new(&program);
|
||||
@@ -114,6 +205,79 @@ impl eframe::App for App {
|
||||
}
|
||||
}
|
||||
|
||||
/// The window's graphics device, watched for its loss.
|
||||
struct LostWatch {
|
||||
lost: eframe::egui_wgpu::DeviceLost,
|
||||
/// Where the next start runs once this device is lost ([`device_lost::restart_on_software`]).
|
||||
restart_on_software: bool,
|
||||
/// When the loss was seen: the app is closing to start again.
|
||||
lost_at: Option<u64>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// The graphics device was lost (#4172): the window can't draw again, so close, keeping
|
||||
/// unsaved changes for recovery as a forced quit does (the window can't ask about them), and
|
||||
/// start again once closed ([`device_lost::restart`]).
|
||||
fn watch_device(&mut self) {
|
||||
let Some(gpu) = self.gpu.as_mut().filter(|g| g.lost_at.is_none() && g.lost.is_lost()) else { return };
|
||||
let reason = gpu.lost.reason().unwrap_or_default();
|
||||
log::error!("graphics: the device was lost ({reason}); closing to start again");
|
||||
eprintln!("SolidCraft graphics: the device was lost ({reason}); starting again");
|
||||
solidcraft_engine::crash::phase(&format!("graphics device lost: {reason}"));
|
||||
gpu.lost_at = Some(device_lost::now_secs());
|
||||
self.solve.quit_requested = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux file dialogs match patterns by case, so each extension goes in as lower and upper
|
||||
/// case, plus FreeCAD's own mixed casing.
|
||||
fn case_variants(exts: &[&str]) -> Vec<String> {
|
||||
let mut all: Vec<String> = Vec::new();
|
||||
for e in exts {
|
||||
let mut forms = vec![e.to_ascii_lowercase(), e.to_ascii_uppercase()];
|
||||
if e.eq_ignore_ascii_case("fcstd") {
|
||||
forms.push("FCStd".into());
|
||||
}
|
||||
for v in forms {
|
||||
if !all.contains(&v) {
|
||||
all.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
all
|
||||
}
|
||||
|
||||
/// egui's root Ui spans the whole viewport, including native safe areas. Lay out the app
|
||||
/// inside the unobscured rectangle so its application bar can receive native pointer input.
|
||||
fn content_ui(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let content = ui.ctx().content_rect();
|
||||
let above = egui::Rect::from_min_max(ui.max_rect().min, egui::pos2(ui.max_rect().right(), content.top()));
|
||||
ui.painter().rect_filled(above, 0.0, solidcraft_ui_egui::theme::Tokens::get().app_bar);
|
||||
ui.scope_builder(egui::UiBuilder::new().max_rect(content), |ui| app.ui(ui));
|
||||
}
|
||||
|
||||
/// A transparent macOS title bar still intercepts clicks at the top in fullscreen (#3643).
|
||||
/// `contentLayoutRect` reports the whole content view once that bar auto-hides, so reserve
|
||||
/// the native titled-window height even then. AppKit supplies the height in logical points;
|
||||
/// egui's UI zoom is separate from the display's Retina scale.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn macos_fullscreen_content(ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
let Some(mtm) = objc2::MainThreadMarker::new() else { return };
|
||||
let frame = objc2_app_kit::NSWindow::frameRectForContentRect_styleMask(Default::default(), objc2_app_kit::NSWindowStyleMask::Titled, mtm);
|
||||
fullscreen_content_insets(raw, frame.size.height as f32, ctx.zoom_factor());
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn fullscreen_content_insets(raw: &mut egui::RawInput, title_height: f32, zoom: f32) {
|
||||
let fullscreen = raw.viewports.get(&raw.viewport_id).and_then(|v| v.fullscreen).unwrap_or(false);
|
||||
let mut insets = raw.safe_area_insets.unwrap_or_default();
|
||||
if fullscreen && title_height.is_finite() && title_height > 0.0 && zoom.is_finite() && zoom > 0.0 {
|
||||
insets.0.top = insets.0.top.max(title_height / zoom);
|
||||
}
|
||||
// Explicitly reset on leaving fullscreen: None would retain the preceding frame's inset.
|
||||
raw.safe_area_insets = Some(insets);
|
||||
}
|
||||
|
||||
fn services(cc: &eframe::CreationContext<'_>) -> Services {
|
||||
let parent = rfd::AsyncFileDialog::new().set_parent(cc);
|
||||
let open_parent = parent.clone();
|
||||
@@ -125,48 +289,14 @@ fn services(cc: &eframe::CreationContext<'_>) -> Services {
|
||||
let mut dialog = open_parent.clone();
|
||||
if !first.is_empty() {
|
||||
let label = first.iter().map(|e| e.to_uppercase()).collect::<Vec<_>>().join(", ");
|
||||
let both: Vec<String> = first.iter().flat_map(|e| [e.to_lowercase(), e.to_uppercase()]).collect();
|
||||
dialog = dialog.add_filter(label, &both);
|
||||
dialog = dialog.add_filter(label, &case_variants(first));
|
||||
}
|
||||
let future = dialog
|
||||
.add_filter(
|
||||
"Designs, CAD files and meshes",
|
||||
&[
|
||||
io::DESIGN_EXT,
|
||||
io::LEGACY_DESIGN_EXT,
|
||||
"json",
|
||||
"step",
|
||||
"stp",
|
||||
"STEP",
|
||||
"STP",
|
||||
"igs",
|
||||
"iges",
|
||||
"IGS",
|
||||
"IGES",
|
||||
"FCStd",
|
||||
"fcstd",
|
||||
"3dm",
|
||||
"3DM",
|
||||
"3mf",
|
||||
"3MF",
|
||||
"stl",
|
||||
"STL",
|
||||
"obj",
|
||||
"OBJ",
|
||||
"glb",
|
||||
"GLB",
|
||||
"gltf",
|
||||
"GLTF",
|
||||
],
|
||||
)
|
||||
.add_filter("SolidCraft design (container or JSON)", &[io::DESIGN_EXT, io::LEGACY_DESIGN_EXT, "json"])
|
||||
.add_filter("STEP", &["step", "stp", "STEP", "STP"])
|
||||
.add_filter("IGES", &["igs", "iges", "IGS", "IGES"])
|
||||
.add_filter("FreeCAD and Rhino", &["FCStd", "fcstd", "3dm", "3DM"])
|
||||
.add_filter("Mesh (3MF, STL, OBJ, glTF)", &["3mf", "3MF", "stl", "STL", "obj", "OBJ", "glb", "GLB", "gltf", "GLTF"])
|
||||
.add_filter("Sketch drawings (DXF, SVG)", &["dxf", "DXF", "svg", "SVG"])
|
||||
.add_filter("All files", &["*"])
|
||||
.pick_file();
|
||||
// The groups come from the format registry, so a new format joins this dialog by itself.
|
||||
dialog = dialog.add_filter("Designs, CAD files and meshes", &case_variants(&io::formats::open_extensions(true)));
|
||||
for (label, exts) in io::formats::import_filters(true) {
|
||||
dialog = dialog.add_filter(label, &case_variants(&exts));
|
||||
}
|
||||
let future = dialog.add_filter("All files", &["*"]).pick_file();
|
||||
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
|
||||
})),
|
||||
// One kind of file (the agent chat's attachments): only the types it names, as given and in
|
||||
@@ -206,6 +336,7 @@ fn services(cc: &eframe::CreationContext<'_>) -> Services {
|
||||
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
|
||||
})),
|
||||
graphics_path: graphics_path().map(|p| p.to_string_lossy().to_string()),
|
||||
window_system: window_system_now(),
|
||||
http_get: Some(std::sync::Arc::new(http_get)),
|
||||
http_download: Some(std::sync::Arc::new(http_download)),
|
||||
// Already an installed app: only the browser build offers to install.
|
||||
@@ -284,7 +415,7 @@ fn agent_http(r: &solidcraft_ui_egui::agent_chat::backend::HttpRequest) -> Resul
|
||||
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||
.tls_config(tls)
|
||||
.http_status_as_error(false)
|
||||
.timeout_global(Some(Duration::from_secs(r.timeout_s.clamp(1, 900))))
|
||||
.timeout_global(Some(Duration::from_secs(r.timeout_s.clamp(1, 3600))))
|
||||
.timeout_connect(Some(Duration::from_secs(10)))
|
||||
.user_agent("SolidCraft")
|
||||
.max_redirects(0)
|
||||
@@ -375,14 +506,15 @@ fn http_call(url: &str, timeout: std::time::Duration) -> Result<ureq::http::Resp
|
||||
}
|
||||
|
||||
/// The per-user settings folder: eframe's storage (`app.ron`) and the anti-aliasing preference.
|
||||
fn settings_dir() -> Option<std::path::PathBuf> {
|
||||
eframe::storage_dir("SolidCraft")
|
||||
fn settings_dir() -> Result<Option<std::path::PathBuf>, String> {
|
||||
let selected = std::env::var_os("SOLIDCRAFT_SETTINGS_DIR");
|
||||
startup::settings_directory(selected.as_deref(), eframe::storage_dir("SolidCraft"))
|
||||
}
|
||||
|
||||
/// The anti-aliasing preference, kept next to eframe's storage (it is needed before the
|
||||
/// window opens).
|
||||
fn graphics_path() -> Option<std::path::PathBuf> {
|
||||
settings_dir().map(|d| d.join("graphics.json"))
|
||||
settings_dir().ok().flatten().map(|d| d.join("graphics.json"))
|
||||
}
|
||||
|
||||
/// Compatibility shim (docs/compatibility.md): on the first launch after the rename, copy the
|
||||
@@ -392,7 +524,7 @@ fn graphics_path() -> Option<std::path::PathBuf> {
|
||||
/// old app id's `~/.var/app` folder; that copy comes first, as it is the newest. The old folders
|
||||
/// are never changed.
|
||||
fn adopt_legacy_settings() {
|
||||
let (Some(current), Some(legacy)) = (settings_dir(), eframe::storage_dir("SolveCraft")) else { return };
|
||||
let (Some(current), Some(legacy)) = (settings_dir().ok().flatten(), eframe::storage_dir("SolveCraft")) else { return };
|
||||
let legacy_eframe = eframe::storage_dir(solidcraft_engine::io::LEGACY_APP_ID);
|
||||
let old_flatpak = solidcraft_engine::io::legacy_flatpak_path(¤t);
|
||||
if let Err(e) = adopt_settings(¤t, old_flatpak.as_deref(), &legacy, legacy_eframe.as_deref()) {
|
||||
@@ -431,6 +563,18 @@ fn backend_preference() -> Option<String> {
|
||||
graphics().and_then(|v| v.get("backend").and_then(serde_json::Value::as_str).map(str::to_string))
|
||||
}
|
||||
|
||||
/// The viewport scene's sample count: the app's anti-aliasing (the window's pass is 1x), clamped
|
||||
/// by the backend's policy (OpenGL renders 1x, see `effective_msaa_samples`). A count the device
|
||||
/// cannot render with falls back to the supported default.
|
||||
fn scene_samples(backend: eframe::wgpu::Backend, samples: u32, supported: &[u32]) -> u32 {
|
||||
let requested = eframe::egui_wgpu::effective_msaa_samples(backend, samples.max(1));
|
||||
if requested <= 1 || supported.is_empty() || supported.contains(&requested) {
|
||||
requested
|
||||
} else {
|
||||
supported.iter().copied().filter(|&n| n <= solidcraft_ui_egui::prefs::Prefs::default().msaa).max().unwrap_or(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Anti-aliasing samples from Preferences (4 when unset).
|
||||
fn msaa() -> u16 {
|
||||
graphics()
|
||||
@@ -453,7 +597,8 @@ const FLOAT_DEPTH_BITS: u8 = 32;
|
||||
const STENCIL_BITS: u8 = 8;
|
||||
const MSAA: u16 = 4;
|
||||
|
||||
fn app_viewport() -> Result<egui::ViewportBuilder, eframe::Error> {
|
||||
/// `custom_titlebar`: no OS decorations, the application bar is the title bar.
|
||||
fn app_viewport(custom_titlebar: bool) -> Result<egui::ViewportBuilder, eframe::Error> {
|
||||
// Let macOS use the bundle's layered icon; eframe's default bitmap would replace it.
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = egui::IconData::default();
|
||||
@@ -466,7 +611,7 @@ fn app_viewport() -> Result<egui::ViewportBuilder, eframe::Error> {
|
||||
.with_inner_size([1600.0, 1000.0])
|
||||
.with_min_inner_size([960.0, 600.0])
|
||||
.with_drag_and_drop(true)
|
||||
.with_decorations(!CUSTOM_TITLEBAR)
|
||||
.with_decorations(!custom_titlebar)
|
||||
.with_fullsize_content_view(true)
|
||||
.with_titlebar_shown(false)
|
||||
.with_title_shown(false)
|
||||
@@ -477,7 +622,14 @@ fn app_viewport() -> Result<egui::ViewportBuilder, eframe::Error> {
|
||||
const APP_ID: &str = solidcraft_engine::io::APP_ID;
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
adopt_legacy_settings();
|
||||
#[cfg(target_os = "macos")]
|
||||
objc2_foundation::NSProcessInfo::processInfo().setProcessName(objc2_foundation::ns_string!("SolidCraft"));
|
||||
mark("main");
|
||||
// Validate before legacy adoption, crash markers or any user preference read.
|
||||
let settings = settings_dir().map_err(|error| eframe::Error::AppCreation(error.into()))?;
|
||||
if std::env::var_os("SOLIDCRAFT_SETTINGS_DIR").is_none() {
|
||||
adopt_legacy_settings();
|
||||
}
|
||||
let mut safe_mode = startup::env_on(std::env::var(startup::SAFE_MODE_ENV).ok().as_deref());
|
||||
let mut control_port: Option<u16> = solidcraft_engine::geom::env::var("CONTROL_PORT").and_then(|p| p.parse().ok());
|
||||
let mut files = Vec::new();
|
||||
@@ -514,7 +666,6 @@ fn main() -> eframe::Result {
|
||||
}
|
||||
// Settings that don't read are moved aside; launches that never got a window are counted,
|
||||
// and after a few in a row this one starts in safe mode (docs/book/src/guide/troubleshooting.md).
|
||||
let settings = settings_dir();
|
||||
let mut notes: Vec<String> = Vec::new();
|
||||
let mut failed_starts = 0;
|
||||
if let Some(dir) = &settings {
|
||||
@@ -526,6 +677,11 @@ fn main() -> eframe::Result {
|
||||
notes.push(format!("The last {failed_starts} launches stopped before the window opened, so this one is in safe mode."));
|
||||
}
|
||||
let cpu_renderer = safe_mode || startup::cpu_renderer_env(std::env::var(startup::RENDERER_ENV).ok().as_deref());
|
||||
// The launch before this one closed because its graphics device was lost (`App::watch_device`).
|
||||
let after_loss = device_lost::recent(std::env::var(device_lost::LOST_ENV).ok().as_deref(), device_lost::now_secs());
|
||||
if after_loss {
|
||||
notes.push(device_lost::note(cpu_renderer));
|
||||
}
|
||||
if safe_mode {
|
||||
notes.push("Safe mode: default settings (yours are kept for the next normal start), no recovery offer, software rendering.".into());
|
||||
}
|
||||
@@ -547,13 +703,21 @@ fn main() -> eframe::Result {
|
||||
// module on Windows, and on every system the startup step the app was in.
|
||||
#[cfg(target_os = "windows")]
|
||||
native_crash::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,
|
||||
_ => eframe::wgpu::Backends::all(),
|
||||
};
|
||||
let custom_titlebar = CUSTOM_TITLEBAR && !startup::system_title_bar(cfg!(target_os = "windows"), started);
|
||||
eframe::egui_wgpu::set_startup_hook(startup_step);
|
||||
solidcraft_engine::crash::phase("window");
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: app_viewport()?,
|
||||
depth_buffer: depth_bits,
|
||||
stencil_buffer: STENCIL_BITS,
|
||||
multisampling: samples,
|
||||
viewport: app_viewport(custom_titlebar)?,
|
||||
// The window's pass (egui's) has no depth and one sample: the viewport draws its scene
|
||||
// offscreen with its own depth and MSAA, then blits it, so the UI pass pays for neither.
|
||||
depth_buffer: 0,
|
||||
stencil_buffer: 0,
|
||||
multisampling: 1,
|
||||
// Kept with the other settings, not in a folder named after the app id. Safe mode reads
|
||||
// and writes a scratch copy, leaving the user's settings as they were.
|
||||
persistence_path: if safe_mode {
|
||||
@@ -570,12 +734,24 @@ fn main() -> eframe::Result {
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
let mut options = options;
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
if prefer_x11(
|
||||
std::env::var_os("DISPLAY").is_some(),
|
||||
std::env::var_os("WAYLAND_DISPLAY").is_some(),
|
||||
std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref(),
|
||||
solidcraft_engine::geom::env::var("WAYLAND").as_deref(),
|
||||
) {
|
||||
let x11 = {
|
||||
// SOLIDCRAFT_WAYLAND wins over Preferences › Graphics › Window system (#3083).
|
||||
let environment = solidcraft_engine::geom::env::var("WAYLAND");
|
||||
let preference = window_system_preference();
|
||||
let wayland_override = environment.as_deref().or(window_system_override(preference.as_deref()));
|
||||
let wayland_display = std::env::var_os("WAYLAND_DISPLAY").is_some();
|
||||
let x11 = prefer_x11(
|
||||
std::env::var_os("DISPLAY").is_some(),
|
||||
wayland_display,
|
||||
std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref(),
|
||||
wayland_override,
|
||||
);
|
||||
// Without a Wayland socket winit opens on X11 anyway.
|
||||
let _ = WINDOW_SYSTEM.set(if x11 || !wayland_display { "x11" } else { "wayland" });
|
||||
x11
|
||||
};
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
if x11 {
|
||||
options.event_loop_builder = Some(Box::new(|b| {
|
||||
use winit::platform::x11::EventLoopBuilderExtX11;
|
||||
b.with_x11();
|
||||
@@ -587,22 +763,31 @@ fn main() -> eframe::Result {
|
||||
let created = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let created_in_app = created.clone();
|
||||
let starting = settings.clone();
|
||||
mark("run_native");
|
||||
let result = eframe::run_native(
|
||||
"SolidCraft",
|
||||
options,
|
||||
Box::new(move |cc| {
|
||||
created_in_app.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
solidcraft_engine::crash::phase("app setup");
|
||||
solidcraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx);
|
||||
mark("window and device ready");
|
||||
solidcraft_ui_egui::fonts::setup_bundled_fonts(&cc.egui_ctx);
|
||||
solidcraft_ui_egui::fonts::load_system_fonts_in_background(&cc.egui_ctx);
|
||||
mark("fonts");
|
||||
let mut app = SolveApp::new(Session::default(), services(cc));
|
||||
app.apply_off_thread = Some(solidcraft_ui_egui::dialogs::APPLY_AT_ONCE);
|
||||
app.custom_titlebar = CUSTOM_TITLEBAR;
|
||||
// Windows: the OS hit test offers the resize border, at the window's DPI, from the
|
||||
// first message (#1381).
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
app.native_resize_border = CUSTOM_TITLEBAR && native_frame::install(cc);
|
||||
if CUSTOM_TITLEBAR && !app.native_resize_border {
|
||||
// Windows: the taskbar button shows the mark even when slow first frames keep the
|
||||
// taskbar waiting for the window's icon.
|
||||
if !native_frame::set_class_icon(cc) {
|
||||
eprintln!("SolidCraft window: no class icon; the taskbar may show a generic one");
|
||||
}
|
||||
// The OS hit test offers the resize border, at the window's DPI, from the first
|
||||
// message (#1381).
|
||||
app.native_resize_border = custom_titlebar && native_frame::install(cc);
|
||||
if custom_titlebar && !app.native_resize_border {
|
||||
eprintln!("SolidCraft window: no native resize border; egui edge zones instead");
|
||||
}
|
||||
}
|
||||
@@ -611,10 +796,17 @@ fn main() -> eframe::Result {
|
||||
if let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))).filter(|_| !safe_mode) {
|
||||
app.load_prefs(&p);
|
||||
}
|
||||
mark("app state and prefs");
|
||||
let gpu = cc.wgpu_render_state.as_ref().map(|rs| LostWatch {
|
||||
lost: rs.device_lost.clone(),
|
||||
restart_on_software: device_lost::restart_on_software(after_loss, cpu_renderer),
|
||||
lost_at: None,
|
||||
});
|
||||
if let Some(rs) = &cc.wgpu_render_state {
|
||||
eprintln!("SolidCraft graphics: {:?}, MSAA {}x (requested {}x)", rs.adapter.get_info().backend, rs.msaa_samples, samples);
|
||||
let scene_samples = scene_samples(rs.adapter.get_info().backend, u32::from(samples), &rs.msaa_supported);
|
||||
eprintln!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
|
||||
solidcraft_engine::crash::phase("viewport pipelines (shader compile)");
|
||||
app.set_wgpu(rs, depth_bits, STENCIL_BITS, rs.msaa_samples);
|
||||
app.set_wgpu_split(rs, depth_bits, STENCIL_BITS, scene_samples);
|
||||
solidcraft_ui_egui::gpu::install_picture_renderer(rs);
|
||||
// The graphics driver is loaded now; it may have put its own exception filter
|
||||
// in front of ours.
|
||||
@@ -624,12 +816,15 @@ fn main() -> eframe::Result {
|
||||
solidcraft_ui_egui::prefs::check_msaa(&mut app, u32::from(samples));
|
||||
let info = rs.adapter.get_info();
|
||||
solidcraft_engine::crash::set_environment(&format!(
|
||||
"Renderer: {:?} {} ({}), MSAA {}x",
|
||||
info.backend, info.name, info.driver, rs.msaa_samples
|
||||
"Renderer: {:?} {} ({}), MSAA {scene_samples}x",
|
||||
info.backend, info.name, info.driver
|
||||
));
|
||||
}
|
||||
if let Some(port) = control_port {
|
||||
let rx = control_server::start(port, cc.egui_ctx.clone());
|
||||
mark("viewport pipelines");
|
||||
// `--control`/the environment name a port; otherwise the preference (on by default)
|
||||
// opens the default one (docs/control-protocol.md).
|
||||
if let Some(listen_on) = solidcraft_engine::control_auth::plan(control_port, app.preferences.agent_control) {
|
||||
let rx = control_server::start(listen_on, cc.egui_ctx.clone());
|
||||
app = app.with_control(rx);
|
||||
}
|
||||
// The agent chat's MCP bridge reaches the app through an endpoint of its own, started
|
||||
@@ -644,6 +839,9 @@ fn main() -> eframe::Result {
|
||||
app.session.mark_saved();
|
||||
app.session.undo.clear();
|
||||
}
|
||||
// Lock files next to open designs: a design open on another computer says so (#5467),
|
||||
// the files given at launch included.
|
||||
solidcraft_ui_egui::open_elsewhere::start(&mut app);
|
||||
// A launch from Finder: its files arrived before the window.
|
||||
#[cfg(target_os = "macos")]
|
||||
let files = {
|
||||
@@ -680,13 +878,30 @@ fn main() -> eframe::Result {
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
let space_mouse = space_mouse::SpaceMouse::start(&cc.egui_ctx);
|
||||
#[cfg(target_os = "macos")]
|
||||
let native_menu = match native_menu::NativeMenu::new(&app, &cc.egui_ctx) {
|
||||
Ok(menu) => Some(menu),
|
||||
Err(error) => {
|
||||
app.set_status(format!("System menu: {error}"), true);
|
||||
None
|
||||
}
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
app.native_file_menu = native_menu.is_some();
|
||||
}
|
||||
mark("app created");
|
||||
solidcraft_engine::crash::phase("first frame");
|
||||
Ok(Box::new(App {
|
||||
solve: app,
|
||||
starting,
|
||||
frames: 0,
|
||||
take_focus_unwanted: control_port.is_some(),
|
||||
gpu,
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
space_mouse,
|
||||
#[cfg(target_os = "macos")]
|
||||
native_menu,
|
||||
}))
|
||||
}),
|
||||
);
|
||||
@@ -717,6 +932,9 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
|
||||
eprintln!("SolidCraft graphics: starting {backends:?}");
|
||||
setup.instance_descriptor.backends = backends;
|
||||
}
|
||||
let intel_mac = cfg!(all(target_os = "macos", target_arch = "x86_64"));
|
||||
let indirect = std::env::var(startup::INDIRECT_VALIDATION_ENV).ok();
|
||||
setup.instance_descriptor.flags = startup::window_instance_flags(setup.instance_descriptor.flags, intel_mac, indirect.as_deref());
|
||||
if cpu {
|
||||
setup.native_adapter_selector = Some(std::sync::Arc::new(startup::pick_cpu_adapter));
|
||||
}
|
||||
@@ -754,6 +972,36 @@ fn no_window(error: eframe::Error, tried_cpu: bool, settings: Option<&std::path:
|
||||
Err(error)
|
||||
}
|
||||
|
||||
/// Linux: the window system this run opened with (`wayland` or `x11`), for Preferences (#3083).
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
static WINDOW_SYSTEM: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
|
||||
|
||||
/// The window system this run uses, where there is a choice (Linux).
|
||||
fn window_system_now() -> Option<&'static str> {
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
return WINDOW_SYSTEM.get().copied();
|
||||
#[cfg(not(all(unix, not(target_os = "macos"))))]
|
||||
None
|
||||
}
|
||||
|
||||
/// Preferences › Graphics › Window system (`graphics.json`'s `window_system`), read before the
|
||||
/// window opens.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn window_system_preference() -> Option<String> {
|
||||
graphics().and_then(|v| v.get("window_system").and_then(serde_json::Value::as_str).map(str::to_string))
|
||||
}
|
||||
|
||||
/// The window system preference as the `SOLIDCRAFT_WAYLAND` value it stands for: `wayland` is
|
||||
/// `1`, `x11` is `0`, and `auto` (or anything else) leaves the choice to [`prefer_x11`].
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn window_system_override(preference: Option<&str>) -> Option<&'static str> {
|
||||
match preference.map(str::trim) {
|
||||
Some("wayland") => Some("1"),
|
||||
Some("x11") => Some("0"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to run through XWayland rather than native Wayland. winit has no file drag-and-drop
|
||||
/// on Wayland (only on X11), so dropping files from the file manager showed a "no" cursor: use
|
||||
/// XWayland when it's there. Except on niri, whose XWayland (xwayland-satellite) breaks window
|
||||
@@ -791,14 +1039,27 @@ mod backend_tests {
|
||||
assert!(prefer_x11(true, true, Some("niri"), Some("0")));
|
||||
assert!(!prefer_x11(false, true, Some("niri"), Some("0")));
|
||||
}
|
||||
|
||||
/// Preferences › Graphics › Window system stands for the same values (#3083).
|
||||
#[test]
|
||||
fn the_window_system_preference_maps_to_the_override() {
|
||||
use super::window_system_override as over;
|
||||
assert_eq!(
|
||||
(over(Some("wayland")), over(Some("x11")), over(Some("auto")), over(None), over(Some("junk"))),
|
||||
(Some("1"), Some("0"), None, None, None)
|
||||
);
|
||||
// Wayland chosen on GNOME keeps native Wayland; X11 chosen on niri goes through XWayland.
|
||||
assert!(!prefer_x11(true, true, Some("GNOME"), over(Some("wayland"))));
|
||||
assert!(prefer_x11(true, true, Some("niri"), over(Some("x11"))));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod icon_tests {
|
||||
mod viewport_tests {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn macos_preserves_the_bundle_icon_instead_of_setting_a_runtime_bitmap() {
|
||||
let viewport = super::app_viewport().expect("viewport");
|
||||
let viewport = super::app_viewport(super::CUSTOM_TITLEBAR).expect("viewport");
|
||||
assert_eq!(viewport.icon.as_deref(), Some(&eframe::egui::IconData::default()));
|
||||
assert_eq!(viewport.app_id.as_deref(), Some(super::APP_ID));
|
||||
}
|
||||
@@ -806,12 +1067,89 @@ mod icon_tests {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[test]
|
||||
fn desktop_viewport_supplies_the_original_mark_to_the_window() {
|
||||
let viewport = super::app_viewport().expect("viewport");
|
||||
let viewport = super::app_viewport(super::CUSTOM_TITLEBAR).expect("viewport");
|
||||
let icon = viewport.icon.expect("running window must have an icon");
|
||||
assert_eq!((icon.width, icon.height), (64, 64));
|
||||
assert_eq!(icon.rgba, super::app_icon::rgba(64).expect("original mark"));
|
||||
assert_eq!(viewport.app_id.as_deref(), Some(super::APP_ID));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen_bar_clears_native_chrome_and_undo_redo_keep_working() {
|
||||
use egui::{Event, PointerButton, Pos2, RawInput, Rect, ViewportId, vec2};
|
||||
use serde_json::json;
|
||||
use solidcraft_ui_egui::scenario::handle_at;
|
||||
|
||||
// AppKit points are independent of Retina scale and of the user's UI zoom.
|
||||
for (zoom, retina) in [(1.0, 1.0), (1.0, 2.0), (1.5, 2.0), (2.0, 2.0)] {
|
||||
let ctx = egui::Context::default();
|
||||
solidcraft_ui_egui::fonts::setup_bundled_fonts(&ctx);
|
||||
ctx.set_zoom_factor(zoom);
|
||||
let mut app = super::SolveApp::new(super::Session::default(), Default::default());
|
||||
app.integrated_titlebar = true;
|
||||
for (command, params) in [
|
||||
("sketch.create", json!({"plane":"XY"})),
|
||||
("sketch.rectangle.two_point", json!({"p0":[0,0],"p1":[40,30]})),
|
||||
("sketch.finish", json!({})),
|
||||
("solid.extrude", json!({"distance":15})),
|
||||
] {
|
||||
app.run(command, params).unwrap();
|
||||
}
|
||||
let mut time = 0.0;
|
||||
let mut frame = |app: &mut super::SolveApp, fullscreen, events| {
|
||||
time += 0.05;
|
||||
let mut raw = RawInput {
|
||||
screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1600.0, 1000.0))),
|
||||
time: Some(time),
|
||||
events,
|
||||
..Default::default()
|
||||
};
|
||||
let viewport = raw.viewports.get_mut(&ViewportId::ROOT).unwrap();
|
||||
viewport.fullscreen = Some(fullscreen);
|
||||
viewport.native_pixels_per_point = Some(retina);
|
||||
super::fullscreen_content_insets(&mut raw, 32.0, zoom);
|
||||
let _ = ctx.run_ui(raw, |ui| super::content_ui(app, ui));
|
||||
};
|
||||
// Enter, leave, and enter again: no stale inset when returning to a normal window.
|
||||
for fullscreen in [false, true, false, true] {
|
||||
frame(&mut app, fullscreen, vec![]);
|
||||
frame(&mut app, fullscreen, vec![]);
|
||||
let top = if fullscreen { 32.0 / zoom } else { 0.0 };
|
||||
for name in ["appbar:file", "appbar:undo", "appbar:redo", "appbar:help"] {
|
||||
let at = handle_at(name).unwrap();
|
||||
assert!((at.y - top - 17.0).abs() < 0.1, "{name}: {at:?}, fullscreen={fullscreen}, zoom={zoom}");
|
||||
}
|
||||
for (handle, bodies) in [("appbar:undo", 0), ("appbar:redo", 1)] {
|
||||
let at = handle_at(handle).unwrap();
|
||||
frame(&mut app, fullscreen, vec![Event::PointerMoved(at)]);
|
||||
for pressed in [true, false] {
|
||||
frame(
|
||||
&mut app,
|
||||
fullscreen,
|
||||
vec![Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() }],
|
||||
);
|
||||
}
|
||||
assert_eq!(app.session.model.state().bodies.len(), bodies, "{handle}, fullscreen={fullscreen}, zoom={zoom}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen_inset_preserves_existing_safe_areas_and_resets_on_exit() {
|
||||
use egui::epaint::MarginF32;
|
||||
use egui::{RawInput, SafeAreaInsets, ViewportId};
|
||||
let mut raw = RawInput::default();
|
||||
raw.viewports.get_mut(&ViewportId::ROOT).unwrap().fullscreen = Some(true);
|
||||
raw.safe_area_insets = Some(SafeAreaInsets(MarginF32 { top: 40.0, left: 5.0, right: 6.0, bottom: 7.0 }));
|
||||
super::fullscreen_content_insets(&mut raw, 32.0, 2.0);
|
||||
assert_eq!(raw.safe_area_insets.unwrap().0, MarginF32 { top: 40.0, left: 5.0, right: 6.0, bottom: 7.0 });
|
||||
|
||||
raw.viewports.get_mut(&ViewportId::ROOT).unwrap().fullscreen = Some(false);
|
||||
raw.safe_area_insets = None;
|
||||
super::fullscreen_content_insets(&mut raw, 32.0, 1.0);
|
||||
assert_eq!(raw.safe_area_insets, Some(SafeAreaInsets::default()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -848,4 +1186,13 @@ mod msaa_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_scene_follows_the_backend_policy_too() {
|
||||
use eframe::wgpu::Backend;
|
||||
assert_eq!(super::scene_samples(Backend::Gl, 4, &[1, 4]), 1);
|
||||
assert_eq!(super::scene_samples(Backend::Gl, 4, &[]), 1);
|
||||
assert_eq!(super::scene_samples(Backend::Vulkan, 4, &[1, 4]), 4);
|
||||
assert_eq!(super::scene_samples(Backend::Dx12, 8, &[1, 2, 4]), 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
//! window rescales once at the crossing and stays under the pointer, instead of the OS's move
|
||||
//! loop and winit disagreeing about a frame the window doesn't have.
|
||||
//!
|
||||
//! Replacing a window procedure has no safe API, hence the `unsafe_code` allowance on this
|
||||
//! module (the app crate denies it; see `native_crash.rs` and `open_documents.rs` for the others).
|
||||
//! Replacing a window procedure and setting a window class's icon have no safe API, hence the
|
||||
//! `unsafe_code` allowance on this module (the app crate denies it; see `native_crash.rs` and
|
||||
//! `open_documents.rs` for the others).
|
||||
//! The procedure is installed once, on the thread that owns the window, for the main window only,
|
||||
//! and calls the previous one (winit's) for everything it doesn't answer.
|
||||
//!
|
||||
//! [`set_class_icon`] gives the window's class the exe's icon, so the taskbar button shows the
|
||||
//! mark even when the window is too busy to answer the taskbar's request for its icon.
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::sync::atomic::{AtomicIsize, Ordering};
|
||||
@@ -26,12 +30,81 @@ use std::sync::atomic::{AtomicIsize, Ordering};
|
||||
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
||||
use solidcraft_ui_egui::titlebar;
|
||||
use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, RECT, SIZE, WPARAM};
|
||||
use windows_sys::Win32::Graphics::Gdi::{GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromWindow};
|
||||
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows_sys::Win32::UI::HiDpi::{GetDpiForWindow, GetSystemMetricsForDpi};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
CallWindowProcW, GWL_STYLE, GWLP_WNDPROC, GetClientRect, GetWindowLongPtrW, GetWindowRect, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTLEFT,
|
||||
HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, IsIconic, IsZoomed, SM_CXPADDEDBORDER, SM_CXSIZEFRAME, SetWindowLongPtrW, WM_GETDPISCALEDSIZE,
|
||||
WM_NCDESTROY, WM_NCHITTEST, WNDPROC, WS_THICKFRAME,
|
||||
CallWindowProcW, GCLP_HICON, GCLP_HICONSM, GWL_STYLE, GWLP_WNDPROC, GetClientRect, GetWindowLongPtrW, GetWindowRect, HTBOTTOM, HTBOTTOMLEFT,
|
||||
HTBOTTOMRIGHT, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, IMAGE_ICON, IsIconic, IsZoomed, LR_DEFAULTCOLOR, LoadImageW, SM_CXICON,
|
||||
SM_CXPADDEDBORDER, SM_CXSIZEFRAME, SM_CXSMICON, SetWindowLongPtrW, WM_GETDPISCALEDSIZE, WM_NCDESTROY, WM_NCHITTEST, WNDPROC, WS_THICKFRAME,
|
||||
};
|
||||
// windows-sys has SetClassLongPtrW only on 64-bit targets; on 32-bit Windows it is SetClassLongW.
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::SetClassLongPtrW;
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::SetClassLongW as SetClassLongPtrW;
|
||||
|
||||
/// The work area (the monitor without the taskbar) of the monitor nearest the window, in physical
|
||||
/// pixels: left, top, width, height.
|
||||
pub fn work_area(window: &impl HasWindowHandle) -> Option<(i32, i32, i32, i32)> {
|
||||
let handle = window.window_handle().ok()?;
|
||||
let RawWindowHandle::Win32(w) = handle.as_raw() else { return None };
|
||||
let hwnd = w.hwnd.get() as HWND;
|
||||
// SAFETY: `hwnd` is the live main window; `info` is a MONITORINFO with its size set, as
|
||||
// GetMonitorInfoW requires, and the monitor handle comes from the call before it.
|
||||
let rc = unsafe {
|
||||
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
let mut info: MONITORINFO = std::mem::zeroed();
|
||||
info.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
|
||||
if monitor.is_null() || GetMonitorInfoW(monitor, &mut info) == 0 {
|
||||
return None;
|
||||
}
|
||||
info.rcWork
|
||||
};
|
||||
Some((rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top))
|
||||
}
|
||||
|
||||
/// The exe's icon resource (`build.rs` embeds it as `1 ICON`).
|
||||
const ICON_RESOURCE: usize = 1;
|
||||
|
||||
/// Give the main window's class the exe's icon, big and small. Call it before the window is
|
||||
/// shown (eframe's app creator).
|
||||
///
|
||||
/// The taskbar asks a window for its icon when the window appears. eframe sets the window's
|
||||
/// icon before that, while the window is still hidden, and then shows the window at the end of
|
||||
/// its first frame; if that frame is slow, or startup kept the thread busy long enough for
|
||||
/// Windows to mark the window as not responding, the request isn't answered in time. The taskbar then
|
||||
/// takes the window class's icon and doesn't ask again. winit registers its class without one,
|
||||
/// so the button showed Windows' generic icon for as long as the window was open.
|
||||
/// `false` if it isn't a Win32 window or the exe's icon doesn't load; SetClassLongPtrW's own
|
||||
/// failure can't be told from a class that had no icon (both return 0), so it isn't reported.
|
||||
pub fn set_class_icon(window: &impl HasWindowHandle) -> bool {
|
||||
let Ok(handle) = window.window_handle() else { return false };
|
||||
let RawWindowHandle::Win32(w) = handle.as_raw() else { return false };
|
||||
let hwnd = w.hwnd.get() as HWND;
|
||||
// SAFETY: a plain query on the live main window.
|
||||
let dpi = unsafe { GetDpiForWindow(hwnd) };
|
||||
let (Some(big), Some(small)) = (exe_icon(SM_CXICON, dpi), exe_icon(SM_CXSMICON, dpi)) else { return false };
|
||||
// SAFETY: `hwnd` is the live main window and both icons are valid handles. The class keeps
|
||||
// them for the rest of the process, so they are never destroyed. `as _`: on 32-bit Windows
|
||||
// this is SetClassLongW (see the imports), which takes an i32.
|
||||
unsafe {
|
||||
SetClassLongPtrW(hwnd, GCLP_HICON, big as _);
|
||||
SetClassLongPtrW(hwnd, GCLP_HICONSM, small as _);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// The exe's icon at the size of system metric `metric` (`SM_CXICON`, `SM_CXSMICON`) at `dpi`.
|
||||
fn exe_icon(metric: i32, dpi: u32) -> Option<windows_sys::Win32::Foundation::HANDLE> {
|
||||
// SAFETY: GetModuleHandleW(null) is this exe, loaded for the life of the process; the name
|
||||
// is an integer resource id (MAKEINTRESOURCE), which LoadImageW accepts in place of a string.
|
||||
let icon = unsafe {
|
||||
let size = GetSystemMetricsForDpi(metric, dpi);
|
||||
LoadImageW(GetModuleHandleW(std::ptr::null()), std::ptr::without_provenance(ICON_RESOURCE), IMAGE_ICON, size, size, LR_DEFAULTCOLOR)
|
||||
};
|
||||
(!icon.is_null()).then_some(icon)
|
||||
}
|
||||
|
||||
/// The window procedure ours replaced (winit's), as an address; 0 before [`install`].
|
||||
static PREVIOUS: AtomicIsize = AtomicIsize::new(0);
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
//! macOS system menus, owned and updated on eframe's main thread.
|
||||
|
||||
use std::sync::mpsc::{Receiver, sync_channel};
|
||||
|
||||
use muda::{
|
||||
AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu,
|
||||
accelerator::{Accelerator, Code, Modifiers},
|
||||
};
|
||||
use solidcraft_ui_egui::{SolveApp, toolbar::FileAction};
|
||||
|
||||
pub struct NativeMenu {
|
||||
menu: Menu,
|
||||
actions: Vec<(MenuItem, FileAction)>,
|
||||
help_actions: Vec<(MenuItem, &'static str)>,
|
||||
events: Receiver<MenuEvent>,
|
||||
recent: Submenu,
|
||||
recent_paths: Vec<String>,
|
||||
}
|
||||
|
||||
impl NativeMenu {
|
||||
pub fn new(app: &SolveApp, ctx: &egui::Context) -> muda::Result<Self> {
|
||||
let menu = Menu::new();
|
||||
let application = Submenu::new("SolidCraft", true);
|
||||
let file = Submenu::new(app.tr("ui.file"), true);
|
||||
let edit = Submenu::new(app.tr_label("Edit"), true);
|
||||
let recent = Submenu::new(app.tr("ui.open_recent"), true);
|
||||
let window = Submenu::new("Window", true);
|
||||
let help = Submenu::new(app.tr("ui.help"), true);
|
||||
let mut help_actions = Vec::new();
|
||||
let mut actions = Vec::new();
|
||||
let mut add = |parent: &Submenu, label: &str, action: FileAction, shortcut: Option<Accelerator>| -> muda::Result<()> {
|
||||
let item = MenuItem::new(label, Self::action_enabled(&action, app, ctx), shortcut);
|
||||
parent.append(&item)?;
|
||||
actions.push((item, action));
|
||||
Ok(())
|
||||
};
|
||||
application.append(&PredefinedMenuItem::about(
|
||||
Some("About SolidCraft"),
|
||||
Some(AboutMetadata { name: Some("SolidCraft".into()), version: Some(env!("CARGO_PKG_VERSION").into()), ..Default::default() }),
|
||||
))?;
|
||||
application.append(&PredefinedMenuItem::separator())?;
|
||||
add(
|
||||
&application,
|
||||
&format!("{}\u{2026}", app.tr("ui.preferences")),
|
||||
FileAction::Settings,
|
||||
Some(Accelerator::new(Modifiers::META, Code::Comma)),
|
||||
)?;
|
||||
add(&application, &format!("{}\u{2026}", app.tr("ui.keyboard_shortcuts")), FileAction::Shortcuts, None)?;
|
||||
application.append(&PredefinedMenuItem::separator())?;
|
||||
application.append(&PredefinedMenuItem::services(None))?;
|
||||
application.append(&PredefinedMenuItem::separator())?;
|
||||
application.append_items(&[
|
||||
&PredefinedMenuItem::hide(Some("Hide SolidCraft")),
|
||||
&PredefinedMenuItem::hide_others(None),
|
||||
&PredefinedMenuItem::show_all(None),
|
||||
&PredefinedMenuItem::separator(),
|
||||
])?;
|
||||
add(&application, &format!("{} SolidCraft", app.tr("ui.quit")), FileAction::Quit, Some(Accelerator::new(Modifiers::META, Code::KeyQ)))?;
|
||||
add(&file, app.tr("ui.new_design"), FileAction::New, Some(Accelerator::new(Modifiers::META, Code::KeyN)))?;
|
||||
add(&file, &format!("{}\u{2026}", app.tr("ui.open")), FileAction::Open, Some(Accelerator::new(Modifiers::META, Code::KeyO)))?;
|
||||
file.append(&recent)?;
|
||||
file.append(&PredefinedMenuItem::separator())?;
|
||||
for (label, command) in
|
||||
[("Insert STEP\u{2026}", "file.insert_step"), ("Insert Mesh\u{2026}", "file.insert_mesh"), ("Insert Design", "component.insert_design")]
|
||||
{
|
||||
add(&file, app.tr_label(label), FileAction::Command(command), None)?;
|
||||
}
|
||||
file.append(&PredefinedMenuItem::separator())?;
|
||||
add(&file, app.tr("ui.save"), FileAction::Save, Some(Accelerator::new(Modifiers::META, Code::KeyS)))?;
|
||||
add(
|
||||
&file,
|
||||
&format!("{}\u{2026}", app.tr("ui.save_as")),
|
||||
FileAction::SaveAs,
|
||||
Some(Accelerator::new(Modifiers::META | Modifiers::SHIFT, Code::KeyS)),
|
||||
)?;
|
||||
add(&file, app.tr_label("Save As JSON\u{2026}"), FileAction::SaveJson, None)?;
|
||||
file.append(&PredefinedMenuItem::separator())?;
|
||||
for (label, action) in [
|
||||
("Export STL\u{2026}", FileAction::ExportMesh("stl")),
|
||||
("Export 3MF\u{2026}", FileAction::ExportMesh("3mf")),
|
||||
("Export STEP\u{2026}", FileAction::Export("step")),
|
||||
("Export Current View as DXF\u{2026}", FileAction::Command("file.export_view_dxf")),
|
||||
("Export OBJ\u{2026}", FileAction::Export("obj")),
|
||||
("Export URDF\u{2026}", FileAction::Export("urdf")),
|
||||
("Export Design as JSON\u{2026}", FileAction::Export("json")),
|
||||
] {
|
||||
add(&file, app.tr_label(label), action, None)?;
|
||||
}
|
||||
file.append(&PredefinedMenuItem::separator())?;
|
||||
add(&file, app.tr_label("Design Properties"), FileAction::Command("document.metadata"), None)?;
|
||||
add(&edit, app.tr("ui.undo"), FileAction::Undo, Some(Accelerator::new(Modifiers::META, Code::KeyZ)))?;
|
||||
add(&edit, app.tr("ui.redo"), FileAction::Redo, Some(Accelerator::new(Modifiers::META | Modifiers::SHIFT, Code::KeyZ)))?;
|
||||
add(&window, app.tr_label("Agent Chat"), FileAction::AgentChat, None)?;
|
||||
window.append(&PredefinedMenuItem::separator())?;
|
||||
window.append_items(&[
|
||||
&PredefinedMenuItem::minimize(None),
|
||||
&PredefinedMenuItem::maximize(None),
|
||||
&PredefinedMenuItem::fullscreen(None),
|
||||
&PredefinedMenuItem::separator(),
|
||||
&PredefinedMenuItem::bring_all_to_front(None),
|
||||
])?;
|
||||
for (action, label) in [
|
||||
("docs", "Documentation"),
|
||||
("shortcuts", "Keyboard Shortcuts"),
|
||||
("messages", "Messages\u{2026}"),
|
||||
("report", "Report an Issue\u{2026}"),
|
||||
("crashes", "Crash Reports\u{2026}"),
|
||||
("install_cli", "Install solidcraft-cli command"),
|
||||
] {
|
||||
let item = MenuItem::new(app.tr_label(label), true, None);
|
||||
help.append(&item)?;
|
||||
help_actions.push((item, action));
|
||||
}
|
||||
menu.append_items(&[&application, &file, &edit, &window, &help])?;
|
||||
let (sender, events) = sync_channel(64);
|
||||
let ctx = ctx.clone();
|
||||
MenuEvent::set_event_handler(Some(move |event| {
|
||||
let _ = sender.try_send(event);
|
||||
ctx.request_repaint();
|
||||
}));
|
||||
menu.init_for_nsapp();
|
||||
let mut native = Self { menu, actions, help_actions, events, recent, recent_paths: Vec::new() };
|
||||
native.update_recent(app)?;
|
||||
Ok(native)
|
||||
}
|
||||
|
||||
pub fn raw_input_hook(&mut self, app: &mut SolveApp, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
for event in self.events.try_iter().take(64) {
|
||||
if let Some((_, action)) = self.help_actions.iter().find(|(item, _)| item.id() == &event.id) {
|
||||
solidcraft_ui_egui::help::run(app, ctx, action);
|
||||
}
|
||||
if let Some((_, action)) = self.actions.iter().find(|(item, _)| item.id() == &event.id) {
|
||||
if matches!(action, FileAction::Undo | FileAction::Redo) && ctx.egui_wants_keyboard_input() {
|
||||
raw.events.push(egui::Event::Key {
|
||||
key: egui::Key::Z,
|
||||
physical_key: None,
|
||||
pressed: true,
|
||||
repeat: false,
|
||||
modifiers: egui::Modifiers { command: true, mac_cmd: true, shift: matches!(action, FileAction::Redo), ..Default::default() },
|
||||
});
|
||||
} else {
|
||||
action.run(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn action_enabled(action: &FileAction, app: &SolveApp, ctx: &egui::Context) -> bool {
|
||||
action.enabled(app)
|
||||
|| (matches!(action, FileAction::Undo | FileAction::Redo) && ctx.egui_wants_keyboard_input() && FileAction::Settings.enabled(app))
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, app: &mut SolveApp, ctx: &egui::Context) {
|
||||
for (item, action) in &self.actions {
|
||||
let enabled = Self::action_enabled(action, app, ctx);
|
||||
if item.is_enabled() != enabled {
|
||||
item.set_enabled(enabled);
|
||||
}
|
||||
}
|
||||
if !self.recent_paths.iter().eq(app.home.recent.iter().take(32))
|
||||
&& let Err(error) = self.update_recent(app)
|
||||
{
|
||||
app.set_status(format!("Recent designs menu: {error}"), true);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_recent(&mut self, app: &SolveApp) -> muda::Result<()> {
|
||||
while self.recent.remove_at(0).is_some() {}
|
||||
self.actions.retain(|(_, action)| !matches!(action, FileAction::OpenRecent(_) | FileAction::ClearRecent));
|
||||
self.recent_paths = app.home.recent.iter().take(32).cloned().collect();
|
||||
for path in &self.recent_paths {
|
||||
let action = FileAction::OpenRecent(path.clone());
|
||||
let item = MenuItem::new(path, action.enabled(app), None);
|
||||
self.recent.append(&item)?;
|
||||
self.actions.push((item, action));
|
||||
}
|
||||
self.recent.append(&PredefinedMenuItem::separator())?;
|
||||
let clear = MenuItem::new(app.tr("ui.clear_recent"), !self.recent_paths.is_empty(), None);
|
||||
self.recent.append(&clear)?;
|
||||
self.actions.push((clear, FileAction::ClearRecent));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NativeMenu {
|
||||
fn drop(&mut self) {
|
||||
MenuEvent::set_event_handler(None::<fn(MenuEvent)>);
|
||||
self.menu.remove_for_nsapp();
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,29 @@
|
||||
//! its report descriptor and ids under `/sys/class/hidraw/hidrawN/device/`. Reading them is plain
|
||||
//! file I/O, so no udev or hidapi library is needed.
|
||||
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use solidcraft_space_mouse::{Descriptor, Layout};
|
||||
|
||||
const SYSFS: &str = "/sys/class/hidraw";
|
||||
const DEV: &str = "/dev";
|
||||
|
||||
/// `O_NONBLOCK` on Linux's common architectures (x86, ARM, RISC-V). The device is opened with it so
|
||||
/// a read with nothing to read returns at once and the reader can see whether it should stop.
|
||||
pub const O_NONBLOCK: i32 = 0o4000;
|
||||
|
||||
/// Opens a hidraw node the way the Direct reader reads it: read-only and non-blocking.
|
||||
pub fn open(path: &Path) -> std::io::Result<std::fs::File> {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
std::fs::OpenOptions::new().read(true).custom_flags(O_NONBLOCK).open(path)
|
||||
}
|
||||
|
||||
/// How long the reader waits when the device has nothing to say, which is also the most a report
|
||||
/// can be delayed.
|
||||
const IDLE_POLL: Duration = Duration::from_millis(5);
|
||||
|
||||
pub struct Device {
|
||||
pub path: PathBuf,
|
||||
pub layout: Layout,
|
||||
@@ -23,6 +39,30 @@ pub fn find() -> Option<Device> {
|
||||
names.into_iter().find_map(|name| probe(&Path::new(SYSFS).join(&name), Path::new(DEV).join(&name)))
|
||||
}
|
||||
|
||||
/// Hands every report to `publish` until the device is gone (end of file or an error) or `stop`
|
||||
/// says so. `source` is non-blocking: when it has nothing, `stop` is asked and the reader waits
|
||||
/// [`IDLE_POLL`]. Reports are read and handed over exactly as they were with blocking reads.
|
||||
pub fn read_reports(mut source: impl Read, mut publish: impl FnMut(&[u8]), mut stop: impl FnMut() -> bool) {
|
||||
let mut report = [0_u8; super::REPORT_BUFFER_BYTES];
|
||||
loop {
|
||||
match source.read(&mut report) {
|
||||
Ok(0) => return,
|
||||
Ok(size) => publish(report.get(..size).unwrap_or_default()),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
if stop() {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(IDLE_POLL);
|
||||
}
|
||||
Err(error) => {
|
||||
log::info!("3D mouse disconnected: {error}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn probe(sysfs: &Path, path: PathBuf) -> Option<Device> {
|
||||
let descriptor = Descriptor::parse(&std::fs::read(sysfs.join("device/report_descriptor")).ok()?);
|
||||
// A controller that declares no motion axis (buttons only, say) isn't read.
|
||||
@@ -42,6 +82,73 @@ fn ids(uevent: &str) -> Option<(u16, u16)> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A real EACCES, as a user without the udev rule gets: a file of the user's own with no permission
|
||||
/// bits. Skipped for root, which can open anything.
|
||||
#[test]
|
||||
fn a_device_without_permission_is_denied_and_the_daemon_takes_over_within_the_backoff() {
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
if std::fs::metadata("/proc/self").is_ok_and(|meta| meta.uid() == 0) {
|
||||
return;
|
||||
}
|
||||
let dir = std::env::temp_dir().join(format!("sc-hidraw-denied-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let node = dir.join("hidraw0");
|
||||
std::fs::write(&node, [0_u8; 4]).unwrap();
|
||||
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o000)).unwrap();
|
||||
let denied = open(&node);
|
||||
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o600)).unwrap();
|
||||
assert!(open(&node).is_ok(), "the same node opens once it is readable");
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
assert_eq!(denied.err().map(|error| error.kind()), Some(std::io::ErrorKind::PermissionDenied));
|
||||
// The failure is the choice's `since_failed`, with an injected clock: inside the backoff the
|
||||
// daemon is read, whichever driver was asked for; after it the device is tried again.
|
||||
use super::super::{Pick, SpaceMouseDriver, UNREADABLE_RETRY, choose, direct_usable};
|
||||
for wanted in [SpaceMouseDriver::Automatic, SpaceMouseDriver::Spacenavd, SpaceMouseDriver::Direct] {
|
||||
for ago in [Duration::ZERO, Duration::from_secs(29)] {
|
||||
assert_eq!(choose(wanted, true, direct_usable(true, Some(ago), UNREADABLE_RETRY)), Some(Pick::Spacenavd), "{wanted:?} {ago:?}");
|
||||
}
|
||||
}
|
||||
assert_eq!(choose(SpaceMouseDriver::Direct, true, direct_usable(true, Some(UNREADABLE_RETRY), UNREADABLE_RETRY)), Some(Pick::Direct));
|
||||
// And the daemon's own reader delivers over a fake connection.
|
||||
let (mut daemon, client) = std::os::unix::net::UnixStream::pair().unwrap();
|
||||
let mut record = [0_u8; 32];
|
||||
record[4..8].copy_from_slice(&100_i32.to_ne_bytes());
|
||||
std::io::Write::write_all(&mut daemon, &record).unwrap();
|
||||
drop(daemon);
|
||||
let mut seen = 0;
|
||||
super::super::spacenavd::read(client, |_| seen += 1, |_, _| {}, || false, Duration::from_secs(3600));
|
||||
assert_eq!(seen, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_come_through_idle_gaps_until_the_device_ends_or_a_stop() {
|
||||
use crate::space_mouse::fake::Script;
|
||||
let mut script = Script::new();
|
||||
script.timeout(std::io::ErrorKind::WouldBlock).data(&[1, 2, 3]).timeout(std::io::ErrorKind::WouldBlock).data(&[4]).end();
|
||||
let (mut seen, mut asked) = (Vec::new(), 0);
|
||||
read_reports(
|
||||
script,
|
||||
|report| seen.push(report.to_vec()),
|
||||
|| {
|
||||
asked += 1;
|
||||
false
|
||||
},
|
||||
);
|
||||
assert_eq!(seen, [vec![1, 2, 3], vec![4]]);
|
||||
assert_eq!(asked, 2);
|
||||
let mut script = Script::new();
|
||||
script.data(&[1]).timeout(std::io::ErrorKind::WouldBlock).data(&[2]);
|
||||
let mut seen = Vec::new();
|
||||
read_reports(script, |report| seen.push(report.to_vec()), || true);
|
||||
assert_eq!(seen, [vec![1]], "a stop at an idle moment ends the read");
|
||||
// An unplugged device is an error, which ends the read as before.
|
||||
let mut script = Script::new();
|
||||
script.data(&[1]).fail(std::io::ErrorKind::BrokenPipe).data(&[2]);
|
||||
let mut seen = Vec::new();
|
||||
read_reports(script, |report| seen.push(report.to_vec()), || false);
|
||||
assert_eq!(seen, [vec![1]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ids_come_from_the_uevent() {
|
||||
let wireless = "DRIVER=hid-generic\nHID_ID=0005:0000256F:0000C63A\nHID_NAME=SpaceMouse Wireless BT\n";
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
//! to its clients, so a program that reads the HID device itself bypasses that setup. Over its
|
||||
//! socket the daemon sends fixed 32-byte records: eight native-endian `i32`, the first the kind of
|
||||
//! record (0 for motion, then x y z rx ry rz and the milliseconds since the last record).
|
||||
//! Other records (button presses, from newer daemons also configuration changes) are ignored.
|
||||
//! Button records (1 pressed, 2 released, then the button's number from 0) are read too; other
|
||||
//! records (from newer daemons also configuration changes) are ignored.
|
||||
//!
|
||||
//! The socket is plain file I/O, so no library is needed, and it needs no udev rule: the daemon
|
||||
//! is what has access to the device.
|
||||
@@ -11,6 +12,7 @@
|
||||
use std::io::Read;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use solidcraft_ui_egui::space_mouse::{Axes, FULL_SCALE};
|
||||
|
||||
@@ -23,6 +25,11 @@ const RECORD_BYTES: usize = 32;
|
||||
/// sensitivity). Our axes are in the raw HID units of [`FULL_SCALE`].
|
||||
const DAEMON_FULL_SCALE: i64 = 500;
|
||||
|
||||
/// Whether the daemon's socket is there, without connecting to it.
|
||||
pub fn exists() -> bool {
|
||||
SOCKETS.iter().any(|path| Path::new(path).exists())
|
||||
}
|
||||
|
||||
pub fn connect() -> Option<UnixStream> {
|
||||
SOCKETS.iter().find_map(|path| connect_to(Path::new(path)))
|
||||
}
|
||||
@@ -49,6 +56,22 @@ fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The button a button record is about, from 0, and whether it went down; `None` for any other
|
||||
/// record, or a button past the 32 a bitmask holds.
|
||||
fn button(record: &[u8; RECORD_BYTES]) -> Option<(u32, bool)> {
|
||||
let mut data = [0_i32; 2];
|
||||
for (slot, bytes) in data.iter_mut().zip(record.as_chunks::<4>().0) {
|
||||
*slot = i32::from_ne_bytes(*bytes);
|
||||
}
|
||||
let [kind, number] = data;
|
||||
let number = u32::try_from(number).ok().filter(|&n| n < 32)?;
|
||||
match kind {
|
||||
1 => Some((number, true)),
|
||||
2 => Some((number, false)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A daemon value in our raw units. Out-of-range values stay in `i16`: they are input from another
|
||||
/// process.
|
||||
fn unit(value: impl Into<i64>) -> i16 {
|
||||
@@ -56,18 +79,53 @@ fn unit(value: impl Into<i64>) -> i16 {
|
||||
i16::try_from(scaled.clamp(i64::from(i16::MIN), i64::from(i16::MAX))).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Hands every motion record to `publish` until the daemon closes the connection or fails. A
|
||||
/// partial record at the end is dropped.
|
||||
pub fn read(mut stream: impl Read, mut publish: impl FnMut(Axes)) {
|
||||
/// Hands every motion record to `publish` and every button record to `pressed` until the daemon
|
||||
/// closes the connection or fails, or `stop` says so. A stream with a read timeout
|
||||
/// (`set_read_timeout`) times out when the daemon is quiet, which is no record and no disconnect:
|
||||
/// `stop` is asked, and the read goes on, with what of a record had arrived kept. `stop` is also asked
|
||||
/// at least every `check_every` while records keep arriving, so a daemon that never pauses for that
|
||||
/// long (a moving cap sends every few milliseconds) does not keep the reader from noticing that the
|
||||
/// device it prefers is back. A partial record at the end is dropped.
|
||||
pub fn read(
|
||||
mut stream: impl Read,
|
||||
mut publish: impl FnMut(Axes),
|
||||
mut pressed: impl FnMut(u32, bool),
|
||||
mut stop: impl FnMut() -> bool,
|
||||
check_every: Duration,
|
||||
) {
|
||||
let mut record = [0_u8; RECORD_BYTES];
|
||||
let mut filled = 0;
|
||||
let mut asked = Instant::now();
|
||||
loop {
|
||||
match stream.read_exact(&mut record) {
|
||||
Ok(()) => {
|
||||
if let Some(axes) = decode(&record) {
|
||||
publish(axes);
|
||||
if asked.elapsed() >= check_every {
|
||||
asked = Instant::now();
|
||||
if stop() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
match stream.read(record.get_mut(filled..).unwrap_or_default()) {
|
||||
Ok(0) => {
|
||||
log::info!("3D mouse: spacenavd connection ended");
|
||||
return;
|
||||
}
|
||||
Ok(size) => {
|
||||
filled += size;
|
||||
if filled == RECORD_BYTES {
|
||||
filled = 0;
|
||||
if let Some(axes) = decode(&record) {
|
||||
publish(axes);
|
||||
} else if let Some((number, down)) = button(&record) {
|
||||
pressed(number, down);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
|
||||
Err(error) if matches!(error.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut) => {
|
||||
asked = Instant::now();
|
||||
if stop() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
log::info!("3D mouse: spacenavd connection ended: {error}");
|
||||
return;
|
||||
@@ -113,6 +171,10 @@ mod tests {
|
||||
fn other_records_and_hostile_values_are_harmless() {
|
||||
// A button record (kind 1, press) and one of an unknown kind are not motion.
|
||||
assert_eq!(decode(&record([1, 0, 1, 0, 0, 0, 0, 0])), None);
|
||||
// Nor are hostile button numbers a button.
|
||||
assert_eq!(button(&record([1, -1, 0, 0, 0, 0, 0, 0])), None);
|
||||
assert_eq!(button(&record([1, i32::MAX, 0, 0, 0, 0, 0, 0])), None);
|
||||
assert_eq!(button(&record([7, 3, 0, 0, 0, 0, 0, 0])), None);
|
||||
assert_eq!(decode(&record([7, 500, 500, 500, 500, 500, 500, 0])), None);
|
||||
// Nothing overflows on the extremes of `i32`.
|
||||
let extreme = decode(&record([0, i32::MIN, i32::MIN, i32::MAX, i32::MAX, i32::MIN, i32::MIN, -1]));
|
||||
@@ -126,14 +188,84 @@ mod tests {
|
||||
let (mut daemon, client) = UnixStream::pair().expect("a socket pair");
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend(record([0, 100, 0, 0, 0, 0, 0, 8]));
|
||||
bytes.extend(record([2, 1, 0, 0, 0, 0, 0, 0])); // a button release: skipped
|
||||
bytes.extend(record([1, 4, 0, 0, 0, 0, 0, 0])); // button 4 down
|
||||
bytes.extend(record([2, 4, 0, 0, 0, 0, 0, 0])); // and up
|
||||
bytes.extend(record([1, 40, 0, 0, 0, 0, 0, 0])); // past the 32 a bitmask holds: skipped
|
||||
bytes.extend(record([0, 0, 0, 0, 0, 0, 0, 900]));
|
||||
bytes.extend([1, 2, 3]); // the daemon went away mid-record
|
||||
daemon.write_all(&bytes).expect("written");
|
||||
drop(daemon);
|
||||
let mut seen = Vec::new();
|
||||
read(client, |axes| seen.push(axes.translation));
|
||||
let (mut seen, mut buttons) = (Vec::new(), Vec::new());
|
||||
read(client, |axes| seen.push(axes.translation), |number, down| buttons.push((number, down)), || false, Duration::from_secs(3600));
|
||||
assert_eq!(seen, [[70, 0, 0], [0, 0, 0]]);
|
||||
assert_eq!(buttons, [(4, true), (4, false)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_quiet_daemon_times_out_without_losing_the_record_it_was_sending() {
|
||||
use crate::space_mouse::fake::Script;
|
||||
use std::io::ErrorKind::{TimedOut, WouldBlock};
|
||||
let bytes = record([0, 100, 0, 0, 0, 0, 0, 8]);
|
||||
let mut script = Script::new();
|
||||
script.timeout(WouldBlock).data(&bytes[..10]).timeout(TimedOut).timeout(WouldBlock).data(&bytes[10..]).timeout(WouldBlock).end();
|
||||
let (mut seen, mut asked) = (Vec::new(), 0);
|
||||
read(
|
||||
script,
|
||||
|axes| seen.push(axes.translation),
|
||||
|_, _| {},
|
||||
|| {
|
||||
asked += 1;
|
||||
false
|
||||
},
|
||||
Duration::from_secs(3600),
|
||||
);
|
||||
assert_eq!(seen, [[70, 0, 0]]);
|
||||
assert_eq!(asked, 4, "each timeout asks whether to stop");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stop_ends_the_read_at_the_next_timeout_only() {
|
||||
use crate::space_mouse::fake::Script;
|
||||
let bytes = record([0, 100, 0, 0, 0, 0, 0, 8]);
|
||||
let mut script = Script::new();
|
||||
script.data(&bytes).timeout(std::io::ErrorKind::WouldBlock).data(&bytes);
|
||||
let mut seen = 0;
|
||||
read(script, |_| seen += 1, |_, _| {}, || true, Duration::from_secs(3600));
|
||||
assert_eq!(seen, 1, "the record before the timeout arrived; the one after never was read");
|
||||
}
|
||||
|
||||
/// A daemon that never goes quiet (a moving cap sends every few milliseconds) is still asked whether to
|
||||
/// stop, at the interval given, so the reader can go back to a device that returned.
|
||||
#[test]
|
||||
fn a_busy_daemon_is_asked_whether_to_stop_on_a_timer() {
|
||||
// Ends by itself after half a second, so a read that never asks fails the assertion below
|
||||
// instead of hanging the test.
|
||||
struct Busy(Instant);
|
||||
impl Read for Busy {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if self.0.elapsed() > Duration::from_millis(500) {
|
||||
return Ok(0);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(2));
|
||||
let bytes = record([0, 100, 0, 0, 0, 0, 0, 8]);
|
||||
let size = bytes.len().min(buf.len());
|
||||
buf[..size].copy_from_slice(&bytes[..size]);
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
let (mut seen, mut asked) = (0, 0);
|
||||
read(
|
||||
Busy(Instant::now()),
|
||||
|_| seen += 1,
|
||||
|_, _| {},
|
||||
|| {
|
||||
asked += 1;
|
||||
asked == 3
|
||||
},
|
||||
Duration::from_millis(10),
|
||||
);
|
||||
assert_eq!(asked, 3, "asked on the timer although no read ever timed out");
|
||||
assert!(seen > 3, "records kept coming meanwhile: {seen}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//! Windows: finding 3D mice without asking any other device anything (#2614).
|
||||
//!
|
||||
//! hidapi's enumeration opens every HID interface and reads its serial number, manufacturer and
|
||||
//! product strings: on USB, three control requests to every keyboard, mouse and touchscreen. The
|
||||
//! reader repeated it every two seconds while no 3D mouse was plugged in, and on some machines the
|
||||
//! keyboard then kept dropping off the bus and the cursor jumped. Plug and Play already names each
|
||||
//! interface's top-level usage in its hardware IDs (`HID_DEVICE_UP:0001_U:0008`: Generic Desktop,
|
||||
//! Multi-Axis Controller), so the reader asks it, which sends nothing to any device, and opens only
|
||||
//! the interfaces that are 3D mice.
|
||||
//!
|
||||
//! The configuration manager has no safe API, hence the `unsafe_code` allowance on this module
|
||||
//! (the app crate denies it; see `native_crash.rs`).
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{
|
||||
CM_GET_DEVICE_INTERFACE_LIST_PRESENT, CM_Get_DevNode_PropertyW, CM_Get_Device_Interface_List_SizeW, CM_Get_Device_Interface_ListW,
|
||||
CM_Get_Device_Interface_PropertyW, CM_LOCATE_DEVNODE_NORMAL, CM_Locate_DevNodeW, CONFIGRET, CR_BUFFER_SMALL, CR_SUCCESS,
|
||||
};
|
||||
use windows_sys::Win32::Devices::HumanInterfaceDevice::GUID_DEVINTERFACE_HID;
|
||||
use windows_sys::Win32::Devices::Properties::{
|
||||
DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING, DEVPROP_TYPE_STRING_LIST, DEVPROPTYPE,
|
||||
};
|
||||
use windows_sys::Win32::Foundation::DEVPROPKEY;
|
||||
|
||||
/// The hardware ID Windows gives a top-level collection with the Generic Desktop page's
|
||||
/// Multi-Axis Controller usage.
|
||||
const MULTI_AXIS_CONTROLLER: &str = "HID_DEVICE_UP:0001_U:0008";
|
||||
/// Largest property read, in bytes: hardware IDs are a few hundred.
|
||||
const MAX_PROPERTY_BYTES: u32 = 64 * 1024;
|
||||
/// Tries at reading the interface list, which can grow between asking its size and reading it.
|
||||
const LIST_TRIES: usize = 4;
|
||||
|
||||
/// The paths of the present HID interfaces whose top-level collection is a multi-axis controller.
|
||||
pub fn multi_axis_controllers() -> Vec<CString> {
|
||||
interfaces()
|
||||
.into_iter()
|
||||
.filter(|path| {
|
||||
let Some(instance) = interface_strings(path, &DEVPKEY_Device_InstanceId).and_then(|ids| ids.into_iter().next()) else {
|
||||
return false;
|
||||
};
|
||||
devnode_strings(&instance, &DEVPKEY_Device_HardwareIds).is_some_and(|ids| is_multi_axis(&ids))
|
||||
})
|
||||
.filter_map(|path| CString::new(path).ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether a device with these hardware IDs is a multi-axis controller.
|
||||
fn is_multi_axis(hardware_ids: &[String]) -> bool {
|
||||
hardware_ids.iter().any(|id| id.eq_ignore_ascii_case(MULTI_AXIS_CONTROLLER))
|
||||
}
|
||||
|
||||
/// The strings of a nul-separated list (`REG_MULTI_SZ`, or one string), without empty ones.
|
||||
fn split_multi_sz(buffer: &[u16]) -> Vec<String> {
|
||||
buffer.split(|c| *c == 0).filter(|s| !s.is_empty()).map(String::from_utf16_lossy).collect()
|
||||
}
|
||||
|
||||
/// `s` as a nul-terminated wide string.
|
||||
fn wide(s: &str) -> Vec<u16> {
|
||||
s.encode_utf16().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
/// The paths of the present HID interfaces.
|
||||
fn interfaces() -> Vec<String> {
|
||||
for _ in 0..LIST_TRIES {
|
||||
let mut len = 0u32;
|
||||
// SAFETY: `len` is a valid place for the size; the class GUID is a static; no device filter.
|
||||
let sized = unsafe { CM_Get_Device_Interface_List_SizeW(&mut len, &GUID_DEVINTERFACE_HID, null(), CM_GET_DEVICE_INTERFACE_LIST_PRESENT) };
|
||||
if sized != CR_SUCCESS || len == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut buffer = vec![0u16; len as usize];
|
||||
// SAFETY: `buffer` holds `len` wide characters, the length the call is given.
|
||||
let listed =
|
||||
unsafe { CM_Get_Device_Interface_ListW(&GUID_DEVINTERFACE_HID, null(), buffer.as_mut_ptr(), len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT) };
|
||||
match listed {
|
||||
CR_SUCCESS => return split_multi_sz(&buffer),
|
||||
CR_BUFFER_SMALL => continue,
|
||||
_ => return Vec::new(),
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// A string or string-list property, read by `get` (property type, buffer, its size in bytes) in
|
||||
/// two calls: the size, then the value.
|
||||
fn property_strings(get: impl Fn(*mut DEVPROPTYPE, *mut u8, *mut u32) -> CONFIGRET) -> Option<Vec<String>> {
|
||||
let mut kind: DEVPROPTYPE = 0;
|
||||
let mut size = 0u32;
|
||||
if get(&mut kind, null_mut(), &mut size) != CR_BUFFER_SMALL || size == 0 || size > MAX_PROPERTY_BYTES {
|
||||
return None;
|
||||
}
|
||||
let mut buffer = vec![0u16; (size as usize).div_ceil(2)];
|
||||
let mut size = (buffer.len() * 2) as u32;
|
||||
if get(&mut kind, buffer.as_mut_ptr().cast(), &mut size) != CR_SUCCESS || (kind != DEVPROP_TYPE_STRING && kind != DEVPROP_TYPE_STRING_LIST) {
|
||||
return None;
|
||||
}
|
||||
Some(split_multi_sz(&buffer))
|
||||
}
|
||||
|
||||
/// A string property of the device interface at `path`.
|
||||
fn interface_strings(path: &str, key: &DEVPROPKEY) -> Option<Vec<String>> {
|
||||
let path = wide(path);
|
||||
property_strings(|kind, buffer, size| {
|
||||
// SAFETY: `path` is nul-terminated and outlives the call; `buffer` is null (asking the size)
|
||||
// or holds `*size` bytes, as `property_strings` sets up.
|
||||
unsafe { CM_Get_Device_Interface_PropertyW(path.as_ptr(), key, kind, buffer, size, 0) }
|
||||
})
|
||||
}
|
||||
|
||||
/// A string property of the device with instance ID `instance`.
|
||||
fn devnode_strings(instance: &str, key: &DEVPROPKEY) -> Option<Vec<String>> {
|
||||
let instance = wide(instance);
|
||||
let mut devnode = 0u32;
|
||||
// SAFETY: `instance` is nul-terminated and outlives the call; `devnode` is a valid place for the result.
|
||||
if unsafe { CM_Locate_DevNodeW(&mut devnode, instance.as_ptr(), CM_LOCATE_DEVNODE_NORMAL) } != CR_SUCCESS {
|
||||
return None;
|
||||
}
|
||||
property_strings(|kind, buffer, size| {
|
||||
// SAFETY: `buffer` is null (asking the size) or holds `*size` bytes, as `property_strings` sets up.
|
||||
unsafe { CM_Get_DevNode_PropertyW(devnode, key, kind, buffer, size, 0) }
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The hardware IDs Windows gives a wired 3Dconnexion device's motion interface, and a
|
||||
/// keyboard's: only the first is a multi-axis controller.
|
||||
#[test]
|
||||
fn only_the_multi_axis_usage_is_a_3d_mouse() {
|
||||
let ids = |list: &[&str]| list.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||
let space_mouse = ids(&["HID\\VID_256F&PID_C635&REV_0437&MI_00", "HID\\VID_256F&UP:0001_U:0008", "HID_DEVICE_UP:0001_U:0008", "HID_DEVICE"]);
|
||||
let keyboard = ids(&[
|
||||
"HID\\VID_046D&PID_C31C&REV_6400&MI_00",
|
||||
"HID\\VID_046D&UP:0001_U:0006",
|
||||
"HID_DEVICE_SYSTEM_KEYBOARD",
|
||||
"HID_DEVICE_UP:0001_U:0006",
|
||||
"HID_DEVICE",
|
||||
]);
|
||||
let vendor = ids(&["HID\\VID_256F&UP:FF00_U:0001", "HID_DEVICE_UP:FF00_U:0001", "HID_DEVICE_UPR:FF00-FFFF", "HID_DEVICE"]);
|
||||
assert!(is_multi_axis(&space_mouse));
|
||||
assert!(is_multi_axis(&ids(&["hid_device_up:0001_u:0008"])), "IDs compare without regard to case");
|
||||
assert!(!is_multi_axis(&keyboard) && !is_multi_axis(&vendor) && !is_multi_axis(&[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nul_separated_lists_split_into_their_strings() {
|
||||
let buffer: Vec<u16> = "a\\b\0HID_DEVICE\0\0".encode_utf16().collect();
|
||||
assert_eq!(split_multi_sz(&buffer), ["a\\b", "HID_DEVICE"]);
|
||||
assert_eq!(split_multi_sz(&wide("one")), ["one"]);
|
||||
assert!(split_multi_sz(&[0, 0]).is_empty() && split_multi_sz(&[]).is_empty());
|
||||
}
|
||||
|
||||
/// The scan asks Plug and Play only: on any machine it returns, and every path it returns is a
|
||||
/// present HID interface whose hardware IDs name the multi-axis usage.
|
||||
#[test]
|
||||
fn the_scan_returns_only_multi_axis_interfaces() {
|
||||
let found = multi_axis_controllers();
|
||||
let all = interfaces();
|
||||
for path in &found {
|
||||
let path = path.to_str().unwrap_or_default();
|
||||
assert!(all.iter().any(|p| p == path), "{path}");
|
||||
let instance = interface_strings(path, &DEVPKEY_Device_InstanceId).and_then(|ids| ids.into_iter().next()).unwrap_or_default();
|
||||
assert!(devnode_strings(&instance, &DEVPKEY_Device_HardwareIds).is_some_and(|ids| is_multi_axis(&ids)), "{path}");
|
||||
}
|
||||
eprintln!("{} HID interfaces present, {} multi-axis controllers", all.len(), found.len());
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,25 @@ pub const RENDERER_ENV: &str = "SOLIDCRAFT_RENDERER";
|
||||
pub const FAILED_STARTS_FOR_SAFE_MODE: u32 = 2;
|
||||
const MARKER: &str = "starting";
|
||||
|
||||
/// Explicit application-settings isolation for side-by-side builds and native UI
|
||||
/// acceptance. A supplied invalid path refuses startup rather than falling back
|
||||
/// to the user's ordinary settings. No directory is read or created here.
|
||||
pub fn settings_directory(override_path: Option<&std::ffi::OsStr>, default: Option<PathBuf>) -> Result<Option<PathBuf>, String> {
|
||||
match override_path {
|
||||
None => Ok(default),
|
||||
Some(raw) => {
|
||||
let path = PathBuf::from(raw);
|
||||
if raw.is_empty() || !path.is_absolute() {
|
||||
return Err("SOLIDCRAFT_SETTINGS_DIR must be a nonempty absolute directory path".into());
|
||||
}
|
||||
if path.exists() && !path.is_dir() {
|
||||
return Err("SOLIDCRAFT_SETTINGS_DIR names a file, not a directory".into());
|
||||
}
|
||||
Ok(Some(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an environment value switches something on.
|
||||
pub fn env_on(v: Option<&str>) -> bool {
|
||||
v.map(str::trim).is_some_and(|v| !v.is_empty() && v != "0" && !v.eq_ignore_ascii_case("false"))
|
||||
@@ -189,6 +208,29 @@ pub fn has_hardware_adapter(backends: eframe::wgpu::Backends) -> bool {
|
||||
adapters.iter().any(|a| a.get_info().device_type != eframe::wgpu::DeviceType::Cpu)
|
||||
}
|
||||
|
||||
/// The environment variable that switches wgpu's indirect-call validation on or off.
|
||||
pub const INDIRECT_VALIDATION_ENV: &str = "WGPU_VALIDATION_INDIRECT_CALL";
|
||||
|
||||
/// The instance flags for the window. wgpu's indirect-call validation compiles a compute shader
|
||||
/// when the device is created, and on Intel Macs (macOS 12 with Intel Iris graphics, #3024,
|
||||
/// #3080, #4605) the Metal compiler refuses it: the device is lost and the window never opens.
|
||||
/// So on an Intel Mac the validation is left off, unless the user turned it on or off with
|
||||
/// `WGPU_VALIDATION_INDIRECT_CALL` themselves.
|
||||
pub fn window_instance_flags(flags: eframe::wgpu::InstanceFlags, intel_mac: bool, env: Option<&str>) -> eframe::wgpu::InstanceFlags {
|
||||
if intel_mac && env.map(str::trim).is_none_or(str::is_empty) { flags - eframe::wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL } else { flags }
|
||||
}
|
||||
|
||||
/// Windows with OpenGL: the window keeps the system title bar (#2063). The window without OS
|
||||
/// decorations keeps its caption style while having no frame, and some OpenGL drivers (older
|
||||
/// Intel HD/UHD) then show the picture a caption's height lower than the window, so it no longer
|
||||
/// lines up with the pointer. Direct3D 12 and Vulkan draw it in place and keep the application bar
|
||||
/// as the title bar. `backends` are the ones wgpu starts: OpenGL alone is decided before the
|
||||
/// window opens.
|
||||
pub fn system_title_bar(windows: bool, backends: eframe::wgpu::Backends) -> bool {
|
||||
use eframe::wgpu::Backends;
|
||||
windows && backends.contains(Backends::GL) && !backends.intersects(Backends::DX12 | Backends::VULKAN)
|
||||
}
|
||||
|
||||
/// The message shown when no window could be opened at all.
|
||||
pub fn no_window_message(error: &str, settings: Option<&Path>) -> String {
|
||||
let folder = settings.map(|d| d.display().to_string()).unwrap_or_else(|| "the SolidCraft settings folder".into());
|
||||
@@ -214,6 +256,32 @@ mod tests {
|
||||
d
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn isolated_settings_are_explicit_absolute_and_never_fall_back() {
|
||||
let default = std::env::temp_dir().join("ordinary-settings");
|
||||
assert_eq!(settings_directory(None, Some(default.clone())).unwrap(), Some(default.clone()));
|
||||
let selected = std::env::temp_dir().join("solidcraft-isolated-settings-fixture");
|
||||
assert_eq!(settings_directory(Some(selected.as_os_str()), Some(default.clone())).unwrap(), Some(selected));
|
||||
for raw in ["", "relative/settings", "../ordinary"] {
|
||||
assert!(settings_directory(Some(std::ffi::OsStr::new(raw)), Some(default.clone())).is_err());
|
||||
}
|
||||
let file = std::env::current_exe().unwrap();
|
||||
assert!(settings_directory(Some(file.as_os_str()), Some(default)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intel_macs_start_without_indirect_call_validation() {
|
||||
use eframe::wgpu::InstanceFlags as F;
|
||||
let on = F::VALIDATION | F::VALIDATION_INDIRECT_CALL;
|
||||
assert_eq!(window_instance_flags(on, true, None), F::VALIDATION);
|
||||
assert_eq!(window_instance_flags(on, true, Some(" ")), F::VALIDATION);
|
||||
// The user's own choice stands, on or off.
|
||||
assert_eq!(window_instance_flags(on, true, Some("1")), on);
|
||||
assert_eq!(window_instance_flags(F::VALIDATION, true, Some("0")), F::VALIDATION);
|
||||
// Every other machine keeps what it had.
|
||||
assert_eq!(window_instance_flags(on, false, None), on);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switches_read_from_the_environment() {
|
||||
assert!(env_on(Some("1")) && env_on(Some("yes")) && !env_on(Some("0")) && !env_on(Some(" ")) && !env_on(None) && !env_on(Some("false")));
|
||||
@@ -290,6 +358,18 @@ mod tests {
|
||||
assert_eq!(choose_backends(true, false, false, Some(""), probe(&[Backends::GL])), Some(Backends::GL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_opengl_on_windows_gets_the_system_title_bar() {
|
||||
use eframe::wgpu::Backends;
|
||||
assert!(system_title_bar(true, Backends::GL));
|
||||
for b in [Backends::DX12, Backends::VULKAN, Backends::DX12 | Backends::GL, Backends::VULKAN | Backends::GL, Backends::all()] {
|
||||
assert!(!system_title_bar(true, b), "{b:?}");
|
||||
}
|
||||
for b in [Backends::GL, Backends::VULKAN, Backends::METAL] {
|
||||
assert!(!system_title_bar(false, b), "{b:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wgpu_backend_the_preference_and_the_software_renderer_override_the_order() {
|
||||
use eframe::wgpu::Backends;
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
//! Keeping the restored window reachable.
|
||||
//!
|
||||
//! The window comes back where it was left. After a monitor was unplugged, the scale changed or
|
||||
//! the settings came from a larger screen, that can put its title bar above the top edge or
|
||||
//! under the taskbar, out of reach of the mouse. [`fit`] pulls a window back into the monitor's
|
||||
//! work area (the screen without the taskbar), in whatever unit the caller measures both, as
|
||||
//! long as it is the same one.
|
||||
|
||||
/// A rectangle: left, top, width, height.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Area {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub w: f32,
|
||||
pub h: f32,
|
||||
}
|
||||
|
||||
impl Area {
|
||||
pub const fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
|
||||
Area { x, y, w, h }
|
||||
}
|
||||
fn right(self) -> f32 {
|
||||
self.x + self.w
|
||||
}
|
||||
fn bottom(self) -> f32 {
|
||||
self.y + self.h
|
||||
}
|
||||
fn sane(self) -> bool {
|
||||
[self.x, self.y, self.w, self.h].iter().all(|v| v.is_finite()) && self.w > 0.0 && self.h > 0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// How much of the title bar must stay over the work area at least, to grab it.
|
||||
pub const REACHABLE: f32 = 100.0;
|
||||
/// The size a window gets when its saved geometry is unusable, before it is fitted to the screen.
|
||||
pub const DEFAULT_SIZE: (f32, f32) = (1600.0, 1000.0);
|
||||
|
||||
/// `window` moved and shrunk so that it can be reached on `work`: no larger than the work area
|
||||
/// (but not below `min`), the title bar not above the work area's top, the window not below its
|
||||
/// bottom, and at least [`REACHABLE`] of it over the work area horizontally. A window that is
|
||||
/// fine is returned unchanged. Geometry that makes no sense (not finite, empty), or a work
|
||||
/// area that does not, gives the default size centred on the work area.
|
||||
pub fn fit(window: Area, work: Area, min: (f32, f32)) -> Area {
|
||||
if !work.sane() {
|
||||
return window;
|
||||
}
|
||||
let min = (min.0.min(work.w), min.1.min(work.h));
|
||||
if !window.sane() {
|
||||
let w = DEFAULT_SIZE.0.min(work.w * 0.9).max(min.0);
|
||||
let h = DEFAULT_SIZE.1.min(work.h * 0.9).max(min.1);
|
||||
return Area::new(work.x + (work.w - w) / 2.0, work.y + (work.h - h) / 2.0, w, h);
|
||||
}
|
||||
let w = window.w.clamp(min.0, work.w);
|
||||
let h = window.h.clamp(min.1, work.h);
|
||||
let reach = REACHABLE.min(w);
|
||||
// A window that had to shrink goes fully onto the screen; one that fits may keep straddling two.
|
||||
let x = if w < window.w { window.x.max(work.x).min(work.right() - w) } else { window.x.max(work.x + reach - w).min(work.right() - reach) };
|
||||
let y = window.y.min(work.bottom() - h).max(work.y);
|
||||
Area::new(x, y, w, h)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const WORK: Area = Area::new(0.0, 0.0, 2560.0, 1400.0);
|
||||
const MIN: (f32, f32) = (960.0, 600.0);
|
||||
|
||||
#[test]
|
||||
fn a_window_on_screen_is_left_alone() {
|
||||
let w = Area::new(300.0, 120.0, 1600.0, 1000.0);
|
||||
assert_eq!(fit(w, WORK, MIN), w);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_title_bar_above_the_top_or_under_the_taskbar_comes_back() {
|
||||
let fitted = fit(Area::new(300.0, -400.0, 1600.0, 1000.0), WORK, MIN);
|
||||
assert_eq!((fitted.y, fitted.h), (0.0, 1000.0));
|
||||
let fitted = fit(Area::new(300.0, 1300.0, 1600.0, 1000.0), WORK, MIN);
|
||||
assert_eq!(fitted.y + fitted.h, 1400.0);
|
||||
// A taskbar at the top moves the work area down.
|
||||
let below_bar = Area::new(0.0, 48.0, 2560.0, 1352.0);
|
||||
assert_eq!(fit(Area::new(300.0, 0.0, 1600.0, 1000.0), below_bar, MIN).y, 48.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_from_a_monitor_that_is_gone_keeps_a_grabbable_part_on_screen() {
|
||||
let right = fit(Area::new(5000.0, 100.0, 1600.0, 1000.0), WORK, MIN);
|
||||
assert_eq!(right.x, 2560.0 - REACHABLE);
|
||||
let left = fit(Area::new(-4000.0, 100.0, 1600.0, 1000.0), WORK, MIN);
|
||||
assert_eq!(left.x + left.w, REACHABLE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_larger_than_the_screen_shrinks_to_it() {
|
||||
let small = Area::new(0.0, 0.0, 1280.0, 720.0);
|
||||
let fitted = fit(Area::new(-20.0, -30.0, 3200.0, 2000.0), small, MIN);
|
||||
assert_eq!(fitted, small);
|
||||
// Never below the minimum, unless the screen itself is smaller.
|
||||
assert_eq!(fit(Area::new(0.0, 0.0, 100.0, 100.0), WORK, MIN).w, 960.0);
|
||||
assert_eq!(fit(Area::new(0.0, 0.0, 100.0, 100.0), Area::new(0.0, 0.0, 800.0, 500.0), MIN).h, 500.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unusable_geometry_is_centred_at_a_sensible_size() {
|
||||
for bad in [Area::new(f32::NAN, 0.0, 1600.0, 1000.0), Area::new(0.0, 0.0, 0.0, 0.0), Area::new(0.0, 0.0, f32::INFINITY, 10.0)] {
|
||||
let fitted = fit(bad, WORK, MIN);
|
||||
assert_eq!(fitted, Area::new(480.0, 200.0, 1600.0, 1000.0));
|
||||
}
|
||||
let small = Area::new(100.0, 50.0, 1366.0, 728.0);
|
||||
let fitted = fit(Area::new(f32::NAN, 0.0, 1.0, 1.0), small, MIN);
|
||||
assert!(fitted.w <= small.w && fitted.h <= small.h && fitted.x >= small.x && fitted.y >= small.y);
|
||||
// No work area to go by: nothing to change.
|
||||
let w = Area::new(1.0, 2.0, 3.0, 4.0);
|
||||
assert_eq!(fit(w, Area::new(0.0, 0.0, 0.0, 0.0), MIN), w);
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ The CLI still supports arbitrary PNG/ICO exports with `solidcraft-cli icon`.
|
||||
## Code and provenance
|
||||
|
||||
Resources remain compiled by their owning layer through existing APIs:
|
||||
`ui-egui::icons::paint` and `Tokens` at L5, model presets in L2 `doc`, and sketch
|
||||
`ui-egui::icons::paint_icon` and `Tokens` at L5, model presets in L2 `doc`, and sketch
|
||||
fonts at L1. State, projection, interaction, commands and GPU bindings stay in
|
||||
their current modules. Embedded data uses `CARGO_MANIFEST_DIR`; application code
|
||||
needs no runtime resource registry or repository lookup.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//! Original illustrative opaque finishes; not measured optical or engineering data.
|
||||
//! (name, sRGB color, metal fraction, perceptual roughness). IOR is an illustrative 1.5.
|
||||
//! Separate names preserve the scalar library's appearance and saved assignments.
|
||||
pub const OPAQUE_FINISHES: [(&str, [u8; 3], f64, f64); 6] = [
|
||||
("Steel - Satin PBR", [158, 164, 172], 1.0, 0.4),
|
||||
("Aluminum - Satin PBR", [204, 208, 214], 1.0, 0.3),
|
||||
("Copper - Clean PBR", [204, 122, 80], 1.0, 0.25),
|
||||
("ABS - Molded PBR", [222, 222, 216], 0.0, 0.4),
|
||||
("PLA - Smooth PBR", [110, 160, 222], 0.0, 0.35),
|
||||
("Wood - Oak Uniform PBR Preview", [190, 146, 92], 0.0, 0.65),
|
||||
];
|
||||
@@ -0,0 +1,292 @@
|
||||
//! Original starter visual data, MIT OR Apache-2.0. No external material library.
|
||||
use crate::appearance_pack::{Category, GlassBoundary, GlassRecipe, OpaqueRecipe, TextureAxis, TextureRecipe, VisualPreset};
|
||||
|
||||
const WOOD: &str = "Uniform opaque preview only; grain resources/mapping are not applied";
|
||||
const SOLAR: &str = "Uniform opaque preview only; no cell grid, busbars, electricity or efficiency claim";
|
||||
const REGOLITH: &str = "Uniform opaque preview only; no granular displacement or measured lunar optical data";
|
||||
const OPAQUE: &str = "Illustrative opaque isotropic PBR; no measured alloy, polymer or engineering properties";
|
||||
|
||||
const fn look(color: [u8; 3], metallic: f64, roughness: f64, dielectric_ior: f64) -> OpaqueRecipe {
|
||||
OpaqueRecipe { color, metallic, roughness, dielectric_ior }
|
||||
}
|
||||
const fn maps(id: &'static str, repeat_mm: [f64; 2], grain: bool) -> Option<TextureRecipe> {
|
||||
Some(TextureRecipe { id, repeat_mm, grain_axis: if grain { Some(TextureAxis::V) } else { None } })
|
||||
}
|
||||
const fn glass(transmission_linear_rgb: [f64; 3], ior: f64, reference_path_mm: f64, boundary: GlassBoundary) -> GlassRecipe {
|
||||
GlassRecipe { transmission_linear_rgb, dielectric_ior: ior, reference_path_mm, boundary }
|
||||
}
|
||||
|
||||
pub const STARTER_PACK: [VisualPreset; 40] = [
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.oak-honey",
|
||||
"Wood - Honey Oak Uniform Preview",
|
||||
Category::Wood,
|
||||
look([190, 145, 88], 0.0, 0.64, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.oak-honey.maps-v1", [120.0, 240.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.oak-smoked",
|
||||
"Wood - Smoked Oak Uniform Preview",
|
||||
Category::Wood,
|
||||
look([91, 72, 55], 0.0, 0.68, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.oak-smoked.maps-v1", [120.0, 240.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.walnut-dark",
|
||||
"Wood - Dark Walnut Uniform Preview",
|
||||
Category::Wood,
|
||||
look([99, 66, 43], 0.0, 0.60, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.walnut-dark.maps-v1", [100.0, 220.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.maple-light",
|
||||
"Wood - Light Maple Uniform Preview",
|
||||
Category::Wood,
|
||||
look([221, 192, 141], 0.0, 0.57, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.maple-light.maps-v1", [100.0, 200.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.cherry-warm",
|
||||
"Wood - Warm Cherry Uniform Preview",
|
||||
Category::Wood,
|
||||
look([164, 99, 65], 0.0, 0.58, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.cherry-warm.maps-v1", [110.0, 220.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.ash-pale",
|
||||
"Wood - Pale Ash Uniform Preview",
|
||||
Category::Wood,
|
||||
look([209, 186, 146], 0.0, 0.70, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.ash-pale.maps-v1", [120.0, 260.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.birch-cream",
|
||||
"Wood - Cream Birch Uniform Preview",
|
||||
Category::Wood,
|
||||
look([228, 206, 163], 0.0, 0.63, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.birch-cream.maps-v1", [100.0, 200.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.wood.teak-golden",
|
||||
"Wood - Golden Teak Uniform Preview",
|
||||
Category::Wood,
|
||||
look([167, 122, 66], 0.0, 0.61, 1.5),
|
||||
WOOD,
|
||||
maps("starter.wood.teak-golden.maps-v1", [110.0, 250.0], true),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.carbon-steel-polished",
|
||||
"Metal - Polished Carbon Steel",
|
||||
Category::Metal,
|
||||
look([161, 166, 173], 1.0, 0.16, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.stainless-soft-satin",
|
||||
"Metal - Soft Satin Stainless",
|
||||
Category::Metal,
|
||||
look([187, 193, 201], 1.0, 0.36, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.aluminum-bead-blast",
|
||||
"Metal - Bead Blast Aluminum",
|
||||
Category::Metal,
|
||||
look([195, 201, 210], 1.0, 0.58, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.aluminum-silver-polish",
|
||||
"Metal - Silver Polish Aluminum",
|
||||
Category::Metal,
|
||||
look([218, 224, 232], 1.0, 0.13, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.copper-aged-warm",
|
||||
"Metal - Warm Aged Copper",
|
||||
Category::Metal,
|
||||
look([171, 95, 57], 1.0, 0.48, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.brass-champagne",
|
||||
"Metal - Champagne Satin Brass",
|
||||
Category::Metal,
|
||||
look([205, 173, 102], 1.0, 0.32, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.bronze-dark-cast",
|
||||
"Metal - Dark Cast Bronze",
|
||||
Category::Metal,
|
||||
look([133, 95, 58], 1.0, 0.66, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.titanium-matte",
|
||||
"Metal - Matte Gray Titanium",
|
||||
Category::Metal,
|
||||
look([147, 149, 157], 1.0, 0.53, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.gold-warm",
|
||||
"Metal - Warm Reflective Gold",
|
||||
Category::Metal,
|
||||
look([235, 190, 82], 1.0, 0.18, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.metal.silver-cool",
|
||||
"Metal - Cool Reflective Silver",
|
||||
Category::Metal,
|
||||
look([225, 230, 237], 1.0, 0.12, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.abs-charcoal",
|
||||
"Plastic - Charcoal ABS",
|
||||
Category::Plastic,
|
||||
look([43, 45, 48], 0.0, 0.44, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.abs-warm-white",
|
||||
"Plastic - Warm White ABS",
|
||||
Category::Plastic,
|
||||
look([232, 229, 220], 0.0, 0.40, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque("starter.plastic.pla-coral", "Plastic - Coral PLA", Category::Plastic, look([218, 99, 83], 0.0, 0.43, 1.5), OPAQUE, None),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.petg-cyan-opaque",
|
||||
"Plastic - Opaque Cyan PETG",
|
||||
Category::Plastic,
|
||||
look([36, 148, 167], 0.0, 0.29, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.nylon-ivory",
|
||||
"Plastic - Ivory Nylon",
|
||||
Category::Plastic,
|
||||
look([220, 213, 190], 0.0, 0.62, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.pc-graphite-opaque",
|
||||
"Plastic - Opaque Graphite PC",
|
||||
Category::Plastic,
|
||||
look([70, 76, 84], 0.0, 0.27, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.hdpe-natural-opaque",
|
||||
"Plastic - Opaque Natural HDPE",
|
||||
Category::Plastic,
|
||||
look([221, 222, 207], 0.0, 0.48, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque("starter.plastic.pom-black", "Plastic - Black POM", Category::Plastic, look([29, 31, 35], 0.0, 0.32, 1.5), OPAQUE, None),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.tpu-matte-black",
|
||||
"Plastic - Matte Black TPU",
|
||||
Category::Plastic,
|
||||
look([31, 30, 32], 0.0, 0.83, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.plastic.peek-amber-opaque",
|
||||
"Plastic - Opaque Amber PEEK",
|
||||
Category::Plastic,
|
||||
look([157, 119, 62], 0.0, 0.51, 1.5),
|
||||
OPAQUE,
|
||||
None,
|
||||
),
|
||||
VisualPreset::glass(
|
||||
"starter.glass.clear-soda-lime",
|
||||
"Glass Recipe - Clear Soda Lime",
|
||||
glass([0.92, 0.98, 0.96], 1.52, 3.0, GlassBoundary::Closed),
|
||||
),
|
||||
VisualPreset::glass("starter.glass.low-iron-clear", "Glass Recipe - Low Iron Clear", glass([0.98, 0.99, 1.0], 1.50, 4.0, GlassBoundary::Closed)),
|
||||
VisualPreset::glass("starter.glass.smoky-neutral", "Glass Recipe - Smoky Neutral", glass([0.35, 0.38, 0.42], 1.52, 3.0, GlassBoundary::Closed)),
|
||||
VisualPreset::glass("starter.glass.amber-tint", "Glass Recipe - Amber Tint", glass([0.84, 0.38, 0.08], 1.51, 3.0, GlassBoundary::Closed)),
|
||||
VisualPreset::glass("starter.glass.cobalt-blue", "Glass Recipe - Cobalt Blue", glass([0.06, 0.14, 0.76], 1.53, 3.0, GlassBoundary::Closed)),
|
||||
VisualPreset::glass(
|
||||
"starter.glass.thin-clear",
|
||||
"Glass Recipe - Thin Clear Sheet",
|
||||
glass([0.95, 0.98, 0.99], 1.50, 2.0, GlassBoundary::Thin { thickness_mm: 2.0 }),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.solar.mono-blue",
|
||||
"Solar Panel - Mono Blue Uniform Preview",
|
||||
Category::Solar,
|
||||
look([20, 41, 85], 0.0, 0.20, 1.5),
|
||||
SOLAR,
|
||||
maps("starter.solar.mono-blue.maps-v1", [160.0, 160.0], false),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.solar.thin-film-dark",
|
||||
"Solar Panel - Dark Thin Film Uniform Preview",
|
||||
Category::Solar,
|
||||
look([28, 33, 42], 0.0, 0.28, 1.5),
|
||||
SOLAR,
|
||||
maps("starter.solar.thin-film-dark.maps-v1", [100.0, 200.0], false),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.regolith.mare-charcoal",
|
||||
"Regolith - Mare Charcoal Uniform Preview",
|
||||
Category::Regolith,
|
||||
look([56, 56, 55], 0.0, 0.97, 1.5),
|
||||
REGOLITH,
|
||||
maps("starter.regolith.mare-charcoal.maps-v1", [200.0, 200.0], false),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.regolith.highland-pale",
|
||||
"Regolith - Highland Pale Uniform Preview",
|
||||
Category::Regolith,
|
||||
look([116, 113, 106], 0.0, 0.93, 1.5),
|
||||
REGOLITH,
|
||||
maps("starter.regolith.highland-pale.maps-v1", [200.0, 200.0], false),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.regolith.dust-neutral",
|
||||
"Regolith - Neutral Dust Uniform Preview",
|
||||
Category::Regolith,
|
||||
look([86, 85, 81], 0.0, 1.0, 1.5),
|
||||
REGOLITH,
|
||||
maps("starter.regolith.dust-neutral.maps-v1", [150.0, 150.0], false),
|
||||
),
|
||||
VisualPreset::opaque(
|
||||
"starter.regolith.agglutinate-dark",
|
||||
"Regolith - Dark Agglutinate Uniform Preview",
|
||||
Category::Regolith,
|
||||
look([66, 63, 60], 0.0, 0.89, 1.5),
|
||||
REGOLITH,
|
||||
maps("starter.regolith.agglutinate-dark.maps-v1", [200.0, 200.0], false),
|
||||
),
|
||||
];
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 629 B After Width: | Height: | Size: 660 B |
|
Before Width: | Height: | Size: 944 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -1,88 +1,93 @@
|
||||
//! Original SolidCraft mark, shared by desktop, packaging and CLI.
|
||||
//! Original SolidCraft mark, shared by desktop, packaging and CLI: the borderless mark, five
|
||||
//! faces with transparent gaps between them, drawn from the macOS icon's
|
||||
//! `macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg`.
|
||||
type P = (f32, f32);
|
||||
|
||||
/// One face in that SVG's units: its outline, and its linear gradient from one point and
|
||||
/// colour to another.
|
||||
struct Face {
|
||||
outline: &'static [P],
|
||||
from: (P, [u8; 3]),
|
||||
to: (P, [u8; 3]),
|
||||
}
|
||||
|
||||
const FACES: [Face; 5] = [
|
||||
// Inner side of the upper step.
|
||||
Face {
|
||||
outline: &[(547.883, 282.66), (709.797, 188.211), (709.797, 375.985)],
|
||||
from: ((619.14, 179.734), [29, 99, 198]),
|
||||
to: ((694.687, 396.761), [21, 73, 158]),
|
||||
},
|
||||
// Top of the lower step.
|
||||
Face {
|
||||
outline: &[(727.797, 593.626), (727.797, 407.136), (889.571, 500.381)],
|
||||
from: ((619.14, 413.244), [69, 159, 255]),
|
||||
to: ((790.839, 605.547), [35, 121, 231]),
|
||||
},
|
||||
// Right face, with the recess.
|
||||
Face {
|
||||
outline: &[(530.0, 521.17), (691.797, 427.912), (691.797, 655.928), (907.594, 531.545), (907.594, 740.159), (530.0, 957.8)],
|
||||
from: ((632.876, 399.508), [27, 102, 207]),
|
||||
to: ((783.971, 968.175), [18, 69, 150]),
|
||||
},
|
||||
// Left face, stepped.
|
||||
Face {
|
||||
outline: &[(116.406, 510.769), (116.406, 303.529), (494.0, 521.17), (494.0, 957.8), (314.203, 854.167), (314.203, 624.777)],
|
||||
from: ((207.063, 303.357), [37, 141, 245]),
|
||||
to: ((426.837, 968.175), [20, 97, 218]),
|
||||
},
|
||||
// Top of the extruded profile.
|
||||
Face {
|
||||
outline: &[(134.374, 272.334), (512.0, 53.364), (691.828, 157.015), (476.117, 282.846), (673.752, 396.761), (512.0, 489.993)],
|
||||
from: ((385.63, 53.364), [81, 173, 255]),
|
||||
to: ((522.989, 510.769), [43, 131, 241]),
|
||||
},
|
||||
];
|
||||
|
||||
/// The mark's extent in SVG units: left, top, width, height.
|
||||
const BOUNDS: (f32, f32, f32, f32) = (116.406, 53.364, 791.188, 904.436);
|
||||
|
||||
/// This block is the icon image data
|
||||
pub fn rgba(size: usize) -> Result<Vec<u8>, String> {
|
||||
if !(1..=2048).contains(&size) {
|
||||
return Err("icon size must be 1..=2048".into());
|
||||
}
|
||||
type P = (f32, f32);
|
||||
// Coverage buffer at 4x4 samples per pixel (2x2 for big sizes), averaged down at the end.
|
||||
let ss = if size > 512 { 2 } else { 4 };
|
||||
let n = size * ss;
|
||||
let mut buf = vec![[0u8; 4]; n * n];
|
||||
let s = n as f32 / 412.217; // SVG units -> samples: the logo fills the height
|
||||
let ox = (n as f32 - 357.0 * s) / 2.0; // and is centred sideways
|
||||
// Fill a convex polygon given in SVG units.
|
||||
let mut fill = |pts: &[P], col: [u8; 3]| {
|
||||
let v: Vec<P> = pts.iter().map(|p| (ox + p.0 * s, p.1 * s)).collect();
|
||||
let (bx, by, bw, bh) = BOUNDS;
|
||||
let s = n as f32 / bh; // SVG units -> samples: the mark fills the height
|
||||
let (ox, oy) = ((n as f32 - bw * s) / 2.0 - bx * s, -by * s); // and is centred sideways
|
||||
// Fill each face's outline (even-odd spans per row, so concave outlines work) with its gradient.
|
||||
for face in &FACES {
|
||||
let v: Vec<P> = face.outline.iter().map(|p| (ox + p.0 * s, oy + p.1 * s)).collect();
|
||||
let (((fx, fy), c0), ((tx, ty), c1)) = (face.from, face.to);
|
||||
let (gx, gy) = (tx - fx, ty - fy);
|
||||
let g2 = gx * gx + gy * gy;
|
||||
let (top, bottom) = v.iter().fold((f32::MAX, f32::MIN), |(a, b), p| (a.min(p.1), b.max(p.1)));
|
||||
let mut xs = Vec::with_capacity(v.len());
|
||||
for y in ((top - 0.5).ceil() as i32).max(0)..=((bottom - 0.5).floor() as i32).min(n as i32 - 1) {
|
||||
let cy = y as f32 + 0.5;
|
||||
let (mut lo, mut hi) = (f32::MAX, f32::MIN);
|
||||
xs.clear();
|
||||
for i in 0..v.len() {
|
||||
let (a, b) = (v[i], v[(i + 1) % v.len()]);
|
||||
if (a.1 <= cy) != (b.1 <= cy) {
|
||||
let x = a.0 + (cy - a.1) / (b.1 - a.1) * (b.0 - a.0);
|
||||
lo = lo.min(x);
|
||||
hi = hi.max(x);
|
||||
xs.push(a.0 + (cy - a.1) / (b.1 - a.1) * (b.0 - a.0));
|
||||
}
|
||||
}
|
||||
if lo > hi {
|
||||
continue;
|
||||
}
|
||||
for x in ((lo - 0.5).ceil() as i32).max(0)..=((hi - 0.5).floor() as i32).min(n as i32 - 1) {
|
||||
buf[y as usize * n + x as usize] = [col[0], col[1], col[2], 255];
|
||||
xs.sort_by(f32::total_cmp);
|
||||
for &[from, to] in xs.as_chunks::<2>().0 {
|
||||
for x in ((from - 0.5).ceil() as i32).max(0)..=((to - 0.5).floor() as i32).min(n as i32 - 1) {
|
||||
// The sample's place along the gradient, in SVG units.
|
||||
let (px, py) = ((x as f32 + 0.5 - ox) / s, (cy - oy) / s);
|
||||
let t = (((px - fx) * gx + (py - fy) * gy) / g2).clamp(0.0, 1.0);
|
||||
let mix = |k: usize| (f32::from(c0[k]) + (f32::from(c1[k]) - f32::from(c0[k])) * t).round() as u8;
|
||||
buf[y as usize * n + x as usize] = [mix(0), mix(1), mix(2), 255];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Faces: each is a few overlapping convex pieces (the filler only does convex shapes).
|
||||
let (light, mid, dark) = ([96, 158, 236], [58, 136, 238], [55, 108, 197]);
|
||||
fill(&[(3.5, 105.9), (177.0, 5.8), (263.4, 55.6), (263.8, 156.0), (177.0, 406.4)], light);
|
||||
fill(&[(177.0, 206.1), (263.8, 156.0), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)], light);
|
||||
fill(&[(3.5, 105.9), (177.0, 206.1), (177.0, 406.4)], mid);
|
||||
fill(&[(3.5, 105.9), (3.5, 205.7), (90.3, 256.2), (177.0, 206.1)], mid);
|
||||
fill(&[(177.0, 206.1), (90.3, 256.2), (90.0, 356.2), (177.0, 406.4)], mid);
|
||||
fill(&[(264.1, 155.8), (177.0, 206.1), (177.0, 406.4), (263.8, 256.2)], dark);
|
||||
fill(&[(263.8, 256.2), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)], dark);
|
||||
fill(&[(177.0, 206.1), (177.0, 406.4), (350.5, 306.3)], dark);
|
||||
fill(&[(263.8, 55.9), (263.8, 156.0), (177.1, 105.9)], dark);
|
||||
// White edge lines, 10 units wide: a rectangle per segment and a disc at each inner corner.
|
||||
let white = [255, 255, 255];
|
||||
let mut stroke = |pts: &[P], closed: bool| {
|
||||
let m = pts.len();
|
||||
for i in 0..(if closed { m } else { m - 1 }) {
|
||||
let (a, b) = (pts[i], pts[(i + 1) % m]);
|
||||
let l = ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt();
|
||||
let (nx, ny) = (-(b.1 - a.1) / l * 5.0, (b.0 - a.0) / l * 5.0);
|
||||
fill(&[(a.0 + nx, a.1 + ny), (b.0 + nx, b.1 + ny), (b.0 - nx, b.1 - ny), (a.0 - nx, a.1 - ny)], white);
|
||||
}
|
||||
for c in &pts[if closed { 0 } else { 1 }..if closed { m } else { m - 1 }] {
|
||||
let disc: Vec<P> = (0..16)
|
||||
.map(|k| {
|
||||
let t = k as f32 * std::f32::consts::TAU / 16.0;
|
||||
(c.0 + 5.0 * t.cos(), c.1 + 5.0 * t.sin())
|
||||
})
|
||||
.collect();
|
||||
fill(&disc, white);
|
||||
}
|
||||
};
|
||||
stroke(&[(263.8, 55.9), (177.1, 105.9), (263.8, 156.0), (263.8, 256.2), (350.6, 206.1)], false);
|
||||
stroke(&[(5.0, 105.9), (178.5, 206.1), (178.5, 406.4)], false);
|
||||
stroke(&[(177.0, 206.1), (263.8, 156.0)], false);
|
||||
stroke(
|
||||
&[
|
||||
(178.5, 5.8),
|
||||
(5.0, 105.9),
|
||||
(5.0, 206.1),
|
||||
(91.7, 256.2),
|
||||
(91.7, 356.4),
|
||||
(178.5, 406.4),
|
||||
(352.0, 306.3),
|
||||
(352.0, 206.1),
|
||||
(265.2, 156.0),
|
||||
(265.2, 55.9),
|
||||
],
|
||||
true,
|
||||
);
|
||||
}
|
||||
// Average each ss x ss block down to one pixel (straight alpha = covered share).
|
||||
let mut rgba = Vec::with_capacity(size * size * 4);
|
||||
for y in 0..size {
|
||||
@@ -123,4 +128,20 @@ mod tests {
|
||||
assert!(super::rgba(0).is_err());
|
||||
assert!(super::rgba(2049).is_err());
|
||||
}
|
||||
|
||||
/// Borderless: no white edge lines, and the gaps between faces are see-through. At 256 px
|
||||
/// the gap between the left and right faces (SVG x 494..530) is transparent on the row
|
||||
/// through the middle of the mark, and no opaque pixel is white.
|
||||
#[test]
|
||||
fn shared_mark_is_borderless() {
|
||||
let size = 256;
|
||||
let pixels = super::rgba(size).expect("valid size");
|
||||
let px = |x: usize, y: usize| pixels.as_chunks::<4>().0[y * size + x];
|
||||
let s = size as f32 / 904.436;
|
||||
let gap_x = ((size as f32 - 791.188 * s) / 2.0 + (512.0 - 116.406) * s) as usize;
|
||||
let row = ((750.0 - 53.364) * s) as usize;
|
||||
assert_eq!(px(gap_x, row)[3], 0, "the seam between the left and right faces is a gap");
|
||||
assert!(px(gap_x - 8, row)[3] == 255 && px(gap_x + 8, row)[3] == 255, "faces on both sides of the gap");
|
||||
assert!(pixels.as_chunks::<4>().0.iter().all(|p| p[3] == 0 || p[0] < 200), "no white edges");
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 364 KiB After Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 44 KiB |
@@ -91,7 +91,7 @@ Palette changes preserve accepted artwork and never update review hashes.
|
||||
| `source/` | Rust viewport glyphs, markers, navigation and pointer; not command icons |
|
||||
|
||||
`crates/ui-egui/build.rs` embeds SVGs, palette and aliases. `icon_svg.rs` parses and
|
||||
rasterises them for `icons::paint`; adding an icon still requires its registry or
|
||||
rasterises them for `icons::paint_icon`; adding an icon still requires its registry or
|
||||
UI assignment and an application rebuild. Review status never controls rendering.
|
||||
|
||||
## Checks and reports
|
||||
|
||||
@@ -78,7 +78,7 @@ Its projected circle uses the same axes; do not arbitrarily flatten the ellipse.
|
||||
|
||||
Only use the stable `source` identifiers below in model SVGs. The renderer maps roles to
|
||||
light/dark theme values. `currentColor` is the background line-art role, not a way
|
||||
to add arbitrary colours. All values and accents come from `design/palette.toml`.
|
||||
to add arbitrary colours. Default display values and semantic accents come from `design/palette.toml`. Theme files may override those display roles and ramps through `icon_palette`; source identifiers and accent assignments stay unchanged.
|
||||
|
||||
| Role | Author helper | Light value | Use |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -96,9 +96,9 @@ The source hexes identify paint roles, not the displayed colour. **Never recolou
|
||||
SVG to apply a workspace accent.** Keep these identifiers and every finished shape
|
||||
unchanged; the runtime and review page resolve them through `design/palette.toml`.
|
||||
|
||||
That file is the single colour authority: edit `light`/`dark` in the relevant
|
||||
That file is the default colour authority: edit `light`/`dark` in the relevant
|
||||
`accents` ramp; `active-3` is its primary stroke and `active-1/2` are shaded faces.
|
||||
Do not edit a role's `source` identifier. `variants` holds colour-vision adjustments.
|
||||
Do not edit a role's `source` identifier. Theme `icon_palette` overrides affect display colours only; colour-vision variants still take precedence. `variants` holds colour-vision adjustments.
|
||||
A role with `use = "active-3"` follows that stroke automatically.
|
||||
|
||||
| Meaning | Accent | Assignment |
|
||||
|
||||
@@ -29,6 +29,7 @@ ground = "c_fix"
|
||||
|
||||
# Same finished drawing, different semantic operation. Colours live only in palette.toml.
|
||||
sketch_break = { master = "break", accent = "sketch" }
|
||||
sketch_cancel = { master = "close", accent = "sketch" }
|
||||
sketch_dxf = { master = "dxf", accent = "sketch" }
|
||||
sketch_move = { master = "move", accent = "sketch" }
|
||||
sketch_scale = { master = "scale", accent = "sketch" }
|
||||
|
||||
@@ -104,6 +104,7 @@ flange false
|
||||
flat false
|
||||
fold false
|
||||
folder false
|
||||
frame_member false
|
||||
gear false
|
||||
graph false
|
||||
ground false
|
||||
@@ -141,18 +142,14 @@ new false
|
||||
normal false
|
||||
offset false
|
||||
offset_face false
|
||||
offset_one_side false
|
||||
offset_symmetric false
|
||||
offset_two_sides false
|
||||
open false
|
||||
orbit false
|
||||
origin false
|
||||
pan false
|
||||
params false
|
||||
paste false
|
||||
pattern_center false
|
||||
path_flange false
|
||||
pattern_circ false
|
||||
pattern_flip false
|
||||
pattern_path false
|
||||
pattern_rect false
|
||||
perforate false
|
||||
@@ -208,12 +205,15 @@ scale false
|
||||
screw false
|
||||
section false
|
||||
select false
|
||||
send false
|
||||
settings false
|
||||
sheet_rules false
|
||||
shell false
|
||||
shield false
|
||||
silhouette false
|
||||
sketch false
|
||||
sketch_break false
|
||||
sketch_cancel false
|
||||
sketch_chamfer false
|
||||
sketch_chamfer_angle false
|
||||
sketch_chamfer_equal false
|
||||
@@ -242,6 +242,7 @@ split_face false
|
||||
spun false
|
||||
star false
|
||||
status_error false
|
||||
status_recovery false
|
||||
style false
|
||||
suppress false
|
||||
surface_blend false
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Send message</title>
|
||||
<path d="M32 51L32 13M17 28L32 13L47 28" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Keyboard shortcuts</title>
|
||||
<path d="M13 16L51 16A5 5 0 0 1 56 21L56 43A5 5 0 0 1 51 48L13 48A5 5 0 0 1 8 43L8 21A5 5 0 0 1 13 16Z" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="20" cy="27" r="2.8" fill="currentColor"/>
|
||||
<circle cx="32" cy="27" r="2.8" fill="currentColor"/>
|
||||
<circle cx="44" cy="27" r="2.8" fill="currentColor"/>
|
||||
<path d="M20 39L44 39" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 607 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Graphics display</title>
|
||||
<path d="M13 10L51 10A5 5 0 0 1 56 15L56 37A5 5 0 0 1 51 42L13 42A5 5 0 0 1 8 37L8 15A5 5 0 0 1 13 10ZM32 42L32 54M24 54L40 54" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Mouse navigation</title>
|
||||
<path d="M32 8A13 13 0 0 1 45 21L45 43A13 13 0 0 1 19 43L19 21A13 13 0 0 1 32 8ZM32 8L32 24" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 299 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Interface palette</title>
|
||||
<path d="M32 8C18.75 8 8 18.75 8 32C8 45.25 18.75 56 32 56C37 56 39 52.5 36.5 49C34 45.5 36 42 40 42L45 42C51.1 42 56 37.1 56 31C56 19 45 8 32 8Z" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="21" cy="29" r="2.8" fill="currentColor"/>
|
||||
<circle cx="29" cy="20" r="2.8" fill="currentColor"/>
|
||||
<circle cx="41" cy="23" r="2.8" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 522 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Plugins</title>
|
||||
<path d="M24 8L24 21M40 8L40 21M16 21L48 21L48 29A16 16 0 0 1 16 29L16 21ZM32 45L32 56" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 285 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Approval required</title>
|
||||
<path d="M32 9L51 16L51 31C51 43 43 51 32 56C21 51 13 43 13 31L13 16Z" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 278 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Stop response</title>
|
||||
<path d="M20 17L44 17A3 3 0 0 1 47 20L47 44A3 3 0 0 1 44 47L20 47A3 3 0 0 1 17 44L17 20A3 3 0 0 1 20 17Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Frame Member</title>
|
||||
<path d="M9.25 18.86L17.63 23.7" fill="none" stroke="#ffffff" stroke-width="4.4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9.25 18.86L17.63 23.7" fill="none" stroke="#236ead" stroke-width="2.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="9.25" cy="18.86" r="3" fill="#236ead"/>
|
||||
<path d="M11.88 20.39L40.62 36.98L40.62 50.25L11.88 33.66Z" fill="#328bd3"/>
|
||||
<path d="M23.38 13.75L52.12 30.34L40.62 36.98L11.88 20.39Z" fill="#b9ddf8"/>
|
||||
<path d="M52.12 30.34L40.62 36.98L40.62 50.25L52.12 43.61ZM49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="#8e9faf" fill-rule="evenodd"/>
|
||||
<defs><clipPath id="fm_hole"><path d="M49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#fm_hole)">
|
||||
<path d="M49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="#236ead"/>
|
||||
<path d="M49.8 34.35L49.8 42.28L42.86 38.27L42.86 30.34Z" fill="#328bd3"/>
|
||||
<path d="M49.8 42.28L42.94 46.24L35.99 42.23L42.86 38.27Z" fill="#b9ddf8"/>
|
||||
</g>
|
||||
<path d="M49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23.38 13.75L52.12 30.34L52.12 43.61L40.62 50.25L11.88 33.66L11.88 20.39Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M11.88 20.39L40.62 36.98L52.12 30.34" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M40.62 36.98L40.62 50.25" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M41.74 37.62L54.75 45.14" fill="none" stroke="#ffffff" stroke-width="4.4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M41.74 37.62L54.75 45.14" fill="none" stroke="#236ead" stroke-width="2.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="54.75" cy="45.14" r="3" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Path Flange</title>
|
||||
<path d="M9.5 44L23.5 44A6 6 0 0 0 29.5 38L29.5 26A6 6 0 0 1 35.5 20L43.5 20A6 6 0 0 1 47.74 21.76L54.81 28.83" fill="none" stroke="#34465b" stroke-width="11.4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9.5 44L23.5 44" fill="none" stroke="#b8c3cf" stroke-width="6.3" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23.5 44A6 6 0 0 0 29.5 38L29.5 26A6 6 0 0 1 35.5 20L43.5 20A6 6 0 0 1 47.74 21.76L54.81 28.83" fill="none" stroke="#328bd3" stroke-width="6.3" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 638 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Recover unsaved design</title>
|
||||
<circle cx="32" cy="33" r="22" fill="#b9ddf8"/>
|
||||
<path d="M10 33A22 22 0 1 0 16.44 17.44L10 24" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L10 24L22 24" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 21L32 33L41 39" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 563 B |
@@ -99,14 +99,23 @@ pub(crate) fn breadcrumb(p: &egui::Painter, c: egui::Pos2, col: Color32) {
|
||||
}
|
||||
|
||||
pub(crate) fn brand_mark(p: &egui::Painter, c: Pos2) {
|
||||
let s = 20.0 / 412.217;
|
||||
brand_mark_styled(p, c, 20.0, crate::theme::Tokens::get().logo, true);
|
||||
}
|
||||
|
||||
/// The same original mark as a single quiet silhouette, without face seams or outlines.
|
||||
pub(crate) fn brand_mark_silhouette(p: &egui::Painter, c: Pos2, height: f32, ink: Color32) {
|
||||
brand_mark_styled(p, c, height, [ink; 3], false);
|
||||
}
|
||||
|
||||
fn brand_mark_styled(p: &egui::Painter, c: Pos2, height: f32, colours: [Color32; 3], edges: bool) {
|
||||
let s = height / 412.217;
|
||||
let m = |pts: &[(f32, f32)]| -> Vec<egui::Pos2> { pts.iter().map(|&(x, y)| pos2(c.x + (x - 178.5) * s, c.y + (y - 206.1) * s)).collect() };
|
||||
// egui only fills convex shapes, so each face is a few overlapping convex pieces.
|
||||
let face = |col: Color32, pts: &[(f32, f32)]| {
|
||||
p.add(egui::Shape::convex_polygon(m(pts), col, Stroke::NONE));
|
||||
};
|
||||
// The faces are separated by gaps that show the application bar, not by white lines.
|
||||
let [light, mid, dark, _] = crate::theme::Tokens::get().logo;
|
||||
let [light, mid, dark] = colours;
|
||||
let edge_col = crate::theme::Tokens::get().app_bar;
|
||||
face(light, &[(3.5, 105.9), (177.0, 5.8), (263.4, 55.6), (263.8, 156.0), (177.0, 406.4)]);
|
||||
face(light, &[(177.0, 206.1), (263.8, 156.0), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)]);
|
||||
@@ -117,7 +126,10 @@ pub(crate) fn brand_mark(p: &egui::Painter, c: Pos2) {
|
||||
face(dark, &[(263.8, 256.2), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)]);
|
||||
face(dark, &[(177.0, 206.1), (177.0, 406.4), (350.5, 306.3)]);
|
||||
face(dark, &[(263.8, 55.9), (263.8, 156.0), (177.1, 105.9)]);
|
||||
// White edge lines.
|
||||
if !edges {
|
||||
return;
|
||||
}
|
||||
// The seams between the faces and around the mark, in the bar's colour, so they read as gaps.
|
||||
let edge = Stroke::new(10.0 * s, edge_col);
|
||||
let line = |pts: &[(f32, f32)]| {
|
||||
p.add(egui::Shape::line(m(pts), edge));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Constraint, assembly snap and dimension marker artwork.
|
||||
//! Constraint, assembly snap, hole placement and dimension marker artwork.
|
||||
use crate::dialogs_assembly::SnapKind;
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Color32, Pos2, Stroke, vec2};
|
||||
@@ -57,6 +57,39 @@ pub(crate) fn snap(painter: &egui::Painter, c: Pos2, tip: Option<Pos2>, kind: Sn
|
||||
painter.galley(at, g, t.text);
|
||||
}
|
||||
}
|
||||
/// A place a hole can snap to, before the cursor reaches it: a small hollow mark of its kind
|
||||
/// (a square for a face's centre, a ring for a round edge's centre).
|
||||
pub(crate) fn snap_hint(painter: &egui::Painter, c: Pos2, kind: SnapKind, col: Color32) {
|
||||
let ic = Tokens::get().icons;
|
||||
for stroke in [Stroke::new(3.5, ic.marker_halo), Stroke::new(1.4, col)] {
|
||||
match kind {
|
||||
SnapKind::FaceCenter => {
|
||||
painter.rect_stroke(egui::Rect::from_center_size(c, vec2(8.0, 8.0)), 1.0, stroke, egui::StrokeKind::Middle);
|
||||
}
|
||||
SnapKind::CircleCenter => {
|
||||
painter.circle_stroke(c, 4.5, stroke);
|
||||
}
|
||||
SnapKind::EdgeMid | SnapKind::Vertex => {
|
||||
let r = 5.0;
|
||||
let diamond = vec![c + vec2(0.0, -r), c + vec2(r, 0.0), c + vec2(0.0, r), c + vec2(-r, 0.0)];
|
||||
painter.add(egui::Shape::convex_polygon(diamond, Color32::TRANSPARENT, stroke));
|
||||
}
|
||||
}
|
||||
}
|
||||
painter.circle_filled(c, 1.3, col);
|
||||
}
|
||||
|
||||
/// A placed hole's drag handle: a ring round the hole's centre with a cross through it, filled
|
||||
/// while it is hovered or dragged.
|
||||
pub(crate) fn hole_handle(painter: &egui::Painter, c: Pos2, col: Color32, hot: bool) {
|
||||
let ic = Tokens::get().icons;
|
||||
let r = 6.0;
|
||||
painter.circle_filled(c, r + 3.5, ic.marker_halo);
|
||||
painter.circle(c, r, if hot { col } else { col.gamma_multiply(0.35) }, Stroke::new(1.6, col));
|
||||
let ink = Stroke::new(1.3, if hot { ic.highlight } else { col });
|
||||
painter.line_segment([c - vec2(r + 3.0, 0.0), c + vec2(r + 3.0, 0.0)], ink);
|
||||
painter.line_segment([c - vec2(0.0, r + 3.0), c + vec2(0.0, r + 3.0)], ink);
|
||||
}
|
||||
pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
|
||||
Some(match name {
|
||||
"Coincident" => "c_coincident",
|
||||
@@ -75,8 +108,8 @@ pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
|
||||
}
|
||||
pub(crate) fn glyph_letter(name: &str) -> &'static str {
|
||||
match name {
|
||||
"Horizontal" => "H",
|
||||
"Vertical" => "V",
|
||||
"Horizontal" => "-",
|
||||
"Vertical" => "|",
|
||||
_ => "?",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Vybrat…",
|
||||
"Circular": "Kruhově",
|
||||
"Circumference": "Obvod",
|
||||
"Clockwise": "Ve směru hodinových ručiček",
|
||||
"Close draft": "Zavřít koncept",
|
||||
"Collisions": "Kolize",
|
||||
"Colour": "Barva",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "HISTORIE",
|
||||
"Height": "Výška",
|
||||
"Hidden edges": "Skryté hrany",
|
||||
"History layout": "Rozložení historie",
|
||||
"Holes": "Otvory",
|
||||
"Holes (edges on them)": "Otvory (jejich hrany)",
|
||||
"Home view": "Výchozí pohled",
|
||||
@@ -232,7 +234,7 @@
|
||||
"No crash reports.": "Žádná hlášení o pádech.",
|
||||
"No matching appearance presets.": "Žádné odpovídající předvolby vzhledu.",
|
||||
"No matching appearances": "Žádné odpovídající vzhledy",
|
||||
"No visible bodies to export.": "Žádná viditelná tělesa k exportu.",
|
||||
"No visible bodies or sketches to export.": "Žádná viditelná tělesa ani náčrty k exportu.",
|
||||
"None": "Žádné",
|
||||
"Not available yet": "Zatím není k dispozici",
|
||||
"Notch": "Zářez",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Search commands": "Hledat příkazy",
|
||||
"Search this library": "Prohledat tuto knihovnu",
|
||||
"Section": "Řez",
|
||||
"Section position": "Poloha průřezu",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vybrat jiné: podržte levé tlačítko na modelu (nebo pravé tlačítko › Vybrat jiné…) a zobrazí se vše pod kurzorem; dalším kliknutím na stejné místo vyberete další prvek. Dvojklikem prostředním tlačítkem přejdete na výchozí pohled (Předvolby › Navigace).",
|
||||
"Select bodies (a face picks its body)": "Vyberte tělesa (plocha vybere své těleso)",
|
||||
"Select bodies or faces instead": "Vyberte místo toho tělesa nebo plochy",
|
||||
@@ -332,6 +335,7 @@
|
||||
"Show Agent Cursor": "Zobrazit kurzor agenta",
|
||||
"Show in folder": "Zobrazit ve složce",
|
||||
"Show the pointer an AI agent moves (#35)": "Zobrazit ukazatel, kterým pohybuje AI agent (#35)",
|
||||
"Show tool at cursor": "Zobrazit nástroj u kurzoru",
|
||||
"Signed axes": "Osy se znaménkem",
|
||||
"Size": "Velikost",
|
||||
"Sketch constraints": "Vazby náčrtu",
|
||||
@@ -344,9 +348,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft se zavřel dříve, než se tyto návrhy uložily. Lze otevřít jejich poslední automatické uložení.",
|
||||
"Specialise": "Specializovat",
|
||||
"Speed": "Rychlost",
|
||||
"Square": "Čtverec",
|
||||
"Stand it up": "Postavit",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Postavit: otočení o čtvrt otáčky kolem osy X jako jeden prvek Posunutí, který lze vrátit.",
|
||||
"Stand this part up?": "Postavit tento díl?",
|
||||
"Start angle": "Počáteční úhel",
|
||||
"Start bulge": "Vypouklost začátku",
|
||||
"Start continuity": "Návaznost začátku",
|
||||
"Step": "Krok",
|
||||
@@ -411,6 +417,7 @@
|
||||
"angle, height": "úhel, výška",
|
||||
"appearance.dielectric_ior": "Index lomu dielektrika",
|
||||
"appearance.metallic": "Kovovost",
|
||||
"appearance.only_available": "Pouze dostupné vzhledy",
|
||||
"appearance.pbr_help": "Tento povrch zobrazíte v renderu nebo v produktovém PNG. Bez lomu a průchodu světla.",
|
||||
"appearance.pbr_opaque": "Neprůhledné PBR",
|
||||
"appearance.roughness": "Drsnost",
|
||||
@@ -529,6 +536,10 @@
|
||||
"cmd.file_insert_step": "Vložit STEP",
|
||||
"cmd.file_save_mesh": "Uložit jako síť",
|
||||
"cmd.file_slicers": "Nainstalované slicery",
|
||||
"cmd.frame_member": "Prvek rámu",
|
||||
"cmd.frame_profile_insert": "Vložit profil",
|
||||
"cmd.frame_profile_list": "Knihovna profilů",
|
||||
"cmd.frame_profile_properties": "Vlastnosti profilu",
|
||||
"cmd.inspect_accessibility": "Analýza přístupnosti",
|
||||
"cmd.inspect_center_of_mass": "Těžiště",
|
||||
"cmd.inspect_clearance": "Vůle",
|
||||
@@ -568,6 +579,7 @@
|
||||
"cmd.material_create": "Nový fyzický materiál",
|
||||
"cmd.material_delete": "Smazat fyzický materiál",
|
||||
"cmd.material_edit": "Upravit fyzický materiál",
|
||||
"cmd.material_inspect": "Zkontrolovat fyzický materiál",
|
||||
"cmd.material_list": "Seznam materiálů",
|
||||
"cmd.material_properties_directional": "Směrový modul pružnosti",
|
||||
"cmd.material_properties_validate": "Ověřit data fyzického materiálu",
|
||||
@@ -614,6 +626,8 @@
|
||||
"cmd.plastic_rules": "Seznam pravidel pro plasty",
|
||||
"cmd.plastic_snap_fit": "Zacvakávací spoj",
|
||||
"cmd.render_backends": "Renderovací jádra",
|
||||
"cmd.render_camera_fit": "Zarámovat objekt renderu",
|
||||
"cmd.render_camera_get": "Prohlédnout kameru renderu",
|
||||
"cmd.render_cancel": "Zrušit render",
|
||||
"cmd.render_export": "Exportovat obrázek renderu",
|
||||
"cmd.render_preflight": "Zkontrolovat požadavek na render",
|
||||
@@ -621,6 +635,7 @@
|
||||
"cmd.render_settings_apply": "Použít kameru renderu",
|
||||
"cmd.render_settings_get": "Nastavení renderu",
|
||||
"cmd.render_settings_set": "Uložit nastavení renderu",
|
||||
"cmd.render_stage_backdrop": "Uložit fyzické pozadí renderu",
|
||||
"cmd.render_start": "Renderovat obrázek",
|
||||
"cmd.render_status": "Průběh renderu",
|
||||
"cmd.select_clear": "Zrušit výběr",
|
||||
@@ -831,7 +846,7 @@
|
||||
"dxf.progress.projection": "Promítání geometrie (3/5)",
|
||||
"dxf.progress.write": "Zápis DXF (5/5)",
|
||||
"dxf.tolerance": "Tolerance křivek",
|
||||
"dxf.tolerance_help": "Pouze viditelná tělesa. Křivky jsou nahrazeny úsečkami. Menší tolerance dává jemnější křivky, ale zabere více času a paměti.",
|
||||
"dxf.tolerance_help": "Pouze viditelná tělesa a zobrazené náčrty. Křivky jsou nahrazeny úsečkami. Menší tolerance dává jemnější křivky, ale zabere více času a paměti.",
|
||||
"file_load.built": "Dosud sestaveno: {built} z {total} prvků.",
|
||||
"file_load.converting": "Převod tělesa {done} z {total}",
|
||||
"file_load.inserting": "Vkládání geometrie",
|
||||
@@ -856,19 +871,57 @@
|
||||
"help.mit": "Licence MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Licence třetích stran",
|
||||
"home.back": "Zpět na {name}",
|
||||
"home.crashes": "Hlášení o pádech",
|
||||
"home.design_workspace": "Návrh",
|
||||
"home.documentation": "Dokumentace",
|
||||
"home.export": "Exportovat",
|
||||
"home.export_hint": "Vyberte formát pro aktuální návrh.",
|
||||
"home.export_json_hint": "Upravitelný návrh a historie příkazů",
|
||||
"home.export_mesh_hint": "Síť pro tisk nebo sdílení",
|
||||
"home.export_step_hint": "Přesná geometrie tělesa",
|
||||
"home.import": "Importovat…",
|
||||
"home.import_hint": "STEP, IGES nebo síť do nového návrhu",
|
||||
"home.in_this_browser": "v tomto prohlížeči",
|
||||
"home.missing": "chybí",
|
||||
"home.month_01": "led",
|
||||
"home.month_02": "úno",
|
||||
"home.month_03": "bře",
|
||||
"home.month_04": "dub",
|
||||
"home.month_05": "kvě",
|
||||
"home.month_06": "čer",
|
||||
"home.month_07": "čvc",
|
||||
"home.month_08": "srp",
|
||||
"home.month_09": "zář",
|
||||
"home.month_10": "říj",
|
||||
"home.month_11": "lis",
|
||||
"home.month_12": "pro",
|
||||
"home.new_inches": "Nový návrh v palcích",
|
||||
"home.new_inches_hint": "Palce · otevře se v pracovním prostoru Návrh",
|
||||
"home.new_mm": "Nový návrh",
|
||||
"home.new_mm_hint": "Milimetry · otevře se v pracovním prostoru Návrh",
|
||||
"home.open": "Otevřít…",
|
||||
"home.opened_days_ago": "Otevřeno před {count} dny",
|
||||
"home.opened_hours_ago": "Otevřeno před {count} h",
|
||||
"home.opened_just_now": "Otevřeno právě teď",
|
||||
"home.opened_minutes_ago": "Otevřeno před {count} min",
|
||||
"home.open_hint": "Návrh ve formátu .solidcraft ({shortcut})",
|
||||
"home.open_now": "Otevřít nyní",
|
||||
"home.opened_days_ago": "Před {count} dny",
|
||||
"home.opened_hours_ago": "Před {count} h",
|
||||
"home.opened_just_now": "Právě teď",
|
||||
"home.opened_minutes_ago": "Před {count} min",
|
||||
"home.opening": "otevírání…",
|
||||
"home.properties": "Vlastnosti návrhu",
|
||||
"home.recent": "Nedávné",
|
||||
"home.recent_empty": "Zde se zobrazí návrhy, které otevřete nebo uložíte.",
|
||||
"home.recovery_count": "Návrhy k obnovení: {count}",
|
||||
"home.recovery_empty": "Žádné návrhy k obnovení.",
|
||||
"home.recovery_hint": "SolidCraft se zavřel před uložením těchto návrhů.",
|
||||
"home.report_issue": "Nahlásit problém",
|
||||
"home.samples": "Ukázky",
|
||||
"home.settings": "Nastavení",
|
||||
"home.short_date": "{day}. {month}",
|
||||
"home.shortcuts": "Klávesové zkratky",
|
||||
"home.start_hint": "Vytvořte návrh, otevřete nedávný soubor nebo prozkoumejte ukázku.",
|
||||
"home.whats_new": "Co je nového",
|
||||
"home.yesterday": "Včera",
|
||||
"install.button_tip": "Nainstalovat SolidCraft jako aplikaci: otevře se ve vlastním okně a funguje i offline",
|
||||
"install.dock_1": "Zvolte Soubor › Přidat do Docku.",
|
||||
"install.dock_2": "Klikněte na Přidat.",
|
||||
@@ -946,6 +999,16 @@
|
||||
"pattern.direction_1": "Směr 1",
|
||||
"pattern.direction_2": "Směr 2",
|
||||
"pattern.flip_direction": "Převrátit směr",
|
||||
"perf.click_for_details": "Klikněte pro podrobnosti: graf časů snímků, histogram a co vykreslila GPU",
|
||||
"perf.draw_calls": "Volání vykreslení",
|
||||
"perf.line_segments": "Úsečky",
|
||||
"perf.low_1": "1% minimum",
|
||||
"perf.renderer": "Vykreslování",
|
||||
"perf.scene_build": "Sestavení scény",
|
||||
"perf.triangles": "Trojúhelníky",
|
||||
"perf.ui_time": "Čas UI",
|
||||
"perf.viewport": "Pohled",
|
||||
"perf.worst": "nejhorší",
|
||||
"pick a hole to seat it, or OK places it at the origin": "vyberte otvor pro usazení, nebo OK umístí do počátku",
|
||||
"pick one or two vertices, edges or faces": "vyberte jeden nebo dva vrcholy, hrany či plochy",
|
||||
"prefs.cpu": "CPU (softwarově)",
|
||||
@@ -958,20 +1021,51 @@
|
||||
"recovery.min_ago": "před {count} min",
|
||||
"recovery.never_saved": "nikdy neuloženo",
|
||||
"render.background": "Pozadí scény",
|
||||
"render.bloom_enable": "Záře ve výstupu",
|
||||
"render.bloom_help": "Kreativní záře z lineárních jasů se třemi normalizovanými průchody. Poloměr v pixelech výstupu, přičtení před sRGB. Nejde o simulaci čočky, objemu ani zlepšení konvergence. Surové EXR se nemění.",
|
||||
"render.bloom_opaque": "Záře vyžaduje neprůhledný výstup. Průhledné PNG se odmítne, neprovede se skryté sloučení.",
|
||||
"render.bloom_radius": "Poloměr záře",
|
||||
"render.bloom_strength": "Síla záře",
|
||||
"render.bloom_threshold": "Lineární práh jasů",
|
||||
"render.cpu_aa_help": "Prostorové vzorky: více vzorků zlepší hrany a prodlouží vykreslení.",
|
||||
"render.cpu_key_rim": "Hlavní a obrysové",
|
||||
"render.cpu_lighting": "Předvolba osvětlení",
|
||||
"render.cpu_neutral": "Neutrální",
|
||||
"render.cpu_opaque_required": "CPU trasování vyžaduje neprůhledné pozadí. Vypněte průhledné pozadí.",
|
||||
"render.cpu_options_pending": "Panel podporuje vzorky hran, předvolby světel, stíny a expozici. Další volby jsou v API.",
|
||||
"render.cpu_primary_samples": "Vzorky hran na pixel",
|
||||
"render.cpu_shadows": "Vrhat stíny",
|
||||
"render.cpu_summary": "Neprůhledný CPU PNG · světla · stíny · bez skla a textur",
|
||||
"render.cpu_warm_cool": "Teplé a studené",
|
||||
"render.exr_pending": "GPU může uchovat surové lineární EXR. Komprese, vícevrstvé obrazy a pomocné průchody zatím nejsou dostupné.",
|
||||
"render.fit": "Přizpůsobit produktu",
|
||||
"render.gpu_depth": "Maximální hloubka cesty",
|
||||
"render.gpu_options_help": "Tyto volby platí pro tuto úlohu. Expozice vychází z uloženého nastavení.",
|
||||
"render.gpu_path_failed": "Trasování cest GPU není dostupné",
|
||||
"render.gpu_path_first_setup": "První příprava na této GPU může trvat několik minut; SolidCraft lze mezitím používat. Při dalších spuštěních se použijí zkompilované shadery, pokud je ovladač uchová.",
|
||||
"render.gpu_path_preparing": "Trasování cest GPU: kompilace shaderů…",
|
||||
"render.gpu_seed": "Náhodné semeno",
|
||||
"render.gpu_unavailable": "Trasování cest GPU není na tomto zařízení připraveno.",
|
||||
"render.image_advanced": "Pokročilé nastavení GPU",
|
||||
"render.image_cancelled": "Render zrušen",
|
||||
"render.image_cancelling": "Rušení",
|
||||
"render.image_completed": "Obrázek připraven k exportu",
|
||||
"render.image_encoding": "Kódování PNG",
|
||||
"render.image_export": "Exportovat PNG",
|
||||
"render.image_export": "Exportovat hotový obrázek",
|
||||
"render.image_exposure": "Expozice",
|
||||
"render.image_failed": "Render selhal",
|
||||
"render.image_height": "Výška",
|
||||
"render.image_help": "Náhledový obrázek z CPU · PNG · přibližná průhlednost",
|
||||
"render.image_host_missing": "Není dostupný vykreslovací hostitel.",
|
||||
"render.image_method": "Metoda obrazu",
|
||||
"render.image_overwrite": "Nahradit existující soubor",
|
||||
"render.image_preparing": "Příprava obrázku",
|
||||
"render.image_progressive": "Progresivní render",
|
||||
"render.image_queued": "Čeká na render",
|
||||
"render.image_reduce": "Zkuste menší obrázek nebo jednodušší scénu.",
|
||||
"render.image_running": "Renderování obrázku",
|
||||
"render.image_samples": "Vzorků na pixel",
|
||||
"render.image_scene_stale": "Před kontrolou nebo spuštěním dokončete modelování nebo obnovte scénu.",
|
||||
"render.image_stale": "Tento obrázek používá scénu zachycenou před pozdějšími změnami.",
|
||||
"render.image_start": "Spustit render",
|
||||
"render.image_supported": "Požadavek na render je podporován; fronta a paměť se znovu ověří při spuštění.",
|
||||
@@ -981,17 +1075,135 @@
|
||||
"render.image_unknown": "Stav renderu není k dispozici",
|
||||
"render.image_use_largest": "Použít největší velikost v tomto poměru stran:",
|
||||
"render.image_width": "Šířka",
|
||||
"render.material_absorption": "Absorpce R · G · B (na mm)",
|
||||
"render.material_add": "Přidat nebo upravit těleso",
|
||||
"render.material_assignments": "Přiřazení těles",
|
||||
"render.material_binding_enable": "Zahrnout úpravy tohoto tělesa",
|
||||
"render.material_box": "Osově zarovnaný kvádr",
|
||||
"render.material_browse": "Vybrat obrázek…",
|
||||
"render.material_certification": "Uzavřené sklo používá geometrickou vzdálenost. GPU příprava ověřuje topologii, orientaci, překryvy a optická rozhraní; kontrola jen přijímá požadavek.",
|
||||
"render.material_choose_body": "Vybrat těleso scény",
|
||||
"render.material_closed": "Uzavřená objemová síť",
|
||||
"render.material_coat": "Čirý povlak na základním materiálu",
|
||||
"render.material_coat_enable": "Zapnout čirý povlak",
|
||||
"render.material_coat_help": "Ilustrativní dielektrický povlak na stávajícím neprůhledném vzhledu. Nulová váha ponechá základ beze změny.",
|
||||
"render.material_color_map": "Obraz základní barvy (sRGB)",
|
||||
"render.material_custom": "Vlastní ilustrativní optika",
|
||||
"render.material_enable": "Použít tyto úpravy materiálů GPU",
|
||||
"render.material_filter": "Filtrovat tělesa scény…",
|
||||
"render.material_filter_help": "Zobrazuje se nejvýše 128 shod. U velkých scén zpřesněte filtr.",
|
||||
"render.material_glass": "Sklo a optická absorpce",
|
||||
"render.material_glass_enable": "Použít fyzikální sklo na těleso",
|
||||
"render.material_glass_exclusive": "Tento renderer nepodporuje sklo s povlakem nebo mapami na témže tělese.",
|
||||
"render.material_glass_help": "Původní ilustrativní recepty. Vzorky ukazují propustnost, ne render. Absorpce je −ln(lineární propustnosti) / referenční vzdálenost; alfa se nemění.",
|
||||
"render.material_gpu_required": "Aktivní úpravy vyžadují GPU tracing. Před kontrolou nebo spuštěním vyberte GPU nebo úpravy vypněte.",
|
||||
"render.material_ior": "Index lomu (IOR)",
|
||||
"render.material_limits": "Nejvýše 128 nastavení těles, 32 oddělených skel, 64 povlaků a 128 vazeb textur. Shodné mapy sdílejí paměť; limity zařízení a celkové paměti zůstávají aktivní.",
|
||||
"render.material_map_invalid": "Cesty map musí být omezené místní cesty; síla normál musí být konečná mezi 0 a 1.",
|
||||
"render.material_map_required": "Před použitím map vyberte obraz barvy nebo normál.",
|
||||
"render.material_maps": "Rovinné mapy barvy a normál",
|
||||
"render.material_maps_enable": "Zapnout mapy neprůhledného tělesa",
|
||||
"render.material_maps_help": "PNG/JPEG do 1024×1024; celkem 16 MiB kódovaných dat. U a V jsou kolmé vektory opakování ve světových mm (nejvýše 128 opakování). Barva je sRGB, normály OpenGL +Y. Soubory se zachytí při kontrole/spuštění.",
|
||||
"render.material_normal_map": "Obraz normál (OpenGL, +Y)",
|
||||
"render.material_normal_strength": "Síla normál (0–1)",
|
||||
"render.material_origin": "Počátek mapování X · Y · Z (mm)",
|
||||
"render.material_refresh": "Obnovit tělesa scény",
|
||||
"render.material_refresh_needed": "Scéna se změnila. Před přidáním cíle obnovte seznam.",
|
||||
"render.material_remove": "Odstranit přiřazení",
|
||||
"render.material_repeat_u": "Jedno opakování U X · Y · Z (mm)",
|
||||
"render.material_repeat_v": "Jedno opakování V X · Y · Z (mm)",
|
||||
"render.material_revalidate": "Znovu ověřit těleso",
|
||||
"render.material_revalidate_needed": "Přiřazení patří ke starší revizi scény. Před renderem znovu ověřte přesnou identitu tělesa.",
|
||||
"render.material_roughness": "Drsnost povlaku (0,045–1)",
|
||||
"render.material_shadow": "Propouštět přímé stínové paprsky",
|
||||
"render.material_target_missing": "Pořadí a název tělesa již nesouhlasí. Odstraňte přiřazení a znovu výslovně vyberte těleso.",
|
||||
"render.material_thickness": "Tloušťka vrstvy (mm)",
|
||||
"render.material_thin": "Tenká vrstva",
|
||||
"render.material_title": "Materiály GPU obrazu",
|
||||
"render.material_transient": "Dočasné optické úpravy pro tuto kartu. Neukládají se do návrhu, nastavení renderu ani knihovny materiálů.",
|
||||
"render.material_weight": "Váha povlaku (0–1)",
|
||||
"render.method_cpu": "Trasování paprsků CPU",
|
||||
"render.method_gpu": "Trasování cest GPU",
|
||||
"render.method_raster": "CPU rastr",
|
||||
"render.navigation_help": "Kamera se ovládá stejně jako v návrhu (Předvolby › Navigace). Uložit nastavení zaznamená aktuální kameru.",
|
||||
"render.opaque_image_summary": "Náhledové PNG · neprůhledné PBR · smíšená průhlednost",
|
||||
"render.opaque_preview_help": "Neprůhledné PBR a jednoduché barvy; smíšená průhlednost. Lom světla, HDRI a finální render zatím nejsou k dispozici.",
|
||||
"render.output_format_status": "PNG je přesný dokončený obraz. Volitelné GPU EXR uchovává lineární FLOAT32 radianci před filtry. Export nikdy nenahrazuje výsledek náhledem nebo jinou úlohou.",
|
||||
"render.perspective": "Perspektivní kamera",
|
||||
"render.preview": "Integrovaný náhled",
|
||||
"render.preview_help": "Rastrové stínování a smíšená průhlednost. Lom světla, HDRI a finální render zatím nejsou k dispozici.",
|
||||
"render.progressive_image_summary": "Náhledové PNG · neprůhledné PBR · postupné vzorkování",
|
||||
"render.reload_settings": "Znovu načíst uložené nastavení",
|
||||
"render.restore_camera": "Obnovit uloženou kameru",
|
||||
"render.result_actual_help": "Jeden pixel obrázku na jeden pixel obrazovky.",
|
||||
"render.result_capture": "Zachycená revize",
|
||||
"render.result_current": "Zachycená revize modelu je aktuální.",
|
||||
"render.result_empty": "Zatím není hotový obrázek. Zkontrolujte nastavení a spusťte vykreslování.",
|
||||
"render.result_fit": "Přizpůsobit obrázek",
|
||||
"render.result_job": "Úloha",
|
||||
"render.result_navigation": "Posun a přiblížení používají nastavení navigace. Mezerník + tažení posouvá.",
|
||||
"render.result_open": "Otevřít hotový obrázek na hlavním plátně.",
|
||||
"render.result_preview": "Živý náhled",
|
||||
"render.result_retained": "Zobrazuje se poslední hotový obrázek; novější vykreslení jej zatím nenahradilo.",
|
||||
"render.result_title": "Výsledek vykreslení",
|
||||
"render.return_design": "Zpět k návrhu",
|
||||
"render.ribbon_backgrounds": "Pozadí",
|
||||
"render.ribbon_check": "Kontrola",
|
||||
"render.ribbon_check_help": "Ověřit přesný aktuální návrh renderu. Bez skrytého přepnutí vykreslovače, zmenšení nebo nahrazení zdrojů.",
|
||||
"render.ribbon_edit_lights": "Upravit světla",
|
||||
"render.ribbon_environment_help": "HDR osvětlení, pozadí kamery a fyzické plochy jsou samostatné ovládací prvky s potvrzením nahrazení.",
|
||||
"render.ribbon_finish_dialog": "Nejprve dokončete dialog souboru nebo potvrzení.",
|
||||
"render.ribbon_frame": "Zarámovat",
|
||||
"render.ribbon_hdr_library": "Knihovna HDR",
|
||||
"render.ribbon_job_active": "Render již běží; před spuštěním dalšího jej výslovně zrušte.",
|
||||
"render.ribbon_light_rigs": "Sestavy světel",
|
||||
"render.ribbon_lights_help": "Zkontrolovat světelné sestavy nebo upravit podporovaná světla; aktuální nastavení nahradí až výslovné použití.",
|
||||
"render.ribbon_materials": "Materiály",
|
||||
"render.ribbon_materials_help": "Otevřít inspektor vzhledu a původní povrchy. Optické úpravy nemění skrytě technické vlastnosti.",
|
||||
"render.ribbon_no_active_job": "Není aktivní render ke zrušení.",
|
||||
"render.ribbon_no_result": "Před exportem dokončete render.",
|
||||
"render.ribbon_no_saved_camera": "Před obnovením kamery uložte záběr.",
|
||||
"render.ribbon_saved_camera": "Uložená kamera",
|
||||
"render.ribbon_shots": "Záběry",
|
||||
"render.ribbon_studios": "Studia",
|
||||
"render.ribbon_studios_help": "Procházejte dostupná studia a před použitím zkontrolujte nahrazované prvky. Koncepty zůstávají označené.",
|
||||
"render.ribbon_surface_maps": "Mapy povrchu",
|
||||
"render.save_setup": "Uložit nastavení",
|
||||
"render.setup_name": "Název nastavení",
|
||||
"render.studio_add_area": "Přidat plošné světlo",
|
||||
"render.studio_ambient": "Konstantní zář prostředí",
|
||||
"render.studio_aperture": "Poloměr clony",
|
||||
"render.studio_area": "Plošné světlo",
|
||||
"render.studio_area_help": "Nejvýše čtyři jednostranné obdélníky. Kolmé poloviční hrany U a V určují velikost; U × V míří na osvětlenou stranu. Souřadnice jsou světové milimetry.",
|
||||
"render.studio_center": "Střed X, Y, Z (mm)",
|
||||
"render.studio_denoise": "Potlačit šum v hotovém obrazu",
|
||||
"render.studio_denoise_help": "Závěrečný filtr respektuje hrany; může změkčit detaily. Sklo a ostré zrcadlové povrchy se nefiltrují.",
|
||||
"render.studio_direction": "Směr ke slunci (X, Y, Z)",
|
||||
"render.studio_environment": "Prostředí",
|
||||
"render.studio_environment_bounds": "Intenzita prostředí musí být 0–100 a otočení −360° až 360°.",
|
||||
"render.studio_environment_none": "Bez mapy prostředí",
|
||||
"render.studio_focus": "Osová zaostřovací vzdálenost",
|
||||
"render.studio_half_u": "Poloviční hrana U (mm)",
|
||||
"render.studio_half_v": "Poloviční hrana V (mm)",
|
||||
"render.studio_hdr": "Soubor Radiance HDR",
|
||||
"render.studio_hdr_browse": "Vybrat HDR…",
|
||||
"render.studio_hdr_help": "Radiance .hdr, panorama 2:1, nejvýše 2048 × 1024. EXR není dostupné. Předpoklad barev platí jen při neurčených primárních barvách.",
|
||||
"render.studio_hdr_required": "Vyberte místní soubor .hdr.",
|
||||
"render.studio_hdr_srgb": "Neurčené primární barvy chápat jako lineární sRGB",
|
||||
"render.studio_intensity": "Intenzita prostředí",
|
||||
"render.studio_lens": "Hloubka ostrosti",
|
||||
"render.studio_lens_perspective": "Hloubka ostrosti vyžaduje perspektivní kameru.",
|
||||
"render.studio_lighting": "Studiové osvětlení",
|
||||
"render.studio_lighting_enable": "Použít vlastní studiové osvětlení",
|
||||
"render.studio_radiance": "Lineární RGB zář",
|
||||
"render.studio_remove_area": "Odebrat plošné světlo",
|
||||
"render.studio_rotation": "Otočení prostředí",
|
||||
"render.studio_runtime_help": "Optické úpravy těles jsou v Materiálech GPU obrazu. Ovládání studia a materiálů je dočasné a nemění uložené vzhledy.",
|
||||
"render.studio_sky": "Umělecká obloha",
|
||||
"render.studio_sky_help": "Původní přechod oblohy pro osvětlení; nejde o kalibrovaný model atmosféry.",
|
||||
"render.studio_sun": "Slunce",
|
||||
"render.studio_sun_radius": "Úhlový poloměr",
|
||||
"render.studio_tone_map": "Mapování tónů",
|
||||
"render.unavailable_preview": "Toto nastavení vyžaduje nedostupný profil náhledu.",
|
||||
"render.unsaved": "Neuložené změny náhledu",
|
||||
"render.use_preview": "Použít integrovaný náhled",
|
||||
@@ -1021,7 +1233,8 @@
|
||||
"search.try": "Zadejte název příkazu, prvek v tomto návrhu nebo parametr. Chcete-li spustit příkaz s hodnotami, zadejte jeho id a objekt JSON: {example}",
|
||||
"search.valid": "Platné",
|
||||
"settings.display_quality_note": "Jemnější úrovně kreslí zakřivené plochy a hrany hladčeji, ale síťování trvá déle. Mění se jen zobrazení, nikdy návrh ani exporty.",
|
||||
"settings.frame_rate_note": "Zobrazuje snímky za sekundu v pravém dolním rohu pohledu. Dokud je zapnuto, pohled se překresluje nepřetržitě.",
|
||||
"settings.frame_rate_details_note": "Přidá graf časů posledních snímků s vykreslenými trojúhelníky, histogram časů snímků a to, co vykreslila GPU. Kliknutím na snímkovou frekvenci v pohledu je zobrazíte nebo skryjete.",
|
||||
"settings.frame_rate_note": "Zobrazuje snímky za sekundu v pravém dolním rohu pohledu.",
|
||||
"settings.measure_axis_distances": "Zobrazit vzdálenosti X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Při otevřeném měření zobrazit kromě celkové vzdálenosti i složky ve směru os.",
|
||||
"settings.msaa_applies_on_restart": "Vyhlazování se projeví při příštím spuštění SolidCraftu",
|
||||
@@ -1077,12 +1290,115 @@
|
||||
"toolbar.customize_note": "Karta {tab}. Zaškrtnutím příkazu přidáte jeho tlačítko na panel nástrojů. Můžete také přetáhnout řádek z rozbalovací nabídky panelu na panel nástrojů, odstranit tlačítko přetažením mimo panel nástrojů nebo použít připínáček na řádku rozbalovací nabídky.",
|
||||
"toolbar.image": "OBRÁZEK",
|
||||
"toolbar.render": "RENDER",
|
||||
"toolbar.render_appearance": "VZHLED",
|
||||
"toolbar.render_camera": "KAMERA",
|
||||
"toolbar.render_environment": "PROSTŘEDÍ",
|
||||
"toolbar.render_lighting": "OSVĚTLENÍ",
|
||||
"toolbar.render_scene": "SCÉNA",
|
||||
"trace.points": "Bodů podél cesty: {count}",
|
||||
"trace.stopped_by_contact": "Končí ve vzorku {sample}: {a} by prošlo skrz {b}",
|
||||
"trace.stopped_by_loop": "Končí ve vzorku {sample}: smyčku přes {joint} už nelze dále uzavřít",
|
||||
"ui.1_degree_of_freedom": "1 stupeň volnosti",
|
||||
"ui.3d_mouse_axes_reassigned": "Osy jsou v Předvolbách přeřazené a to platí i po této kalibraci.",
|
||||
"ui.3d_mouse_button": "Tlačítko {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nic",
|
||||
"ui.3d_mouse_button_settings": "Nastavení 3D myši",
|
||||
"ui.3d_mouse_buttons": "Tlačítka",
|
||||
"ui.3d_mouse_buttons_hint": "Stiskněte tlačítko na 3D myši, ať ho tu najdete, a vyberte, co má dělat.",
|
||||
"ui.3d_mouse_buttons_no_device": "Připojte 3D myš a nastavte, co dělají její tlačítka.",
|
||||
"ui.3d_mouse_buttons_reset": "Obnovit výchozí tlačítka",
|
||||
"ui.3d_mouse_calibrate": "Kalibrovat…",
|
||||
"ui.3d_mouse_calibrate_tip": "Pohybujte hlavicí podle pokynů v okně, aby se SolidCraft naučil, kterým směrem a jak daleko se pohybuje každá osa této 3D myši. Uloží se pro tuto 3D myš.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrace 3D myši",
|
||||
"ui.3d_mouse_calibrate_wait": "Jednou pohněte 3D myší, aby ji SolidCraft uviděl.",
|
||||
"ui.3d_mouse_calibrated": "Zkalibrováno",
|
||||
"ui.3d_mouse_calibration": "Kalibrace 3D myši",
|
||||
"ui.3d_mouse_calibration_done": "Hotovo. Uložte ji a použije se vždy, když bude tato 3D myš připojená.",
|
||||
"ui.3d_mouse_calibration_no_response": "Jedna osa se skoro nepohnula, takže ji nelze zkalibrovat. Začněte znovu a pokaždé hlavicí pohněte dál.",
|
||||
"ui.3d_mouse_circles_help": "Kruhy ukazují, co zařízení posílá po kalibraci. Zkušební model ukazuje, co s tím udělá pohled po obrácení, rychlosti a přiřazení os.",
|
||||
"ui.3d_mouse_dead_zone": "Mrtvá zóna",
|
||||
"ui.3d_mouse_dead_zone_tip": "Jak daleko se musí hlavice pohnout, než se pohne pohled, jako podíl jejího plného rozsahu. Zvyšte ji, pokud pohled ujíždí, i když je hlavice v klidu.",
|
||||
"ui.3d_mouse_denied": "SolidCraft vidí 3D myš, ale nesmí ji číst. Pravidlo udev najdete v poznámkách k nastavení 3D myši.",
|
||||
"ui.3d_mouse_denied_short": "Nalezeno, ale nelze číst",
|
||||
"ui.3d_mouse_driven_by": "Který kalibrovaný pohyb zařízení ovládá tuto osu. Když vyberete pohyb, který už používá jiná osa, obě se prohodí.",
|
||||
"ui.3d_mouse_driven_by_label": "ovládá",
|
||||
"ui.3d_mouse_driver": "Ovladač 3D myši",
|
||||
"ui.3d_mouse_driver_auto": "Automaticky",
|
||||
"ui.3d_mouse_driver_direct": "Přímo (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Zatím se čte přes spacenavd: zařízení samotné se nepodařilo otevřít.",
|
||||
"ui.3d_mouse_driver_only_direct": "Zařízení se čte přímo; spacenavd neběží",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Čte se přes spacenavd, jiná možnost není",
|
||||
"ui.3d_mouse_forget": "Zapomenout kalibraci",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vlevo ve výřezu",
|
||||
"ui.3d_mouse_ghost_left_window": "Vlevo v okně",
|
||||
"ui.3d_mouse_ghost_position": "Poloha",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vpravo ve výřezu",
|
||||
"ui.3d_mouse_hint": "Posun a otáčení pohledu současně; náčrty používají jen posun a přiblížení.",
|
||||
"ui.3d_mouse_kind_cap": "Návrat do středu",
|
||||
"ui.3d_mouse_kind_question": "Jaký typ 3D myši to je?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Hodnota zůstává, dokud ji držíte, jako u zařízení typu „Návrat do středu“.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Hodnota se vrací zpět, jakmile přestanete hýbat, jako u trackballu.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotace X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotace Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotace Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Posun X",
|
||||
"ui.3d_mouse_motion_translation_y": "Posun Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Posun Z",
|
||||
"ui.3d_mouse_navigate": "Navigace pomocí 3D myši",
|
||||
"ui.3d_mouse_none": "3D myš nenalezena",
|
||||
"ui.3d_mouse_not_calibrated": "Nezkalibrováno",
|
||||
"ui.3d_mouse_offer": "Kalibrace 3D myši opraví prohozené nebo obrácené osy a nastaví její rychlost a pocit z ovládání.",
|
||||
"ui.3d_mouse_offer_never": "Už nezobrazovat",
|
||||
"ui.3d_mouse_offer_swap": "Přepnout na Hidraw (přímý ovladač)",
|
||||
"ui.3d_mouse_reading": "Hodnoty",
|
||||
"ui.3d_mouse_reset_axes": "Obnovit přiřazení os",
|
||||
"ui.3d_mouse_reset_view": "Obnovit pohled",
|
||||
"ui.3d_mouse_restore": "Obnovit výchozí nastavení 3D myši",
|
||||
"ui.3d_mouse_restore_ask": "Vrátit nastavení 3D myši na výchozí hodnoty? Kalibrace a přiřazení tlačítek zůstanou.",
|
||||
"ui.3d_mouse_section": "3D myš",
|
||||
"ui.3d_mouse_show_in_view": "Zobrazit 3D myš ve 3D pohledu",
|
||||
"ui.3d_mouse_show_in_view_tip": "Obrázek vaší 3D myši v levém dolním rohu, který se hýbe s ní.",
|
||||
"ui.3d_mouse_skip": "Moje zařízení to neumí",
|
||||
"ui.3d_mouse_skip_tip": "Přeskočí tento pohyb v obou směrech a kalibrace ho vynechá.",
|
||||
"ui.3d_mouse_speed": "Rychlost 3D myši",
|
||||
"ui.3d_mouse_step_lift": "Zvedněte hlavici nahoru, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_press": "Zatlačte hlavici dolů, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_pull": "Přitáhněte hlavici k sobě, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_push": "Odtlačte hlavici od sebe, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_rest": "Dejte ruku z hlavice a nechte ji v klidu.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Dejte ruku z kuličky a nechte ji v klidu.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Otočte rolovacím kroužkem o pár cvaknutí po směru hodinových ručiček.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Otočte rolovacím kroužkem o pár cvaknutí proti směru hodinových ručiček.",
|
||||
"ui.3d_mouse_step_roll_away": "Několikrát kutálejte kuličkou od sebe, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_roll_left": "Několikrát kutálejte kuličkou doleva, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_roll_right": "Několikrát kutálejte kuličkou doprava, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_roll_toward": "Několikrát kutálejte kuličkou k sobě, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_slide_left": "Posuňte hlavici doleva, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_slide_right": "Posuňte hlavici doprava, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Několikrát roztočte kuličku po směru hodinových ručiček, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Několikrát roztočte kuličku proti směru hodinových ručiček, pak ji nechte zastavit.",
|
||||
"ui.3d_mouse_step_tilt_away": "Nakloňte vršek hlavice od sebe (okraj nejblíž vám jde nahoru), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_tilt_left": "Nakloňte hlavici doleva (levá hrana dolů), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_tilt_right": "Nakloňte hlavici doprava (pravá hrana dolů), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Nakloňte vršek hlavice k sobě (okraj nejblíž vám jde dolů), podržte a pusťte.",
|
||||
"ui.3d_mouse_step_touch": "Položte ruku na hlavici, ale netlačte na ni.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Otočte hlavici po směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.3d_mouse_switch_cap": "Kalibrovat jako návrat do středu",
|
||||
"ui.3d_mouse_switch_trackball": "Kalibrovat jako trackball",
|
||||
"ui.3d_mouse_view_axis": "Osa pohledu",
|
||||
"ui.3d_mouse_view_off": "Vypnuto",
|
||||
"ui.3d_mouse_view_off_tip": "Profil ovladače 3D myši tuto osu vypíná, takže její pohyb s pohledem nehýbe. Když tu vyberete pohyb jiné osy, prohodí se s ní a s pohledem přestane hýbat.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit vlevo / vpravo",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit nahoru / dolů",
|
||||
"ui.3d_mouse_view_pan_sideways": "Posun vlevo / vpravo",
|
||||
"ui.3d_mouse_view_pan_up_down": "Posun nahoru / dolů",
|
||||
"ui.3d_mouse_view_roll": "Natočení",
|
||||
"ui.3d_mouse_view_unmapped": "Osa {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Čeká na pohyb",
|
||||
"ui.3d_mouse_wiggle": "Pokud máte nějakou připojenou, zahýbejte s ní.",
|
||||
"ui.3d_sketch": "3D náčrt",
|
||||
"ui.activate": "Aktivovat",
|
||||
"ui.active_component": "Aktivní komponenta",
|
||||
@@ -1092,6 +1408,7 @@
|
||||
"ui.agent_chat_add": "Připojit soubory",
|
||||
"ui.agent_chat_add_file": "Vybrat soubor…",
|
||||
"ui.agent_chat_allow": "Povolit",
|
||||
"ui.agent_chat_allow_once": "Povolit jednou",
|
||||
"ui.agent_chat_allowed": "Povoleno",
|
||||
"ui.agent_chat_answered": "Odpověď: {option}",
|
||||
"ui.agent_chat_approval": "Povolit agentovi tuto akci?",
|
||||
@@ -1143,12 +1460,16 @@
|
||||
"ui.agent_chat_files_left_out": "K jedné zprávě lze připojit nejvýše {max} souborů; vynecháno: {files}",
|
||||
"ui.agent_chat_files_loading": "Čekání na přečtení souborů",
|
||||
"ui.agent_chat_files_max": "K jedné zprávě nejvýše {max} souborů",
|
||||
"ui.agent_chat_flatpak_none_found": "Nebyl nalezen žádný kódovací agent. Sandbox Flatpaku ve výchozím stavu nevidí agenty nainstalované ve vaší domovské složce a nemá přístup k síti. Chcete-li povolit Claude Code a jeho adaptér ACP nainstalované přes nvm nebo do ~/.local/bin, spusťte v terminálu tento příkaz a poté SolidCraft restartujte. Udělí přístup k uvedeným programům a přihlašovacím souborům a také přístup k síti:",
|
||||
"ui.agent_chat_import_failed": "{name}: import selhal: {error}",
|
||||
"ui.agent_chat_intro": "Chatujte s kódovacím agentem, kterého již používáte, nebo s modelem na zvoleném endpointu. Pracuje na otevřeném návrhu pomocí nástrojů SolidCraftu: jeho změny vidíte průběžně a každou zprávu lze vrátit jedním krokem. Co smí dělat bez ptaní, nastavíte v Předvolby > AI.",
|
||||
"ui.agent_chat_key_in_env": "klíč nastaven",
|
||||
"ui.agent_chat_left": "Vlevo",
|
||||
"ui.agent_chat_limit": "Agent dosáhl limitu {limit} změn na jednu zprávu. Pošlete další zprávu, aby mohl pokračovat.",
|
||||
"ui.agent_chat_minimize": "Minimalizovat do pruhu",
|
||||
"ui.agent_chat_mode_all": "Vše",
|
||||
"ui.agent_chat_mode_ask": "Ptát se",
|
||||
"ui.agent_chat_mode_edits": "Úpravy",
|
||||
"ui.agent_chat_model_tip": "Model, který agent používá. Vaše volba se pro tohoto agenta zapamatuje.",
|
||||
"ui.agent_chat_new": "Nový chat",
|
||||
"ui.agent_chat_new_confirm": "Začít nový chat? Aktuální bude vymazán.",
|
||||
@@ -1161,6 +1482,7 @@
|
||||
"ui.agent_chat_none_found_short": "Agent nenalezen",
|
||||
"ui.agent_chat_not_installed": "Není nainstalován",
|
||||
"ui.agent_chat_not_signed_in": "nepřihlášen",
|
||||
"ui.agent_chat_open_settings": "Otevřít předvolby",
|
||||
"ui.agent_chat_own_window": "Vlastní okno",
|
||||
"ui.agent_chat_paste_picture": "Vložit obrázek",
|
||||
"ui.agent_chat_permission": "Agent žádá o spuštění: {title}",
|
||||
@@ -1185,6 +1507,8 @@
|
||||
"ui.agent_chat_send": "Odeslat",
|
||||
"ui.agent_chat_send_now": "Odeslat hned",
|
||||
"ui.agent_chat_send_now_tip": "Zastavit aktuální krok agenta a poslat tuto zprávu jako další (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft komunikuje s programovacím agentem nainstalovaným v tomto počítači. Vyberte ho v části Předvolby › AI.",
|
||||
"ui.agent_chat_setup_title": "Nastavit asistenta",
|
||||
"ui.agent_chat_side": "Strana chatu s agentem",
|
||||
"ui.agent_chat_signed_in": "přihlášen",
|
||||
"ui.agent_chat_state_auth": "Vyžadováno přihlášení",
|
||||
@@ -1201,6 +1525,7 @@
|
||||
"ui.agent_chat_undo_label": "Chat s agentem: {message}",
|
||||
"ui.agent_chat_undone": "(vráceno)",
|
||||
"ui.agent_chat_view_attached": "Pohled přiložen",
|
||||
"ui.agent_chat_waiting": "Čeká na vás",
|
||||
"ui.agent_cursor": "Kurzor agenta",
|
||||
"ui.agent_cursor_speed": "Rychlost kurzoru agenta",
|
||||
"ui.ai_add": "Přidat",
|
||||
@@ -1258,7 +1583,6 @@
|
||||
"ui.ai_permissions": "Oprávnění",
|
||||
"ui.ai_preset": "Předvolba",
|
||||
"ui.ai_remove": "Odebrat",
|
||||
"ui.ai_request_limit": "zastaveno po 24 požadavcích na jednu zprávu; pro pokračování pošlete další zprávu",
|
||||
"ui.ai_reset": "Obnovit",
|
||||
"ui.ai_settings": "Nastavení AI…",
|
||||
"ui.ai_show_features": "Zobrazit funkce AI",
|
||||
@@ -1303,6 +1627,7 @@
|
||||
"ui.blocked": "Blokováno",
|
||||
"ui.bodies": "Tělesa",
|
||||
"ui.bodies_all_if_none": "Tělesa (všechna, pokud žádné)",
|
||||
"ui.bottom": "Zespodu",
|
||||
"ui.box": "Kvádr",
|
||||
"ui.break": "Přerušit",
|
||||
"ui.browser_bodies_one": "{count} těleso",
|
||||
@@ -1441,6 +1766,7 @@
|
||||
"ui.flip": "Převrátit",
|
||||
"ui.format": "Formát",
|
||||
"ui.frame_rate": "Snímková frekvence",
|
||||
"ui.frame_rate_details": "Podrobnosti snímkové frekvence",
|
||||
"ui.front": "Zepředu",
|
||||
"ui.fully_constrained": "Plně zavazbeno",
|
||||
"ui.function": "Funkce",
|
||||
@@ -1455,6 +1781,7 @@
|
||||
"ui.grid_follows_view_hint": "Boční pohledy zobrazí mřížku na svislé rovině, která je k nim přivrácená, místo na zemi",
|
||||
"ui.ground_shadow": "Stín na podložce",
|
||||
"ui.groupSelected": "Seskupit vybrané",
|
||||
"ui.hatch": "Šrafování",
|
||||
"ui.heading_add_ins": "DOPLŇKY",
|
||||
"ui.heading_assemble": "SESTAVA",
|
||||
"ui.heading_box": "KVÁDR",
|
||||
@@ -1528,6 +1855,13 @@
|
||||
"ui.hideOrigin": "Skrýt počátek",
|
||||
"ui.hidePlanes": "Skrýt roviny",
|
||||
"ui.hideProfile": "Skrýt profil",
|
||||
"ui.hide_origin_by_default": "Ve výchozím stavu skrýt počátek",
|
||||
"ui.hide_origin_note": "Nové návrhy začínají se skrytou složkou Počátek. Návrh si ponechá vlastní nastavení počátku, jakmile ho zobrazíte nebo skryjete.",
|
||||
"ui.history_back": "O krok zpět",
|
||||
"ui.history_below_browser": "Pod prohlížečem",
|
||||
"ui.history_first": "Přejít na začátek",
|
||||
"ui.history_forward": "O krok vpřed",
|
||||
"ui.history_last": "Přejít na konec",
|
||||
"ui.history_title": "Historie",
|
||||
"ui.hole": "Otvor",
|
||||
"ui.hole_tap_type": "Typ závitu otvoru",
|
||||
@@ -1545,6 +1879,8 @@
|
||||
"ui.joints": "Klouby",
|
||||
"ui.keyboard_shortcuts": "Klávesové zkratky",
|
||||
"ui.language": "Jazyk",
|
||||
"ui.layout_compact": "Kompaktní",
|
||||
"ui.layout_floating": "Plovoucí",
|
||||
"ui.length_decimals": "Desetinná místa délky",
|
||||
"ui.light": "Světlý",
|
||||
"ui.line": "Úsečka",
|
||||
@@ -1682,6 +2018,7 @@
|
||||
"ui.reverse_zoom_direction": "Obrátit směr přiblížení",
|
||||
"ui.revolve": "Rotace",
|
||||
"ui.ribbon_exit": "Ukončit",
|
||||
"ui.ribbon_labels": "Zobrazovat text pod ikonami pásu karet",
|
||||
"ui.ribbon_pin_footer": "Připnuté příkazy se zobrazují na pásu karet. Najetím na řádek jej připnete nebo odepnete.",
|
||||
"ui.ribbon_workspace": "Pracovní prostředí",
|
||||
"ui.right": "Zprava",
|
||||
@@ -1737,34 +2074,6 @@
|
||||
"ui.smooth_wheel_zoom": "Plynulé přiblížení kolečkem",
|
||||
"ui.smooth_wheel_zoom_tip": "Vypnutím se každý pohyb kolečka projeví okamžitě, bez dobíhání",
|
||||
"ui.snap": "Přichytávání",
|
||||
"ui.space_mouse_calibrate": "Kalibrovat…",
|
||||
"ui.space_mouse_calibrate_tip": "Pohybujte hlavicí podle pokynů v okně, aby se SolidCraft naučil, kterým směrem a jak daleko se pohybuje každá osa této 3D myši. Uloží se pro tuto 3D myš.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrace 3D myši",
|
||||
"ui.space_mouse_calibrated": "Zkalibrováno",
|
||||
"ui.space_mouse_calibration": "Kalibrace 3D myši",
|
||||
"ui.space_mouse_calibration_done": "Hotovo. Uložte ji a použije se vždy, když bude tato 3D myš připojená.",
|
||||
"ui.space_mouse_calibration_no_response": "Jedna osa se skoro nepohnula, takže ji nelze zkalibrovat. Začněte znovu a pokaždé hlavicí pohněte dál.",
|
||||
"ui.space_mouse_dead_zone": "Mrtvá zóna",
|
||||
"ui.space_mouse_dead_zone_tip": "Jak daleko se musí hlavice pohnout, než se pohne pohled, jako podíl jejího plného rozsahu. Zvyšte ji, pokud pohled ujíždí, i když je hlavice v klidu.",
|
||||
"ui.space_mouse_forget": "Zapomenout kalibraci",
|
||||
"ui.space_mouse_hint": "Posun a otáčení pohledu současně; náčrty používají jen posun a přiblížení.",
|
||||
"ui.space_mouse_navigate": "Navigace pomocí SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "3D myš nenalezena",
|
||||
"ui.space_mouse_not_calibrated": "Nezkalibrováno",
|
||||
"ui.space_mouse_reading": "Hodnoty",
|
||||
"ui.space_mouse_step_lift": "Zvedněte hlavici nahoru, podržte a pusťte.",
|
||||
"ui.space_mouse_step_press": "Zatlačte hlavici dolů, podržte a pusťte.",
|
||||
"ui.space_mouse_step_pull": "Přitáhněte hlavici k sobě, podržte a pusťte.",
|
||||
"ui.space_mouse_step_push": "Odtlačte hlavici od sebe, podržte a pusťte.",
|
||||
"ui.space_mouse_step_rest": "Dejte ruku z hlavice a nechte ji v klidu.",
|
||||
"ui.space_mouse_step_slide_left": "Posuňte hlavici doleva, podržte a pusťte.",
|
||||
"ui.space_mouse_step_slide_right": "Posuňte hlavici doprava, podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_back": "Nakloňte hlavici dozadu (přední hrana nahoru), podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_forward": "Nakloňte hlavici dopředu (přední hrana dolů), podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_left": "Nakloňte hlavici doleva (levá hrana dolů), podržte a pusťte.",
|
||||
"ui.space_mouse_step_tilt_right": "Nakloňte hlavici doprava (pravá hrana dolů), podržte a pusťte.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Otočte hlavici po směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
|
||||
"ui.spacing": "Rozteč",
|
||||
"ui.spacing_2": "Rozteč 2",
|
||||
"ui.sphere": "Koule",
|
||||
@@ -1836,6 +2145,7 @@
|
||||
"ui.workspace_design_tabs": "Model · Plocha · Sestava · Vložit · Kontrola · Správa · Zobrazení",
|
||||
"ui.workspace_drawing_description": "2D výkresy s pohledy, kótami a popisovými poli",
|
||||
"ui.workspace_footer": "Všechna pracovní prostředí pracují se stejným návrhem, historií a výběrem; mění se pouze karty pásu karet. Přepínač je zamknutý, dokud se upravuje náčrt nebo prvek.",
|
||||
"ui.workspace_layout": "Rozložení pracovní plochy",
|
||||
"ui.workspace_manufacture_description": "Slicing, dráhy nástroje a nastavení",
|
||||
"ui.workspace_planned": "Plánováno",
|
||||
"ui.workspace_render_description": "Náhled produktu, nastavení scény a renderované obrázky",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Vælg…",
|
||||
"Circular": "Cirkulær",
|
||||
"Circumference": "Omkreds",
|
||||
"Clockwise": "Med uret",
|
||||
"Close draft": "Luk kladde",
|
||||
"Collisions": "Kollisioner",
|
||||
"Colour": "Farve",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "HISTORIK",
|
||||
"Height": "Højde",
|
||||
"Hidden edges": "Skjulte kanter",
|
||||
"History layout": "Historiklayout",
|
||||
"Holes": "Huller",
|
||||
"Holes (edges on them)": "Huller (kanterne omkring dem)",
|
||||
"Home view": "Hjemmevisning",
|
||||
@@ -232,7 +234,7 @@
|
||||
"No crash reports.": "Ingen nedbrudsrapporter.",
|
||||
"No matching appearance presets.": "Ingen matchende udseendeforudindstillinger.",
|
||||
"No matching appearances": "Ingen matchende udseender",
|
||||
"No visible bodies to export.": "Der er ingen synlige emner at eksportere.",
|
||||
"No visible bodies or sketches to export.": "Der er ingen synlige emner eller skitser at eksportere.",
|
||||
"None": "Ingen",
|
||||
"Not available yet": "Endnu ikke tilgængelig",
|
||||
"Notch": "Udskæring",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Search commands": "Søg efter kommandoer",
|
||||
"Search this library": "Søg i dette bibliotek",
|
||||
"Section": "Snit",
|
||||
"Section position": "Profilets placering",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vælg andet: Hold venstre museknap nede uden at flytte markøren på modellen (eller højreklik › Vælg andet…) for at vise alt under markøren. Klik igen samme sted for at vælge næste objekt. Dobbeltklik på den midterste museknap for at gå til Hjem (Indstillinger › Navigation).",
|
||||
"Select bodies (a face picks its body)": "Vælg emner (en flade vælger sit emne)",
|
||||
"Select bodies or faces instead": "Vælg emner eller flader i stedet",
|
||||
@@ -332,6 +335,7 @@
|
||||
"Show Agent Cursor": "Vis agentmarkør",
|
||||
"Show in folder": "Vis i mappe",
|
||||
"Show the pointer an AI agent moves (#35)": "Vis markøren, som en AI-agent bevæger (#35)",
|
||||
"Show tool at cursor": "Vis værktøj ved markøren",
|
||||
"Signed axes": "Akser med fortegn",
|
||||
"Size": "Størrelse",
|
||||
"Sketch constraints": "Skitsebindinger",
|
||||
@@ -344,9 +348,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft lukkede, før disse designs blev gemt. Den seneste automatiske lagring kan åbnes.",
|
||||
"Specialise": "Specialisér",
|
||||
"Speed": "Hastighed",
|
||||
"Square": "Kvadrat",
|
||||
"Stand it up": "Stil op",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Stil op: En kvart omdrejning om X som én flyttefeature, der kan fortrydes.",
|
||||
"Stand this part up?": "Stil dette emne op?",
|
||||
"Start angle": "Startvinkel",
|
||||
"Start bulge": "Udbuling ved starten",
|
||||
"Start continuity": "Overgang ved starten",
|
||||
"Step": "Trin",
|
||||
@@ -411,6 +417,7 @@
|
||||
"angle, height": "vinkel, højde",
|
||||
"appearance.dielectric_ior": "Dielektrisk brydningsindeks",
|
||||
"appearance.metallic": "Metallisk",
|
||||
"appearance.only_available": "Kun tilgængelige udseender",
|
||||
"appearance.pbr_help": "Se denne overflade i Rendering eller i en billedfil. Lyset brydes ikke og passerer ikke gennem overfladen.",
|
||||
"appearance.pbr_opaque": "Uigennemsigtig PBR",
|
||||
"appearance.roughness": "Ruhed",
|
||||
@@ -529,6 +536,10 @@
|
||||
"cmd.file_insert_step": "Indsæt STEP",
|
||||
"cmd.file_save_mesh": "Gem som mesh",
|
||||
"cmd.file_slicers": "Installerede slicere",
|
||||
"cmd.frame_member": "Rammeelement",
|
||||
"cmd.frame_profile_insert": "Indsæt profil",
|
||||
"cmd.frame_profile_list": "Profilbibliotek",
|
||||
"cmd.frame_profile_properties": "Profilegenskaber",
|
||||
"cmd.inspect_accessibility": "Tilgængelighedsanalyse",
|
||||
"cmd.inspect_center_of_mass": "Massecentrum",
|
||||
"cmd.inspect_clearance": "Frigangskontrol",
|
||||
@@ -568,6 +579,7 @@
|
||||
"cmd.material_create": "Nyt fysisk materiale",
|
||||
"cmd.material_delete": "Slet fysisk materiale",
|
||||
"cmd.material_edit": "Redigér fysisk materiale",
|
||||
"cmd.material_inspect": "Inspicér fysisk materiale",
|
||||
"cmd.material_list": "Vis materialer",
|
||||
"cmd.material_properties_directional": "Retningsafhængigt elasticitetsmodul",
|
||||
"cmd.material_properties_validate": "Validér fysiske materialedata",
|
||||
@@ -831,7 +843,7 @@
|
||||
"dxf.progress.projection": "Projicerer geometri (3/5)",
|
||||
"dxf.progress.write": "Skriver DXF (5/5)",
|
||||
"dxf.tolerance": "Kurvetolerance",
|
||||
"dxf.tolerance_help": "Kun synlige emner. Kurver består af rette linjestykker. En mindre tolerance giver finere kurver og kræver mere tid og hukommelse.",
|
||||
"dxf.tolerance_help": "Kun synlige emner og viste skitser. Kurver består af rette linjestykker. En mindre tolerance giver finere kurver og kræver mere tid og hukommelse.",
|
||||
"file_load.built": "Bygget indtil videre: {built} af {total} features.",
|
||||
"file_load.converting": "Konverterer solid {done} af {total}",
|
||||
"file_load.inserting": "Indsætter geometri",
|
||||
@@ -856,19 +868,57 @@
|
||||
"help.mit": "MIT-licens",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Tredjepartslicenser",
|
||||
"home.back": "Tilbage til {name}",
|
||||
"home.crashes": "Nedbrudsrapporter",
|
||||
"home.design_workspace": "Design",
|
||||
"home.documentation": "Dokumentation",
|
||||
"home.export": "Eksportér",
|
||||
"home.export_hint": "Vælg et format til det aktuelle design.",
|
||||
"home.export_json_hint": "Redigerbart design og kommandohistorik",
|
||||
"home.export_mesh_hint": "Mesh til udskrivning eller deling",
|
||||
"home.export_step_hint": "Præcis geometri for massive legemer",
|
||||
"home.import": "Importér…",
|
||||
"home.import_hint": "STEP, IGES eller mesh til et nyt design",
|
||||
"home.in_this_browser": "i denne webbrowser",
|
||||
"home.missing": "mangler",
|
||||
"home.month_01": "jan.",
|
||||
"home.month_02": "feb.",
|
||||
"home.month_03": "mar.",
|
||||
"home.month_04": "apr.",
|
||||
"home.month_05": "maj",
|
||||
"home.month_06": "jun.",
|
||||
"home.month_07": "jul.",
|
||||
"home.month_08": "aug.",
|
||||
"home.month_09": "sep.",
|
||||
"home.month_10": "okt.",
|
||||
"home.month_11": "nov.",
|
||||
"home.month_12": "dec.",
|
||||
"home.new_inches": "Nyt design i tommer",
|
||||
"home.new_inches_hint": "Tommer · åbner i Design",
|
||||
"home.new_mm": "Nyt design",
|
||||
"home.new_mm_hint": "Millimeter · åbner i Design",
|
||||
"home.open": "Åbn…",
|
||||
"home.opened_days_ago": "Åbnet for {count} dage siden",
|
||||
"home.opened_hours_ago": "Åbnet for {count} t siden",
|
||||
"home.opened_just_now": "Netop åbnet",
|
||||
"home.opened_minutes_ago": "Åbnet for {count} min siden",
|
||||
"home.open_hint": "Et .solidcraft-design ({shortcut})",
|
||||
"home.open_now": "Åbn nu",
|
||||
"home.opened_days_ago": "For {count} dage siden",
|
||||
"home.opened_hours_ago": "For {count} t siden",
|
||||
"home.opened_just_now": "Lige nu",
|
||||
"home.opened_minutes_ago": "For {count} min siden",
|
||||
"home.opening": "åbner…",
|
||||
"home.properties": "Designegenskaber",
|
||||
"home.recent": "Seneste",
|
||||
"home.recent_empty": "Design, du åbner eller gemmer, vises her.",
|
||||
"home.recovery_count": "Design til gendannelse: {count}",
|
||||
"home.recovery_empty": "Ingen design kan gendannes.",
|
||||
"home.recovery_hint": "SolidCraft lukkede, før disse design blev gemt.",
|
||||
"home.report_issue": "Rapportér et problem",
|
||||
"home.samples": "Eksempler",
|
||||
"home.settings": "Indstillinger",
|
||||
"home.short_date": "{day}. {month}",
|
||||
"home.shortcuts": "Tastaturgenveje",
|
||||
"home.start_hint": "Opret et design, åbn en nylig fil, eller udforsk et eksempel.",
|
||||
"home.whats_new": "Nyheder",
|
||||
"home.yesterday": "I går",
|
||||
"install.button_tip": "Installér SolidCraft som en app: Den åbner i sit eget vindue og virker offline",
|
||||
"install.dock_1": "Vælg Arkiv › Føj til Dock.",
|
||||
"install.dock_2": "Klik på Tilføj.",
|
||||
@@ -1021,7 +1071,7 @@
|
||||
"search.try": "Skriv et kommandonavn, en feature i dette design eller en parameter. Du kører en kommando med værdier ved at skrive dens id og et JSON-objekt: {example}",
|
||||
"search.valid": "Gyldig",
|
||||
"settings.display_quality_note": "Finere niveauer viser krumme flader og kanter mere jævnt og tager længere tid at opbygge mesh til. Kun modelvisningen ændres, aldrig designet eller eksporten.",
|
||||
"settings.frame_rate_note": "Viser billeder pr. sekund nederst til højre i modelvisningen. Når dette er slået til, gentegnes visningen løbende.",
|
||||
"settings.frame_rate_note": "Viser billeder pr. sekund nederst til højre i modelvisningen.",
|
||||
"settings.measure_axis_distances": "Vis X/Y/Z-afstande",
|
||||
"settings.measure_axis_distances_help": "Vis afstanden langs X-, Y- og Z-aksen sammen med den samlede afstand i Mål.",
|
||||
"settings.msaa_applies_on_restart": "Kantudjævning træder i kraft, næste gang SolidCraft starter",
|
||||
@@ -1082,7 +1132,106 @@
|
||||
"trace.stopped_by_contact": "Slutter ved prøve {sample}: {a} ville gå gennem {b}",
|
||||
"trace.stopped_by_loop": "Slutter ved prøve {sample}: Løkken gennem {joint} kan ikke lukkes yderligere",
|
||||
"ui.1_degree_of_freedom": "1 frihedsgrad",
|
||||
"ui.3d_mouse_axes_reassigned": "Akserne er omtildelt i Indstillinger, og det gælder stadig oven på denne kalibrering.",
|
||||
"ui.3d_mouse_button": "Knap {number}",
|
||||
"ui.3d_mouse_button_nothing": "Ingenting",
|
||||
"ui.3d_mouse_button_settings": "Indstillinger for 3D-mus",
|
||||
"ui.3d_mouse_buttons": "Knapper",
|
||||
"ui.3d_mouse_buttons_hint": "Tryk på en knap på din 3D-mus for at finde den her, og vælg så, hvad den gør.",
|
||||
"ui.3d_mouse_buttons_no_device": "Tilslut en 3D-mus for at vælge, hvad dens knapper gør.",
|
||||
"ui.3d_mouse_buttons_reset": "Gendan standardknapper",
|
||||
"ui.3d_mouse_calibrate": "Kalibrér…",
|
||||
"ui.3d_mouse_calibrate_tip": "Bevæg knoppen som vist i vinduet, så SolidCraft kan registrere retningen og bevægelsesområdet for hver akse. Indstillingerne gemmes til denne 3D-mus.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrér 3D-mus",
|
||||
"ui.3d_mouse_calibrate_wait": "Bevæg 3D-musen én gang, så SolidCraft kan se den.",
|
||||
"ui.3d_mouse_calibrated": "Kalibreret",
|
||||
"ui.3d_mouse_calibration": "Kalibrering af 3D-mus",
|
||||
"ui.3d_mouse_calibration_done": "Færdig. Gem kalibreringen, så den bruges, hver gang denne 3D-mus tilsluttes.",
|
||||
"ui.3d_mouse_calibration_no_response": "En af akserne bevægede sig for lidt til at kunne kalibreres. Start forfra, og bevæg knoppen længere hver gang.",
|
||||
"ui.3d_mouse_circles_help": "Cirklerne viser, hvad enheden sender efter kalibrering. Testmodellen viser, hvad visningen gør med det efter omvending, hastighed og akseplacering.",
|
||||
"ui.3d_mouse_dead_zone": "Dødzone",
|
||||
"ui.3d_mouse_dead_zone_tip": "Hvor langt knoppen skal bevæges, før visningen reagerer, angivet som en andel af dens fulde bevægelse. Øg værdien, hvis visningen bevæger sig, når du ikke rører knoppen.",
|
||||
"ui.3d_mouse_denied": "SolidCraft kan se en 3D-mus, men må ikke læse den. Se opsætningsnoterne til 3D-mus for udev-reglen.",
|
||||
"ui.3d_mouse_denied_short": "Fundet, men kan ikke læses",
|
||||
"ui.3d_mouse_driven_by": "Hvilken kalibreret bevægelse af enheden der styrer denne akse. Vælger du en bevægelse, som en anden akse allerede bruger, bytter de to plads.",
|
||||
"ui.3d_mouse_driven_by_label": "styres af",
|
||||
"ui.3d_mouse_driver": "3D-musedriver",
|
||||
"ui.3d_mouse_driver_auto": "Automatisk",
|
||||
"ui.3d_mouse_driver_direct": "Direkte (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Læser indtil videre gennem spacenavd: selve enheden kunne ikke åbnes.",
|
||||
"ui.3d_mouse_driver_only_direct": "Læser enheden direkte; spacenavd kører ikke",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Læser gennem spacenavd, den eneste mulighed",
|
||||
"ui.3d_mouse_forget": "Slet kalibrering",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Venstre visningsområde",
|
||||
"ui.3d_mouse_ghost_left_window": "Venstre vindue",
|
||||
"ui.3d_mouse_ghost_position": "Placering",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Højre visningsområde",
|
||||
"ui.3d_mouse_hint": "Flyt og rotér visningen samtidigt; skitser bruger kun panorering og zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Vend tilbage til midten",
|
||||
"ui.3d_mouse_kind_question": "Hvilken slags 3D-mus er det?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Værdien bliver, mens du holder, som på en enhed af typen „Vend tilbage til midten“.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Værdien falder tilbage, så snart du stopper, som på en trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Forskydning X",
|
||||
"ui.3d_mouse_motion_translation_y": "Forskydning Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Forskydning Z",
|
||||
"ui.3d_mouse_navigate": "Navigér med 3D-musen",
|
||||
"ui.3d_mouse_none": "Ingen 3D-mus fundet",
|
||||
"ui.3d_mouse_not_calibrated": "Ikke kalibreret",
|
||||
"ui.3d_mouse_offer": "Kalibrering af din 3D-mus retter ombyttede eller vendte akser og indstiller dens hastighed og fornemmelse.",
|
||||
"ui.3d_mouse_offer_never": "Vis ikke igen",
|
||||
"ui.3d_mouse_offer_swap": "Skift til Hidraw (direkte driver)",
|
||||
"ui.3d_mouse_reading": "Aflæser",
|
||||
"ui.3d_mouse_reset_axes": "Nulstil aksetildeling",
|
||||
"ui.3d_mouse_reset_view": "Nulstil visning",
|
||||
"ui.3d_mouse_restore": "Gendan 3D-musens standardværdier",
|
||||
"ui.3d_mouse_restore_ask": "Sæt 3D-musens indstillinger tilbage til standard? Din kalibrering og knaptildeling bliver.",
|
||||
"ui.3d_mouse_section": "3D-mus",
|
||||
"ui.3d_mouse_show_in_view": "Vis 3D-musen i 3D-visningen",
|
||||
"ui.3d_mouse_show_in_view_tip": "Et billede af din 3D-mus i nederste venstre hjørne, der bevæger sig, når du bevæger den.",
|
||||
"ui.3d_mouse_skip": "Min enhed kan ikke det",
|
||||
"ui.3d_mouse_skip_tip": "Springer bevægelsen over begge veje, og kalibreringen udelader den.",
|
||||
"ui.3d_mouse_speed": "3D-musens hastighed",
|
||||
"ui.3d_mouse_step_lift": "Løft knoppen op, hold den, og slip.",
|
||||
"ui.3d_mouse_step_press": "Tryk knoppen ned, hold den, og slip.",
|
||||
"ui.3d_mouse_step_pull": "Træk knoppen mod dig, hold den, og slip.",
|
||||
"ui.3d_mouse_step_push": "Skub knoppen væk fra dig, hold den, og slip.",
|
||||
"ui.3d_mouse_step_rest": "Fjern hånden fra knoppen, og lad den stå stille.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Fjern hånden fra kuglen, og lad den ligge stille.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Drej rullehjulet et par klik med uret.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Drej rullehjulet et par klik mod uret.",
|
||||
"ui.3d_mouse_step_roll_away": "Rul kuglen væk fra dig et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_roll_left": "Rul kuglen mod venstre et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_roll_right": "Rul kuglen mod højre et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_roll_toward": "Rul kuglen ind mod dig et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_slide_left": "Skub knoppen mod venstre, hold den, og slip.",
|
||||
"ui.3d_mouse_step_slide_right": "Skub knoppen mod højre, hold den, og slip.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Drej kuglen rundt med uret et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Drej kuglen rundt mod uret et par gange, og lad den stoppe.",
|
||||
"ui.3d_mouse_step_tilt_away": "Vip toppen af knoppen væk fra dig (kanten nærmest dig går op), hold den, og slip.",
|
||||
"ui.3d_mouse_step_tilt_left": "Vip knoppen mod venstre (venstre kant ned), hold den, og slip.",
|
||||
"ui.3d_mouse_step_tilt_right": "Vip knoppen mod højre (højre kant ned), hold den, og slip.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Vip toppen af knoppen ind mod dig (kanten nærmest dig går ned), hold den, og slip.",
|
||||
"ui.3d_mouse_step_touch": "Hvil hånden på knoppen uden at skubbe til den.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Drej knoppen med uret, hold den, og slip.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Drej knoppen mod uret, hold den, og slip.",
|
||||
"ui.3d_mouse_switch_cap": "Kalibrer som vend tilbage til midten",
|
||||
"ui.3d_mouse_switch_trackball": "Kalibrér som trackball",
|
||||
"ui.3d_mouse_view_axis": "Visningsakse",
|
||||
"ui.3d_mouse_view_off": "Fra",
|
||||
"ui.3d_mouse_view_off_tip": "3D-musedriverens profil slår denne akse fra, så dens bevægelse flytter ikke visningen. Vælger du en anden akses bevægelse her, bytter de to plads, og den bevægelse flytter ikke længere visningen.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit venstre / højre",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit op / ned",
|
||||
"ui.3d_mouse_view_pan_sideways": "Panorér venstre / højre",
|
||||
"ui.3d_mouse_view_pan_up_down": "Panorér op / ned",
|
||||
"ui.3d_mouse_view_roll": "Rul",
|
||||
"ui.3d_mouse_view_unmapped": "Akse {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Venter på bevægelse",
|
||||
"ui.3d_mouse_wiggle": "Hvis du har en tilsluttet, så bevæg den lidt.",
|
||||
"ui.3d_sketch": "3D-skitse",
|
||||
"ui.activate": "Aktivér",
|
||||
"ui.active_component": "Aktiv komponent",
|
||||
@@ -1092,6 +1241,7 @@
|
||||
"ui.agent_chat_add": "Vedhæft filer",
|
||||
"ui.agent_chat_add_file": "Vælg en fil…",
|
||||
"ui.agent_chat_allow": "Tillad",
|
||||
"ui.agent_chat_allow_once": "Tillad én gang",
|
||||
"ui.agent_chat_allowed": "Tilladt",
|
||||
"ui.agent_chat_answered": "Svar: {option}",
|
||||
"ui.agent_chat_approval": "Tillad agenten at gøre dette?",
|
||||
@@ -1143,12 +1293,16 @@
|
||||
"ui.agent_chat_files_left_out": "Højst {max} filer pr. besked; udeladt: {files}",
|
||||
"ui.agent_chat_files_loading": "Venter på indlæsning af filerne",
|
||||
"ui.agent_chat_files_max": "Højst {max} filer i én besked",
|
||||
"ui.agent_chat_flatpak_none_found": "Ingen kodeagent fundet. Flatpak-sandkassen kan som standard ikke se agenter, der er installeret i din hjemmemappe, og har ingen netværksadgang. For at tillade Claude Code og dets ACP-adapter installeret via nvm eller i ~/.local/bin skal du køre denne kommando i en terminal og derefter genstarte SolidCraft. Den giver adgang til de nævnte programmer og loginfiler samt netværksadgang:",
|
||||
"ui.agent_chat_import_failed": "{name}: Importen mislykkedes: {error}",
|
||||
"ui.agent_chat_intro": "Chat med en kodeagent, du allerede bruger, eller en modeltjeneste, du vælger. Den arbejder på det åbne design gennem SolidCrafts værktøjer: Ændringer vises undervejs og fortrydes som ét trin pr. besked. Hvad den må gøre uden at spørge, angives i Indstillinger > AI.",
|
||||
"ui.agent_chat_key_in_env": "nøgle angivet",
|
||||
"ui.agent_chat_left": "Venstre",
|
||||
"ui.agent_chat_limit": "Agenten nåede grænsen på {limit} ændringer for én besked. Send en ny besked for at lade den fortsætte.",
|
||||
"ui.agent_chat_minimize": "Minimér til en stribe",
|
||||
"ui.agent_chat_mode_all": "Alt",
|
||||
"ui.agent_chat_mode_ask": "Spørg",
|
||||
"ui.agent_chat_mode_edits": "Ændringer",
|
||||
"ui.agent_chat_model_tip": "Modellen, som agenten bruger. Dit valg huskes for denne agent.",
|
||||
"ui.agent_chat_new": "Ny chat",
|
||||
"ui.agent_chat_new_confirm": "Start en ny chat? Den aktuelle chat ryddes.",
|
||||
@@ -1161,6 +1315,7 @@
|
||||
"ui.agent_chat_none_found_short": "Ingen agent fundet",
|
||||
"ui.agent_chat_not_installed": "Ikke installeret",
|
||||
"ui.agent_chat_not_signed_in": "ikke logget ind",
|
||||
"ui.agent_chat_open_settings": "Åbn indstillinger",
|
||||
"ui.agent_chat_own_window": "Eget vindue",
|
||||
"ui.agent_chat_paste_picture": "Indsæt et billede",
|
||||
"ui.agent_chat_permission": "Agenten beder om at køre: {title}",
|
||||
@@ -1185,6 +1340,8 @@
|
||||
"ui.agent_chat_send": "Send",
|
||||
"ui.agent_chat_send_now": "Send nu",
|
||||
"ui.agent_chat_send_now_tip": "Stop agentens aktuelle trin, og send denne som den næste (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft kommunikerer med en kodeagent, der er installeret på denne computer. Vælg en under Indstillinger › AI.",
|
||||
"ui.agent_chat_setup_title": "Opsæt en assistent",
|
||||
"ui.agent_chat_side": "Agentchattens side",
|
||||
"ui.agent_chat_signed_in": "logget ind",
|
||||
"ui.agent_chat_state_auth": "Login kræves",
|
||||
@@ -1201,6 +1358,7 @@
|
||||
"ui.agent_chat_undo_label": "Agentchat: {message}",
|
||||
"ui.agent_chat_undone": "(fortrudt)",
|
||||
"ui.agent_chat_view_attached": "Visning vedhæftet",
|
||||
"ui.agent_chat_waiting": "Venter på dig",
|
||||
"ui.agent_cursor": "Agentmarkør",
|
||||
"ui.agent_cursor_speed": "Agentmarkørens hastighed",
|
||||
"ui.ai_add": "Tilføj",
|
||||
@@ -1258,7 +1416,6 @@
|
||||
"ui.ai_permissions": "Tilladelser",
|
||||
"ui.ai_preset": "Forudindstilling",
|
||||
"ui.ai_remove": "Fjern",
|
||||
"ui.ai_request_limit": "stoppet efter 24 anmodninger for én besked; send en ny besked for at fortsætte",
|
||||
"ui.ai_reset": "Nulstil",
|
||||
"ui.ai_settings": "AI-indstillinger…",
|
||||
"ui.ai_show_features": "Vis AI-funktioner",
|
||||
@@ -1303,6 +1460,7 @@
|
||||
"ui.blocked": "Blokeret",
|
||||
"ui.bodies": "Emner",
|
||||
"ui.bodies_all_if_none": "Emner (alle, hvis ingen er valgt)",
|
||||
"ui.bottom": "Nedenfra",
|
||||
"ui.box": "Boks",
|
||||
"ui.break": "Bryd",
|
||||
"ui.browser_bodies_one": "{count} emne",
|
||||
@@ -1455,6 +1613,7 @@
|
||||
"ui.grid_follows_view_hint": "Sidevisninger viser gitteret på det lodrette plan, der vender mod dem, i stedet for grundplanet",
|
||||
"ui.ground_shadow": "Skygge på grundplan",
|
||||
"ui.groupSelected": "Gruppér valgte",
|
||||
"ui.hatch": "Skravering",
|
||||
"ui.heading_add_ins": "ADD-INS",
|
||||
"ui.heading_assemble": "SAMLING",
|
||||
"ui.heading_box": "BOKS",
|
||||
@@ -1528,6 +1687,11 @@
|
||||
"ui.hideOrigin": "Skjul nulpunkt",
|
||||
"ui.hidePlanes": "Skjul planer",
|
||||
"ui.hideProfile": "Skjul profil",
|
||||
"ui.history_back": "Gå ét trin tilbage",
|
||||
"ui.history_below_browser": "Under browseren",
|
||||
"ui.history_first": "Gå til begyndelsen",
|
||||
"ui.history_forward": "Gå ét trin frem",
|
||||
"ui.history_last": "Gå til slutningen",
|
||||
"ui.history_title": "Historik",
|
||||
"ui.hole": "Hul",
|
||||
"ui.hole_tap_type": "Hullets gevindtype",
|
||||
@@ -1545,6 +1709,8 @@
|
||||
"ui.joints": "Led",
|
||||
"ui.keyboard_shortcuts": "Tastaturgenveje",
|
||||
"ui.language": "Sprog",
|
||||
"ui.layout_compact": "Kompakt",
|
||||
"ui.layout_floating": "Flydende",
|
||||
"ui.length_decimals": "Længdedecimaler",
|
||||
"ui.light": "Lys",
|
||||
"ui.line": "Linje",
|
||||
@@ -1682,6 +1848,7 @@
|
||||
"ui.reverse_zoom_direction": "Vend zoomretning",
|
||||
"ui.revolve": "Omdrejning",
|
||||
"ui.ribbon_exit": "Afslut",
|
||||
"ui.ribbon_labels": "Vis tekst under ikonerne på båndet",
|
||||
"ui.ribbon_pin_footer": "Fastgjorte kommandoer vises på båndet. Hold musen over en række for at fastgøre eller frigøre den.",
|
||||
"ui.ribbon_workspace": "Arbejdsområde",
|
||||
"ui.right": "Fra højre",
|
||||
@@ -1737,34 +1904,6 @@
|
||||
"ui.smooth_wheel_zoom": "Jævn zoom med musehjul",
|
||||
"ui.smooth_wheel_zoom_tip": "Slå fra for at anvende hver hjulbevægelse straks uden efterfølgende udjævning",
|
||||
"ui.snap": "Fastgør til geometri",
|
||||
"ui.space_mouse_calibrate": "Kalibrér…",
|
||||
"ui.space_mouse_calibrate_tip": "Bevæg knoppen som vist i vinduet, så SolidCraft kan registrere retningen og bevægelsesområdet for hver akse. Indstillingerne gemmes til denne 3D-mus.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrér 3D-mus",
|
||||
"ui.space_mouse_calibrated": "Kalibreret",
|
||||
"ui.space_mouse_calibration": "Kalibrering af 3D-mus",
|
||||
"ui.space_mouse_calibration_done": "Færdig. Gem indstillingerne, så de bruges, hver gang denne 3D-mus tilsluttes.",
|
||||
"ui.space_mouse_calibration_no_response": "En af akserne bevægede sig for lidt til at kunne kalibreres. Start forfra, og bevæg knoppen længere hver gang.",
|
||||
"ui.space_mouse_dead_zone": "Dødzone",
|
||||
"ui.space_mouse_dead_zone_tip": "Hvor langt knoppen skal bevæges, før visningen reagerer, angivet som en andel af dens fulde bevægelse. Øg værdien, hvis visningen bevæger sig, når du ikke rører knoppen.",
|
||||
"ui.space_mouse_forget": "Nulstil kalibrering",
|
||||
"ui.space_mouse_hint": "Flyt og rotér visningen samtidigt; skitser bruger kun panorering og zoom.",
|
||||
"ui.space_mouse_navigate": "Navigér med SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Ingen 3D-mus fundet",
|
||||
"ui.space_mouse_not_calibrated": "Ikke kalibreret",
|
||||
"ui.space_mouse_reading": "Aflæser",
|
||||
"ui.space_mouse_step_lift": "Løft knoppen op, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_press": "Tryk knoppen ned, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_pull": "Træk knoppen mod dig, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_push": "Skub knoppen væk fra dig, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_rest": "Fjern hånden fra knoppen, og lad den stå stille.",
|
||||
"ui.space_mouse_step_slide_left": "Skub knoppen mod venstre, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_slide_right": "Skub knoppen mod højre, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_back": "Vip knoppen bagud (forkanten op), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_forward": "Vip knoppen fremad (forkanten ned), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_left": "Vip knoppen mod venstre (venstre kant ned), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_tilt_right": "Vip knoppen mod højre (højre kant ned), hold den dér, og slip.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Drej knoppen med uret, hold den dér, og slip.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Drej knoppen mod uret, hold den dér, og slip.",
|
||||
"ui.spacing": "Afstand",
|
||||
"ui.spacing_2": "Afstand 2",
|
||||
"ui.sphere": "Kugle",
|
||||
@@ -1836,6 +1975,7 @@
|
||||
"ui.workspace_design_tabs": "Model · Overflade · Saml · Indsæt · Undersøg · Administrér · Visning",
|
||||
"ui.workspace_drawing_description": "2D-tegninger med visninger, mål og tegningshoveder",
|
||||
"ui.workspace_footer": "Alle arbejdsområder arbejder på det samme design, den samme historik og den samme markering; kun båndets faner ændres. Skifteren er låst, mens en skitse eller feature redigeres.",
|
||||
"ui.workspace_layout": "Arbejdsområdets layout",
|
||||
"ui.workspace_manufacture_description": "Slicing, værktøjsbaner og opsætning",
|
||||
"ui.workspace_planned": "Planlagt",
|
||||
"ui.workspace_render_description": "Produktforhåndsvisning, scene-indstillinger og renderede billeder",
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"Center X": "Centre X",
|
||||
"Center Y": "Centre Y",
|
||||
"Center of mass": "Centre of mass",
|
||||
"Customize Toolbar": "Customise Toolbar",
|
||||
"Mass Center": "Mass Centre",
|
||||
"Modeled": "Modelled",
|
||||
"cmd.construct_point_center": "Point At Centre Of Circle/Sphere/Torus",
|
||||
"cmd.inspect_center_of_mass": "Centre of Mass",
|
||||
"cmd.render_aging_describe": "Layered Surface Aging",
|
||||
"cmd.render_effects_describe": "Lens and Creative Effects",
|
||||
"cmd.sketch_arc_center_point": "Centre Point Arc",
|
||||
"cmd.sketch_circle_center": "Centre Diameter Circle",
|
||||
"cmd.sketch_rectangle_center": "Centre Rectangle",
|
||||
"cmd.sketch_slot_arc_center": "Centre Point Arc Slot",
|
||||
"cmd.sketch_slot_center_point": "Centre Point Slot",
|
||||
"cmd.sketch_slot_center_to_center": "Centre to Centre Slot",
|
||||
"pattern.alignment_hint": "Counts include the original. Centre balances copies; Flip chooses the extra side for even totals.",
|
||||
"render.aging_add": "Add aging layer",
|
||||
"render.aging_airborne": "Airborne dust / participating medium",
|
||||
"render.aging_airborne_help": "Airborne dust is a separate finite homogeneous scattering volume. It changes transport and raw EXR, not the surface-aging stack. Existing volume/glass/denoising restrictions remain explicit; no atmosphere is added automatically.",
|
||||
"render.aging_all_bodies": "All eligible opaque bodies",
|
||||
"render.aging_chips": "Chipped paint",
|
||||
"render.aging_class_any": "All opaque material classes",
|
||||
"render.aging_class_dielectric": "Nonmetallic surfaces only",
|
||||
"render.aging_class_metal": "Metallic surfaces only",
|
||||
"render.aging_color": "Layer colour · sRGB",
|
||||
"render.aging_coverage": "Coverage",
|
||||
"render.aging_dust": "Deposited dust",
|
||||
"render.aging_empty": "No aging layers: the original surfaces remain unchanged.",
|
||||
"render.aging_fading": "Faded coating",
|
||||
"render.aging_grime": "Grime & staining",
|
||||
"render.aging_help": "Combine up to eight optical layers, in order. Rust and patina are authored appearances, not age/corrosion simulations. Scale is in world millimetres. No geometry, mass or engineering property is changed; glass and passive stages are excluded.",
|
||||
"render.aging_metallic": "Layer metallic fraction",
|
||||
"render.aging_origin": "Field origin · world mm",
|
||||
"render.aging_patina": "Copper-green patina",
|
||||
"render.aging_refresh_targets": "Inspect current bodies",
|
||||
"render.aging_roughness": "Layer roughness",
|
||||
"render.aging_rust": "Oxidized iron / rust",
|
||||
"render.aging_scale": "Pattern scale",
|
||||
"render.aging_scratches": "Fine scratches",
|
||||
"render.aging_target": "Target & coordinates",
|
||||
"render.aging_target_help": "A named target must still match its captured index and name. Global masks are world-anchored; they do not follow moving parts. This is neither cavity detection nor true edge wear. Filter the list to reach additional bodies.",
|
||||
"render.aging_target_search": "Filter body names",
|
||||
"render.aging_upward": "Upward-facing deposition",
|
||||
"render.backdrop_x": "Centre X",
|
||||
"render.backdrop_y": "Centre Y",
|
||||
"render.background_camera_section": "Camera background · PNG presentation",
|
||||
"render.background_completed_frozen": "The displayed result is a frozen completed image. Editing a background does not repaint that result automatically. Use Regrade for camera-only changes, or Render for a new physical stage.",
|
||||
"render.background_hdr_separate": "The current HDR resources illuminate and reflect on surfaces; they are not automatically displayed as a panorama behind the subject. Changing a gradient does not rotate or recolour the lighting.",
|
||||
"render.background_lighting_section": "HDR illumination · separate from camera background",
|
||||
"render.background_no_alpha_override": "A camera background is not added to transparent output silently. Raw-master regrading uses captured coverage and does not reapply denoising; the source EXR remains unchanged.",
|
||||
"render.background_regrade": "Apply to retained raw master",
|
||||
"render.background_stage_occlusion": "This shot includes physical stage geometry. A large floor or wall can fill the frame and hide a camera-only background. Clear or resize the stage deliberately, or change its physical reflectance. Regrading cannot remove captured geometry.",
|
||||
"render.background_workflow": "Choose the background you intend: a camera-only colour/gradient, a physical stage that receives light, or HDR illumination. They are independent. Live Preview is a modelling view; render a new image or regrade a retained master to see camera finishing.",
|
||||
"render.chromatic_offset": "Chromatic edge offset",
|
||||
"render.effects_gpu_only": "These effects require GPU path tracing. Changing the page never switches the renderer automatically.",
|
||||
"render.effects_help": "Combine ordered colour looks and image-space lens effects. Rendering and raw-master regrading use the same saved settings. Raw EXR stays unchanged. These are original artistic controls, not calibrated lens or branded-film emulations.",
|
||||
"render.effects_reset_lens": "Reset lens effects",
|
||||
"render.effects_seed": "Pattern seed",
|
||||
"render.film_grain": "Photographic grain",
|
||||
"render.flare_ghosts": "Ghost rings per source",
|
||||
"render.flare_halo": "Flare halo",
|
||||
"render.flare_streak": "Anamorphic-style streak",
|
||||
"render.flare_strength": "Highlight flare strength",
|
||||
"render.flare_threshold": "Scene-linear flare threshold",
|
||||
"render.layer_down": "Move down",
|
||||
"render.layer_enabled": "Layer enabled",
|
||||
"render.layer_remove": "Remove layer",
|
||||
"render.layer_strength": "Layer strength",
|
||||
"render.layer_up": "Move up",
|
||||
"render.lens_effects_help": "Flare uses up to four separated bright sources in the actual linear image. Ghosts, halo, streaks, chromatic offsets and radial distortion are artistic image-space approximations. Spatial effects need opaque output; outside samples extend the image edge. Grain is deterministic. No physics, EXR or geometry is altered.",
|
||||
"render.lens_effects_title": "Lens-inspired & photographic",
|
||||
"render.look_add": "Add colour look",
|
||||
"render.look_bleach": "Bleach-bypass-inspired",
|
||||
"render.look_cool": "Cool",
|
||||
"render.look_cyanotype": "Cyanotype-inspired",
|
||||
"render.look_faded": "Faded print",
|
||||
"render.look_mono": "Monochrome",
|
||||
"render.look_sepia": "Sepia",
|
||||
"render.look_strength": "Look strength",
|
||||
"render.look_teal_orange": "Teal / orange",
|
||||
"render.look_title": "Creative look stack",
|
||||
"render.look_warm": "Warm",
|
||||
"render.opaque_preview_help": "Opaque PBR and simple colours; blended transparency. Refraction, HDRI and final rendering are not available yet.",
|
||||
"render.page_aging": "Aging & contamination",
|
||||
"render.page_background": "Backgrounds",
|
||||
"render.page_effects": "Lens & finishing effects",
|
||||
"render.radial_distortion": "Radial distortion",
|
||||
"render.ribbon_aging": "Aging",
|
||||
"render.ribbon_effects": "Effects",
|
||||
"render.sharpening": "Detail sharpening",
|
||||
"render.soft_focus": "Soft focus",
|
||||
"sketch_text.align_center": "Centre",
|
||||
"ui.3d_mouse_step_pull": "Pull the cap towards you, hold it, then let go.",
|
||||
"ui.center": "Centre",
|
||||
"ui.centerline": "Centreline",
|
||||
"ui.customizeToolbar": "Customise Toolbar…",
|
||||
"ui.design_properties.license": "Licence",
|
||||
"ui.design_properties.organization": "Organisation",
|
||||
"ui.layout_compact": "Compact",
|
||||
"ui.layout_floating": "Floating",
|
||||
"ui.ribbon_labels": "Show text under ribbon icons",
|
||||
"ui.workspace_layout": "Workspace layout",
|
||||
"ui.zoom_to_cursor_tip": "The wheel zooms towards the point under the cursor (off: towards the view's centre)",
|
||||
"update.prefs_flatpak": "Flatpak updates this copy ({version}): run {command} or use your software centre. SolidCraft doesn't check."
|
||||
}
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Choisir…",
|
||||
"Circular": "Circulaire",
|
||||
"Circumference": "Circonférence",
|
||||
"Clockwise": "Horaire",
|
||||
"Close draft": "Fermer le brouillon",
|
||||
"Collisions": "Collisions",
|
||||
"Colour": "Couleur",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "HISTORIQUE",
|
||||
"Height": "Hauteur",
|
||||
"Hidden edges": "Arêtes cachées",
|
||||
"History layout": "Disposition de l’historique",
|
||||
"Holes": "Trous",
|
||||
"Holes (edges on them)": "Trous (par leurs arêtes)",
|
||||
"Home view": "Vue d'accueil",
|
||||
@@ -232,7 +234,7 @@
|
||||
"No crash reports.": "Aucun rapport de plantage.",
|
||||
"No matching appearance presets.": "Aucun préréglage d’apparence correspondant.",
|
||||
"No matching appearances": "Aucune apparence correspondante",
|
||||
"No visible bodies to export.": "Aucun corps visible à exporter.",
|
||||
"No visible bodies or sketches to export.": "Aucun corps ni esquisse visible à exporter.",
|
||||
"None": "Aucun",
|
||||
"Not available yet": "Pas encore disponible",
|
||||
"Notch": "Encoche",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Search commands": "Rechercher des commandes",
|
||||
"Search this library": "Rechercher dans cette bibliothèque",
|
||||
"Section": "Coupe",
|
||||
"Section position": "Position de la section",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Sélectionner un autre élément : maintenir le bouton gauche immobile sur le modèle (ou clic droit › Sélectionner un autre…) pour lister tout ce qui se trouve sous le curseur ; cliquer de nouveau au même endroit pour sélectionner l'élément suivant. Double-cliquer avec le bouton du milieu pour revenir à l'accueil (Préférences › Navigation).",
|
||||
"Select bodies (a face picks its body)": "Sélectionner des corps (une face sélectionne son corps)",
|
||||
"Select bodies or faces instead": "Sélectionner plutôt des corps ou des faces",
|
||||
@@ -332,6 +335,7 @@
|
||||
"Show Agent Cursor": "Afficher le curseur de l'agent",
|
||||
"Show in folder": "Afficher dans le dossier",
|
||||
"Show the pointer an AI agent moves (#35)": "Afficher le pointeur déplacé par un agent IA (#35)",
|
||||
"Show tool at cursor": "Afficher l'outil près du curseur",
|
||||
"Signed axes": "Axes signés",
|
||||
"Size": "Taille",
|
||||
"Sketch constraints": "Contraintes d'esquisse",
|
||||
@@ -344,9 +348,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft s'est fermé avant l'enregistrement de ces conceptions. Leur dernier enregistrement automatique peut être ouvert.",
|
||||
"Specialise": "Spécialiser",
|
||||
"Speed": "Vitesse",
|
||||
"Square": "Carré",
|
||||
"Stand it up": "Redresser",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Redresser : un quart de tour autour de X, sous forme d'une fonction Déplacer que vous pouvez annuler.",
|
||||
"Stand this part up?": "Redresser cette pièce ?",
|
||||
"Start angle": "Angle de départ",
|
||||
"Start bulge": "Renflement de début",
|
||||
"Start continuity": "Continuité de début",
|
||||
"Step": "Étape",
|
||||
@@ -411,6 +417,7 @@
|
||||
"angle, height": "angle, hauteur",
|
||||
"appearance.dielectric_ior": "Indice de réfraction (diélectrique)",
|
||||
"appearance.metallic": "Métallique",
|
||||
"appearance.only_available": "Apparences disponibles uniquement",
|
||||
"appearance.pbr_help": "Voir cette surface dans l'espace Rendu ou dans un PNG du produit. Sans réfraction ni transmission.",
|
||||
"appearance.pbr_opaque": "PBR opaque",
|
||||
"appearance.roughness": "Rugosité",
|
||||
@@ -529,6 +536,10 @@
|
||||
"cmd.file_insert_step": "Insérer un STEP",
|
||||
"cmd.file_save_mesh": "Enregistrer comme maillage",
|
||||
"cmd.file_slicers": "Slicers installés",
|
||||
"cmd.frame_member": "Élément de structure",
|
||||
"cmd.frame_profile_insert": "Insérer un profilé",
|
||||
"cmd.frame_profile_list": "Bibliothèque de profilés",
|
||||
"cmd.frame_profile_properties": "Propriétés du profilé",
|
||||
"cmd.inspect_accessibility": "Analyse d'accessibilité",
|
||||
"cmd.inspect_center_of_mass": "Centre de gravité",
|
||||
"cmd.inspect_clearance": "Jeu",
|
||||
@@ -568,6 +579,7 @@
|
||||
"cmd.material_create": "Nouveau matériau physique",
|
||||
"cmd.material_delete": "Supprimer le matériau physique",
|
||||
"cmd.material_edit": "Modifier le matériau physique",
|
||||
"cmd.material_inspect": "Inspecter le matériau physique",
|
||||
"cmd.material_list": "Lister les matériaux",
|
||||
"cmd.material_properties_directional": "Module d'élasticité directionnel",
|
||||
"cmd.material_properties_validate": "Valider les données du matériau physique",
|
||||
@@ -831,7 +843,7 @@
|
||||
"dxf.progress.projection": "Projection de la géométrie (3/5)",
|
||||
"dxf.progress.write": "Écriture du DXF (5/5)",
|
||||
"dxf.tolerance": "Tolérance des courbes",
|
||||
"dxf.tolerance_help": "Corps visibles uniquement. Les courbes sont approchées par des segments de droite. Une tolérance plus faible donne des courbes plus fines, au prix de plus de temps et de mémoire.",
|
||||
"dxf.tolerance_help": "Corps visibles et esquisses affichées uniquement. Les courbes sont approchées par des segments de droite. Une tolérance plus faible donne des courbes plus fines, au prix de plus de temps et de mémoire.",
|
||||
"file_load.built": "Construit jusqu’ici : {built} entités sur {total}.",
|
||||
"file_load.converting": "Conversion du solide {done} sur {total}",
|
||||
"file_load.inserting": "Insertion de la géométrie",
|
||||
@@ -855,20 +867,70 @@
|
||||
"help.licences": "Licences",
|
||||
"help.mit": "Licence MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.solid.chamfer": "Coupe les arêtes sélectionnées par un biseau plat de la distance indiquée. À utiliser pour casser des arêtes vives ou faciliter l'entrée d'une goupille ou d'une vis.",
|
||||
"help.solid.combine": "Joint des corps, les retire d'un corps cible ou ne garde que le volume qu'ils ont en commun, et peut conserver les corps outils. À utiliser pour fusionner des pièces, tailler une forme avec une autre ou repérer où deux corps se recouvrent.",
|
||||
"help.solid.extrude": "Pousse le profil d'une esquisse en 3D sur une distance, en ajoutant ou en retirant de la matière. À utiliser pour transformer un contour plat en bloc, en bossage ou en poche.",
|
||||
"help.solid.fillet": "Arrondit les arêtes sélectionnées par un raccord circulaire lisse du rayon indiqué. À utiliser pour adoucir des arêtes vives par souci de solidité, de confort ou d'aspect.",
|
||||
"help.solid.hole": "Perce un trou rond du diamètre indiqué en un point choisi d'une face. À utiliser pour les passages de vis, les goupilles et autres ouvertures rondes simples.",
|
||||
"help.solid.loft": "Raccorde en douceur deux profils d'esquisse ou plus, ou un profil et un point, par un solide lissé. À utiliser pour les transitions, entonnoirs, poignées et toute forme dont la section change sur sa longueur.",
|
||||
"help.solid.mirror": "Crée une copie symétrique des corps ou fonctions sélectionnés par rapport à un plan, et peut la fusionner avec l'original. À utiliser pour les pièces symétriques : on modélise une moitié et on obtient les deux.",
|
||||
"help.solid.pattern.circular": "Répète les objets sélectionnés autour d'un axe, selon le nombre et l'angle total indiqués. À utiliser pour les cercles de perçages, rayons, ailettes et dents.",
|
||||
"help.solid.pattern.rectangular": "Répète les objets sélectionnés dans une ou deux directions, selon le nombre et l'espacement indiqués. À utiliser pour les rangées et grilles de trous, nervures, goupilles ou pieds.",
|
||||
"help.solid.revolve": "Fait tourner le profil d'une esquisse autour d'un axe pour obtenir un solide de révolution. À utiliser pour les arbres, boutons, bols et toute pièce ronde autour d'une ligne centrale.",
|
||||
"help.solid.shell": "Évide un solide en laissant des parois de l'épaisseur indiquée, et peut ouvrir les faces choisies. À utiliser pour les boîtes, couvercles et autres pièces à parois minces.",
|
||||
"help.solid.sweep": "Entraîne le profil d'une esquisse le long d'une trajectoire pour obtenir un solide. À utiliser pour les tuyaux, rails, poignées et toute pièce de section constante qui suit une courbe.",
|
||||
"help.third_party": "Licences tierces",
|
||||
"home.back": "Revenir à {name}",
|
||||
"home.crashes": "Rapports de plantage",
|
||||
"home.design_workspace": "Conception",
|
||||
"home.documentation": "Documentation",
|
||||
"home.export": "Exporter",
|
||||
"home.export_hint": "Choisissez un format pour la conception actuelle.",
|
||||
"home.export_json_hint": "Conception modifiable et historique des commandes",
|
||||
"home.export_mesh_hint": "Maillage pour l’impression ou le partage",
|
||||
"home.export_step_hint": "Géométrie solide précise",
|
||||
"home.import": "Importer…",
|
||||
"home.import_hint": "STEP, IGES ou maillage dans une nouvelle conception",
|
||||
"home.in_this_browser": "dans ce navigateur",
|
||||
"home.missing": "introuvable",
|
||||
"home.month_01": "janv.",
|
||||
"home.month_02": "févr.",
|
||||
"home.month_03": "mars",
|
||||
"home.month_04": "avr.",
|
||||
"home.month_05": "mai",
|
||||
"home.month_06": "juin",
|
||||
"home.month_07": "juil.",
|
||||
"home.month_08": "août",
|
||||
"home.month_09": "sept.",
|
||||
"home.month_10": "oct.",
|
||||
"home.month_11": "nov.",
|
||||
"home.month_12": "déc.",
|
||||
"home.new_inches": "Nouvelle conception en pouces",
|
||||
"home.new_inches_hint": "Pouces · ouvre l’espace Conception",
|
||||
"home.new_mm": "Nouvelle conception",
|
||||
"home.new_mm_hint": "Millimètres · ouvre l’espace Conception",
|
||||
"home.open": "Ouvrir…",
|
||||
"home.opened_days_ago": "Ouvert il y a {count} j",
|
||||
"home.opened_hours_ago": "Ouvert il y a {count} h",
|
||||
"home.opened_just_now": "Ouvert à l'instant",
|
||||
"home.opened_minutes_ago": "Ouvert il y a {count} min",
|
||||
"home.open_hint": "Une conception .solidcraft ({shortcut})",
|
||||
"home.open_now": "Ouvrir maintenant",
|
||||
"home.opened_days_ago": "Il y a {count} j",
|
||||
"home.opened_hours_ago": "Il y a {count} h",
|
||||
"home.opened_just_now": "À l’instant",
|
||||
"home.opened_minutes_ago": "Il y a {count} min",
|
||||
"home.opening": "ouverture…",
|
||||
"home.properties": "Propriétés de la conception",
|
||||
"home.recent": "Récents",
|
||||
"home.recent_empty": "Les conceptions que vous ouvrez ou enregistrez apparaissent ici.",
|
||||
"home.recovery_count": "Conceptions récupérables : {count}",
|
||||
"home.recovery_empty": "Aucune conception récupérable.",
|
||||
"home.recovery_hint": "SolidCraft s’est fermé avant l’enregistrement de ces conceptions.",
|
||||
"home.report_issue": "Signaler un problème",
|
||||
"home.samples": "Exemples",
|
||||
"home.settings": "Paramètres",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Raccourcis clavier",
|
||||
"home.start_hint": "Créez une conception, ouvrez un fichier récent ou explorez un exemple.",
|
||||
"home.whats_new": "Nouveautés",
|
||||
"home.yesterday": "Hier",
|
||||
"install.button_tip": "Installer SolidCraft comme une app : il s'ouvre dans sa propre fenêtre et fonctionne hors ligne",
|
||||
"install.dock_1": "Choisissez Fichier › Ajouter au Dock.",
|
||||
"install.dock_2": "Cliquez sur Ajouter.",
|
||||
@@ -999,13 +1061,13 @@
|
||||
"sample.bracket.name": "Équerre en tôle",
|
||||
"sample.enclosure.about": "Boîte en coque avec lèvre, bossages de vis, appui et clip",
|
||||
"sample.enclosure.name": "Boîtier plastique",
|
||||
"sample.flange.about": "Moyeu et bride avec un réseau circulaire piloté par un paramètre",
|
||||
"sample.flange.about": "Moyeu et bride avec une répétition circulaire pilotée par un paramètre",
|
||||
"sample.flange.name": "Bride boulonnée",
|
||||
"sample.hinge.about": "Deux composants et une liaison pivot, entraînée à 60°",
|
||||
"sample.hinge.name": "Assemblage de charnière",
|
||||
"sample.plate.about": "Plaque paramétrique avec congés, perçages et un bossage alésé",
|
||||
"sample.plate.name": "Plaque d'exemple",
|
||||
"sample.vents.about": "Rainures en réseau rectangulaire : lignes, colonnes et pas sont des paramètres",
|
||||
"sample.vents.about": "Rainures en répétition rectangulaire : lignes, colonnes et pas sont des paramètres",
|
||||
"sample.vents.name": "Plaque d'aération",
|
||||
"search.command_keys": "Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
|
||||
"search.commands": "Commandes",
|
||||
@@ -1021,12 +1083,14 @@
|
||||
"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.",
|
||||
"settings.frame_rate_note": "Affiche les images par seconde dans le coin inférieur droit de la vue. Tant qu'elle est activée, la vue est redessinée en continu.",
|
||||
"settings.frame_rate_note": "Affiche les images par seconde dans le coin inférieur droit de la vue.",
|
||||
"settings.measure_axis_distances": "Afficher les cotes X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Afficher les cotes suivant les axes du repère global en plus de la distance totale pendant la mesure.",
|
||||
"settings.msaa_applies_on_restart": "L'anticrénelage s'appliquera au prochain démarrage de SolidCraft",
|
||||
"settings.msaa_reset": "{setting} : {requested}× n'est pas pris en charge par ce périphérique graphique ; la valeur est désormais {reset}× (pris en charge : {supported}).",
|
||||
"settings.msaa_unsupported": "Non pris en charge par ce périphérique graphique",
|
||||
"settings.reference_geometry_through_bodies": "Afficher la géométrie de référence à travers les corps",
|
||||
"settings.reference_geometry_through_bodies_help": "Afficher les contours des esquisses inactives et les plans de référence par-dessus les corps ombrés",
|
||||
"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×.",
|
||||
@@ -1082,7 +1146,106 @@
|
||||
"trace.stopped_by_contact": "Se termine à l'échantillon {sample} : {a} traverserait {b}",
|
||||
"trace.stopped_by_loop": "Se termine à l'échantillon {sample} : la boucle passant par {joint} ne peut plus se fermer",
|
||||
"ui.1_degree_of_freedom": "1 degré de liberté",
|
||||
"ui.3d_mouse_axes_reassigned": "Les axes sont réattribués dans les Préférences, et cela s'applique toujours en plus de ce calibrage.",
|
||||
"ui.3d_mouse_button": "Bouton {number}",
|
||||
"ui.3d_mouse_button_nothing": "Rien",
|
||||
"ui.3d_mouse_button_settings": "Réglages de la souris 3D",
|
||||
"ui.3d_mouse_buttons": "Boutons",
|
||||
"ui.3d_mouse_buttons_hint": "Appuyez sur un bouton de votre souris 3D pour le trouver ici, puis choisissez ce qu'il fait.",
|
||||
"ui.3d_mouse_buttons_no_device": "Branchez une souris 3D pour choisir ce que font ses boutons.",
|
||||
"ui.3d_mouse_buttons_reset": "Rétablir les boutons par défaut",
|
||||
"ui.3d_mouse_calibrate": "Calibrer…",
|
||||
"ui.3d_mouse_calibrate_tip": "Déplacez le capuchon comme la fenêtre le demande, pour que SolidCraft apprenne dans quel sens et jusqu'où va chaque axe de cette souris 3D. Enregistré pour cette souris 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrer la souris 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Bougez la souris 3D une fois pour que SolidCraft la voie.",
|
||||
"ui.3d_mouse_calibrated": "Calibrée",
|
||||
"ui.3d_mouse_calibration": "Calibrage de la souris 3D",
|
||||
"ui.3d_mouse_calibration_done": "Terminé. Enregistrez-le pour l'utiliser chaque fois que cette souris 3D est connectée.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un axe a à peine bougé, il ne peut donc pas être calibré. Recommencez en déplaçant le capuchon plus loin à chaque fois.",
|
||||
"ui.3d_mouse_circles_help": "Les cercles montrent ce que l'appareil envoie après calibrage. Le modèle d'essai montre ce que la vue en fait, après inversion, vitesse et affectation des axes.",
|
||||
"ui.3d_mouse_dead_zone": "Zone morte",
|
||||
"ui.3d_mouse_dead_zone_tip": "De combien le capuchon doit bouger avant que la vue ne bouge, en part de sa course complète. Augmentez-la si la vue dérive alors que le capuchon est au repos.",
|
||||
"ui.3d_mouse_denied": "SolidCraft voit une souris 3D mais n'a pas le droit de la lire. La règle udev est dans les notes d'installation de la souris 3D.",
|
||||
"ui.3d_mouse_denied_short": "Trouvée, mais illisible",
|
||||
"ui.3d_mouse_driven_by": "Quel mouvement calibré de l'appareil pilote cet axe. Choisir un mouvement déjà utilisé par un autre axe les échange.",
|
||||
"ui.3d_mouse_driven_by_label": "piloté par",
|
||||
"ui.3d_mouse_driver": "Pilote de la souris 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatique",
|
||||
"ui.3d_mouse_driver_direct": "Direct (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Lecture via spacenavd pour l'instant : l'appareil lui-même n'a pas pu être ouvert.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lecture directe du périphérique ; spacenavd n'est pas lancé",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lecture via spacenavd, seule possibilité disponible",
|
||||
"ui.3d_mouse_forget": "Oublier le calibrage",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vue à gauche",
|
||||
"ui.3d_mouse_ghost_left_window": "Fenêtre à gauche",
|
||||
"ui.3d_mouse_ghost_position": "Position",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vue à droite",
|
||||
"ui.3d_mouse_hint": "Déplace et fait pivoter la vue en même temps ; dans les esquisses, seuls le panoramique et le zoom sont utilisés.",
|
||||
"ui.3d_mouse_kind_cap": "Retour au centre",
|
||||
"ui.3d_mouse_kind_question": "Quel type de souris 3D est-ce ?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "La valeur reste tant que vous maintenez, comme sur un appareil de type « Retour au centre ».",
|
||||
"ui.3d_mouse_looks_like_trackball": "La valeur retombe dès que vous arrêtez de bouger, comme sur un trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotation X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translation X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translation Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translation Z",
|
||||
"ui.3d_mouse_navigate": "Naviguer avec la souris 3D",
|
||||
"ui.3d_mouse_none": "Aucune souris 3D trouvée",
|
||||
"ui.3d_mouse_not_calibrated": "Non calibrée",
|
||||
"ui.3d_mouse_offer": "Calibrer votre souris 3D corrige les axes échangés ou inversés et règle sa vitesse et sa sensation.",
|
||||
"ui.3d_mouse_offer_never": "Ne plus afficher",
|
||||
"ui.3d_mouse_offer_swap": "Passer à Hidraw (pilote direct)",
|
||||
"ui.3d_mouse_reading": "Lecture",
|
||||
"ui.3d_mouse_reset_axes": "Réinitialiser l'attribution des axes",
|
||||
"ui.3d_mouse_reset_view": "Réinitialiser la vue",
|
||||
"ui.3d_mouse_restore": "Rétablir les réglages de la souris 3D",
|
||||
"ui.3d_mouse_restore_ask": "Remettre les réglages de la souris 3D par défaut ? Votre calibrage et l'assignation des boutons sont conservés.",
|
||||
"ui.3d_mouse_section": "Souris 3D",
|
||||
"ui.3d_mouse_show_in_view": "Afficher la souris 3D dans la vue 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Une image de votre souris 3D dans le coin inférieur gauche, qui bouge quand vous la bougez.",
|
||||
"ui.3d_mouse_skip": "Mon appareil ne peut pas le faire",
|
||||
"ui.3d_mouse_skip_tip": "Ignore ce mouvement dans les deux sens ; la calibration le laisse de côté.",
|
||||
"ui.3d_mouse_speed": "Vitesse de la souris 3D",
|
||||
"ui.3d_mouse_step_lift": "Soulevez le capuchon, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_press": "Appuyez sur le capuchon, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_pull": "Tirez le capuchon vers vous, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_push": "Poussez le capuchon loin de vous, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_rest": "Lâchez le capuchon et laissez-le immobile.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Lâchez la boule et laissez-la immobile.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Tournez l'anneau de défilement de quelques crans dans le sens horaire.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Tournez l'anneau de défilement de quelques crans dans le sens antihoraire.",
|
||||
"ui.3d_mouse_step_roll_away": "Faites rouler la boule loin de vous plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_roll_left": "Faites rouler la boule vers la gauche plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_roll_right": "Faites rouler la boule vers la droite plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_roll_toward": "Faites rouler la boule vers vous plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_slide_left": "Glissez le capuchon vers la gauche, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_slide_right": "Glissez le capuchon vers la droite, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Faites tourner la boule dans le sens horaire plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Faites tourner la boule dans le sens antihoraire plusieurs fois, puis laissez-la s'arrêter.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclinez le haut du capuchon loin de vous (le bord le plus proche monte), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclinez le capuchon vers la gauche (bord gauche vers le bas), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclinez le capuchon vers la droite (bord droit vers le bas), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclinez le haut du capuchon vers vous (le bord le plus proche descend), maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_touch": "Posez la main sur le capuchon sans le pousser.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Tournez le capuchon dans le sens horaire, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrer comme retour au centre",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrer comme un trackball",
|
||||
"ui.3d_mouse_view_axis": "Axe de la vue",
|
||||
"ui.3d_mouse_view_off": "Désactivé",
|
||||
"ui.3d_mouse_view_off_tip": "Le profil du pilote de la souris 3D désactive cet axe : son mouvement ne déplace pas la vue. Choisir ici le mouvement d'un autre axe les échange, et ce mouvement cesse de déplacer la vue.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbite gauche / droite",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbite haut / bas",
|
||||
"ui.3d_mouse_view_pan_sideways": "Panoramique gauche / droite",
|
||||
"ui.3d_mouse_view_pan_up_down": "Panoramique haut / bas",
|
||||
"ui.3d_mouse_view_roll": "Roulis",
|
||||
"ui.3d_mouse_view_unmapped": "Axe {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "En attente de mouvement",
|
||||
"ui.3d_mouse_wiggle": "Si vous en avez une de branchée, remuez-la.",
|
||||
"ui.3d_sketch": "Esquisse 3D",
|
||||
"ui.activate": "Activer",
|
||||
"ui.active_component": "Composant actif",
|
||||
@@ -1092,6 +1255,7 @@
|
||||
"ui.agent_chat_add": "Joindre des fichiers",
|
||||
"ui.agent_chat_add_file": "Choisir un fichier…",
|
||||
"ui.agent_chat_allow": "Autoriser",
|
||||
"ui.agent_chat_allow_once": "Autoriser une fois",
|
||||
"ui.agent_chat_allowed": "Autorisé",
|
||||
"ui.agent_chat_answered": "Réponse : {option}",
|
||||
"ui.agent_chat_approval": "Autoriser l'agent à effectuer cette action ?",
|
||||
@@ -1143,12 +1307,16 @@
|
||||
"ui.agent_chat_files_left_out": "Un message ne peut contenir que {max} fichiers au plus ; non joints : {files}",
|
||||
"ui.agent_chat_files_loading": "Lecture des fichiers en cours",
|
||||
"ui.agent_chat_files_max": "{max} fichiers au plus par message",
|
||||
"ui.agent_chat_flatpak_none_found": "Aucun agent de programmation trouvé. Par défaut, le bac à sable Flatpak ne voit pas les agents installés dans votre dossier personnel et n’a pas accès au réseau. Pour autoriser Claude Code et son adaptateur ACP installés avec nvm ou dans ~/.local/bin, exécuter cette commande dans un terminal, puis redémarrer SolidCraft. Elle donne accès aux programmes et fichiers de connexion indiqués, ainsi qu’au réseau :",
|
||||
"ui.agent_chat_import_failed": "{name} : l'importation a échoué : {error}",
|
||||
"ui.agent_chat_intro": "Discuter avec un agent de programmation que vous utilisez déjà, ou avec un point d'accès de modèle de votre choix. Il travaille sur la conception ouverte avec les outils de SolidCraft : ses modifications apparaissent au fur et à mesure et s'annulent en une seule étape par message. Ce qu'il peut faire sans demander se règle dans Préférences › IA.",
|
||||
"ui.agent_chat_key_in_env": "clé définie",
|
||||
"ui.agent_chat_left": "Gauche",
|
||||
"ui.agent_chat_limit": "L'agent a atteint la limite de {limit} modifications pour un message. Envoyez un autre message pour qu'il poursuive.",
|
||||
"ui.agent_chat_minimize": "Réduire en bandeau",
|
||||
"ui.agent_chat_mode_all": "Tout",
|
||||
"ui.agent_chat_mode_ask": "Demander",
|
||||
"ui.agent_chat_mode_edits": "Édition",
|
||||
"ui.agent_chat_model_tip": "Le modèle utilisé par l'agent. Votre choix est mémorisé pour cet agent.",
|
||||
"ui.agent_chat_new": "Nouveau chat",
|
||||
"ui.agent_chat_new_confirm": "Démarrer un nouveau chat ? Le chat actuel sera effacé.",
|
||||
@@ -1161,6 +1329,7 @@
|
||||
"ui.agent_chat_none_found_short": "Aucun agent trouvé",
|
||||
"ui.agent_chat_not_installed": "Non installé",
|
||||
"ui.agent_chat_not_signed_in": "non authentifié",
|
||||
"ui.agent_chat_open_settings": "Ouvrir les préférences",
|
||||
"ui.agent_chat_own_window": "Fenêtre séparée",
|
||||
"ui.agent_chat_paste_picture": "Coller une image",
|
||||
"ui.agent_chat_permission": "L'agent demande à exécuter : {title}",
|
||||
@@ -1185,6 +1354,8 @@
|
||||
"ui.agent_chat_send": "Envoyer",
|
||||
"ui.agent_chat_send_now": "Envoyer maintenant",
|
||||
"ui.agent_chat_send_now_tip": "Arrêter l'étape en cours de l'agent et envoyer celui-ci ensuite (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft communique avec un agent de programmation installé sur cet ordinateur. Choisissez-en un dans Préférences › IA.",
|
||||
"ui.agent_chat_setup_title": "Configurer un assistant",
|
||||
"ui.agent_chat_side": "Côté du chat de l'agent",
|
||||
"ui.agent_chat_signed_in": "authentifié",
|
||||
"ui.agent_chat_state_auth": "Authentification requise",
|
||||
@@ -1201,6 +1372,7 @@
|
||||
"ui.agent_chat_undo_label": "Chat de l'agent : {message}",
|
||||
"ui.agent_chat_undone": "(annulé)",
|
||||
"ui.agent_chat_view_attached": "Vue jointe",
|
||||
"ui.agent_chat_waiting": "En attente de votre réponse",
|
||||
"ui.agent_cursor": "Curseur de l'agent",
|
||||
"ui.agent_cursor_speed": "Vitesse du curseur de l'agent",
|
||||
"ui.ai_add": "Ajouter",
|
||||
@@ -1258,7 +1430,6 @@
|
||||
"ui.ai_permissions": "Autorisations",
|
||||
"ui.ai_preset": "Préréglage",
|
||||
"ui.ai_remove": "Retirer",
|
||||
"ui.ai_request_limit": "arrêté après 24 requêtes pour un message ; envoyez un autre message pour poursuivre",
|
||||
"ui.ai_reset": "Réinitialiser",
|
||||
"ui.ai_settings": "Réglages IA…",
|
||||
"ui.ai_show_features": "Afficher les fonctionnalités IA",
|
||||
@@ -1303,6 +1474,7 @@
|
||||
"ui.blocked": "Inaccessible",
|
||||
"ui.bodies": "Corps",
|
||||
"ui.bodies_all_if_none": "Corps (tous si aucun)",
|
||||
"ui.bottom": "Dessous",
|
||||
"ui.box": "Boîte",
|
||||
"ui.break": "Fractionner",
|
||||
"ui.browser_bodies_one": "{count} corps",
|
||||
@@ -1320,7 +1492,7 @@
|
||||
"ui.chamfer_type": "Type de chanfrein",
|
||||
"ui.change_workspace": "Changer d'espace de travail",
|
||||
"ui.circle": "Cercle",
|
||||
"ui.circular_pattern": "Réseau circulaire",
|
||||
"ui.circular_pattern": "Répétition circulaire",
|
||||
"ui.clear": "Effacer",
|
||||
"ui.clear_recent": "Effacer les récents",
|
||||
"ui.clear_the_selection": "Effacer la sélection",
|
||||
@@ -1455,11 +1627,12 @@
|
||||
"ui.grid_follows_view_hint": "Dans les vues de côté, la grille s'affiche sur le plan vertical qui leur fait face plutôt que sur le sol",
|
||||
"ui.ground_shadow": "Ombre au sol",
|
||||
"ui.groupSelected": "Grouper la sélection",
|
||||
"ui.hatch": "Hachures",
|
||||
"ui.heading_add_ins": "COMPLÉMENTS",
|
||||
"ui.heading_assemble": "ASSEMBLER",
|
||||
"ui.heading_box": "BOÎTE",
|
||||
"ui.heading_chamfer": "CHANFREIN",
|
||||
"ui.heading_circular_pattern": "RÉSEAU CIRCULAIRE",
|
||||
"ui.heading_circular_pattern": "RÉPÉTITION CIRCULAIRE",
|
||||
"ui.heading_coil": "HÉLICE",
|
||||
"ui.heading_combine": "COMBINER",
|
||||
"ui.heading_configure": "CONFIGURER",
|
||||
@@ -1492,14 +1665,14 @@
|
||||
"ui.heading_offset_plane": "PLAN DÉCALÉ",
|
||||
"ui.heading_ok": "OK",
|
||||
"ui.heading_parameters": "PARAMÈTRES",
|
||||
"ui.heading_pattern": "RÉSEAU",
|
||||
"ui.heading_pattern_on_path": "RÉSEAU SUR TRAJECTOIRE",
|
||||
"ui.heading_pattern": "RÉPÉTITION",
|
||||
"ui.heading_pattern_on_path": "RÉPÉTITION SUR TRAJECTOIRE",
|
||||
"ui.heading_pipe": "TUYAU",
|
||||
"ui.heading_plane_at_angle": "PLAN INCLINÉ",
|
||||
"ui.heading_plastic": "PLASTIQUE",
|
||||
"ui.heading_position": "POSITION",
|
||||
"ui.heading_prepare": "PRÉPARER",
|
||||
"ui.heading_rectangular_pattern": "RÉSEAU RECTANGULAIRE",
|
||||
"ui.heading_rectangular_pattern": "RÉPÉTITION RECTANGULAIRE",
|
||||
"ui.heading_revolve": "RÉVOLUTION",
|
||||
"ui.heading_right": "DROITE",
|
||||
"ui.heading_save_mesh": "ENREGISTRER COMME MAILLAGE",
|
||||
@@ -1528,6 +1701,11 @@
|
||||
"ui.hideOrigin": "Masquer l'origine",
|
||||
"ui.hidePlanes": "Masquer les plans",
|
||||
"ui.hideProfile": "Masquer le profil",
|
||||
"ui.history_back": "Reculer d’une étape",
|
||||
"ui.history_below_browser": "Sous le navigateur",
|
||||
"ui.history_first": "Aller au début",
|
||||
"ui.history_forward": "Avancer d’une étape",
|
||||
"ui.history_last": "Aller à la fin",
|
||||
"ui.history_title": "Historique",
|
||||
"ui.hole": "Perçage",
|
||||
"ui.hole_tap_type": "Type de taraudage",
|
||||
@@ -1545,6 +1723,8 @@
|
||||
"ui.joints": "Liaisons",
|
||||
"ui.keyboard_shortcuts": "Raccourcis clavier",
|
||||
"ui.language": "Langue",
|
||||
"ui.layout_compact": "Compacte",
|
||||
"ui.layout_floating": "Flottante",
|
||||
"ui.length_decimals": "Décimales des longueurs",
|
||||
"ui.light": "Clair",
|
||||
"ui.line": "Ligne",
|
||||
@@ -1622,7 +1802,7 @@
|
||||
"ui.part_size_shared": "Reconstruit la pièce en remplaçant les entités ajoutées. Toutes les occurrences de ce composant sont mises à jour.",
|
||||
"ui.paste": "Coller",
|
||||
"ui.path": "Trajectoire",
|
||||
"ui.pattern_on_path": "Réseau sur trajectoire",
|
||||
"ui.pattern_on_path": "Répétition sur trajectoire",
|
||||
"ui.perpendicular": "Perpendicularité",
|
||||
"ui.perspective_view": "Vue en perspective",
|
||||
"ui.pinToShortcuts": "Épingler aux raccourcis",
|
||||
@@ -1659,7 +1839,7 @@
|
||||
"ui.rail_fit": "Ajustement au rail",
|
||||
"ui.reachable": "Accessible",
|
||||
"ui.rectangle": "Rectangle",
|
||||
"ui.rectangular_pattern": "Réseau rectangulaire",
|
||||
"ui.rectangular_pattern": "Répétition rectangulaire",
|
||||
"ui.redo": "Rétablir",
|
||||
"ui.relink_design_changed": "L’assemblage a changé pendant le choix d’une source. Relancez Relier à nouveau la source.",
|
||||
"ui.relink_done": "Source reliée à nouveau pour toutes les instances.",
|
||||
@@ -1682,6 +1862,7 @@
|
||||
"ui.reverse_zoom_direction": "Inverser le sens du zoom",
|
||||
"ui.revolve": "Révolution",
|
||||
"ui.ribbon_exit": "Quitter",
|
||||
"ui.ribbon_labels": "Afficher le texte sous les icônes du ruban",
|
||||
"ui.ribbon_pin_footer": "Les commandes épinglées s’affichent dans le ruban. Survolez une ligne pour l’épingler ou la détacher.",
|
||||
"ui.ribbon_workspace": "Espace de travail",
|
||||
"ui.right": "Droite",
|
||||
@@ -1737,34 +1918,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom fluide à la molette",
|
||||
"ui.smooth_wheel_zoom_tip": "Désactiver pour appliquer chaque cran de molette immédiatement, sans lissage",
|
||||
"ui.snap": "Accrochage",
|
||||
"ui.space_mouse_calibrate": "Calibrer…",
|
||||
"ui.space_mouse_calibrate_tip": "Déplacez le capuchon comme la fenêtre le demande, pour que SolidCraft apprenne dans quel sens et jusqu'où va chaque axe de cette souris 3D. Enregistré pour cette souris 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrer la souris 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrée",
|
||||
"ui.space_mouse_calibration": "Calibrage de la souris 3D",
|
||||
"ui.space_mouse_calibration_done": "Terminé. Enregistrez-le pour l'utiliser chaque fois que cette souris 3D est connectée.",
|
||||
"ui.space_mouse_calibration_no_response": "Un axe a à peine bougé, il ne peut donc pas être calibré. Recommencez en déplaçant le capuchon plus loin à chaque fois.",
|
||||
"ui.space_mouse_dead_zone": "Zone morte",
|
||||
"ui.space_mouse_dead_zone_tip": "De combien le capuchon doit bouger avant que la vue ne bouge, en part de sa course complète. Augmentez-la si la vue dérive alors que le capuchon est au repos.",
|
||||
"ui.space_mouse_forget": "Oublier le calibrage",
|
||||
"ui.space_mouse_hint": "Déplace et fait pivoter la vue en même temps ; dans les esquisses, seuls le panoramique et le zoom sont utilisés.",
|
||||
"ui.space_mouse_navigate": "Naviguer avec SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Aucune souris 3D trouvée",
|
||||
"ui.space_mouse_not_calibrated": "Non calibrée",
|
||||
"ui.space_mouse_reading": "Lecture",
|
||||
"ui.space_mouse_step_lift": "Soulevez le capuchon, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_press": "Appuyez sur le capuchon, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_pull": "Tirez le capuchon vers vous, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_push": "Poussez le capuchon loin de vous, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_rest": "Lâchez le capuchon et laissez-le immobile.",
|
||||
"ui.space_mouse_step_slide_left": "Glissez le capuchon vers la gauche, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_slide_right": "Glissez le capuchon vers la droite, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclinez le capuchon vers l'arrière (bord avant vers le haut), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclinez le capuchon vers l'avant (bord avant vers le bas), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclinez le capuchon vers la gauche (bord gauche vers le bas), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclinez le capuchon vers la droite (bord droit vers le bas), maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Tournez le capuchon dans le sens horaire, maintenez, puis relâchez.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
|
||||
"ui.spacing": "Espacement",
|
||||
"ui.spacing_2": "Espacement 2",
|
||||
"ui.sphere": "Sphère",
|
||||
@@ -1836,6 +1989,7 @@
|
||||
"ui.workspace_design_tabs": "Modèle · Surface · Assemblage · Insérer · Inspecter · Gérer · Vue",
|
||||
"ui.workspace_drawing_description": "Mises en plan 2D avec vues, cotes et cartouches",
|
||||
"ui.workspace_footer": "Chaque espace de travail s’applique à la même conception, au même historique et à la même sélection ; seuls les onglets du ruban changent. Le sélecteur est verrouillé pendant la modification d’une esquisse ou d’une entité.",
|
||||
"ui.workspace_layout": "Disposition de l’espace de travail",
|
||||
"ui.workspace_manufacture_description": "Tranchage, parcours d’outil et configuration",
|
||||
"ui.workspace_planned": "Prévu",
|
||||
"ui.workspace_render_description": "Aperçu du produit, paramètres de scène et images rendues",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Escoller…",
|
||||
"Circular": "Circular",
|
||||
"Circumference": "Circunferencia",
|
||||
"Clockwise": "Horario",
|
||||
"Close draft": "Pechar o borrador",
|
||||
"Collisions": "Colisións",
|
||||
"Colour": "Cor",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "HISTORIAL",
|
||||
"Height": "Altura",
|
||||
"Hidden edges": "Arestas ocultas",
|
||||
"History layout": "Disposición do historial",
|
||||
"Holes": "Furados",
|
||||
"Holes (edges on them)": "Furados (arestas sobre eles)",
|
||||
"Home view": "Vista de inicio",
|
||||
@@ -232,7 +234,7 @@
|
||||
"No crash reports.": "Non hai informes de fallos.",
|
||||
"No matching appearance presets.": "Non hai preaxustes de aparencia coincidentes.",
|
||||
"No matching appearances": "Non hai aparencias coincidentes",
|
||||
"No visible bodies to export.": "Non hai corpos visibles para exportar.",
|
||||
"No visible bodies or sketches to export.": "Non hai corpos nin bosquexos visibles para exportar.",
|
||||
"None": "Ningún",
|
||||
"Not available yet": "Aínda non dispoñible",
|
||||
"Notch": "Entalle",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Search commands": "Buscar ordes",
|
||||
"Search this library": "Buscar nesta biblioteca",
|
||||
"Section": "Sección",
|
||||
"Section position": "Posición da sección",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleccionar outro: mantén o botón esquerdo quieto sobre o modelo (ou clic dereito › Seleccionar outro…) para listar todo o que hai baixo o cursor; fai clic de novo no mesmo punto para seleccionar o seguinte. Fai dobre clic co botón central para ir ao Inicio (Preferencias › Navegación).",
|
||||
"Select bodies (a face picks its body)": "Selecciona corpos (unha cara escolle o seu corpo)",
|
||||
"Select bodies or faces instead": "Seleccionar corpos ou caras no seu lugar",
|
||||
@@ -332,6 +335,7 @@
|
||||
"Show Agent Cursor": "Mostrar o cursor do axente",
|
||||
"Show in folder": "Mostrar no cartafol",
|
||||
"Show the pointer an AI agent moves (#35)": "Mostrar o punteiro que move un axente de IA (#35)",
|
||||
"Show tool at cursor": "Mostrar a ferramenta xunto ao cursor",
|
||||
"Signed axes": "Eixes con signo",
|
||||
"Size": "Tamaño",
|
||||
"Sketch constraints": "Restricións do bosquexo",
|
||||
@@ -344,9 +348,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft pechouse antes de gardar estes deseños. Pódese abrir a súa última gravación automática.",
|
||||
"Specialise": "Especializar",
|
||||
"Speed": "Velocidade",
|
||||
"Square": "Cadrado",
|
||||
"Stand it up": "Poñela de pé",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Poñela de pé: un cuarto de volta arredor de X, como unha operación Mover que podes desfacer.",
|
||||
"Stand this part up?": "Poñer esta peza de pé?",
|
||||
"Start angle": "Ángulo inicial",
|
||||
"Start bulge": "Abaulamento inicial",
|
||||
"Start continuity": "Continuidade inicial",
|
||||
"Step": "Paso",
|
||||
@@ -411,6 +417,7 @@
|
||||
"angle, height": "ángulo, altura",
|
||||
"appearance.dielectric_ior": "IOR dieléctrico",
|
||||
"appearance.metallic": "Metálico",
|
||||
"appearance.only_available": "Só aparencias dispoñibles",
|
||||
"appearance.pbr_help": "Ve esta superficie en Render ou nun PNG de produto. Sen refracción nin transmisión.",
|
||||
"appearance.pbr_opaque": "PBR opaco",
|
||||
"appearance.roughness": "Rugosidade",
|
||||
@@ -529,6 +536,10 @@
|
||||
"cmd.file_insert_step": "Inserir STEP",
|
||||
"cmd.file_save_mesh": "Gardar como malla",
|
||||
"cmd.file_slicers": "Laminadores instalados",
|
||||
"cmd.frame_member": "Membro estrutural",
|
||||
"cmd.frame_profile_insert": "Inserir perfil",
|
||||
"cmd.frame_profile_list": "Biblioteca de perfís",
|
||||
"cmd.frame_profile_properties": "Propiedades do perfil",
|
||||
"cmd.inspect_accessibility": "Análise de accesibilidade",
|
||||
"cmd.inspect_center_of_mass": "Centro de masas",
|
||||
"cmd.inspect_clearance": "Folga",
|
||||
@@ -568,6 +579,7 @@
|
||||
"cmd.material_create": "Material físico novo",
|
||||
"cmd.material_delete": "Eliminar o material físico",
|
||||
"cmd.material_edit": "Editar o material físico",
|
||||
"cmd.material_inspect": "Inspeccionar o material físico",
|
||||
"cmd.material_list": "Listar os materiais",
|
||||
"cmd.material_properties_directional": "Módulo elástico direccional",
|
||||
"cmd.material_properties_validate": "Validar os datos do material físico",
|
||||
@@ -831,7 +843,7 @@
|
||||
"dxf.progress.projection": "Proxectando a xeometría (3/5)",
|
||||
"dxf.progress.write": "Escribindo o DXF (5/5)",
|
||||
"dxf.tolerance": "Tolerancia da curva",
|
||||
"dxf.tolerance_help": "Só os corpos visibles. As curvas usan segmentos rectos. Unha tolerancia menor dá curvas máis finas e usa máis tempo e memoria.",
|
||||
"dxf.tolerance_help": "Só os corpos visibles e os bosquexos amosados. As curvas usan segmentos rectos. Unha tolerancia menor dá curvas máis finas e usa máis tempo e memoria.",
|
||||
"file_load.built": "Construído ata agora: {built} de {total} operacións.",
|
||||
"file_load.converting": "Convertendo o sólido {done} de {total}",
|
||||
"file_load.inserting": "Inserindo a xeometría",
|
||||
@@ -856,19 +868,57 @@
|
||||
"help.mit": "Licenza MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Licenzas de terceiros",
|
||||
"home.back": "Volver a {name}",
|
||||
"home.crashes": "Informes de fallos",
|
||||
"home.design_workspace": "Deseño",
|
||||
"home.documentation": "Documentación",
|
||||
"home.export": "Exportar",
|
||||
"home.export_hint": "Escolle un formato para o deseño actual.",
|
||||
"home.export_json_hint": "Deseño editable e historial de comandos",
|
||||
"home.export_mesh_hint": "Malla para imprimir ou compartir",
|
||||
"home.export_step_hint": "Xeometría sólida precisa",
|
||||
"home.import": "Importar…",
|
||||
"home.import_hint": "STEP, IGES ou malla nun novo deseño",
|
||||
"home.in_this_browser": "neste navegador",
|
||||
"home.missing": "non atopado",
|
||||
"home.month_01": "xan.",
|
||||
"home.month_02": "feb.",
|
||||
"home.month_03": "mar.",
|
||||
"home.month_04": "abr.",
|
||||
"home.month_05": "maio",
|
||||
"home.month_06": "xuño",
|
||||
"home.month_07": "xul.",
|
||||
"home.month_08": "ago.",
|
||||
"home.month_09": "set.",
|
||||
"home.month_10": "out.",
|
||||
"home.month_11": "nov.",
|
||||
"home.month_12": "dec.",
|
||||
"home.new_inches": "Novo deseño en polgadas",
|
||||
"home.new_inches_hint": "Polgadas · ábrese en Deseño",
|
||||
"home.new_mm": "Novo deseño",
|
||||
"home.new_mm_hint": "Milímetros · ábrese en Deseño",
|
||||
"home.open": "Abrir…",
|
||||
"home.opened_days_ago": "Aberto hai {count} días",
|
||||
"home.opened_hours_ago": "Aberto hai {count} h",
|
||||
"home.opened_just_now": "Aberto agora mesmo",
|
||||
"home.opened_minutes_ago": "Aberto hai {count} min",
|
||||
"home.open_hint": "Un deseño .solidcraft ({shortcut})",
|
||||
"home.open_now": "Abrir agora",
|
||||
"home.opened_days_ago": "Hai {count} días",
|
||||
"home.opened_hours_ago": "Hai {count} h",
|
||||
"home.opened_just_now": "Agora mesmo",
|
||||
"home.opened_minutes_ago": "Hai {count} min",
|
||||
"home.opening": "abrindo…",
|
||||
"home.properties": "Propiedades do deseño",
|
||||
"home.recent": "Recentes",
|
||||
"home.recent_empty": "Os deseños que abras ou gardes aparecerán aquí.",
|
||||
"home.recovery_count": "Deseños recuperables: {count}",
|
||||
"home.recovery_empty": "Non hai deseños recuperables.",
|
||||
"home.recovery_hint": "SolidCraft pechouse antes de gardar estes deseños.",
|
||||
"home.report_issue": "Informar dunha incidencia",
|
||||
"home.samples": "Exemplos",
|
||||
"home.settings": "Configuración",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Atallos de teclado",
|
||||
"home.start_hint": "Crea un deseño, abre un ficheiro recente ou explora un exemplo.",
|
||||
"home.whats_new": "Novidades",
|
||||
"home.yesterday": "Onte",
|
||||
"install.button_tip": "Instala SolidCraft como aplicación: ábrese na súa propia xanela e funciona sen conexión",
|
||||
"install.dock_1": "Escolle Ficheiro › Engadir ao Dock.",
|
||||
"install.dock_2": "Fai clic en Engadir.",
|
||||
@@ -1021,7 +1071,7 @@
|
||||
"search.try": "Escribe o nome dunha orde, unha operación deste deseño ou un parámetro. Para executar unha orde con valores, escribe o seu id e un obxecto JSON: {example}",
|
||||
"search.valid": "Válido",
|
||||
"settings.display_quality_note": "Os niveis máis finos debuxan as caras e arestas curvas máis suaves e tardan máis en xerar a malla. Só cambia a vista, nunca o deseño nin as exportacións.",
|
||||
"settings.frame_rate_note": "Mostra as imaxes por segundo na esquina inferior dereita da vista. Mentres está activado, a vista redebúxase continuamente.",
|
||||
"settings.frame_rate_note": "Mostra as imaxes por segundo na esquina inferior dereita da vista.",
|
||||
"settings.measure_axis_distances": "Mostrar as distancias en X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Mostra as compoñentes da distancia segundo os eixes do mundo xunto á distancia total mentres Medir está aberto.",
|
||||
"settings.msaa_applies_on_restart": "O antialiasing aplicarase a próxima vez que se inicie SolidCraft",
|
||||
@@ -1082,7 +1132,106 @@
|
||||
"trace.stopped_by_contact": "Remata na mostra {sample}: {a} atravesaría {b}",
|
||||
"trace.stopped_by_loop": "Remata na mostra {sample}: o bucle a través de {joint} non pode pecharse máis",
|
||||
"ui.1_degree_of_freedom": "1 grao de liberdade",
|
||||
"ui.3d_mouse_axes_reassigned": "Os eixes están reasignados en Preferencias, e iso segue aplicándose sobre esta calibración.",
|
||||
"ui.3d_mouse_button": "Botón {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nada",
|
||||
"ui.3d_mouse_button_settings": "Axustes do rato 3D",
|
||||
"ui.3d_mouse_buttons": "Botóns",
|
||||
"ui.3d_mouse_buttons_hint": "Preme un botón do teu rato 3D para atopalo aquí e escolle que fai.",
|
||||
"ui.3d_mouse_buttons_no_device": "Conecta un rato 3D para escoller que fan os seus botóns.",
|
||||
"ui.3d_mouse_buttons_reset": "Restaurar os botóns predeterminados",
|
||||
"ui.3d_mouse_calibrate": "Calibrar…",
|
||||
"ui.3d_mouse_calibrate_tip": "Move o capuchón como pide a xanela para que SolidCraft aprenda cara a onde e ata onde vai cada eixe deste rato 3D. Gárdase para este rato 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrar o rato 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Move o rato 3D unha vez para que SolidCraft o vexa.",
|
||||
"ui.3d_mouse_calibrated": "Calibrado",
|
||||
"ui.3d_mouse_calibration": "Calibración do rato 3D",
|
||||
"ui.3d_mouse_calibration_done": "Feito. Gárdaa para usala sempre que este rato 3D estea conectado.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un eixe case non se moveu, así que non se pode calibrar. Volve comezar e move o capuchón máis lonxe cada vez.",
|
||||
"ui.3d_mouse_circles_help": "Os círculos mostran o que envía o dispositivo tras a calibración. O modelo de proba mostra o que fai a vista con iso, tras a inversión, a velocidade e a asignación de eixes.",
|
||||
"ui.3d_mouse_dead_zone": "Zona morta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Canto ten que moverse o capuchón antes de que se mova a vista, como parte do seu percorrido completo. Auméntaa se a vista se despraza co capuchón en repouso.",
|
||||
"ui.3d_mouse_denied": "SolidCraft ve un rato 3D, pero non ten permiso para lelo. A regra udev está nas notas de configuración do rato 3D.",
|
||||
"ui.3d_mouse_denied_short": "Atopado, pero non se pode ler",
|
||||
"ui.3d_mouse_driven_by": "Que movemento calibrado do dispositivo controla este eixe. Se escolles un que xa usa outro eixe, intercámbianse.",
|
||||
"ui.3d_mouse_driven_by_label": "controlado por",
|
||||
"ui.3d_mouse_driver": "Controlador do rato 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automático",
|
||||
"ui.3d_mouse_driver_direct": "Directo (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Lendo polo de agora a través de spacenavd: non se puido abrir o dispositivo.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lectura directa do dispositivo; spacenavd non está en execución",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, a única opción dispoñible",
|
||||
"ui.3d_mouse_forget": "Esquecer a calibración",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vista esquerda",
|
||||
"ui.3d_mouse_ghost_left_window": "Xanela esquerda",
|
||||
"ui.3d_mouse_ghost_position": "Posición",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vista dereita",
|
||||
"ui.3d_mouse_hint": "Move e rota a vista á vez; os bosquexos só usan desprazamento e zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Retorno ao centro",
|
||||
"ui.3d_mouse_kind_question": "Que tipo de rato 3D é?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "A lectura mantense mentres a suxeitas, como nun dispositivo de tipo «Retorno ao centro».",
|
||||
"ui.3d_mouse_looks_like_trackball": "A lectura volve atrás en canto deixas de moverte, como nun trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotación X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotación Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotación Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translación X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translación Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translación Z",
|
||||
"ui.3d_mouse_navigate": "Navegar co rato 3D",
|
||||
"ui.3d_mouse_none": "Non se atopou ningún rato 3D",
|
||||
"ui.3d_mouse_not_calibrated": "Sen calibrar",
|
||||
"ui.3d_mouse_offer": "Calibrar o teu rato 3D corrixe os eixes trocados ou invertidos e axusta a súa velocidade e sensación.",
|
||||
"ui.3d_mouse_offer_never": "Non mostrar de novo",
|
||||
"ui.3d_mouse_offer_swap": "Cambiar a Hidraw (controlador directo)",
|
||||
"ui.3d_mouse_reading": "Lectura",
|
||||
"ui.3d_mouse_reset_axes": "Restablecer asignación de eixes",
|
||||
"ui.3d_mouse_reset_view": "Restablecer vista",
|
||||
"ui.3d_mouse_restore": "Restaurar valores do rato 3D",
|
||||
"ui.3d_mouse_restore_ask": "Volver aos valores predeterminados da configuración do rato 3D? A calibración e a asignación de botóns consérvanse.",
|
||||
"ui.3d_mouse_section": "Rato 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostrar o rato 3D na vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Unha imaxe do teu rato 3D na esquina inferior esquerda que se move cando o moves.",
|
||||
"ui.3d_mouse_skip": "O meu dispositivo non fai isto",
|
||||
"ui.3d_mouse_skip_tip": "Omite este movemento nos dous sentidos; a calibración déixao fóra.",
|
||||
"ui.3d_mouse_speed": "Velocidade do rato 3D",
|
||||
"ui.3d_mouse_step_lift": "Levanta o capuchón, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_press": "Preme o capuchón cara abaixo, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_pull": "Tira do capuchón cara a ti, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_push": "Empurra o capuchón lonxe de ti, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_rest": "Solta o capuchón e déixao quieto.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Solta a bóla e déixaa quieta.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Xira o anel de desprazamento uns clics no sentido horario.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Xira o anel de desprazamento uns clics no sentido antihorario.",
|
||||
"ui.3d_mouse_step_roll_away": "Fai rodar a bóla lonxe de ti unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_roll_left": "Fai rodar a bóla á esquerda unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_roll_right": "Fai rodar a bóla á dereita unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_roll_toward": "Fai rodar a bóla cara a ti unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_slide_left": "Despraza o capuchón á esquerda, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_slide_right": "Despraza o capuchón á dereita, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Xira a bóla no sentido horario unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Xira a bóla no sentido antihorario unhas cantas veces e déixaa parar.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclina a parte de arriba do capuchón lonxe de ti (o bordo máis próximo sobe), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclina o capuchón á esquerda (bordo esquerdo abaixo), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclina o capuchón á dereita (bordo dereito abaixo), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclina a parte de arriba do capuchón cara a ti (o bordo máis próximo baixa), mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_touch": "Apoia a man no capuchón sen empurralo.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Xira o capuchón no sentido horario, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantén a posición e despois sóltao.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
|
||||
"ui.3d_mouse_view_axis": "Eixe da vista",
|
||||
"ui.3d_mouse_view_off": "Desactivado",
|
||||
"ui.3d_mouse_view_off_tip": "O perfil do controlador do rato 3D desactiva este eixe, así que o seu movemento non move a vista. Se escolles aquí o movemento doutro eixe, intercámbianse e ese movemento deixa de mover a vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbitar á esquerda / dereita",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbitar arriba / abaixo",
|
||||
"ui.3d_mouse_view_pan_sideways": "Desprazar á esquerda / dereita",
|
||||
"ui.3d_mouse_view_pan_up_down": "Desprazar arriba / abaixo",
|
||||
"ui.3d_mouse_view_roll": "Xirar sobre a vista",
|
||||
"ui.3d_mouse_view_unmapped": "Eixe {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Agardando movemento",
|
||||
"ui.3d_mouse_wiggle": "Se tes un conectado, móveo un pouco.",
|
||||
"ui.3d_sketch": "Bosquexo 3D",
|
||||
"ui.activate": "Activar",
|
||||
"ui.active_component": "Compoñente activo",
|
||||
@@ -1092,6 +1241,7 @@
|
||||
"ui.agent_chat_add": "Anexar ficheiros",
|
||||
"ui.agent_chat_add_file": "Escoller un ficheiro…",
|
||||
"ui.agent_chat_allow": "Permitir",
|
||||
"ui.agent_chat_allow_once": "Permitir unha vez",
|
||||
"ui.agent_chat_allowed": "Permitido",
|
||||
"ui.agent_chat_answered": "Resposta: {option}",
|
||||
"ui.agent_chat_approval": "Permitir que o axente faga isto?",
|
||||
@@ -1143,12 +1293,16 @@
|
||||
"ui.agent_chat_files_left_out": "Cunha mensaxe van como máximo {max} ficheiros; deixados fóra: {files}",
|
||||
"ui.agent_chat_files_loading": "Agardando a que se lean os ficheiros",
|
||||
"ui.agent_chat_files_max": "Como máximo {max} ficheiros por mensaxe",
|
||||
"ui.agent_chat_flatpak_none_found": "Non se atopou ningún axente de programación. De forma predeterminada, o illamento de Flatpak non ve os axentes instalados no teu cartafol persoal e non ten acceso á rede. Para permitir Claude Code e o seu adaptador ACP instalados con nvm ou en ~/.local/bin, executa esta orde nun terminal e despois reinicia SolidCraft. Concede acceso aos programas e ficheiros de inicio de sesión indicados, ademais de acceso á rede:",
|
||||
"ui.agent_chat_import_failed": "{name}: fallou a importación: {error}",
|
||||
"ui.agent_chat_intro": "Conversa cun axente de programación que xa uses, ou cun punto de acceso de modelo que escollas. Traballa no deseño aberto a través das ferramentas de SolidCraft: os seus cambios vense mentres os fai e desfanse nun paso por mensaxe. O que pode facer sen preguntar defínese en Preferencias > IA.",
|
||||
"ui.agent_chat_key_in_env": "clave configurada",
|
||||
"ui.agent_chat_left": "Esquerda",
|
||||
"ui.agent_chat_limit": "O axente chegou ao límite de {limit} cambios por mensaxe. Envía outra mensaxe para que continúe.",
|
||||
"ui.agent_chat_minimize": "Minimizar nunha franxa",
|
||||
"ui.agent_chat_mode_all": "Todo",
|
||||
"ui.agent_chat_mode_ask": "Preguntar",
|
||||
"ui.agent_chat_mode_edits": "Edicións",
|
||||
"ui.agent_chat_model_tip": "O modelo que usa o axente. A túa escolla lémbrase para este axente.",
|
||||
"ui.agent_chat_new": "Chat novo",
|
||||
"ui.agent_chat_new_confirm": "Comezar un chat novo? Borrarase o actual.",
|
||||
@@ -1161,6 +1315,7 @@
|
||||
"ui.agent_chat_none_found_short": "Non se atopou ningún axente",
|
||||
"ui.agent_chat_not_installed": "Non instalado",
|
||||
"ui.agent_chat_not_signed_in": "sen sesión iniciada",
|
||||
"ui.agent_chat_open_settings": "Abrir preferencias",
|
||||
"ui.agent_chat_own_window": "Xanela propia",
|
||||
"ui.agent_chat_paste_picture": "Pegar unha imaxe",
|
||||
"ui.agent_chat_permission": "O axente pide executar: {title}",
|
||||
@@ -1185,6 +1340,8 @@
|
||||
"ui.agent_chat_send": "Enviar",
|
||||
"ui.agent_chat_send_now": "Enviar agora",
|
||||
"ui.agent_chat_send_now_tip": "Detén o paso actual do axente e envía isto a continuación (Ctrl+Intro)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft comunícase cun axente de programación instalado neste equipo. Escolle un en Preferencias › IA.",
|
||||
"ui.agent_chat_setup_title": "Configurar un asistente",
|
||||
"ui.agent_chat_side": "Lado do chat co axente",
|
||||
"ui.agent_chat_signed_in": "sesión iniciada",
|
||||
"ui.agent_chat_state_auth": "Hai que iniciar sesión",
|
||||
@@ -1201,6 +1358,7 @@
|
||||
"ui.agent_chat_undo_label": "Chat co axente: {message}",
|
||||
"ui.agent_chat_undone": "(desfeito)",
|
||||
"ui.agent_chat_view_attached": "Vista achegada",
|
||||
"ui.agent_chat_waiting": "Agardando a túa resposta",
|
||||
"ui.agent_cursor": "Cursor do axente",
|
||||
"ui.agent_cursor_speed": "Velocidade do cursor do axente",
|
||||
"ui.ai_add": "Engadir",
|
||||
@@ -1258,7 +1416,6 @@
|
||||
"ui.ai_permissions": "Permisos",
|
||||
"ui.ai_preset": "Axuste predefinido",
|
||||
"ui.ai_remove": "Quitar",
|
||||
"ui.ai_request_limit": "detívose tras 24 solicitudes para unha mensaxe; envía outra mensaxe para continuar",
|
||||
"ui.ai_reset": "Restablecer",
|
||||
"ui.ai_settings": "Axustes de IA…",
|
||||
"ui.ai_show_features": "Mostrar as funcións de IA",
|
||||
@@ -1303,6 +1460,7 @@
|
||||
"ui.blocked": "Bloqueado",
|
||||
"ui.bodies": "Corpos",
|
||||
"ui.bodies_all_if_none": "Corpos (todos se non hai ningún)",
|
||||
"ui.bottom": "Inferior",
|
||||
"ui.box": "Caixa",
|
||||
"ui.break": "Romper",
|
||||
"ui.browser_bodies_one": "{count} corpo",
|
||||
@@ -1455,6 +1613,7 @@
|
||||
"ui.grid_follows_view_hint": "As vistas laterais mostran a grella no plano vertical que ten diante en vez do chan",
|
||||
"ui.ground_shadow": "Sombra no chan",
|
||||
"ui.groupSelected": "Agrupar a selección",
|
||||
"ui.hatch": "Raiado",
|
||||
"ui.heading_add_ins": "COMPLEMENTOS",
|
||||
"ui.heading_assemble": "ENSAMBLAR",
|
||||
"ui.heading_box": "CAIXA",
|
||||
@@ -1528,6 +1687,11 @@
|
||||
"ui.hideOrigin": "Ocultar a orixe",
|
||||
"ui.hidePlanes": "Ocultar os planos",
|
||||
"ui.hideProfile": "Ocultar o perfil",
|
||||
"ui.history_back": "Retroceder un paso",
|
||||
"ui.history_below_browser": "Debaixo do explorador",
|
||||
"ui.history_first": "Ir ao comezo",
|
||||
"ui.history_forward": "Avanzar un paso",
|
||||
"ui.history_last": "Ir ao final",
|
||||
"ui.history_title": "Historial",
|
||||
"ui.hole": "Furado",
|
||||
"ui.hole_tap_type": "Tipo de roscado do furado",
|
||||
@@ -1545,6 +1709,8 @@
|
||||
"ui.joints": "Unións",
|
||||
"ui.keyboard_shortcuts": "Atallos de teclado",
|
||||
"ui.language": "Idioma",
|
||||
"ui.layout_compact": "Compacta",
|
||||
"ui.layout_floating": "Flotante",
|
||||
"ui.length_decimals": "Decimais das lonxitudes",
|
||||
"ui.light": "Claro",
|
||||
"ui.line": "Liña",
|
||||
@@ -1682,6 +1848,7 @@
|
||||
"ui.reverse_zoom_direction": "Inverter a dirección do zoom",
|
||||
"ui.revolve": "Revolución",
|
||||
"ui.ribbon_exit": "Saír",
|
||||
"ui.ribbon_labels": "Mostrar texto baixo as iconas da cinta",
|
||||
"ui.ribbon_pin_footer": "As ordes fixadas móstranse na cinta. Pasa o cursor sobre unha fila para fixala ou soltala.",
|
||||
"ui.ribbon_workspace": "Espazo de traballo",
|
||||
"ui.right": "Dereita",
|
||||
@@ -1737,34 +1904,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom suave coa roda",
|
||||
"ui.smooth_wheel_zoom_tip": "Desactívao para aplicar cada movemento da roda de inmediato, sen suavizado",
|
||||
"ui.snap": "Axuste",
|
||||
"ui.space_mouse_calibrate": "Calibrar…",
|
||||
"ui.space_mouse_calibrate_tip": "Move o capuchón como pide a xanela para que SolidCraft aprenda cara a onde e ata onde vai cada eixe deste rato 3D. Gárdase para este rato 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrar o rato 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrado",
|
||||
"ui.space_mouse_calibration": "Calibración do rato 3D",
|
||||
"ui.space_mouse_calibration_done": "Feito. Gárdaa para usala sempre que este rato 3D estea conectado.",
|
||||
"ui.space_mouse_calibration_no_response": "Un eixe case non se moveu, así que non se pode calibrar. Volve comezar e move o capuchón máis lonxe cada vez.",
|
||||
"ui.space_mouse_dead_zone": "Zona morta",
|
||||
"ui.space_mouse_dead_zone_tip": "Canto ten que moverse o capuchón antes de que se mova a vista, como parte do seu percorrido completo. Auméntaa se a vista se despraza co capuchón en repouso.",
|
||||
"ui.space_mouse_forget": "Esquecer a calibración",
|
||||
"ui.space_mouse_hint": "Move e rota a vista á vez; os bosquexos só usan desprazamento e zoom.",
|
||||
"ui.space_mouse_navigate": "Navegar con SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Non se atopou ningún rato 3D",
|
||||
"ui.space_mouse_not_calibrated": "Sen calibrar",
|
||||
"ui.space_mouse_reading": "Lectura",
|
||||
"ui.space_mouse_step_lift": "Levanta o capuchón, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_press": "Preme o capuchón cara abaixo, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_pull": "Tira do capuchón cara a ti, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_push": "Empurra o capuchón lonxe de ti, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_rest": "Solta o capuchón e déixao quieto.",
|
||||
"ui.space_mouse_step_slide_left": "Despraza o capuchón á esquerda, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_slide_right": "Despraza o capuchón á dereita, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclina o capuchón cara atrás (bordo dianteiro arriba), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclina o capuchón cara adiante (bordo dianteiro abaixo), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclina o capuchón á esquerda (bordo esquerdo abaixo), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclina o capuchón á dereita (bordo dereito abaixo), mantéño e sóltao.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Xira o capuchón no sentido horario, mantéño e sóltao.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantéño e sóltao.",
|
||||
"ui.spacing": "Espazado",
|
||||
"ui.spacing_2": "Espazado 2",
|
||||
"ui.sphere": "Esfera",
|
||||
@@ -1836,6 +1975,7 @@
|
||||
"ui.workspace_design_tabs": "Modelo · Superficie · Montaxe · Inserir · Inspeccionar · Xestionar · Vista",
|
||||
"ui.workspace_drawing_description": "Planos 2D con vistas, cotas e cartelas",
|
||||
"ui.workspace_footer": "Todos os espazos de traballo usan o mesmo deseño, historial e selección; só cambian as pestanas da cinta. O selector bloquéase mentres se edita un bosquexo ou unha operación.",
|
||||
"ui.workspace_layout": "Disposición do espazo de traballo",
|
||||
"ui.workspace_manufacture_description": "Laminado, traxectorias de ferramenta e configuración",
|
||||
"ui.workspace_planned": "Previsto",
|
||||
"ui.workspace_render_description": "Vista previa do produto, axustes de escena e imaxes renderizadas",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Válasszon…",
|
||||
"Circular": "Kör alakú",
|
||||
"Circumference": "Kerület",
|
||||
"Clockwise": "Óramutató járásával megegyező",
|
||||
"Close draft": "Vázlat bezárása",
|
||||
"Collisions": "Ütközések",
|
||||
"Colour": "Szín",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "ELŐZMÉNYEK",
|
||||
"Height": "Magasság",
|
||||
"Hidden edges": "Rejtett élek",
|
||||
"History layout": "Előzmények elrendezése",
|
||||
"Holes": "Furatok",
|
||||
"Holes (edges on them)": "Furatok (rajtuk lévő élek)",
|
||||
"Home view": "Kezdőnézet",
|
||||
@@ -232,7 +234,6 @@
|
||||
"No crash reports.": "Nincsenek összeomlási jelentések.",
|
||||
"No matching appearance presets.": "Nincs egyező megjelenés-előbeállítás.",
|
||||
"No matching appearances": "Nincs egyező megjelenés",
|
||||
"No visible bodies to export.": "Nincs exportálható látható test.",
|
||||
"None": "Nincs",
|
||||
"Not available yet": "Még nem érhető el",
|
||||
"Notch": "Bevágás",
|
||||
@@ -321,6 +322,7 @@
|
||||
"Search commands": "Parancsok keresése",
|
||||
"Search this library": "Keresés ebben a könyvtárban",
|
||||
"Section": "Metszet",
|
||||
"Section position": "Szelvény helyzete",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Más kijelölése: tartsa nyomva a bal gombot egy helyen a modellen (vagy jobb kattintás › Más kijelölése…) a kurzor alatti elemek listázásához; kattintson újra ugyanarra a helyre a következő elem kijelöléséhez. A középső gomb dupla kattintása a kezdőnézetre vált (Beállítások › Navigáció).",
|
||||
"Select bodies (a face picks its body)": "Testek kijelölése (egy lap a testét jelöli ki)",
|
||||
"Select bodies or faces instead": "Inkább testek vagy lapok kijelölése",
|
||||
@@ -332,6 +334,7 @@
|
||||
"Show Agent Cursor": "Ügynök kurzorának megjelenítése",
|
||||
"Show in folder": "Megjelenítés a mappában",
|
||||
"Show the pointer an AI agent moves (#35)": "Az AI-ügynök által mozgatott mutató megjelenítése (#35)",
|
||||
"Show tool at cursor": "Eszköz megjelenítése a kurzornál",
|
||||
"Signed axes": "Előjeles tengelyek",
|
||||
"Size": "Méret",
|
||||
"Sketch constraints": "Vázlatkényszerek",
|
||||
@@ -344,9 +347,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "A SolidCraft bezárult, mielőtt ezek a tervek mentve lettek volna. Az utolsó automatikus mentésük megnyitható.",
|
||||
"Specialise": "Specializálás",
|
||||
"Speed": "Sebesség",
|
||||
"Square": "Négyzet",
|
||||
"Stand it up": "Felállítás",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Felállítás: negyedfordulat az X körül, egyetlen visszavonható Mozgatás elemként.",
|
||||
"Stand this part up?": "Felállítja ezt az alkatrészt?",
|
||||
"Start angle": "Kezdőszög",
|
||||
"Start bulge": "Kezdő ívelés",
|
||||
"Start continuity": "Kezdő folytonosság",
|
||||
"Step": "Lépés",
|
||||
@@ -411,6 +416,7 @@
|
||||
"angle, height": "szög, magasság",
|
||||
"appearance.dielectric_ior": "Dielektromos törésmutató",
|
||||
"appearance.metallic": "Fémes",
|
||||
"appearance.only_available": "Csak elérhető megjelenések",
|
||||
"appearance.pbr_help": "Tekintse meg ezt a felületet Renderelésben vagy termék-PNG-ben. Nincs fénytörés vagy áteresztés.",
|
||||
"appearance.pbr_opaque": "Átlátszatlan PBR",
|
||||
"appearance.roughness": "Érdesség",
|
||||
@@ -529,6 +535,10 @@
|
||||
"cmd.file_insert_step": "STEP beszúrása",
|
||||
"cmd.file_save_mesh": "Mentés hálóként",
|
||||
"cmd.file_slicers": "Telepített szeletelők",
|
||||
"cmd.frame_member": "Szerkezeti elem",
|
||||
"cmd.frame_profile_insert": "Profil beszúrása",
|
||||
"cmd.frame_profile_list": "Profilkönyvtár",
|
||||
"cmd.frame_profile_properties": "Profil tulajdonságai",
|
||||
"cmd.inspect_accessibility": "Hozzáférhetőség-elemzés",
|
||||
"cmd.inspect_center_of_mass": "Tömegközéppont",
|
||||
"cmd.inspect_clearance": "Hézag",
|
||||
@@ -568,6 +578,7 @@
|
||||
"cmd.material_create": "Új fizikai anyag",
|
||||
"cmd.material_delete": "Fizikai anyag törlése",
|
||||
"cmd.material_edit": "Fizikai anyag szerkesztése",
|
||||
"cmd.material_inspect": "Fizikai anyag vizsgálata",
|
||||
"cmd.material_list": "Anyagok listázása",
|
||||
"cmd.material_properties_directional": "Irányfüggő rugalmassági modulus",
|
||||
"cmd.material_properties_validate": "Fizikai anyagadatok ellenőrzése",
|
||||
@@ -856,19 +867,57 @@
|
||||
"help.mit": "MIT licenc",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Harmadik felek licencei",
|
||||
"home.back": "Vissza ide: {name}",
|
||||
"home.crashes": "Összeomlási jelentések",
|
||||
"home.design_workspace": "Tervezés",
|
||||
"home.documentation": "Dokumentáció",
|
||||
"home.export": "Exportálás",
|
||||
"home.export_hint": "Válasszon formátumot az aktuális tervhez.",
|
||||
"home.export_json_hint": "Szerkeszthető terv és parancselőzmények",
|
||||
"home.export_mesh_hint": "Háló nyomtatáshoz vagy megosztáshoz",
|
||||
"home.export_step_hint": "Pontos testgeometria",
|
||||
"home.import": "Importálás…",
|
||||
"home.import_hint": "STEP, IGES vagy háló importálása új tervbe",
|
||||
"home.in_this_browser": "ebben a böngészőben",
|
||||
"home.missing": "hiányzik",
|
||||
"home.month_01": "jan.",
|
||||
"home.month_02": "febr.",
|
||||
"home.month_03": "márc.",
|
||||
"home.month_04": "ápr.",
|
||||
"home.month_05": "máj.",
|
||||
"home.month_06": "jún.",
|
||||
"home.month_07": "júl.",
|
||||
"home.month_08": "aug.",
|
||||
"home.month_09": "szept.",
|
||||
"home.month_10": "okt.",
|
||||
"home.month_11": "nov.",
|
||||
"home.month_12": "dec.",
|
||||
"home.new_inches": "Új terv hüvelykben",
|
||||
"home.new_inches_hint": "Hüvelyk · a Tervezés munkaterületen nyílik meg",
|
||||
"home.new_mm": "Új terv",
|
||||
"home.new_mm_hint": "Milliméter · a Tervezés munkaterületen nyílik meg",
|
||||
"home.open": "Megnyitás…",
|
||||
"home.opened_days_ago": "Megnyitva {count} napja",
|
||||
"home.opened_hours_ago": "Megnyitva {count} órája",
|
||||
"home.opened_just_now": "Épp most megnyitva",
|
||||
"home.opened_minutes_ago": "Megnyitva {count} perce",
|
||||
"home.open_hint": "Egy .solidcraft terv ({shortcut})",
|
||||
"home.open_now": "Jelenleg megnyitva",
|
||||
"home.opened_days_ago": "{count} napja",
|
||||
"home.opened_hours_ago": "{count} órája",
|
||||
"home.opened_just_now": "Épp most",
|
||||
"home.opened_minutes_ago": "{count} perce",
|
||||
"home.opening": "megnyitás…",
|
||||
"home.properties": "Terv tulajdonságai",
|
||||
"home.recent": "Legutóbbi",
|
||||
"home.recent_empty": "A megnyitott vagy mentett tervek itt jelennek meg.",
|
||||
"home.recovery_count": "Helyreállítható tervek: {count}",
|
||||
"home.recovery_empty": "Nincs helyreállítható terv.",
|
||||
"home.recovery_hint": "A SolidCraft a tervek mentése előtt bezárult.",
|
||||
"home.report_issue": "Hiba jelentése",
|
||||
"home.samples": "Minták",
|
||||
"home.settings": "Beállítások",
|
||||
"home.short_date": "{month} {day}.",
|
||||
"home.shortcuts": "Billentyűparancsok",
|
||||
"home.start_hint": "Hozzon létre egy tervet, nyisson meg egy legutóbbi fájlt, vagy fedezzen fel egy mintát.",
|
||||
"home.whats_new": "Újdonságok",
|
||||
"home.yesterday": "Tegnap",
|
||||
"install.button_tip": "A SolidCraft telepítése alkalmazásként: saját ablakban nyílik meg, és offline is működik",
|
||||
"install.dock_1": "Válassza a Fájl › Hozzáadás a Dockhoz lehetőséget.",
|
||||
"install.dock_2": "Kattintson a Hozzáadás gombra.",
|
||||
@@ -1021,7 +1070,7 @@
|
||||
"search.try": "Írja be egy parancs nevét, a terv egy elemét vagy egy paramétert. Parancs értékekkel való futtatásához írja be az azonosítóját és egy JSON-objektumot: {example}",
|
||||
"search.valid": "Érvényes",
|
||||
"settings.display_quality_note": "A finomabb szintek simábban rajzolják az ívelt lapokat és éleket, de tovább tart a hálózás. Csak a nézetablak változik, a terv és az exportok soha.",
|
||||
"settings.frame_rate_note": "Megjeleníti a másodpercenkénti képkockaszámot a nézet jobb alsó sarkában. Bekapcsolt állapotban a nézet folyamatosan újrarajzolódik.",
|
||||
"settings.frame_rate_note": "Megjeleníti a másodpercenkénti képkockaszámot a nézet jobb alsó sarkában.",
|
||||
"settings.measure_axis_distances": "X/Y/Z távolságok megjelenítése",
|
||||
"settings.measure_axis_distances_help": "A világtengely-összetevők méreteit is megjeleníti a teljes távolság mellett, amíg a Mérés nyitva van.",
|
||||
"settings.msaa_applies_on_restart": "Az élsimítás a SolidCraft következő indításakor lép érvénybe",
|
||||
@@ -1082,7 +1131,50 @@
|
||||
"trace.stopped_by_contact": "Vége a(z) {sample}. mintánál: {a} áthatolna ezen: {b}",
|
||||
"trace.stopped_by_loop": "Vége a(z) {sample}. mintánál: a(z) {joint} csuklón átmenő hurok nem zárható tovább",
|
||||
"ui.1_degree_of_freedom": "1 szabadságfok",
|
||||
"ui.3d_mouse_calibrate": "Kalibrálás…",
|
||||
"ui.3d_mouse_calibrate_tip": "Mozgassa a kupakot az ablak kérése szerint, hogy a SolidCraft megtanulja, merre és mennyire mozdul a 3D-egér mindegyik tengelye. Ehhez a 3D-egérhez mentve.",
|
||||
"ui.3d_mouse_calibrate_title": "3D-egér kalibrálása",
|
||||
"ui.3d_mouse_calibrate_wait": "Mozdítsa meg egyszer a 3D-egeret, hogy a SolidCraft lássa.",
|
||||
"ui.3d_mouse_calibrated": "Kalibrálva",
|
||||
"ui.3d_mouse_calibration": "3D-egér kalibrálása",
|
||||
"ui.3d_mouse_calibration_done": "Kész. Mentse, hogy a 3D-egér csatlakoztatásakor mindig használatos legyen.",
|
||||
"ui.3d_mouse_calibration_no_response": "Az egyik tengely alig mozdult, így nem kalibrálható. Kezdje elölről, és minden alkalommal mozgassa a kupakot messzebb.",
|
||||
"ui.3d_mouse_circles_help": "A körök azt mutatják, mit küld az eszköz kalibrálás után. A tesztmodell azt mutatja, mit tesz ezzel a nézet a megfordítás, sebesség és tengelykiosztás után.",
|
||||
"ui.3d_mouse_dead_zone": "Holtsáv",
|
||||
"ui.3d_mouse_dead_zone_tip": "Mennyit kell mozdítani a kupakot, mielőtt a nézet mozdulna, a teljes elmozdulás arányában. Növelje, ha a nézet sodródik, miközben a kupak nyugalomban van.",
|
||||
"ui.3d_mouse_denied": "A SolidCraft látja a 3D-egeret, de nem olvashatja. Az udev-szabályt a 3D-egér beállítási jegyzetei tartalmazzák.",
|
||||
"ui.3d_mouse_denied_short": "Megvan, de nem olvasható",
|
||||
"ui.3d_mouse_driver_fallback": "Egyelőre a spacenavd-n át olvassa: magát az eszközt nem sikerült megnyitni.",
|
||||
"ui.3d_mouse_forget": "Kalibrálás elfelejtése",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Bal nézet",
|
||||
"ui.3d_mouse_ghost_left_window": "Bal ablak",
|
||||
"ui.3d_mouse_ghost_position": "Helyzet",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Jobb nézet",
|
||||
"ui.3d_mouse_hint": "A nézetet együtt mozgatja és forgatja; a vázlatok csak pásztázást és nagyítást használnak.",
|
||||
"ui.3d_mouse_navigate": "Navigálás a 3D-egérrel",
|
||||
"ui.3d_mouse_none": "Nem található 3D-egér",
|
||||
"ui.3d_mouse_not_calibrated": "Nincs kalibrálva",
|
||||
"ui.3d_mouse_offer": "A 3D-egér kalibrálása kijavítja a felcserélt vagy megfordított tengelyeket, és beállítja a sebességét és az érzetét.",
|
||||
"ui.3d_mouse_offer_never": "Ne jelenjen meg többé",
|
||||
"ui.3d_mouse_offer_swap": "Váltás Hidrawra (közvetlen illesztőprogram)",
|
||||
"ui.3d_mouse_reading": "Beolvasás",
|
||||
"ui.3d_mouse_reset_view": "Nézet alaphelyzetbe állítása",
|
||||
"ui.3d_mouse_restore": "3D-egér alapértékeinek visszaállítása",
|
||||
"ui.3d_mouse_restore_ask": "Visszaállítja a 3D-egér beállításait az alapértékekre? A kalibrálás és a gombkiosztás megmarad.",
|
||||
"ui.3d_mouse_speed": "3D-egér sebessége",
|
||||
"ui.3d_mouse_step_lift": "Emelje fel a kupakot, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_press": "Nyomja le a kupakot, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_pull": "Húzza a kupakot maga felé, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_push": "Tolja a kupakot magától el, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_rest": "Vegye le a kezét a kupakról, és hagyja nyugodtan.",
|
||||
"ui.3d_mouse_step_slide_left": "Csúsztassa a kupakot balra, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_slide_right": "Csúsztassa a kupakot jobbra, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_tilt_left": "Billentse balra a kupakot (a bal él lefelé), tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_tilt_right": "Billentse jobbra a kupakot (a jobb él lefelé), tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Csavarja el a kupakot az óramutató járásával megegyezően, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
|
||||
"ui.3d_mouse_waiting": "Mozgásra vár",
|
||||
"ui.3d_mouse_wiggle": "Ha van csatlakoztatva egy, mozgassa meg egy kicsit.",
|
||||
"ui.3d_sketch": "3D vázlat",
|
||||
"ui.activate": "Aktiválás",
|
||||
"ui.active_component": "Aktív komponens",
|
||||
@@ -1092,6 +1184,7 @@
|
||||
"ui.agent_chat_add": "Fájlok csatolása",
|
||||
"ui.agent_chat_add_file": "Válasszon fájlt…",
|
||||
"ui.agent_chat_allow": "Engedélyezés",
|
||||
"ui.agent_chat_allow_once": "Egyszer engedélyezi",
|
||||
"ui.agent_chat_allowed": "Engedélyezve",
|
||||
"ui.agent_chat_answered": "Válasz: {option}",
|
||||
"ui.agent_chat_approval": "Engedélyezi az ügynöknek ezt?",
|
||||
@@ -1143,12 +1236,16 @@
|
||||
"ui.agent_chat_files_left_out": "Egy üzenethez legfeljebb {max} fájl tartozhat; kihagyva: {files}",
|
||||
"ui.agent_chat_files_loading": "Várakozás a fájlok beolvasására",
|
||||
"ui.agent_chat_files_max": "Egy üzenetben legfeljebb {max} fájl lehet",
|
||||
"ui.agent_chat_flatpak_none_found": "Nem található kódolóügynök. A Flatpak homokozó alapértelmezés szerint nem látja a saját mappájába telepített ügynököket, és nincs hálózati hozzáférése. Az nvm-mel vagy a ~/.local/bin mappába telepített Claude Code és ACP-adaptere engedélyezéséhez futtassa ezt a parancsot egy terminálban, majd indítsa újra a SolidCraftot. Hozzáférést ad a felsorolt programokhoz és bejelentkezési fájlokhoz, valamint a hálózathoz:",
|
||||
"ui.agent_chat_import_failed": "{name}: az importálás sikertelen: {error}",
|
||||
"ui.agent_chat_intro": "Csevegjen egy már használt kódolóügynökkel vagy egy választott modellvégponttal. A megnyitott terven a SolidCraft eszközein keresztül dolgozik: a módosításai láthatók közben, és üzenetenként egy lépésben vonhatók vissza. Hogy mit tehet kérdés nélkül, azt a Beállítások > AI menüpontban adhatja meg.",
|
||||
"ui.agent_chat_key_in_env": "kulcs beállítva",
|
||||
"ui.agent_chat_left": "Bal",
|
||||
"ui.agent_chat_limit": "Az ügynök elérte az egy üzenetre eső {limit} módosítás korlátját. Küldjön újabb üzenetet a folytatáshoz.",
|
||||
"ui.agent_chat_minimize": "Összecsukás sávvá",
|
||||
"ui.agent_chat_mode_all": "Minden",
|
||||
"ui.agent_chat_mode_ask": "Kérdez",
|
||||
"ui.agent_chat_mode_edits": "Szerkesztés",
|
||||
"ui.agent_chat_model_tip": "Az ügynök által használt modell. A választás megmarad ennél az ügynöknél.",
|
||||
"ui.agent_chat_new": "Új csevegés",
|
||||
"ui.agent_chat_new_confirm": "Új csevegést indít? A jelenlegi törlődik.",
|
||||
@@ -1161,6 +1258,7 @@
|
||||
"ui.agent_chat_none_found_short": "Nincs ügynök",
|
||||
"ui.agent_chat_not_installed": "Nincs telepítve",
|
||||
"ui.agent_chat_not_signed_in": "nincs bejelentkezve",
|
||||
"ui.agent_chat_open_settings": "Beállítások megnyitása",
|
||||
"ui.agent_chat_own_window": "Saját ablak",
|
||||
"ui.agent_chat_paste_picture": "Kép beillesztése",
|
||||
"ui.agent_chat_permission": "Az ügynök futtatást kér: {title}",
|
||||
@@ -1185,6 +1283,8 @@
|
||||
"ui.agent_chat_send": "Küldés",
|
||||
"ui.agent_chat_send_now": "Küldés most",
|
||||
"ui.agent_chat_send_now_tip": "Az ügynök jelenlegi lépésének leállítása és ennek küldése következőként (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "A SolidCraft a számítógépre telepített programozóügynökkel kommunikál. Válasszon egyet itt: Beállítások › AI.",
|
||||
"ui.agent_chat_setup_title": "Asszisztens beállítása",
|
||||
"ui.agent_chat_side": "Ügynök-csevegés oldala",
|
||||
"ui.agent_chat_signed_in": "bejelentkezve",
|
||||
"ui.agent_chat_state_auth": "Bejelentkezés szükséges",
|
||||
@@ -1201,6 +1301,7 @@
|
||||
"ui.agent_chat_undo_label": "Ügynök-csevegés: {message}",
|
||||
"ui.agent_chat_undone": "(visszavonva)",
|
||||
"ui.agent_chat_view_attached": "Nézet csatolva",
|
||||
"ui.agent_chat_waiting": "Önre vár",
|
||||
"ui.agent_cursor": "Ügynök kurzora",
|
||||
"ui.agent_cursor_speed": "Ügynök kurzorának sebessége",
|
||||
"ui.ai_add": "Hozzáadás",
|
||||
@@ -1258,7 +1359,6 @@
|
||||
"ui.ai_permissions": "Jogosultságok",
|
||||
"ui.ai_preset": "Előbeállítás",
|
||||
"ui.ai_remove": "Eltávolítás",
|
||||
"ui.ai_request_limit": "egy üzenetre eső 24 kérés után leállt; küldjön újabb üzenetet a folytatáshoz",
|
||||
"ui.ai_reset": "Visszaállítás",
|
||||
"ui.ai_settings": "AI-beállítások…",
|
||||
"ui.ai_show_features": "AI-funkciók megjelenítése",
|
||||
@@ -1455,6 +1555,7 @@
|
||||
"ui.grid_follows_view_hint": "Az oldalnézetek a talaj helyett a feléjük néző függőleges síkon mutatják a rácsot",
|
||||
"ui.ground_shadow": "Talajárnyék",
|
||||
"ui.groupSelected": "Kijelöltek csoportosítása",
|
||||
"ui.hatch": "Sraffozás",
|
||||
"ui.heading_add_ins": "BŐVÍTMÉNYEK",
|
||||
"ui.heading_assemble": "ÖSSZESZERELÉS",
|
||||
"ui.heading_box": "DOBOZ",
|
||||
@@ -1528,6 +1629,11 @@
|
||||
"ui.hideOrigin": "Origó elrejtése",
|
||||
"ui.hidePlanes": "Síkok elrejtése",
|
||||
"ui.hideProfile": "Profil elrejtése",
|
||||
"ui.history_back": "Egy lépéssel vissza",
|
||||
"ui.history_below_browser": "Böngésző alatt",
|
||||
"ui.history_first": "Ugrás az elejére",
|
||||
"ui.history_forward": "Egy lépéssel előre",
|
||||
"ui.history_last": "Ugrás a végére",
|
||||
"ui.history_title": "Előzmények",
|
||||
"ui.hole": "Furat",
|
||||
"ui.hole_tap_type": "Furat menettípusa",
|
||||
@@ -1545,6 +1651,8 @@
|
||||
"ui.joints": "Csuklók",
|
||||
"ui.keyboard_shortcuts": "Billentyűparancsok",
|
||||
"ui.language": "Nyelv",
|
||||
"ui.layout_compact": "Kompakt",
|
||||
"ui.layout_floating": "Lebegő",
|
||||
"ui.length_decimals": "Hossz tizedesjegyei",
|
||||
"ui.light": "Világos",
|
||||
"ui.line": "Vonal",
|
||||
@@ -1682,6 +1790,7 @@
|
||||
"ui.reverse_zoom_direction": "Nagyítási irány megfordítása",
|
||||
"ui.revolve": "Forgatás",
|
||||
"ui.ribbon_exit": "Kilépés",
|
||||
"ui.ribbon_labels": "Szöveg megjelenítése a menüszalag ikonjai alatt",
|
||||
"ui.ribbon_pin_footer": "A rögzített parancsok megjelennek a menüszalagon. Egy sor fölé víve rögzíthető vagy feloldható.",
|
||||
"ui.ribbon_workspace": "Munkaterület",
|
||||
"ui.right": "Jobb",
|
||||
@@ -1737,34 +1846,6 @@
|
||||
"ui.smooth_wheel_zoom": "Sima görgőnagyítás",
|
||||
"ui.smooth_wheel_zoom_tip": "Kapcsolja ki az egyes görgőesemények azonnali alkalmazásához, kifutás nélkül",
|
||||
"ui.snap": "Illesztés",
|
||||
"ui.space_mouse_calibrate": "Kalibrálás…",
|
||||
"ui.space_mouse_calibrate_tip": "Mozgassa a kupakot az ablak kérése szerint, hogy a SolidCraft megtanulja, merre és mennyire mozdul a 3D-egér mindegyik tengelye. Ehhez a 3D-egérhez mentve.",
|
||||
"ui.space_mouse_calibrate_title": "3D-egér kalibrálása",
|
||||
"ui.space_mouse_calibrated": "Kalibrálva",
|
||||
"ui.space_mouse_calibration": "3D-egér kalibrálása",
|
||||
"ui.space_mouse_calibration_done": "Kész. Mentse, hogy a 3D-egér csatlakoztatásakor mindig használatos legyen.",
|
||||
"ui.space_mouse_calibration_no_response": "Az egyik tengely alig mozdult, így nem kalibrálható. Kezdje elölről, és minden alkalommal mozgassa a kupakot messzebb.",
|
||||
"ui.space_mouse_dead_zone": "Holtsáv",
|
||||
"ui.space_mouse_dead_zone_tip": "Mennyit kell mozdítani a kupakot, mielőtt a nézet mozdulna, a teljes elmozdulás arányában. Növelje, ha a nézet sodródik, miközben a kupak nyugalomban van.",
|
||||
"ui.space_mouse_forget": "Kalibrálás elfelejtése",
|
||||
"ui.space_mouse_hint": "A nézetet együtt mozgatja és forgatja; a vázlatok csak pásztázást és nagyítást használnak.",
|
||||
"ui.space_mouse_navigate": "Navigálás SpaceNavigator / SpaceMouse eszközzel",
|
||||
"ui.space_mouse_none": "Nem található 3D-egér",
|
||||
"ui.space_mouse_not_calibrated": "Nincs kalibrálva",
|
||||
"ui.space_mouse_reading": "Beolvasás",
|
||||
"ui.space_mouse_step_lift": "Emelje fel a kupakot, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_press": "Nyomja le a kupakot, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_pull": "Húzza a kupakot maga felé, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_push": "Tolja a kupakot magától el, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_rest": "Vegye le a kezét a kupakról, és hagyja nyugodtan.",
|
||||
"ui.space_mouse_step_slide_left": "Csúsztassa a kupakot balra, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_slide_right": "Csúsztassa a kupakot jobbra, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_back": "Billentse hátra a kupakot (az elülső él felfelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_forward": "Billentse előre a kupakot (az elülső él lefelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_left": "Billentse balra a kupakot (a bal él lefelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_tilt_right": "Billentse jobbra a kupakot (a jobb él lefelé), tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Csavarja el a kupakot az óramutató járásával megegyezően, tartsa, majd engedje el.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
|
||||
"ui.spacing": "Térköz",
|
||||
"ui.spacing_2": "2. térköz",
|
||||
"ui.sphere": "Gömb",
|
||||
@@ -1836,6 +1917,7 @@
|
||||
"ui.workspace_design_tabs": "Modell · Felület · Összeszerelés · Beszúrás · Vizsgálat · Kezelés · Nézet",
|
||||
"ui.workspace_drawing_description": "2D rajzok nézetekkel, méretekkel és címblokkokkal",
|
||||
"ui.workspace_footer": "Minden munkaterület ugyanazon a terven, előzményeken és kijelölésen dolgozik; csak a menüszalag lapjai változnak. A váltó zárolva van, amíg vázlatot vagy elemet szerkeszt.",
|
||||
"ui.workspace_layout": "Munkaterület elrendezése",
|
||||
"ui.workspace_manufacture_description": "Szeletelés, szerszámutak és beállítás",
|
||||
"ui.workspace_planned": "Tervezett",
|
||||
"ui.workspace_render_description": "Terméknézet, jelenetbeállítások és renderelt képek",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Pilih…",
|
||||
"Circular": "Melingkar",
|
||||
"Circumference": "Keliling",
|
||||
"Clockwise": "Searah jarum jam",
|
||||
"Close draft": "Tutup draf",
|
||||
"Collisions": "Tabrakan",
|
||||
"Colour": "Warna",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "RIWAYAT",
|
||||
"Height": "Tinggi",
|
||||
"Hidden edges": "Tepi tersembunyi",
|
||||
"History layout": "Tata letak riwayat",
|
||||
"Holes": "Lubang",
|
||||
"Holes (edges on them)": "Lubang (tepi di atasnya)",
|
||||
"Home view": "Tampilan awal",
|
||||
@@ -232,7 +234,6 @@
|
||||
"No crash reports.": "Tidak ada laporan crash.",
|
||||
"No matching appearance presets.": "Tidak ada preset tampilan yang cocok.",
|
||||
"No matching appearances": "Tidak ada tampilan yang cocok",
|
||||
"No visible bodies to export.": "Tidak ada bodi terlihat untuk diekspor.",
|
||||
"None": "Tidak ada",
|
||||
"Not available yet": "Belum tersedia",
|
||||
"Notch": "Takik",
|
||||
@@ -321,6 +322,7 @@
|
||||
"Search commands": "Cari perintah",
|
||||
"Search this library": "Cari di pustaka ini",
|
||||
"Section": "Penampang",
|
||||
"Section position": "Posisi penampang",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Select Other: tahan tombol kiri diam di model (atau klik kanan › Select Other…) untuk mendaftar semua yang ada di bawah kursor; klik lagi di titik yang sama untuk memilih benda berikutnya. Klik dua kali tombol tengah untuk ke Home (Preferensi › Navigasi).",
|
||||
"Select bodies (a face picks its body)": "Pilih bodi (memilih muka berarti memilih bodinya)",
|
||||
"Select bodies or faces instead": "Pilih bodi atau muka saja",
|
||||
@@ -332,6 +334,7 @@
|
||||
"Show Agent Cursor": "Tampilkan Kursor Agen",
|
||||
"Show in folder": "Tampilkan di folder",
|
||||
"Show the pointer an AI agent moves (#35)": "Tampilkan penunjuk yang digerakkan agen AI (#35)",
|
||||
"Show tool at cursor": "Tampilkan alat di dekat kursor",
|
||||
"Signed axes": "Sumbu bertanda",
|
||||
"Size": "Ukuran",
|
||||
"Sketch constraints": "Batasan sketsa",
|
||||
@@ -344,9 +347,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft tertutup sebelum desain ini disimpan. Simpan otomatis terakhirnya dapat dibuka.",
|
||||
"Specialise": "Spesialisasikan",
|
||||
"Speed": "Kecepatan",
|
||||
"Square": "Persegi",
|
||||
"Stand it up": "Tegakkan",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Tegakkan: putaran seperempat terhadap X, sebagai satu fitur Move yang dapat dibatalkan.",
|
||||
"Stand this part up?": "Tegakkan part ini?",
|
||||
"Start angle": "Sudut awal",
|
||||
"Start bulge": "Bulge awal",
|
||||
"Start continuity": "Kontinuitas awal",
|
||||
"Step": "Langkah",
|
||||
@@ -411,6 +416,7 @@
|
||||
"angle, height": "sudut, tinggi",
|
||||
"appearance.dielectric_ior": "IOR Dielektrik",
|
||||
"appearance.metallic": "Metalik",
|
||||
"appearance.only_available": "Hanya tampilan yang tersedia",
|
||||
"appearance.pbr_help": "Lihat permukaan ini di Render atau PNG produk. Tanpa refraksi atau transmisi.",
|
||||
"appearance.pbr_opaque": "PBR Opak",
|
||||
"appearance.roughness": "Kekasaran",
|
||||
@@ -529,6 +535,10 @@
|
||||
"cmd.file_insert_step": "Sisipkan STEP",
|
||||
"cmd.file_save_mesh": "Simpan Sebagai Mesh",
|
||||
"cmd.file_slicers": "Slicer Terpasang",
|
||||
"cmd.frame_member": "Batang Rangka",
|
||||
"cmd.frame_profile_insert": "Sisipkan Profil",
|
||||
"cmd.frame_profile_list": "Pustaka Profil",
|
||||
"cmd.frame_profile_properties": "Properti Profil",
|
||||
"cmd.inspect_accessibility": "Analisis Aksesibilitas",
|
||||
"cmd.inspect_center_of_mass": "Pusat Massa",
|
||||
"cmd.inspect_clearance": "Kelonggaran",
|
||||
@@ -568,6 +578,7 @@
|
||||
"cmd.material_create": "Material Fisik Baru",
|
||||
"cmd.material_delete": "Hapus Material Fisik",
|
||||
"cmd.material_edit": "Edit Material Fisik",
|
||||
"cmd.material_inspect": "Periksa Material Fisik",
|
||||
"cmd.material_list": "Daftar Material",
|
||||
"cmd.material_properties_directional": "Modulus Elastis Arah",
|
||||
"cmd.material_properties_validate": "Validasi Data Material Fisik",
|
||||
@@ -856,19 +867,57 @@
|
||||
"help.mit": "Lisensi MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Lisensi pihak ketiga",
|
||||
"home.back": "Kembali ke {name}",
|
||||
"home.crashes": "Laporan kerusakan",
|
||||
"home.design_workspace": "Desain",
|
||||
"home.documentation": "Dokumentasi",
|
||||
"home.export": "Ekspor",
|
||||
"home.export_hint": "Pilih format untuk desain saat ini.",
|
||||
"home.export_json_hint": "Desain yang dapat diedit dan riwayat perintah",
|
||||
"home.export_mesh_hint": "Mesh untuk pencetakan atau berbagi",
|
||||
"home.export_step_hint": "Geometri benda padat yang presisi",
|
||||
"home.import": "Impor…",
|
||||
"home.import_hint": "STEP, IGES, atau mesh ke dalam desain baru",
|
||||
"home.in_this_browser": "di peramban ini",
|
||||
"home.missing": "hilang",
|
||||
"home.month_01": "Jan",
|
||||
"home.month_02": "Feb",
|
||||
"home.month_03": "Mar",
|
||||
"home.month_04": "Apr",
|
||||
"home.month_05": "Mei",
|
||||
"home.month_06": "Jun",
|
||||
"home.month_07": "Jul",
|
||||
"home.month_08": "Agu",
|
||||
"home.month_09": "Sep",
|
||||
"home.month_10": "Okt",
|
||||
"home.month_11": "Nov",
|
||||
"home.month_12": "Des",
|
||||
"home.new_inches": "Desain baru dalam inci",
|
||||
"home.new_inches_hint": "Inci · terbuka di Desain",
|
||||
"home.new_mm": "Desain baru",
|
||||
"home.new_mm_hint": "Milimeter · terbuka di Desain",
|
||||
"home.open": "Buka…",
|
||||
"home.opened_days_ago": "Dibuka {count} hari lalu",
|
||||
"home.opened_hours_ago": "Dibuka {count} jam lalu",
|
||||
"home.opened_just_now": "Baru saja dibuka",
|
||||
"home.opened_minutes_ago": "Dibuka {count} mnt lalu",
|
||||
"home.open_hint": "Desain .solidcraft ({shortcut})",
|
||||
"home.open_now": "Sedang terbuka",
|
||||
"home.opened_days_ago": "{count} hari lalu",
|
||||
"home.opened_hours_ago": "{count} jam lalu",
|
||||
"home.opened_just_now": "Baru saja",
|
||||
"home.opened_minutes_ago": "{count} mnt lalu",
|
||||
"home.opening": "membuka…",
|
||||
"home.properties": "Properti desain",
|
||||
"home.recent": "Terbaru",
|
||||
"home.recent_empty": "Desain yang Anda buka atau simpan akan muncul di sini.",
|
||||
"home.recovery_count": "Desain yang dapat dipulihkan: {count}",
|
||||
"home.recovery_empty": "Tidak ada desain yang dapat dipulihkan.",
|
||||
"home.recovery_hint": "SolidCraft ditutup sebelum desain ini disimpan.",
|
||||
"home.report_issue": "Laporkan Masalah",
|
||||
"home.samples": "Contoh",
|
||||
"home.settings": "Pengaturan",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Pintasan papan ketik",
|
||||
"home.start_hint": "Buat desain, buka berkas terbaru, atau jelajahi contoh.",
|
||||
"home.whats_new": "Yang baru",
|
||||
"home.yesterday": "Kemarin",
|
||||
"install.button_tip": "Pasang SolidCraft sebagai aplikasi: terbuka di jendelanya sendiri dan berfungsi offline",
|
||||
"install.dock_1": "Pilih File › Add to Dock.",
|
||||
"install.dock_2": "Klik Add.",
|
||||
@@ -1021,7 +1070,7 @@
|
||||
"search.try": "Ketik nama perintah, fitur di desain ini, atau parameter. Untuk menjalankan perintah dengan nilai, ketik id-nya dan objek JSON: {example}",
|
||||
"search.valid": "Valid",
|
||||
"settings.display_quality_note": "Level lebih halus menggambar muka dan tepi lengkung lebih mulus dan butuh waktu meshing lebih lama. Hanya viewport yang berubah, tidak pernah desain atau ekspor.",
|
||||
"settings.frame_rate_note": "Menampilkan frame per detik di sudut kanan bawah tampilan. Saat aktif, tampilan digambar ulang terus-menerus.",
|
||||
"settings.frame_rate_note": "Menampilkan frame per detik di sudut kanan bawah tampilan.",
|
||||
"settings.measure_axis_distances": "Tampilkan jarak X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Tampilkan dimensi komponen sumbu dunia di samping jarak total saat Ukur terbuka.",
|
||||
"settings.msaa_applies_on_restart": "Anti-aliasing berlaku saat SolidCraft dimulai berikutnya",
|
||||
@@ -1082,7 +1131,50 @@
|
||||
"trace.stopped_by_contact": "Berakhir di sampel {sample}: {a} akan menembus {b}",
|
||||
"trace.stopped_by_loop": "Berakhir di sampel {sample}: loop melalui {joint} tidak dapat menutup lebih jauh",
|
||||
"ui.1_degree_of_freedom": "1 derajat kebebasan",
|
||||
"ui.3d_mouse_calibrate": "Kalibrasi…",
|
||||
"ui.3d_mouse_calibrate_tip": "Gerakkan cap sesuai permintaan jendela, agar SolidCraft mempelajari arah dan jarak setiap sumbu mouse 3D ini. Disimpan untuk mouse 3D ini.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibrasi mouse 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Gerakkan mouse 3D sekali agar SolidCraft dapat melihatnya.",
|
||||
"ui.3d_mouse_calibrated": "Terkalibrasi",
|
||||
"ui.3d_mouse_calibration": "Kalibrasi mouse 3D",
|
||||
"ui.3d_mouse_calibration_done": "Selesai. Simpan untuk dipakai setiap kali mouse 3D ini terhubung.",
|
||||
"ui.3d_mouse_calibration_no_response": "Satu sumbu hampir tidak bergerak, sehingga tidak dapat dikalibrasi. Mulai ulang dan gerakkan cap lebih jauh setiap kali.",
|
||||
"ui.3d_mouse_circles_help": "Lingkaran menunjukkan apa yang dikirim perangkat setelah kalibrasi. Model uji menunjukkan apa yang dilakukan tampilan dengannya, setelah pembalikan, kecepatan, dan penetapan sumbu.",
|
||||
"ui.3d_mouse_dead_zone": "Zona mati",
|
||||
"ui.3d_mouse_dead_zone_tip": "Seberapa jauh cap harus bergerak sebelum tampilan bergerak, sebagai bagian dari total jangkauannya. Naikkan jika tampilan bergeser saat cap diam.",
|
||||
"ui.3d_mouse_denied": "SolidCraft melihat mouse 3D tetapi tidak diizinkan membacanya. Lihat catatan penyiapan mouse 3D untuk aturan udev.",
|
||||
"ui.3d_mouse_denied_short": "Ditemukan, tetapi tidak bisa dibaca",
|
||||
"ui.3d_mouse_driver_fallback": "Sementara dibaca lewat spacenavd: perangkatnya sendiri tidak dapat dibuka.",
|
||||
"ui.3d_mouse_forget": "Lupakan kalibrasi",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport kiri",
|
||||
"ui.3d_mouse_ghost_left_window": "Jendela kiri",
|
||||
"ui.3d_mouse_ghost_position": "Posisi",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport kanan",
|
||||
"ui.3d_mouse_hint": "Gerakkan dan putar tampilan bersama; sketsa hanya memakai pan dan zoom.",
|
||||
"ui.3d_mouse_navigate": "Navigasi dengan mouse 3D",
|
||||
"ui.3d_mouse_none": "Mouse 3D tidak ditemukan",
|
||||
"ui.3d_mouse_not_calibrated": "Belum dikalibrasi",
|
||||
"ui.3d_mouse_offer": "Mengkalibrasi mouse 3D memperbaiki sumbu yang tertukar atau terbalik dan mengatur kecepatan serta rasanya.",
|
||||
"ui.3d_mouse_offer_never": "Jangan tampilkan lagi",
|
||||
"ui.3d_mouse_offer_swap": "Beralih ke Hidraw (driver langsung)",
|
||||
"ui.3d_mouse_reading": "Pembacaan",
|
||||
"ui.3d_mouse_reset_view": "Atur ulang tampilan",
|
||||
"ui.3d_mouse_restore": "Pulihkan bawaan mouse 3D",
|
||||
"ui.3d_mouse_restore_ask": "Kembalikan pengaturan mouse 3D ke bawaan? Kalibrasi dan pemetaan tombol tetap ada.",
|
||||
"ui.3d_mouse_speed": "Kecepatan mouse 3D",
|
||||
"ui.3d_mouse_step_lift": "Angkat cap ke atas, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_press": "Tekan cap ke bawah, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_pull": "Tarik cap ke arah Anda, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_push": "Dorong cap menjauh dari Anda, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_rest": "Lepaskan tangan dari cap dan biarkan diam.",
|
||||
"ui.3d_mouse_step_slide_left": "Geser cap ke kiri, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_slide_right": "Geser cap ke kanan, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_left": "Miringkan cap ke kiri (tepi kiri turun), tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_right": "Miringkan cap ke kanan (tepi kanan turun), tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Putar cap searah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.3d_mouse_waiting": "Menunggu gerakan",
|
||||
"ui.3d_mouse_wiggle": "Jika Anda memasang satu, goyangkan sedikit.",
|
||||
"ui.3d_sketch": "Sketsa 3D",
|
||||
"ui.activate": "Aktifkan",
|
||||
"ui.active_component": "Komponen aktif",
|
||||
@@ -1092,6 +1184,7 @@
|
||||
"ui.agent_chat_add": "Lampirkan file",
|
||||
"ui.agent_chat_add_file": "Pilih file…",
|
||||
"ui.agent_chat_allow": "Izinkan",
|
||||
"ui.agent_chat_allow_once": "Izinkan sekali",
|
||||
"ui.agent_chat_allowed": "Diizinkan",
|
||||
"ui.agent_chat_answered": "Jawaban: {option}",
|
||||
"ui.agent_chat_approval": "Izinkan agen melakukan ini?",
|
||||
@@ -1143,12 +1236,16 @@
|
||||
"ui.agent_chat_files_left_out": "Maksimal {max} file per pesan; tidak disertakan: {files}",
|
||||
"ui.agent_chat_files_loading": "Menunggu file selesai dibaca",
|
||||
"ui.agent_chat_files_max": "Maksimal {max} file per pesan",
|
||||
"ui.agent_chat_flatpak_none_found": "Agen coding tidak ditemukan. Secara bawaan, sandbox Flatpak tidak dapat melihat agen yang terpasang di folder rumah Anda dan tidak memiliki akses jaringan. Untuk mengizinkan Claude Code dan adaptor ACP-nya yang dipasang melalui nvm atau di ~/.local/bin, jalankan perintah ini di terminal, lalu mulai ulang SolidCraft. Perintah ini memberi akses ke program dan berkas masuk yang disebutkan, serta akses jaringan:",
|
||||
"ui.agent_chat_import_failed": "{name}: impor gagal: {error}",
|
||||
"ui.agent_chat_intro": "Mengobrol dengan agen coding yang sudah Anda pakai, atau endpoint model pilihan Anda. Agen bekerja pada desain yang terbuka lewat alat SolidCraft: perubahannya tampil saat dibuat dan dapat diurungkan satu langkah per pesan. Yang boleh dilakukannya tanpa bertanya diatur di Preferensi > AI.",
|
||||
"ui.agent_chat_key_in_env": "key diatur",
|
||||
"ui.agent_chat_left": "Kiri",
|
||||
"ui.agent_chat_limit": "Agen mencapai batas {limit} perubahan untuk satu pesan. Kirim pesan lain agar agen melanjutkan.",
|
||||
"ui.agent_chat_minimize": "Kecilkan menjadi strip",
|
||||
"ui.agent_chat_mode_all": "Semua",
|
||||
"ui.agent_chat_mode_ask": "Tanya",
|
||||
"ui.agent_chat_mode_edits": "Edit",
|
||||
"ui.agent_chat_model_tip": "Model yang dipakai agen. Pilihan Anda diingat untuk agen ini.",
|
||||
"ui.agent_chat_new": "Chat baru",
|
||||
"ui.agent_chat_new_confirm": "Mulai chat baru? Chat saat ini akan dibersihkan.",
|
||||
@@ -1161,6 +1258,7 @@
|
||||
"ui.agent_chat_none_found_short": "Agen tidak ditemukan",
|
||||
"ui.agent_chat_not_installed": "Belum terpasang",
|
||||
"ui.agent_chat_not_signed_in": "belum masuk",
|
||||
"ui.agent_chat_open_settings": "Buka preferensi",
|
||||
"ui.agent_chat_own_window": "Jendela sendiri",
|
||||
"ui.agent_chat_paste_picture": "Tempel gambar",
|
||||
"ui.agent_chat_permission": "Agen meminta menjalankan: {title}",
|
||||
@@ -1185,6 +1283,8 @@
|
||||
"ui.agent_chat_send": "Kirim",
|
||||
"ui.agent_chat_send_now": "Kirim sekarang",
|
||||
"ui.agent_chat_send_now_tip": "Hentikan langkah agen saat ini dan kirim ini berikutnya (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft berkomunikasi dengan agen pemrograman yang terpasang di komputer ini. Pilih agen di Preferensi › AI.",
|
||||
"ui.agent_chat_setup_title": "Siapkan asisten",
|
||||
"ui.agent_chat_side": "Sisi chat agen",
|
||||
"ui.agent_chat_signed_in": "sudah masuk",
|
||||
"ui.agent_chat_state_auth": "Perlu masuk",
|
||||
@@ -1201,6 +1301,7 @@
|
||||
"ui.agent_chat_undo_label": "Chat agen: {message}",
|
||||
"ui.agent_chat_undone": "(diurungkan)",
|
||||
"ui.agent_chat_view_attached": "Tampilan dilampirkan",
|
||||
"ui.agent_chat_waiting": "Menunggu Anda",
|
||||
"ui.agent_cursor": "Kursor agen",
|
||||
"ui.agent_cursor_speed": "Kecepatan kursor agen",
|
||||
"ui.ai_add": "Tambah",
|
||||
@@ -1258,7 +1359,6 @@
|
||||
"ui.ai_permissions": "Izin",
|
||||
"ui.ai_preset": "Preset",
|
||||
"ui.ai_remove": "Hapus",
|
||||
"ui.ai_request_limit": "dihentikan setelah 24 permintaan untuk satu pesan; kirim pesan lain untuk melanjutkan",
|
||||
"ui.ai_reset": "Reset",
|
||||
"ui.ai_settings": "Pengaturan AI…",
|
||||
"ui.ai_show_features": "Tampilkan fitur AI",
|
||||
@@ -1455,6 +1555,7 @@
|
||||
"ui.grid_follows_view_hint": "Tampilan samping menampilkan grid pada bidang tegak yang menghadapnya, bukan pada dasar",
|
||||
"ui.ground_shadow": "Bayangan dasar",
|
||||
"ui.groupSelected": "Kelompokkan yang Dipilih",
|
||||
"ui.hatch": "Arsir",
|
||||
"ui.heading_add_ins": "ADD-IN",
|
||||
"ui.heading_assemble": "RAKIT",
|
||||
"ui.heading_box": "KOTAK",
|
||||
@@ -1528,6 +1629,11 @@
|
||||
"ui.hideOrigin": "Sembunyikan Origin",
|
||||
"ui.hidePlanes": "Sembunyikan Bidang",
|
||||
"ui.hideProfile": "Sembunyikan Profil",
|
||||
"ui.history_back": "Mundur satu langkah",
|
||||
"ui.history_below_browser": "Di bawah browser",
|
||||
"ui.history_first": "Ke awal",
|
||||
"ui.history_forward": "Maju satu langkah",
|
||||
"ui.history_last": "Ke akhir",
|
||||
"ui.history_title": "Riwayat",
|
||||
"ui.hole": "Lubang",
|
||||
"ui.hole_tap_type": "Tipe Tap Lubang",
|
||||
@@ -1545,6 +1651,8 @@
|
||||
"ui.joints": "Sambungan",
|
||||
"ui.keyboard_shortcuts": "Pintasan Keyboard",
|
||||
"ui.language": "Bahasa",
|
||||
"ui.layout_compact": "Ringkas",
|
||||
"ui.layout_floating": "Mengambang",
|
||||
"ui.length_decimals": "Desimal panjang",
|
||||
"ui.light": "Terang",
|
||||
"ui.line": "Garis",
|
||||
@@ -1682,6 +1790,7 @@
|
||||
"ui.reverse_zoom_direction": "Balik arah zoom",
|
||||
"ui.revolve": "Revolve",
|
||||
"ui.ribbon_exit": "Keluar",
|
||||
"ui.ribbon_labels": "Tampilkan teks di bawah ikon pita",
|
||||
"ui.ribbon_pin_footer": "Perintah yang disematkan tampil di ribbon. Arahkan kursor ke baris untuk menyematkan atau melepas sematan.",
|
||||
"ui.ribbon_workspace": "Workspace",
|
||||
"ui.right": "Kanan",
|
||||
@@ -1737,34 +1846,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom roda halus",
|
||||
"ui.smooth_wheel_zoom_tip": "Matikan untuk menerapkan setiap peristiwa roda segera, tanpa ekor penghalusan",
|
||||
"ui.snap": "Snap",
|
||||
"ui.space_mouse_calibrate": "Kalibrasi…",
|
||||
"ui.space_mouse_calibrate_tip": "Gerakkan cap sesuai permintaan jendela, agar SolidCraft mempelajari arah dan jarak setiap sumbu mouse 3D ini. Disimpan untuk mouse 3D ini.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibrasi mouse 3D",
|
||||
"ui.space_mouse_calibrated": "Terkalibrasi",
|
||||
"ui.space_mouse_calibration": "Kalibrasi mouse 3D",
|
||||
"ui.space_mouse_calibration_done": "Selesai. Simpan untuk dipakai setiap kali mouse 3D ini terhubung.",
|
||||
"ui.space_mouse_calibration_no_response": "Satu sumbu hampir tidak bergerak, sehingga tidak dapat dikalibrasi. Mulai ulang dan gerakkan cap lebih jauh setiap kali.",
|
||||
"ui.space_mouse_dead_zone": "Zona mati",
|
||||
"ui.space_mouse_dead_zone_tip": "Seberapa jauh cap harus bergerak sebelum tampilan bergerak, sebagai bagian dari total jangkauannya. Naikkan jika tampilan bergeser saat cap diam.",
|
||||
"ui.space_mouse_forget": "Lupakan kalibrasi",
|
||||
"ui.space_mouse_hint": "Gerakkan dan putar tampilan bersama; sketsa hanya memakai pan dan zoom.",
|
||||
"ui.space_mouse_navigate": "Navigasi dengan SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Mouse 3D tidak ditemukan",
|
||||
"ui.space_mouse_not_calibrated": "Belum dikalibrasi",
|
||||
"ui.space_mouse_reading": "Pembacaan",
|
||||
"ui.space_mouse_step_lift": "Angkat cap ke atas, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_press": "Tekan cap ke bawah, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_pull": "Tarik cap ke arah Anda, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_push": "Dorong cap menjauh dari Anda, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_rest": "Lepaskan tangan dari cap dan biarkan diam.",
|
||||
"ui.space_mouse_step_slide_left": "Geser cap ke kiri, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_slide_right": "Geser cap ke kanan, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_back": "Miringkan cap ke belakang (tepi depan naik), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_forward": "Miringkan cap ke depan (tepi depan turun), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_left": "Miringkan cap ke kiri (tepi kiri turun), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_tilt_right": "Miringkan cap ke kanan (tepi kanan turun), tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Putar cap searah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
|
||||
"ui.spacing": "Jarak antar",
|
||||
"ui.spacing_2": "Jarak antar 2",
|
||||
"ui.sphere": "Bola",
|
||||
@@ -1836,6 +1917,7 @@
|
||||
"ui.workspace_design_tabs": "Model · Permukaan · Rakit · Sisipkan · Inspeksi · Kelola · Tampilan",
|
||||
"ui.workspace_drawing_description": "Gambar kerja 2D dengan tampilan, dimensi, dan title block",
|
||||
"ui.workspace_footer": "Setiap workspace bekerja pada desain, riwayat, dan pilihan yang sama; hanya tab ribbon yang berubah. Pengalih terkunci saat sketsa atau fitur sedang diedit.",
|
||||
"ui.workspace_layout": "Tata letak ruang kerja",
|
||||
"ui.workspace_manufacture_description": "Slicing, toolpath, dan setup",
|
||||
"ui.workspace_planned": "Direncanakan",
|
||||
"ui.workspace_render_description": "Pratinjau produk, pengaturan scene, dan gambar render",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Scegli…",
|
||||
"Circular": "Circolare",
|
||||
"Circumference": "Circonferenza",
|
||||
"Clockwise": "Orario",
|
||||
"Close draft": "Chiudi bozza",
|
||||
"Collisions": "Collisioni",
|
||||
"Colour": "Colore",
|
||||
@@ -102,6 +103,7 @@
|
||||
"Curv. Comb": "Pettine curv.",
|
||||
"Curvature (G2)": "Curvatura (G2)",
|
||||
"Curvature change (G3)": "Variazione di curvatura (G3)",
|
||||
"Custom": "Personalizzato",
|
||||
"Customize Toolbar": "Personalizza barra degli strumenti",
|
||||
"Delete": "Elimina",
|
||||
"Delete all": "Elimina tutto",
|
||||
@@ -162,11 +164,13 @@
|
||||
"From Bodies": "Dai corpi",
|
||||
"From edges": "Spigoli di partenza",
|
||||
"Full Length": "Lunghezza totale",
|
||||
"Full thread depth": "Profondità filetto completa",
|
||||
"Gap": "Gioco",
|
||||
"Grid": "Griglia",
|
||||
"HISTORY": "CRONOLOGIA",
|
||||
"Height": "Altezza",
|
||||
"Hidden edges": "Spigoli nascosti",
|
||||
"History layout": "Disposizione cronologia",
|
||||
"Holes": "Fori",
|
||||
"Holes (edges on them)": "Fori (spigoli dei fori)",
|
||||
"Home view": "Vista iniziale",
|
||||
@@ -232,7 +236,8 @@
|
||||
"No crash reports.": "Nessun rapporto di arresto anomalo.",
|
||||
"No matching appearance presets.": "Nessun predefinito di aspetto corrispondente.",
|
||||
"No matching appearances": "Nessun aspetto corrispondente",
|
||||
"No visible bodies to export.": "Nessun corpo visibile da esportare.",
|
||||
"No visible bodies or sketches to export.": "Nessun corpo o schizzo visibile da esportare.",
|
||||
"Nominal diameter": "Diametro nominale",
|
||||
"None": "Nessuno",
|
||||
"Not available yet": "Non ancora disponibile",
|
||||
"Notch": "Intaglio",
|
||||
@@ -256,6 +261,7 @@
|
||||
"Pick the tool bodies": "Seleziona i corpi utensile",
|
||||
"Picked": "Selezionato",
|
||||
"Picked point": "Punto selezionato",
|
||||
"Pitch": "Passo",
|
||||
"Place at the origin instead": "Posiziona invece nell'origine",
|
||||
"Plastic Rules": "Regole per plastica",
|
||||
"Playback": "Riproduzione",
|
||||
@@ -321,6 +327,7 @@
|
||||
"Search commands": "Cerca comandi",
|
||||
"Search this library": "Cerca in questa libreria",
|
||||
"Section": "Sezione",
|
||||
"Section position": "Posizione della sezione",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleziona altro: tieni premuto il pulsante sinistro senza muovere il puntatore sul modello (o fai clic destro › Seleziona altro…) per elencare gli oggetti sotto il cursore; fai di nuovo clic nello stesso punto per selezionare l'oggetto successivo. Fai doppio clic con il pulsante centrale per tornare alla vista iniziale (Preferenze › Navigazione).",
|
||||
"Select bodies (a face picks its body)": "Seleziona corpi (una faccia seleziona il corpo a cui appartiene)",
|
||||
"Select bodies or faces instead": "Seleziona invece corpi o facce",
|
||||
@@ -332,6 +339,7 @@
|
||||
"Show Agent Cursor": "Mostra cursore dell'agente",
|
||||
"Show in folder": "Mostra nella cartella",
|
||||
"Show the pointer an AI agent moves (#35)": "Mostra il puntatore mosso da un agente IA (#35)",
|
||||
"Show tool at cursor": "Mostra lo strumento accanto al cursore",
|
||||
"Signed axes": "Assi con segno",
|
||||
"Size": "Dimensioni",
|
||||
"Sketch constraints": "Vincoli dello schizzo",
|
||||
@@ -344,9 +352,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft si è chiuso prima del salvataggio di questi progetti. È possibile aprire il loro ultimo salvataggio automatico.",
|
||||
"Specialise": "Specializza",
|
||||
"Speed": "Velocità",
|
||||
"Square": "Quadrato",
|
||||
"Stand it up": "Raddrizza",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Raddrizza: un quarto di giro attorno a X, come singola lavorazione Sposta annullabile.",
|
||||
"Stand this part up?": "Raddrizzare questa parte?",
|
||||
"Start angle": "Angolo iniziale",
|
||||
"Start bulge": "Rigonfiamento iniziale",
|
||||
"Start continuity": "Continuità iniziale",
|
||||
"Step": "Passo",
|
||||
@@ -373,6 +383,7 @@
|
||||
"This child is shared. Deleting it removes it from every copy of its parent.": "Questo elemento figlio è condiviso. Eliminandolo verrà rimosso da ogni copia del padre.",
|
||||
"This child is shared. Position changes apply to every copy of its parent.": "Questo elemento figlio è condiviso. Le modifiche alla posizione si applicano a ogni copia del padre.",
|
||||
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "Questa faccia non consente un collegamento persistente. Scegli un'altra faccia o usa esplicitamente un piano fisso.",
|
||||
"Thread depth": "Profondità filetto",
|
||||
"To Offset": "Fino a una distanza",
|
||||
"To edges": "Spigoli di arrivo",
|
||||
"Tolerance": "Tolleranza",
|
||||
@@ -411,6 +422,7 @@
|
||||
"angle, height": "angolo, altezza",
|
||||
"appearance.dielectric_ior": "Indice di rifrazione dielettrico",
|
||||
"appearance.metallic": "Metallicità",
|
||||
"appearance.only_available": "Solo aspetti disponibili",
|
||||
"appearance.pbr_help": "Visualizza questa superficie in Rendering o in un PNG del prodotto. Senza rifrazione né trasmissione.",
|
||||
"appearance.pbr_opaque": "PBR opaco",
|
||||
"appearance.roughness": "Rugosità",
|
||||
@@ -529,6 +541,10 @@
|
||||
"cmd.file_insert_step": "Inserisci STEP",
|
||||
"cmd.file_save_mesh": "Salva come mesh",
|
||||
"cmd.file_slicers": "Slicer installati",
|
||||
"cmd.frame_member": "Membro strutturale",
|
||||
"cmd.frame_profile_insert": "Inserisci profilo",
|
||||
"cmd.frame_profile_list": "Libreria profili",
|
||||
"cmd.frame_profile_properties": "Proprietà del profilo",
|
||||
"cmd.inspect_accessibility": "Analisi di accessibilità",
|
||||
"cmd.inspect_center_of_mass": "Centro di massa",
|
||||
"cmd.inspect_clearance": "Distanza libera",
|
||||
@@ -568,6 +584,7 @@
|
||||
"cmd.material_create": "Nuovo materiale fisico",
|
||||
"cmd.material_delete": "Elimina materiale fisico",
|
||||
"cmd.material_edit": "Modifica materiale fisico",
|
||||
"cmd.material_inspect": "Ispeziona materiale fisico",
|
||||
"cmd.material_list": "Elenca materiali",
|
||||
"cmd.material_properties_directional": "Modulo elastico direzionale",
|
||||
"cmd.material_properties_validate": "Convalida dati del materiale fisico",
|
||||
@@ -831,7 +848,7 @@
|
||||
"dxf.progress.projection": "Proiezione della geometria (3/5)",
|
||||
"dxf.progress.write": "Scrittura DXF (5/5)",
|
||||
"dxf.tolerance": "Tolleranza delle curve",
|
||||
"dxf.tolerance_help": "Solo corpi visibili. Le curve usano segmenti rettilinei. Una tolleranza minore produce curve più precise e richiede più tempo e memoria.",
|
||||
"dxf.tolerance_help": "Solo corpi visibili e schizzi mostrati. Le curve usano segmenti rettilinei. Una tolleranza minore produce curve più precise e richiede più tempo e memoria.",
|
||||
"file_load.built": "Costruite finora: {built} di {total} feature.",
|
||||
"file_load.converting": "Conversione del solido {done} di {total}",
|
||||
"file_load.inserting": "Inserimento della geometria",
|
||||
@@ -856,19 +873,57 @@
|
||||
"help.mit": "Licenza MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Licenze di terze parti",
|
||||
"home.back": "Torna a {name}",
|
||||
"home.crashes": "Rapporti di arresto anomalo",
|
||||
"home.design_workspace": "Progettazione",
|
||||
"home.documentation": "Documentazione",
|
||||
"home.export": "Esporta",
|
||||
"home.export_hint": "Scegli un formato per il progetto attuale.",
|
||||
"home.export_json_hint": "Progetto modificabile e cronologia dei comandi",
|
||||
"home.export_mesh_hint": "Mesh per la stampa o la condivisione",
|
||||
"home.export_step_hint": "Geometria solida precisa",
|
||||
"home.import": "Importa…",
|
||||
"home.import_hint": "STEP, IGES o mesh in un nuovo progetto",
|
||||
"home.in_this_browser": "in questo browser",
|
||||
"home.missing": "non presente",
|
||||
"home.month_01": "gen",
|
||||
"home.month_02": "feb",
|
||||
"home.month_03": "mar",
|
||||
"home.month_04": "apr",
|
||||
"home.month_05": "mag",
|
||||
"home.month_06": "giu",
|
||||
"home.month_07": "lug",
|
||||
"home.month_08": "ago",
|
||||
"home.month_09": "set",
|
||||
"home.month_10": "ott",
|
||||
"home.month_11": "nov",
|
||||
"home.month_12": "dic",
|
||||
"home.new_inches": "Nuovo progetto in pollici",
|
||||
"home.new_inches_hint": "Pollici · si apre in Progettazione",
|
||||
"home.new_mm": "Nuovo progetto",
|
||||
"home.new_mm_hint": "Millimetri · si apre in Progettazione",
|
||||
"home.open": "Apri…",
|
||||
"home.opened_days_ago": "Aperto {count} giorni fa",
|
||||
"home.opened_hours_ago": "Aperto {count} h fa",
|
||||
"home.opened_just_now": "Appena aperto",
|
||||
"home.opened_minutes_ago": "Aperto {count} min fa",
|
||||
"home.open_hint": "Un progetto .solidcraft ({shortcut})",
|
||||
"home.open_now": "Apri ora",
|
||||
"home.opened_days_ago": "{count} giorni fa",
|
||||
"home.opened_hours_ago": "{count} h fa",
|
||||
"home.opened_just_now": "Adesso",
|
||||
"home.opened_minutes_ago": "{count} min fa",
|
||||
"home.opening": "apertura…",
|
||||
"home.properties": "Proprietà del progetto",
|
||||
"home.recent": "Recenti",
|
||||
"home.recent_empty": "I progetti che apri o salvi vengono visualizzati qui.",
|
||||
"home.recovery_count": "Progetti recuperabili: {count}",
|
||||
"home.recovery_empty": "Nessun progetto recuperabile.",
|
||||
"home.recovery_hint": "SolidCraft si è chiuso prima del salvataggio di questi progetti.",
|
||||
"home.report_issue": "Segnala un problema",
|
||||
"home.samples": "Esempi",
|
||||
"home.settings": "Impostazioni",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Scorciatoie da tastiera",
|
||||
"home.start_hint": "Crea un progetto, apri un file recente o esplora un esempio.",
|
||||
"home.whats_new": "Novità",
|
||||
"home.yesterday": "Ieri",
|
||||
"install.button_tip": "Installa SolidCraft come applicazione: si apre in una finestra dedicata e funziona offline",
|
||||
"install.dock_1": "Scegli File › Aggiungi al Dock.",
|
||||
"install.dock_2": "Fai clic su Aggiungi.",
|
||||
@@ -946,6 +1001,16 @@
|
||||
"pattern.direction_1": "Direzione 1",
|
||||
"pattern.direction_2": "Direzione 2",
|
||||
"pattern.flip_direction": "Inverti direzione",
|
||||
"perf.click_for_details": "Fai clic per i dettagli: grafico dei tempi, istogramma e ciò che la GPU ha disegnato",
|
||||
"perf.draw_calls": "Chiamate di disegno",
|
||||
"perf.line_segments": "Segmenti",
|
||||
"perf.low_1": "1% più lento",
|
||||
"perf.renderer": "Renderer",
|
||||
"perf.scene_build": "Costruzione scena",
|
||||
"perf.triangles": "Triangoli",
|
||||
"perf.ui_time": "Tempo UI",
|
||||
"perf.viewport": "Vista",
|
||||
"perf.worst": "peggiore",
|
||||
"pick a hole to seat it, or OK places it at the origin": "seleziona un foro in cui inserirlo, oppure premi OK per posizionarlo nell'origine",
|
||||
"pick one or two vertices, edges or faces": "seleziona uno o due vertici, spigoli o facce",
|
||||
"prefs.cpu": "CPU (software)",
|
||||
@@ -1021,7 +1086,8 @@
|
||||
"search.try": "Digita il nome di un comando, una feature di questo progetto o un parametro. Per eseguire un comando con dei valori, digita il suo id e un oggetto JSON: {example}",
|
||||
"search.valid": "Valido",
|
||||
"settings.display_quality_note": "I livelli più fini rendono più uniformi le facce curve e gli spigoli e richiedono più tempo per la mesh. Cambia solo la vista, mai il progetto o le esportazioni.",
|
||||
"settings.frame_rate_note": "Mostra i fotogrammi al secondo nell'angolo inferiore destro della vista. Quando è attivo, la vista viene ridisegnata continuamente.",
|
||||
"settings.frame_rate_details_note": "Aggiunge un grafico dei tempi degli ultimi fotogrammi con i triangoli disegnati sopra, un istogramma dei tempi dei fotogrammi e ciò che la GPU ha disegnato. Fai clic sulla frequenza nella vista per mostrarli o nasconderli.",
|
||||
"settings.frame_rate_note": "Mostra i fotogrammi al secondo nell'angolo inferiore destro della vista.",
|
||||
"settings.measure_axis_distances": "Mostra distanze X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Mostra le componenti lungo gli assi globali accanto alla distanza totale quando Misura è aperto.",
|
||||
"settings.msaa_applies_on_restart": "L'antialiasing verrà applicato al prossimo avvio di SolidCraft",
|
||||
@@ -1082,7 +1148,106 @@
|
||||
"trace.stopped_by_contact": "Termina al campione {sample}: {a} attraverserebbe {b}",
|
||||
"trace.stopped_by_loop": "Termina al campione {sample}: la catena chiusa che passa per {joint} non può chiudersi ulteriormente",
|
||||
"ui.1_degree_of_freedom": "1 grado di libertà",
|
||||
"ui.3d_mouse_axes_reassigned": "Gli assi sono riassegnati nelle Preferenze, e questo vale anche dopo questa calibrazione.",
|
||||
"ui.3d_mouse_button": "Pulsante {number}",
|
||||
"ui.3d_mouse_button_nothing": "Niente",
|
||||
"ui.3d_mouse_button_settings": "Impostazioni del mouse 3D",
|
||||
"ui.3d_mouse_buttons": "Pulsanti",
|
||||
"ui.3d_mouse_buttons_hint": "Premi un pulsante del tuo mouse 3D per trovarlo qui, poi scegli cosa fa.",
|
||||
"ui.3d_mouse_buttons_no_device": "Collega un mouse 3D per scegliere cosa fanno i suoi pulsanti.",
|
||||
"ui.3d_mouse_buttons_reset": "Ripristina i pulsanti predefiniti",
|
||||
"ui.3d_mouse_calibrate": "Calibra…",
|
||||
"ui.3d_mouse_calibrate_tip": "Muovi la manopola come chiede la finestra, così SolidCraft impara in che direzione e quanto va ogni asse di questo mouse 3D. Viene salvata per questo mouse 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibra mouse 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Muovi il mouse 3D una volta così SolidCraft lo vede.",
|
||||
"ui.3d_mouse_calibrated": "Calibrato",
|
||||
"ui.3d_mouse_calibration": "Calibrazione del mouse 3D",
|
||||
"ui.3d_mouse_calibration_done": "Fatto. Salvala per usarla ogni volta che questo mouse 3D è collegato.",
|
||||
"ui.3d_mouse_calibration_no_response": "Un asse si è mosso appena, quindi non si può calibrare. Ricomincia e muovi la manopola più a fondo ogni volta.",
|
||||
"ui.3d_mouse_circles_help": "I cerchi mostrano ciò che il dispositivo invia dopo la calibrazione. Il modello di prova mostra cosa ne fa la vista, dopo inversione, velocità e assegnazione degli assi.",
|
||||
"ui.3d_mouse_dead_zone": "Zona morta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Quanto deve muoversi la manopola prima che si muova la vista, come quota della sua corsa completa. Aumentala se la vista si sposta con la manopola a riposo.",
|
||||
"ui.3d_mouse_denied": "SolidCraft vede un mouse 3D ma non ha il permesso di leggerlo. La regola udev è nelle note di configurazione del mouse 3D.",
|
||||
"ui.3d_mouse_denied_short": "Trovato, ma non leggibile",
|
||||
"ui.3d_mouse_driven_by": "Quale movimento calibrato del dispositivo comanda questo asse. Se ne scegli uno già usato da un altro asse, i due si scambiano.",
|
||||
"ui.3d_mouse_driven_by_label": "comandato da",
|
||||
"ui.3d_mouse_driver": "Driver del mouse 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatico",
|
||||
"ui.3d_mouse_driver_direct": "Diretto (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Per ora legge tramite spacenavd: il dispositivo stesso non si è potuto aprire.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lettura diretta del dispositivo; spacenavd non è in esecuzione",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lettura tramite spacenavd, l'unica possibilità disponibile",
|
||||
"ui.3d_mouse_forget": "Dimentica calibrazione",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Vista a sinistra",
|
||||
"ui.3d_mouse_ghost_left_window": "Finestra a sinistra",
|
||||
"ui.3d_mouse_ghost_position": "Posizione",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Vista a destra",
|
||||
"ui.3d_mouse_hint": "Sposta e ruota la vista insieme; gli schizzi usano solo panoramica e zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Ritorno al centro",
|
||||
"ui.3d_mouse_kind_question": "Che tipo di mouse 3D è?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Il valore resta finché tieni, come in un dispositivo di tipo «Ritorno al centro».",
|
||||
"ui.3d_mouse_looks_like_trackball": "Il valore torna indietro appena smetti di muovere, come in una trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotazione X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotazione Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotazione Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Traslazione X",
|
||||
"ui.3d_mouse_motion_translation_y": "Traslazione Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Traslazione Z",
|
||||
"ui.3d_mouse_navigate": "Naviga con il mouse 3D",
|
||||
"ui.3d_mouse_none": "Nessun mouse 3D trovato",
|
||||
"ui.3d_mouse_not_calibrated": "Non calibrato",
|
||||
"ui.3d_mouse_offer": "Calibrare il mouse 3D corregge gli assi scambiati o invertiti e imposta velocità e sensibilità.",
|
||||
"ui.3d_mouse_offer_never": "Non mostrare più",
|
||||
"ui.3d_mouse_offer_swap": "Passa a Hidraw (driver diretto)",
|
||||
"ui.3d_mouse_reading": "Lettura",
|
||||
"ui.3d_mouse_reset_axes": "Ripristina assegnazione assi",
|
||||
"ui.3d_mouse_reset_view": "Ripristina vista",
|
||||
"ui.3d_mouse_restore": "Ripristina impostazioni mouse 3D",
|
||||
"ui.3d_mouse_restore_ask": "Riportare le impostazioni del mouse 3D ai valori predefiniti? Calibrazione e assegnazione dei pulsanti restano.",
|
||||
"ui.3d_mouse_section": "Mouse 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostra il mouse 3D nella vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Un'immagine del tuo mouse 3D nell'angolo in basso a sinistra, che si muove quando lo muovi.",
|
||||
"ui.3d_mouse_skip": "Il mio dispositivo non può farlo",
|
||||
"ui.3d_mouse_skip_tip": "Salta questo movimento in entrambi i versi; la calibrazione lo esclude.",
|
||||
"ui.3d_mouse_speed": "Velocità del mouse 3D",
|
||||
"ui.3d_mouse_step_lift": "Solleva la manopola, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_press": "Premi la manopola verso il basso, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_pull": "Tira la manopola verso di te, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_push": "Spingi la manopola lontano da te, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_rest": "Togli la mano dalla manopola e lasciala ferma.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Togli la mano dalla sfera e lasciala ferma.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Gira la ghiera di scorrimento di qualche scatto in senso orario.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Gira la ghiera di scorrimento di qualche scatto in senso antiorario.",
|
||||
"ui.3d_mouse_step_roll_away": "Fai rotolare la sfera lontano da te qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_roll_left": "Fai rotolare la sfera verso sinistra qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_roll_right": "Fai rotolare la sfera verso destra qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_roll_toward": "Fai rotolare la sfera verso di te qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_slide_left": "Fai scorrere la manopola a sinistra, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_slide_right": "Fai scorrere la manopola a destra, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Ruota la sfera in senso orario qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Ruota la sfera in senso antiorario qualche volta, poi lasciala fermare.",
|
||||
"ui.3d_mouse_step_tilt_away": "Inclina la parte superiore della manopola lontano da te (il bordo più vicino sale), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_tilt_left": "Inclina la manopola a sinistra (bordo sinistro in basso), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_tilt_right": "Inclina la manopola a destra (bordo destro in basso), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Inclina la parte superiore della manopola verso di te (il bordo più vicino scende), tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_touch": "Appoggia la mano sulla manopola senza spingerla.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Ruota la manopola in senso orario, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
|
||||
"ui.3d_mouse_switch_cap": "Calibra come ritorno al centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibra come trackball",
|
||||
"ui.3d_mouse_view_axis": "Asse della vista",
|
||||
"ui.3d_mouse_view_off": "Disattivato",
|
||||
"ui.3d_mouse_view_off_tip": "Il profilo del driver del mouse 3D disattiva questo asse, quindi il suo movimento non muove la vista. Se qui scegli il movimento di un altro asse, i due si scambiano e quel movimento smette di muovere la vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbita sinistra / destra",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbita su / giù",
|
||||
"ui.3d_mouse_view_pan_sideways": "Panoramica sinistra / destra",
|
||||
"ui.3d_mouse_view_pan_up_down": "Panoramica su / giù",
|
||||
"ui.3d_mouse_view_roll": "Rollio",
|
||||
"ui.3d_mouse_view_unmapped": "Asse {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "In attesa di movimento",
|
||||
"ui.3d_mouse_wiggle": "Se ne hai uno collegato, muovilo un po'.",
|
||||
"ui.3d_sketch": "Schizzo 3D",
|
||||
"ui.activate": "Attiva",
|
||||
"ui.active_component": "Componente attivo",
|
||||
@@ -1092,6 +1257,7 @@
|
||||
"ui.agent_chat_add": "Allega file",
|
||||
"ui.agent_chat_add_file": "Scegli un file…",
|
||||
"ui.agent_chat_allow": "Consenti",
|
||||
"ui.agent_chat_allow_once": "Consenti una volta",
|
||||
"ui.agent_chat_allowed": "Consentito",
|
||||
"ui.agent_chat_answered": "Risposta: {option}",
|
||||
"ui.agent_chat_approval": "Consentire questa azione all'agente?",
|
||||
@@ -1143,12 +1309,16 @@
|
||||
"ui.agent_chat_files_left_out": "Un messaggio può contenere al massimo {max} file; esclusi: {files}",
|
||||
"ui.agent_chat_files_loading": "In attesa della lettura dei file",
|
||||
"ui.agent_chat_files_max": "Al massimo {max} file per messaggio",
|
||||
"ui.agent_chat_flatpak_none_found": "Nessun agente di programmazione trovato. Per impostazione predefinita, la sandbox Flatpak non vede gli agenti installati nella tua cartella home e non ha accesso alla rete. Per consentire Claude Code e il suo adattatore ACP installati con nvm o in ~/.local/bin, esegui questo comando in un terminale, poi riavvia SolidCraft. Concede l’accesso ai programmi e ai file di accesso elencati, oltre all’accesso alla rete:",
|
||||
"ui.agent_chat_import_failed": "{name}: importazione non riuscita: {error}",
|
||||
"ui.agent_chat_intro": "Conversa con un agente di programmazione che già usi o con un endpoint di modello a tua scelta. Opera sul progetto aperto tramite gli strumenti di SolidCraft: le modifiche sono visibili mentre vengono eseguite e si annullano in un solo passo per messaggio. Le azioni consentite senza conferma si impostano in Preferenze > IA.",
|
||||
"ui.agent_chat_key_in_env": "chiave impostata",
|
||||
"ui.agent_chat_left": "Sinistra",
|
||||
"ui.agent_chat_limit": "L'agente ha raggiunto il limite di {limit} modifiche per messaggio. Invia un altro messaggio per farlo proseguire.",
|
||||
"ui.agent_chat_minimize": "Riduci a una striscia",
|
||||
"ui.agent_chat_mode_all": "Tutto",
|
||||
"ui.agent_chat_mode_ask": "Chiedi",
|
||||
"ui.agent_chat_mode_edits": "Modifiche",
|
||||
"ui.agent_chat_model_tip": "Il modello usato dall'agente. La scelta viene memorizzata per questo agente.",
|
||||
"ui.agent_chat_new": "Nuova chat",
|
||||
"ui.agent_chat_new_confirm": "Avviare una nuova chat? Quella corrente verrà cancellata.",
|
||||
@@ -1161,6 +1331,7 @@
|
||||
"ui.agent_chat_none_found_short": "Nessun agente trovato",
|
||||
"ui.agent_chat_not_installed": "Non installato",
|
||||
"ui.agent_chat_not_signed_in": "accesso non effettuato",
|
||||
"ui.agent_chat_open_settings": "Apri preferenze",
|
||||
"ui.agent_chat_own_window": "Finestra separata",
|
||||
"ui.agent_chat_paste_picture": "Incolla un'immagine",
|
||||
"ui.agent_chat_permission": "L'agente chiede di eseguire: {title}",
|
||||
@@ -1185,6 +1356,8 @@
|
||||
"ui.agent_chat_send": "Invia",
|
||||
"ui.agent_chat_send_now": "Invia subito",
|
||||
"ui.agent_chat_send_now_tip": "Interrompi il passo corrente dell'agente e invia questo messaggio come successivo (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft comunica con un agente di programmazione installato su questo computer. Scegline uno in Preferenze › IA.",
|
||||
"ui.agent_chat_setup_title": "Configura un assistente",
|
||||
"ui.agent_chat_side": "Lato della chat con l'agente",
|
||||
"ui.agent_chat_signed_in": "accesso effettuato",
|
||||
"ui.agent_chat_state_auth": "Accesso richiesto",
|
||||
@@ -1201,6 +1374,7 @@
|
||||
"ui.agent_chat_undo_label": "Chat con l'agente: {message}",
|
||||
"ui.agent_chat_undone": "(annullato)",
|
||||
"ui.agent_chat_view_attached": "Vista allegata",
|
||||
"ui.agent_chat_waiting": "In attesa della tua risposta",
|
||||
"ui.agent_cursor": "Cursore dell'agente",
|
||||
"ui.agent_cursor_speed": "Velocità del cursore dell'agente",
|
||||
"ui.ai_add": "Aggiungi",
|
||||
@@ -1258,7 +1432,6 @@
|
||||
"ui.ai_permissions": "Autorizzazioni",
|
||||
"ui.ai_preset": "Preimpostazione",
|
||||
"ui.ai_remove": "Rimuovi",
|
||||
"ui.ai_request_limit": "arrestato dopo 24 richieste per un messaggio; invia un altro messaggio per proseguire",
|
||||
"ui.ai_reset": "Reimposta",
|
||||
"ui.ai_settings": "Impostazioni IA…",
|
||||
"ui.ai_show_features": "Mostra funzioni IA",
|
||||
@@ -1303,6 +1476,7 @@
|
||||
"ui.blocked": "Bloccato",
|
||||
"ui.bodies": "Corpi",
|
||||
"ui.bodies_all_if_none": "Corpi (tutti se nessuno)",
|
||||
"ui.bottom": "Inferiore",
|
||||
"ui.box": "Parallelepipedo",
|
||||
"ui.break": "Spezza",
|
||||
"ui.browser_bodies_one": "{count} corpo",
|
||||
@@ -1441,6 +1615,7 @@
|
||||
"ui.flip": "Inverti",
|
||||
"ui.format": "Formato",
|
||||
"ui.frame_rate": "Frequenza dei fotogrammi",
|
||||
"ui.frame_rate_details": "Dettagli della frequenza dei fotogrammi",
|
||||
"ui.front": "Frontale",
|
||||
"ui.fully_constrained": "Completamente vincolato",
|
||||
"ui.function": "Funzione",
|
||||
@@ -1455,6 +1630,7 @@
|
||||
"ui.grid_follows_view_hint": "Le viste laterali mostrano la griglia sul piano verticale rivolto verso di esse anziché sul piano di appoggio",
|
||||
"ui.ground_shadow": "Ombra sul piano di appoggio",
|
||||
"ui.groupSelected": "Raggruppa selezionati",
|
||||
"ui.hatch": "Tratteggio",
|
||||
"ui.heading_add_ins": "COMPONENTI AGGIUNTIVI",
|
||||
"ui.heading_assemble": "ASSEMBLA",
|
||||
"ui.heading_box": "PARALLELEPIPEDO",
|
||||
@@ -1528,6 +1704,11 @@
|
||||
"ui.hideOrigin": "Nascondi origine",
|
||||
"ui.hidePlanes": "Nascondi piani",
|
||||
"ui.hideProfile": "Nascondi profilo",
|
||||
"ui.history_back": "Indietro di un passaggio",
|
||||
"ui.history_below_browser": "Sotto il browser",
|
||||
"ui.history_first": "Vai all’inizio",
|
||||
"ui.history_forward": "Avanti di un passaggio",
|
||||
"ui.history_last": "Vai alla fine",
|
||||
"ui.history_title": "Cronologia",
|
||||
"ui.hole": "Foro",
|
||||
"ui.hole_tap_type": "Tipo di maschiatura del foro",
|
||||
@@ -1545,6 +1726,8 @@
|
||||
"ui.joints": "Giunti",
|
||||
"ui.keyboard_shortcuts": "Scorciatoie da tastiera",
|
||||
"ui.language": "Lingua",
|
||||
"ui.layout_compact": "Compatta",
|
||||
"ui.layout_floating": "Flottante",
|
||||
"ui.length_decimals": "Decimali delle lunghezze",
|
||||
"ui.light": "Chiaro",
|
||||
"ui.line": "Linea",
|
||||
@@ -1682,6 +1865,7 @@
|
||||
"ui.reverse_zoom_direction": "Inverti direzione dello zoom",
|
||||
"ui.revolve": "Rivoluzione",
|
||||
"ui.ribbon_exit": "Esci",
|
||||
"ui.ribbon_labels": "Mostra il testo sotto le icone della barra multifunzione",
|
||||
"ui.ribbon_pin_footer": "I comandi fissati compaiono sulla barra multifunzione. Passa sopra una riga per fissarla o rimuoverla.",
|
||||
"ui.ribbon_workspace": "Area di lavoro",
|
||||
"ui.right": "Destra",
|
||||
@@ -1737,34 +1921,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom fluido con rotellina",
|
||||
"ui.smooth_wheel_zoom_tip": "Disattiva per applicare immediatamente ogni evento della rotellina, senza coda di interpolazione",
|
||||
"ui.snap": "Aggancio",
|
||||
"ui.space_mouse_calibrate": "Calibra…",
|
||||
"ui.space_mouse_calibrate_tip": "Muovi la manopola come chiede la finestra, così SolidCraft impara in che direzione e quanto va ogni asse di questo mouse 3D. Viene salvata per questo mouse 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibra mouse 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrato",
|
||||
"ui.space_mouse_calibration": "Calibrazione del mouse 3D",
|
||||
"ui.space_mouse_calibration_done": "Fatto. Salvala per usarla ogni volta che questo mouse 3D è collegato.",
|
||||
"ui.space_mouse_calibration_no_response": "Un asse si è mosso appena, quindi non si può calibrare. Ricomincia e muovi la manopola più a fondo ogni volta.",
|
||||
"ui.space_mouse_dead_zone": "Zona morta",
|
||||
"ui.space_mouse_dead_zone_tip": "Quanto deve muoversi la manopola prima che si muova la vista, come quota della sua corsa completa. Aumentala se la vista si sposta con la manopola a riposo.",
|
||||
"ui.space_mouse_forget": "Dimentica calibrazione",
|
||||
"ui.space_mouse_hint": "Sposta e ruota la vista insieme; gli schizzi usano solo panoramica e zoom.",
|
||||
"ui.space_mouse_navigate": "Naviga con SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Nessun mouse 3D trovato",
|
||||
"ui.space_mouse_not_calibrated": "Non calibrato",
|
||||
"ui.space_mouse_reading": "Lettura",
|
||||
"ui.space_mouse_step_lift": "Solleva la manopola, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_press": "Premi la manopola verso il basso, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_pull": "Tira la manopola verso di te, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_push": "Spingi la manopola lontano da te, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_rest": "Togli la mano dalla manopola e lasciala ferma.",
|
||||
"ui.space_mouse_step_slide_left": "Fai scorrere la manopola a sinistra, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_slide_right": "Fai scorrere la manopola a destra, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_back": "Inclina la manopola all'indietro (bordo anteriore in alto), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_forward": "Inclina la manopola in avanti (bordo anteriore in basso), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_left": "Inclina la manopola a sinistra (bordo sinistro in basso), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_tilt_right": "Inclina la manopola a destra (bordo destro in basso), tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Ruota la manopola in senso orario, tienila, poi lasciala.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
|
||||
"ui.spacing": "Spaziatura",
|
||||
"ui.spacing_2": "Spaziatura 2",
|
||||
"ui.sphere": "Sfera",
|
||||
@@ -1836,6 +1992,7 @@
|
||||
"ui.workspace_design_tabs": "Modello · Superficie · Assembla · Inserisci · Ispeziona · Gestisci · Vista",
|
||||
"ui.workspace_drawing_description": "Disegni 2D con viste, quote e cartigli",
|
||||
"ui.workspace_footer": "Ogni area di lavoro opera sullo stesso progetto, cronologia e selezione; cambiano solo le schede della barra multifunzione. Il selettore è bloccato durante la modifica di uno schizzo o di una feature.",
|
||||
"ui.workspace_layout": "Disposizione dello spazio di lavoro",
|
||||
"ui.workspace_manufacture_description": "Slicing, percorsi utensile e impostazione",
|
||||
"ui.workspace_planned": "Pianificata",
|
||||
"ui.workspace_render_description": "Anteprima del prodotto, impostazioni della scena e immagini renderizzate",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Pilih…",
|
||||
"Circular": "Bulat",
|
||||
"Circumference": "Lilitan",
|
||||
"Clockwise": "Ikut arah jam",
|
||||
"Close draft": "Tutup draf",
|
||||
"Collisions": "Perlanggaran",
|
||||
"Colour": "Warna",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "SEJARAH",
|
||||
"Height": "Ketinggian",
|
||||
"Hidden edges": "Tepi tersembunyi",
|
||||
"History layout": "Susun atur sejarah",
|
||||
"Holes": "Lubang",
|
||||
"Holes (edges on them)": "Lubang (tepi lubang)",
|
||||
"Home view": "Pandangan utama",
|
||||
@@ -232,7 +234,7 @@
|
||||
"No crash reports.": "Tiada laporan ranap.",
|
||||
"No matching appearance presets.": "Tiada pratetap penampilan yang sepadan.",
|
||||
"No matching appearances": "Tiada penampilan yang sepadan",
|
||||
"No visible bodies to export.": "Tiada jasad kelihatan untuk dieksport.",
|
||||
"No visible bodies or sketches to export.": "Tiada jasad atau lakaran kelihatan untuk dieksport.",
|
||||
"None": "Tiada",
|
||||
"Not available yet": "Belum tersedia",
|
||||
"Notch": "Takuk",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Search commands": "Cari arahan",
|
||||
"Search this library": "Cari dalam koleksi ini",
|
||||
"Section": "Keratan",
|
||||
"Section position": "Kedudukan keratan",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Pilih Lain: tahan butang kiri pada model tanpa bergerak (atau klik kanan › Pilih Lain…) untuk senarai objek di bawah kursor. Klik tempat sama untuk objek seterusnya. Klik dua kali butang tengah untuk ke Utama (Tetapan › Navigasi).",
|
||||
"Select bodies (a face picks its body)": "Pilih jasad (klik muka untuk pilih jasadnya)",
|
||||
"Select bodies or faces instead": "Pilih jasad atau muka",
|
||||
@@ -332,6 +335,7 @@
|
||||
"Show Agent Cursor": "Papar Kursor Ejen",
|
||||
"Show in folder": "Papar dalam folder",
|
||||
"Show the pointer an AI agent moves (#35)": "Papar penuding yang digerakkan oleh ejen AI (#35)",
|
||||
"Show tool at cursor": "Tunjukkan alat di sebelah kursor",
|
||||
"Signed axes": "Paksi bertanda",
|
||||
"Size": "Saiz",
|
||||
"Sketch constraints": "Kekangan lakaran",
|
||||
@@ -344,9 +348,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft ditutup sebelum reka bentuk disimpan. Buka simpanan automatik terakhirnya.",
|
||||
"Specialise": "Khususkan",
|
||||
"Speed": "Kelajuan",
|
||||
"Square": "Segi empat sama",
|
||||
"Stand it up": "Tegakkan",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Tegakkan: suku putaran pada X, satu ciri Alih yang boleh dibatalkan.",
|
||||
"Stand this part up?": "Tegakkan bahagian ini?",
|
||||
"Start angle": "Sudut mula",
|
||||
"Start bulge": "Bonjolan permulaan",
|
||||
"Start continuity": "Keselanjaran permulaan",
|
||||
"Step": "Langkah",
|
||||
@@ -411,6 +417,7 @@
|
||||
"angle, height": "sudut, ketinggian",
|
||||
"appearance.dielectric_ior": "IOR Dielektrik",
|
||||
"appearance.metallic": "Sifat Logam",
|
||||
"appearance.only_available": "Rupa yang tersedia sahaja",
|
||||
"appearance.pbr_help": "Lihat dalam Render atau PNG produk. Tiada pembiasan atau penembusan cahaya.",
|
||||
"appearance.pbr_opaque": "PBR Legap",
|
||||
"appearance.roughness": "Kekasaran",
|
||||
@@ -529,6 +536,10 @@
|
||||
"cmd.file_insert_step": "Sisip STEP",
|
||||
"cmd.file_save_mesh": "Simpan sebagai Mesh",
|
||||
"cmd.file_slicers": "Penghiris Terpasang",
|
||||
"cmd.frame_member": "Anggota Rangka",
|
||||
"cmd.frame_profile_insert": "Sisipkan Profil",
|
||||
"cmd.frame_profile_list": "Pustaka Profil",
|
||||
"cmd.frame_profile_properties": "Sifat Profil",
|
||||
"cmd.inspect_accessibility": "Analisis Kebolehcapaian",
|
||||
"cmd.inspect_center_of_mass": "Pusat Jisim",
|
||||
"cmd.inspect_clearance": "Kelegaan",
|
||||
@@ -568,6 +579,7 @@
|
||||
"cmd.material_create": "Bahan Fizikal Baharu",
|
||||
"cmd.material_delete": "Padam Bahan Fizikal",
|
||||
"cmd.material_edit": "Sunting Bahan Fizikal",
|
||||
"cmd.material_inspect": "Periksa Bahan Fizikal",
|
||||
"cmd.material_list": "Senarai Bahan",
|
||||
"cmd.material_properties_directional": "Modulus Keanjalan Berarah",
|
||||
"cmd.material_properties_validate": "Sahkan Data Bahan Fizikal",
|
||||
@@ -831,7 +843,7 @@
|
||||
"dxf.progress.projection": "Mengunjurkan geometri (3/5)",
|
||||
"dxf.progress.write": "Menulis DXF (5/5)",
|
||||
"dxf.tolerance": "Toleransi lengkung",
|
||||
"dxf.tolerance_help": "Jasad kelihatan sahaja. Lengkung guna segmen lurus. Toleransi kecil lebih halus tetapi guna lebih masa dan memori.",
|
||||
"dxf.tolerance_help": "Jasad kelihatan dan lakaran yang ditunjukkan sahaja. Lengkung guna segmen lurus. Toleransi kecil lebih halus tetapi guna lebih masa dan memori.",
|
||||
"file_load.built": "Dibina setakat ini: {built} daripada {total} ciri.",
|
||||
"file_load.converting": "Menukar pepejal {done} daripada {total}",
|
||||
"file_load.inserting": "Menyisip geometri",
|
||||
@@ -856,19 +868,57 @@
|
||||
"help.mit": "Lesen MIT",
|
||||
"help.notice": "NOTIS",
|
||||
"help.third_party": "Lesen pihak ketiga",
|
||||
"home.back": "Kembali ke {name}",
|
||||
"home.crashes": "Laporan ranap",
|
||||
"home.design_workspace": "Reka Bentuk",
|
||||
"home.documentation": "Dokumentasi",
|
||||
"home.export": "Eksport",
|
||||
"home.export_hint": "Pilih format untuk reka bentuk semasa.",
|
||||
"home.export_json_hint": "Reka bentuk boleh sunting dan sejarah perintah",
|
||||
"home.export_mesh_hint": "Jejaring untuk pencetakan atau perkongsian",
|
||||
"home.export_step_hint": "Geometri pepejal yang tepat",
|
||||
"home.import": "Import…",
|
||||
"home.import_hint": "STEP, IGES atau jejaring ke dalam reka bentuk baharu",
|
||||
"home.in_this_browser": "dalam pelayar ini",
|
||||
"home.missing": "tiada",
|
||||
"home.month_01": "Jan",
|
||||
"home.month_02": "Feb",
|
||||
"home.month_03": "Mac",
|
||||
"home.month_04": "Apr",
|
||||
"home.month_05": "Mei",
|
||||
"home.month_06": "Jun",
|
||||
"home.month_07": "Jul",
|
||||
"home.month_08": "Ogo",
|
||||
"home.month_09": "Sep",
|
||||
"home.month_10": "Okt",
|
||||
"home.month_11": "Nov",
|
||||
"home.month_12": "Dis",
|
||||
"home.new_inches": "Reka bentuk baharu dalam inci",
|
||||
"home.new_inches_hint": "Inci · dibuka dalam Reka Bentuk",
|
||||
"home.new_mm": "Reka bentuk baharu",
|
||||
"home.new_mm_hint": "Milimeter · dibuka dalam Reka Bentuk",
|
||||
"home.open": "Buka…",
|
||||
"home.opened_days_ago": "Dibuka {count} hari lalu",
|
||||
"home.opened_hours_ago": "Dibuka {count} jam lalu",
|
||||
"home.opened_just_now": "Baru dibuka",
|
||||
"home.opened_minutes_ago": "Dibuka {count} minit lalu",
|
||||
"home.open_hint": "Reka bentuk .solidcraft ({shortcut})",
|
||||
"home.open_now": "Buka sekarang",
|
||||
"home.opened_days_ago": "{count} hari lalu",
|
||||
"home.opened_hours_ago": "{count} jam lalu",
|
||||
"home.opened_just_now": "Baru sahaja",
|
||||
"home.opened_minutes_ago": "{count} minit lalu",
|
||||
"home.opening": "membuka…",
|
||||
"home.properties": "Sifat reka bentuk",
|
||||
"home.recent": "Terkini",
|
||||
"home.recent_empty": "Reka bentuk yang anda buka atau simpan dipaparkan di sini.",
|
||||
"home.recovery_count": "Reka bentuk boleh dipulihkan: {count}",
|
||||
"home.recovery_empty": "Tiada reka bentuk yang boleh dipulihkan.",
|
||||
"home.recovery_hint": "SolidCraft ditutup sebelum reka bentuk ini disimpan.",
|
||||
"home.report_issue": "Laporkan Isu",
|
||||
"home.samples": "Contoh",
|
||||
"home.settings": "Tetapan",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Pintasan papan kekunci",
|
||||
"home.start_hint": "Cipta reka bentuk, buka fail terkini atau terokai contoh.",
|
||||
"home.whats_new": "Apa yang baharu",
|
||||
"home.yesterday": "Semalam",
|
||||
"install.button_tip": "Pasang SolidCraft: tetingkap sendiri dan boleh guna di luar talian",
|
||||
"install.dock_1": "Pilih File › Add to Dock.",
|
||||
"install.dock_2": "Klik Add.",
|
||||
@@ -1021,7 +1071,7 @@
|
||||
"search.try": "Taip nama arahan, ciri dalam reka bentuk ini, atau parameter. Untuk menjalankan arahan dengan nilai, taip ID-nya dan objek JSON: {example}",
|
||||
"search.valid": "Sah",
|
||||
"settings.display_quality_note": "Tahap halus melicinkan muka dan tepi melengkung, tetapi mesh lebih lambat disiapkan. Hanya paparan berubah, bukan reka bentuk atau eksport.",
|
||||
"settings.frame_rate_note": "Papar bingkai sesaat di kanan bawah. Jika diaktifkan, pandangan dilukis semula tanpa henti.",
|
||||
"settings.frame_rate_note": "Papar bingkai sesaat di kanan bawah.",
|
||||
"settings.measure_axis_distances": "Papar jarak X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Papar jarak paksi dunia X/Y/Z bersama jarak keseluruhan semasa Ukur dibuka.",
|
||||
"settings.msaa_applies_on_restart": "Antialias digunakan apabila SolidCraft dimulakan semula",
|
||||
@@ -1082,7 +1132,106 @@
|
||||
"trace.stopped_by_contact": "Berakhir pada sampel {sample}: {a} akan menembusi {b}",
|
||||
"trace.stopped_by_loop": "Berakhir pada sampel {sample}: gelung melalui {joint} tidak lagi dapat ditutup",
|
||||
"ui.1_degree_of_freedom": "1 darjah kebebasan",
|
||||
"ui.3d_mouse_axes_reassigned": "Paksi ditetapkan semula dalam Keutamaan, dan itu masih terpakai di atas penentukuran ini.",
|
||||
"ui.3d_mouse_button": "Butang {number}",
|
||||
"ui.3d_mouse_button_nothing": "Tiada",
|
||||
"ui.3d_mouse_button_settings": "Tetapan tetikus 3D",
|
||||
"ui.3d_mouse_buttons": "Butang",
|
||||
"ui.3d_mouse_buttons_hint": "Tekan butang pada tetikus 3D anda untuk mencarinya di sini, kemudian pilih fungsinya.",
|
||||
"ui.3d_mouse_buttons_no_device": "Sambungkan tetikus 3D untuk menetapkan fungsi butangnya.",
|
||||
"ui.3d_mouse_buttons_reset": "Pulihkan butang lalai",
|
||||
"ui.3d_mouse_calibrate": "Tentukur…",
|
||||
"ui.3d_mouse_calibrate_tip": "Gerakkan penutup seperti yang diminta tetingkap, supaya SolidCraft mempelajari arah dan jarak setiap paksi tetikus 3D ini. Disimpan untuk tetikus 3D ini.",
|
||||
"ui.3d_mouse_calibrate_title": "Tentukur tetikus 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Gerakkan tetikus 3D sekali supaya SolidCraft dapat melihatnya.",
|
||||
"ui.3d_mouse_calibrated": "Telah ditentukur",
|
||||
"ui.3d_mouse_calibration": "Penentukuran tetikus 3D",
|
||||
"ui.3d_mouse_calibration_done": "Selesai. Simpan untuk digunakan setiap kali tetikus 3D ini disambungkan.",
|
||||
"ui.3d_mouse_calibration_no_response": "Satu paksi hampir tidak bergerak, jadi tidak dapat ditentukur. Mulakan semula dan gerakkan penutup lebih jauh setiap kali.",
|
||||
"ui.3d_mouse_circles_help": "Bulatan menunjukkan apa yang dihantar peranti selepas penentukuran. Model ujian menunjukkan apa yang dilakukan paparan dengannya, selepas songsangan, kelajuan dan penetapan paksi.",
|
||||
"ui.3d_mouse_dead_zone": "Zon mati",
|
||||
"ui.3d_mouse_dead_zone_tip": "Sejauh mana penutup perlu bergerak sebelum pandangan bergerak, sebagai bahagian daripada julat penuhnya. Naikkan jika pandangan hanyut semasa penutup dalam keadaan rehat.",
|
||||
"ui.3d_mouse_denied": "SolidCraft nampak tetikus 3D tetapi tidak dibenarkan membacanya. Lihat nota persediaan tetikus 3D untuk peraturan udev.",
|
||||
"ui.3d_mouse_denied_short": "Dijumpai, tetapi tidak boleh dibaca",
|
||||
"ui.3d_mouse_driven_by": "Gerakan peranti yang telah ditentukur yang memacu paksi ini. Memilih gerakan yang sudah digunakan paksi lain akan menukar kedua-duanya.",
|
||||
"ui.3d_mouse_driven_by_label": "dipacu oleh",
|
||||
"ui.3d_mouse_driver": "Pemacu tetikus 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatik",
|
||||
"ui.3d_mouse_driver_direct": "Terus (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Buat masa ini dibaca melalui spacenavd: peranti itu sendiri tidak dapat dibuka.",
|
||||
"ui.3d_mouse_driver_only_direct": "Membaca peranti secara terus; spacenavd tidak berjalan",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Membaca melalui spacenavd, satu-satunya cara yang ada",
|
||||
"ui.3d_mouse_forget": "Lupakan penentukuran",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport kiri",
|
||||
"ui.3d_mouse_ghost_left_window": "Tetingkap kiri",
|
||||
"ui.3d_mouse_ghost_position": "Kedudukan",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport kanan",
|
||||
"ui.3d_mouse_hint": "Alih dan putar pandangan serentak; dalam lakaran, hanya sorot dan zum digunakan.",
|
||||
"ui.3d_mouse_kind_cap": "Kembali ke tengah",
|
||||
"ui.3d_mouse_kind_question": "Apakah jenis tetikus 3D ini?",
|
||||
"ui.3d_mouse_kind_trackball": "Bola jejak",
|
||||
"ui.3d_mouse_looks_like_cap": "Bacaan kekal selagi anda menahannya, seperti peranti jenis “Kembali ke tengah”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Bacaan kembali sebaik sahaja anda berhenti bergerak, seperti bola jejak.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Putaran X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Putaran Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Putaran Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Anjakan X",
|
||||
"ui.3d_mouse_motion_translation_y": "Anjakan Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Anjakan Z",
|
||||
"ui.3d_mouse_navigate": "Navigasi dengan tetikus 3D",
|
||||
"ui.3d_mouse_none": "Tiada tetikus 3D ditemui",
|
||||
"ui.3d_mouse_not_calibrated": "Belum ditentukur",
|
||||
"ui.3d_mouse_offer": "Menentukur tetikus 3D membetulkan paksi yang bertukar atau terbalik dan menetapkan kelajuan serta rasanya.",
|
||||
"ui.3d_mouse_offer_never": "Jangan tunjuk lagi",
|
||||
"ui.3d_mouse_offer_swap": "Tukar ke Hidraw (pemacu terus)",
|
||||
"ui.3d_mouse_reading": "Bacaan",
|
||||
"ui.3d_mouse_reset_axes": "Tetapkan semula penetapan paksi",
|
||||
"ui.3d_mouse_reset_view": "Set semula paparan",
|
||||
"ui.3d_mouse_restore": "Pulihkan lalai tetikus 3D",
|
||||
"ui.3d_mouse_restore_ask": "Tetapkan semula tetapan tetikus 3D kepada lalai? Penentukuran dan pemetaan butang kekal.",
|
||||
"ui.3d_mouse_section": "Tetikus 3D",
|
||||
"ui.3d_mouse_show_in_view": "Tunjukkan tetikus 3D dalam paparan 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Gambar tetikus 3D anda di sudut kiri bawah, yang bergerak apabila anda menggerakkannya.",
|
||||
"ui.3d_mouse_skip": "Peranti saya tidak boleh buat ini",
|
||||
"ui.3d_mouse_skip_tip": "Langkau gerakan ini kedua-dua arah; penentukuran tidak menggunakannya.",
|
||||
"ui.3d_mouse_speed": "Kelajuan tetikus 3D",
|
||||
"ui.3d_mouse_step_lift": "Angkat penutup ke atas, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_press": "Tekan penutup ke bawah, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_pull": "Tarik penutup ke arah anda, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_push": "Tolak penutup menjauhi anda, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_rest": "Alihkan tangan anda daripada penutup dan biarkan ia diam.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Alihkan tangan anda daripada bola dan biarkan ia diam.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Putar gelang tatal ikut arah jam beberapa klik.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Putar gelang tatal lawan arah jam beberapa klik.",
|
||||
"ui.3d_mouse_step_roll_away": "Gulingkan bola menjauhi anda beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_roll_left": "Gulingkan bola ke kiri beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_roll_right": "Gulingkan bola ke kanan beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_roll_toward": "Gulingkan bola ke arah anda beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_slide_left": "Luncurkan penutup ke kiri, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_slide_right": "Luncurkan penutup ke kanan, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Pusing bola ikut arah jam beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Pusing bola lawan arah jam beberapa kali, kemudian biarkan ia berhenti.",
|
||||
"ui.3d_mouse_step_tilt_away": "Condongkan bahagian atas penutup menjauhi anda (tepi terdekat naik), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_left": "Condongkan penutup ke kiri (tepi kiri turun), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_right": "Condongkan penutup ke kanan (tepi kanan turun), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Condongkan bahagian atas penutup ke arah anda (tepi terdekat turun), tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_touch": "Letakkan tangan anda pada penutup tanpa menolaknya.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Pusing penutup ikut arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.3d_mouse_switch_cap": "Tentukur sebagai kembali ke tengah",
|
||||
"ui.3d_mouse_switch_trackball": "Tentukur sebagai bola jejak",
|
||||
"ui.3d_mouse_view_axis": "Paksi paparan",
|
||||
"ui.3d_mouse_view_off": "Mati",
|
||||
"ui.3d_mouse_view_off_tip": "Profil pemacu tetikus 3D mematikan paksi ini, jadi gerakannya tidak menggerakkan paparan. Memilih gerakan paksi lain di sini menukar kedua-duanya, dan gerakan itu tidak lagi menggerakkan paparan.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbit kiri / kanan",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbit atas / bawah",
|
||||
"ui.3d_mouse_view_pan_sideways": "Pan kiri / kanan",
|
||||
"ui.3d_mouse_view_pan_up_down": "Pan atas / bawah",
|
||||
"ui.3d_mouse_view_roll": "Gelek",
|
||||
"ui.3d_mouse_view_unmapped": "Paksi {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zum",
|
||||
"ui.3d_mouse_waiting": "Menunggu pergerakan",
|
||||
"ui.3d_mouse_wiggle": "Jika anda ada yang disambungkan, gerakkannya sedikit.",
|
||||
"ui.3d_sketch": "Lakaran 3D",
|
||||
"ui.activate": "Aktifkan",
|
||||
"ui.active_component": "Komponen aktif",
|
||||
@@ -1092,6 +1241,7 @@
|
||||
"ui.agent_chat_add": "Lampirkan fail",
|
||||
"ui.agent_chat_add_file": "Pilih fail…",
|
||||
"ui.agent_chat_allow": "Benarkan",
|
||||
"ui.agent_chat_allow_once": "Benarkan sekali",
|
||||
"ui.agent_chat_allowed": "Dibenarkan",
|
||||
"ui.agent_chat_answered": "Jawapan: {option}",
|
||||
"ui.agent_chat_approval": "Benarkan ejen melakukan ini?",
|
||||
@@ -1143,12 +1293,16 @@
|
||||
"ui.agent_chat_files_left_out": "Paling banyak {max} fail bersama satu mesej; ditinggalkan: {files}",
|
||||
"ui.agent_chat_files_loading": "Menunggu fail dibaca",
|
||||
"ui.agent_chat_files_max": "Paling banyak {max} fail pada satu mesej",
|
||||
"ui.agent_chat_flatpak_none_found": "Tiada ejen pengekodan. Secara lalai, kotak pasir Flatpak tidak dapat melihat ejen yang dipasang dalam folder rumah anda dan tiada akses rangkaian. Untuk membenarkan Claude Code dan penyesuai ACP-nya yang dipasang melalui nvm atau dalam ~/.local/bin, jalankan arahan ini dalam terminal, kemudian mulakan semula SolidCraft. Ia memberi akses kepada program dan fail log masuk yang disenaraikan, serta akses rangkaian:",
|
||||
"ui.agent_chat_import_failed": "{name}: import gagal: {error}",
|
||||
"ui.agent_chat_intro": "Sembang dengan ejen pengekodan biasa anda atau titik akhir model pilihan. Ejen mengubah reka bentuk melalui alat SolidCraft; perubahan dipaparkan terus dan boleh dibatalkan sekali bagi setiap mesej. Tetapkan kebenaran di Tetapan > AI.",
|
||||
"ui.agent_chat_key_in_env": "kunci tersedia",
|
||||
"ui.agent_chat_left": "Kiri",
|
||||
"ui.agent_chat_limit": "Ejen telah mencapai had {limit} perubahan untuk satu mesej. Hantar mesej lain untuk meneruskan.",
|
||||
"ui.agent_chat_minimize": "Kecilkan kepada bar",
|
||||
"ui.agent_chat_mode_all": "Semua",
|
||||
"ui.agent_chat_mode_ask": "Tanya",
|
||||
"ui.agent_chat_mode_edits": "Suntingan",
|
||||
"ui.agent_chat_model_tip": "Model yang digunakan oleh ejen. Pilihan anda disimpan untuk ejen ini.",
|
||||
"ui.agent_chat_new": "Sembang baharu",
|
||||
"ui.agent_chat_new_confirm": "Mulakan sembang baharu? Sembang semasa akan dikosongkan.",
|
||||
@@ -1161,6 +1315,7 @@
|
||||
"ui.agent_chat_none_found_short": "Tiada ejen ditemui",
|
||||
"ui.agent_chat_not_installed": "Tidak dipasang",
|
||||
"ui.agent_chat_not_signed_in": "belum log masuk",
|
||||
"ui.agent_chat_open_settings": "Buka tetapan",
|
||||
"ui.agent_chat_own_window": "Tetingkap sendiri",
|
||||
"ui.agent_chat_paste_picture": "Tampal gambar",
|
||||
"ui.agent_chat_permission": "Benarkan ejen jalankan: {title}",
|
||||
@@ -1185,6 +1340,8 @@
|
||||
"ui.agent_chat_send": "Hantar",
|
||||
"ui.agent_chat_send_now": "Hantar terus",
|
||||
"ui.agent_chat_send_now_tip": "Hentikan langkah semasa ejen dan hantar mesej ini selepas itu (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft berkomunikasi dengan ejen pengekodan yang dipasang pada komputer ini. Pilih ejen dalam Tetapan › AI.",
|
||||
"ui.agent_chat_setup_title": "Sediakan pembantu",
|
||||
"ui.agent_chat_side": "Sisi sembang ejen",
|
||||
"ui.agent_chat_signed_in": "sudah log masuk",
|
||||
"ui.agent_chat_state_auth": "Perlu log masuk",
|
||||
@@ -1201,6 +1358,7 @@
|
||||
"ui.agent_chat_undo_label": "Sembang ejen: {message}",
|
||||
"ui.agent_chat_undone": "(dibatalkan)",
|
||||
"ui.agent_chat_view_attached": "Paparan dilampirkan",
|
||||
"ui.agent_chat_waiting": "Menunggu anda",
|
||||
"ui.agent_cursor": "Kursor ejen",
|
||||
"ui.agent_cursor_speed": "Kelajuan kursor ejen",
|
||||
"ui.ai_add": "Tambah",
|
||||
@@ -1258,7 +1416,6 @@
|
||||
"ui.ai_permissions": "Kebenaran",
|
||||
"ui.ai_preset": "Pratetap",
|
||||
"ui.ai_remove": "Buang",
|
||||
"ui.ai_request_limit": "berhenti selepas 24 permintaan untuk satu mesej; hantar mesej lain untuk meneruskan",
|
||||
"ui.ai_reset": "Set Semula",
|
||||
"ui.ai_settings": "Tetapan AI…",
|
||||
"ui.ai_show_features": "Papar ciri AI",
|
||||
@@ -1303,6 +1460,7 @@
|
||||
"ui.blocked": "Disekat",
|
||||
"ui.bodies": "Jasad",
|
||||
"ui.bodies_all_if_none": "Jasad (semua jika tiada)",
|
||||
"ui.bottom": "Bawah",
|
||||
"ui.box": "Kotak",
|
||||
"ui.break": "Putuskan",
|
||||
"ui.browser_bodies_one": "{count} jasad",
|
||||
@@ -1455,6 +1613,7 @@
|
||||
"ui.grid_follows_view_hint": "Pandangan sisi guna grid satah tegak yang menghadapnya, bukan satah tanah",
|
||||
"ui.ground_shadow": "Bayang tanah",
|
||||
"ui.groupSelected": "Kumpulkan Pilihan",
|
||||
"ui.hatch": "Lorek",
|
||||
"ui.heading_add_ins": "TAMBAHAN",
|
||||
"ui.heading_assemble": "PASANG",
|
||||
"ui.heading_box": "KOTAK",
|
||||
@@ -1528,6 +1687,11 @@
|
||||
"ui.hideOrigin": "Sembunyi Titik Asal",
|
||||
"ui.hidePlanes": "Sembunyi Satah",
|
||||
"ui.hideProfile": "Sembunyi Profil",
|
||||
"ui.history_back": "Undur satu langkah",
|
||||
"ui.history_below_browser": "Di bawah pelayar",
|
||||
"ui.history_first": "Pergi ke permulaan",
|
||||
"ui.history_forward": "Maju satu langkah",
|
||||
"ui.history_last": "Pergi ke penghujung",
|
||||
"ui.history_title": "Sejarah",
|
||||
"ui.hole": "Lubang",
|
||||
"ui.hole_tap_type": "Jenis Bebenang Lubang",
|
||||
@@ -1545,6 +1709,8 @@
|
||||
"ui.joints": "Sendi",
|
||||
"ui.keyboard_shortcuts": "Pintasan Kekunci",
|
||||
"ui.language": "Bahasa",
|
||||
"ui.layout_compact": "Padat",
|
||||
"ui.layout_floating": "Terapung",
|
||||
"ui.length_decimals": "Perpuluhan panjang",
|
||||
"ui.light": "Cerah",
|
||||
"ui.line": "Garis",
|
||||
@@ -1682,6 +1848,7 @@
|
||||
"ui.reverse_zoom_direction": "Songsangkan arah zum",
|
||||
"ui.revolve": "Putar",
|
||||
"ui.ribbon_exit": "Keluar",
|
||||
"ui.ribbon_labels": "Tunjukkan teks di bawah ikon reben",
|
||||
"ui.ribbon_pin_footer": "Arahan tersemat dipaparkan pada reben. Lalukan tetikus pada baris untuk menyemat atau menyahsemat.",
|
||||
"ui.ribbon_workspace": "Ruang kerja",
|
||||
"ui.right": "Kanan",
|
||||
@@ -1737,34 +1904,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zum roda lancar",
|
||||
"ui.smooth_wheel_zoom_tip": "Matikan untuk mengezum serta-merta bagi setiap putaran roda tetikus, tanpa gerakan susulan",
|
||||
"ui.snap": "Lekap",
|
||||
"ui.space_mouse_calibrate": "Tentukur…",
|
||||
"ui.space_mouse_calibrate_tip": "Gerakkan penutup seperti yang diminta tetingkap, supaya SolidCraft mempelajari arah dan jarak setiap paksi tetikus 3D ini. Disimpan untuk tetikus 3D ini.",
|
||||
"ui.space_mouse_calibrate_title": "Tentukur tetikus 3D",
|
||||
"ui.space_mouse_calibrated": "Telah ditentukur",
|
||||
"ui.space_mouse_calibration": "Penentukuran tetikus 3D",
|
||||
"ui.space_mouse_calibration_done": "Selesai. Simpan untuk digunakan setiap kali tetikus 3D ini disambungkan.",
|
||||
"ui.space_mouse_calibration_no_response": "Satu paksi hampir tidak bergerak, jadi tidak dapat ditentukur. Mulakan semula dan gerakkan penutup lebih jauh setiap kali.",
|
||||
"ui.space_mouse_dead_zone": "Zon mati",
|
||||
"ui.space_mouse_dead_zone_tip": "Sejauh mana penutup perlu bergerak sebelum pandangan bergerak, sebagai bahagian daripada julat penuhnya. Naikkan jika pandangan hanyut semasa penutup dalam keadaan rehat.",
|
||||
"ui.space_mouse_forget": "Lupakan penentukuran",
|
||||
"ui.space_mouse_hint": "Alih dan putar pandangan serentak; dalam lakaran, hanya sorot dan zum digunakan.",
|
||||
"ui.space_mouse_navigate": "Navigasi dengan SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Tiada tetikus 3D ditemui",
|
||||
"ui.space_mouse_not_calibrated": "Belum ditentukur",
|
||||
"ui.space_mouse_reading": "Bacaan",
|
||||
"ui.space_mouse_step_lift": "Angkat penutup ke atas, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_press": "Tekan penutup ke bawah, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_pull": "Tarik penutup ke arah anda, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_push": "Tolak penutup menjauhi anda, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_rest": "Alihkan tangan anda daripada penutup dan biarkan ia diam.",
|
||||
"ui.space_mouse_step_slide_left": "Luncurkan penutup ke kiri, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_slide_right": "Luncurkan penutup ke kanan, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_back": "Condongkan penutup ke belakang (tepi depan naik), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_forward": "Condongkan penutup ke hadapan (tepi depan turun), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_left": "Condongkan penutup ke kiri (tepi kiri turun), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_tilt_right": "Condongkan penutup ke kanan (tepi kanan turun), tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Pusing penutup ikut arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
|
||||
"ui.spacing": "Selang",
|
||||
"ui.spacing_2": "Selang 2",
|
||||
"ui.sphere": "Sfera",
|
||||
@@ -1836,6 +1975,7 @@
|
||||
"ui.workspace_design_tabs": "Model · Permukaan · Pasang · Sisip · Periksa · Urus · Pandangan",
|
||||
"ui.workspace_drawing_description": "Lukisan 2D dengan pandangan, dimensi dan blok tajuk",
|
||||
"ui.workspace_footer": "Setiap ruang kerja berfungsi pada reka bentuk, sejarah dan pilihan yang sama; hanya tab reben berubah. Penukar dikunci semasa lakaran atau ciri sedang disunting.",
|
||||
"ui.workspace_layout": "Susun atur ruang kerja",
|
||||
"ui.workspace_manufacture_description": "Pemotongan lapisan, laluan alat dan persediaan",
|
||||
"ui.workspace_planned": "Dirancang",
|
||||
"ui.workspace_render_description": "Pratonton produk, tetapan adegan dan imej render",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Kiezen…",
|
||||
"Circular": "Cirkelvormig",
|
||||
"Circumference": "Omtrek",
|
||||
"Clockwise": "Met de klok mee",
|
||||
"Close draft": "Concept sluiten",
|
||||
"Collisions": "Botsingen",
|
||||
"Colour": "Kleur",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "GESCHIEDENIS",
|
||||
"Height": "Hoogte",
|
||||
"Hidden edges": "Verborgen randen",
|
||||
"History layout": "Geschiedenisindeling",
|
||||
"Holes": "Gaten",
|
||||
"Holes (edges on them)": "Gaten (randen erop)",
|
||||
"Home view": "Beginweergave",
|
||||
@@ -232,7 +234,6 @@
|
||||
"No crash reports.": "Geen crashrapporten.",
|
||||
"No matching appearance presets.": "Geen overeenkomende uiterlijkvoorinstellingen.",
|
||||
"No matching appearances": "Geen overeenkomende uiterlijken",
|
||||
"No visible bodies to export.": "Geen zichtbare lichamen om te exporteren.",
|
||||
"None": "Geen",
|
||||
"Not available yet": "Nog niet beschikbaar",
|
||||
"Notch": "Inkeping",
|
||||
@@ -321,6 +322,7 @@
|
||||
"Search commands": "Opdrachten zoeken",
|
||||
"Search this library": "Deze bibliotheek doorzoeken",
|
||||
"Section": "Doorsnede",
|
||||
"Section position": "Positie van de doorsnede",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Overige selecteren: houd de linkermuisknop stil op het model (of klik met rechts › Overige selecteren…) om alles onder de cursor weer te geven; klik opnieuw op dezelfde plek om het volgende item daar te selecteren. Dubbelklik met de middelste knop om naar Home te gaan (Voorkeuren › Navigatie).",
|
||||
"Select bodies (a face picks its body)": "Lichamen selecteren (een vlak kiest zijn lichaam)",
|
||||
"Select bodies or faces instead": "In plaats daarvan lichamen of vlakken selecteren",
|
||||
@@ -332,6 +334,7 @@
|
||||
"Show Agent Cursor": "Agentcursor tonen",
|
||||
"Show in folder": "Tonen in map",
|
||||
"Show the pointer an AI agent moves (#35)": "De aanwijzer tonen die een AI-agent verplaatst (#35)",
|
||||
"Show tool at cursor": "Gereedschap bij de cursor tonen",
|
||||
"Signed axes": "Assen met teken",
|
||||
"Size": "Grootte",
|
||||
"Sketch constraints": "Schetsbeperkingen",
|
||||
@@ -344,9 +347,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft is gesloten voordat deze ontwerpen zijn opgeslagen. Hun laatste automatische opslag kan worden geopend.",
|
||||
"Specialise": "Specialiseren",
|
||||
"Speed": "Snelheid",
|
||||
"Square": "Vierkant",
|
||||
"Stand it up": "Rechtop zetten",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Rechtop zetten: een kwartslag om X, als één Verplaatsen-kenmerk dat u ongedaan kunt maken.",
|
||||
"Stand this part up?": "Dit onderdeel rechtop zetten?",
|
||||
"Start angle": "Beginhoek",
|
||||
"Start bulge": "Beginbolling",
|
||||
"Start continuity": "Begincontinuïteit",
|
||||
"Step": "Stap",
|
||||
@@ -411,6 +416,7 @@
|
||||
"angle, height": "hoek, hoogte",
|
||||
"appearance.dielectric_ior": "Diëlektrische IOR",
|
||||
"appearance.metallic": "Metaalachtig",
|
||||
"appearance.only_available": "Alleen beschikbare verschijningen",
|
||||
"appearance.pbr_help": "Bekijk dit oppervlak in Render of een product-PNG. Geen breking of transmissie.",
|
||||
"appearance.pbr_opaque": "Ondoorzichtige PBR",
|
||||
"appearance.roughness": "Ruwheid",
|
||||
@@ -529,6 +535,10 @@
|
||||
"cmd.file_insert_step": "STEP invoegen",
|
||||
"cmd.file_save_mesh": "Opslaan als mesh",
|
||||
"cmd.file_slicers": "Geïnstalleerde slicers",
|
||||
"cmd.frame_member": "Constructiedeel",
|
||||
"cmd.frame_profile_insert": "Profiel invoegen",
|
||||
"cmd.frame_profile_list": "Profielbibliotheek",
|
||||
"cmd.frame_profile_properties": "Profieleigenschappen",
|
||||
"cmd.inspect_accessibility": "Toegankelijkheidsanalyse",
|
||||
"cmd.inspect_center_of_mass": "Zwaartepunt",
|
||||
"cmd.inspect_clearance": "Speling",
|
||||
@@ -568,6 +578,7 @@
|
||||
"cmd.material_create": "Nieuw fysiek materiaal",
|
||||
"cmd.material_delete": "Fysiek materiaal verwijderen",
|
||||
"cmd.material_edit": "Fysiek materiaal bewerken",
|
||||
"cmd.material_inspect": "Fysiek materiaal inspecteren",
|
||||
"cmd.material_list": "Materialen weergeven",
|
||||
"cmd.material_properties_directional": "Richtingsafhankelijke elasticiteitsmodulus",
|
||||
"cmd.material_properties_validate": "Gegevens fysiek materiaal valideren",
|
||||
@@ -856,19 +867,57 @@
|
||||
"help.mit": "MIT-licentie",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Licenties van derden",
|
||||
"home.back": "Terug naar {name}",
|
||||
"home.crashes": "Crashrapporten",
|
||||
"home.design_workspace": "Ontwerp",
|
||||
"home.documentation": "Documentatie",
|
||||
"home.export": "Exporteren",
|
||||
"home.export_hint": "Kies een indeling voor het huidige ontwerp.",
|
||||
"home.export_json_hint": "Bewerkbaar ontwerp en opdrachtgeschiedenis",
|
||||
"home.export_mesh_hint": "Mesh om af te drukken of te delen",
|
||||
"home.export_step_hint": "Nauwkeurige volumegeometrie",
|
||||
"home.import": "Importeren…",
|
||||
"home.import_hint": "STEP, IGES of een mesh in een nieuw ontwerp",
|
||||
"home.in_this_browser": "in deze browser",
|
||||
"home.missing": "ontbreekt",
|
||||
"home.month_01": "jan.",
|
||||
"home.month_02": "feb.",
|
||||
"home.month_03": "mrt.",
|
||||
"home.month_04": "apr.",
|
||||
"home.month_05": "mei",
|
||||
"home.month_06": "jun.",
|
||||
"home.month_07": "jul.",
|
||||
"home.month_08": "aug.",
|
||||
"home.month_09": "sep.",
|
||||
"home.month_10": "okt.",
|
||||
"home.month_11": "nov.",
|
||||
"home.month_12": "dec.",
|
||||
"home.new_inches": "Nieuw ontwerp in inches",
|
||||
"home.new_inches_hint": "Inches · opent in Ontwerp",
|
||||
"home.new_mm": "Nieuw ontwerp",
|
||||
"home.new_mm_hint": "Millimeters · opent in Ontwerp",
|
||||
"home.open": "Openen…",
|
||||
"home.opened_days_ago": "{count} dagen geleden geopend",
|
||||
"home.opened_hours_ago": "{count} u geleden geopend",
|
||||
"home.opened_just_now": "Zojuist geopend",
|
||||
"home.opened_minutes_ago": "{count} min geleden geopend",
|
||||
"home.open_hint": "Een .solidcraft-ontwerp ({shortcut})",
|
||||
"home.open_now": "Momenteel geopend",
|
||||
"home.opened_days_ago": "{count} dagen geleden",
|
||||
"home.opened_hours_ago": "{count} u geleden",
|
||||
"home.opened_just_now": "Zojuist",
|
||||
"home.opened_minutes_ago": "{count} min geleden",
|
||||
"home.opening": "openen…",
|
||||
"home.properties": "Ontwerpeigenschappen",
|
||||
"home.recent": "Recent",
|
||||
"home.recent_empty": "Ontwerpen die u opent of opslaat, verschijnen hier.",
|
||||
"home.recovery_count": "Herstelbare ontwerpen: {count}",
|
||||
"home.recovery_empty": "Geen herstelbare ontwerpen.",
|
||||
"home.recovery_hint": "SolidCraft is gesloten voordat deze ontwerpen werden opgeslagen.",
|
||||
"home.report_issue": "Probleem melden",
|
||||
"home.samples": "Voorbeelden",
|
||||
"home.settings": "Instellingen",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Sneltoetsen",
|
||||
"home.start_hint": "Maak een ontwerp, open een recent bestand of bekijk een voorbeeld.",
|
||||
"home.whats_new": "Wat is er nieuw",
|
||||
"home.yesterday": "Gisteren",
|
||||
"install.button_tip": "Installeer SolidCraft als app: deze opent in een eigen venster en werkt offline",
|
||||
"install.dock_1": "Kies Bestand › Toevoegen aan Dock.",
|
||||
"install.dock_2": "Klik op Voeg toe.",
|
||||
@@ -1021,7 +1070,7 @@
|
||||
"search.try": "Typ een opdrachtnaam, een kenmerk in dit ontwerp of een parameter. Om een opdracht met waarden uit te voeren, typt u de id en een JSON-object: {example}",
|
||||
"search.valid": "Geldig",
|
||||
"settings.display_quality_note": "Fijnere niveaus tekenen gebogen vlakken en randen vloeiender en kosten meer tijd om te meshen. Alleen de viewport verandert, nooit het ontwerp of de exports.",
|
||||
"settings.frame_rate_note": "Toont frames per seconde in de rechteronderhoek van de weergave. Zolang dit aan staat, wordt de weergave continu opnieuw getekend.",
|
||||
"settings.frame_rate_note": "Toont frames per seconde in de rechteronderhoek van de weergave.",
|
||||
"settings.measure_axis_distances": "X/Y/Z-afstanden tonen",
|
||||
"settings.measure_axis_distances_help": "Toon de componentmaten langs de wereldassen naast de totale afstand terwijl Meten open is.",
|
||||
"settings.msaa_applies_on_restart": "Anti-aliasing wordt toegepast bij de volgende start van SolidCraft",
|
||||
@@ -1082,7 +1131,50 @@
|
||||
"trace.stopped_by_contact": "Eindigt bij monster {sample}: {a} zou door {b} heen gaan",
|
||||
"trace.stopped_by_loop": "Eindigt bij monster {sample}: de lus via {joint} kan niet verder sluiten",
|
||||
"ui.1_degree_of_freedom": "1 vrijheidsgraad",
|
||||
"ui.3d_mouse_calibrate": "Kalibreren…",
|
||||
"ui.3d_mouse_calibrate_tip": "Beweeg de dop zoals het venster vraagt, zodat SolidCraft leert welke kant elke as van deze 3D-muis op gaat en hoe ver. Opgeslagen voor deze 3D-muis.",
|
||||
"ui.3d_mouse_calibrate_title": "3D-muis kalibreren",
|
||||
"ui.3d_mouse_calibrate_wait": "Beweeg de 3D-muis één keer, zodat SolidCraft hem ziet.",
|
||||
"ui.3d_mouse_calibrated": "Gekalibreerd",
|
||||
"ui.3d_mouse_calibration": "Kalibratie van 3D-muis",
|
||||
"ui.3d_mouse_calibration_done": "Gereed. Sla op om het te gebruiken wanneer deze 3D-muis is aangesloten.",
|
||||
"ui.3d_mouse_calibration_no_response": "Een as bewoog nauwelijks en kan dus niet worden gekalibreerd. Begin opnieuw en beweeg de dop telkens verder.",
|
||||
"ui.3d_mouse_circles_help": "De cirkels tonen wat het apparaat na kalibratie stuurt. Het testmodel toont wat de weergave ermee doet, na omkeren, snelheid en asindeling.",
|
||||
"ui.3d_mouse_dead_zone": "Dode zone",
|
||||
"ui.3d_mouse_dead_zone_tip": "Hoe ver de dop moet bewegen voordat de weergave dat doet, als aandeel van de volledige slag. Verhoog dit als de weergave afdrijft terwijl de dop in rust is.",
|
||||
"ui.3d_mouse_denied": "SolidCraft ziet een 3D-muis maar mag hem niet lezen. Zie de installatienotities voor de 3D-muis voor de udev-regel.",
|
||||
"ui.3d_mouse_denied_short": "Gevonden, maar niet leesbaar",
|
||||
"ui.3d_mouse_driver_fallback": "Voorlopig via spacenavd gelezen: het apparaat zelf kon niet worden geopend.",
|
||||
"ui.3d_mouse_forget": "Kalibratie vergeten",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport links",
|
||||
"ui.3d_mouse_ghost_left_window": "Venster links",
|
||||
"ui.3d_mouse_ghost_position": "Positie",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport rechts",
|
||||
"ui.3d_mouse_hint": "Verplaats en roteer de weergave samen; schetsen gebruiken alleen pannen en zoomen.",
|
||||
"ui.3d_mouse_navigate": "Navigeren met de 3D-muis",
|
||||
"ui.3d_mouse_none": "Geen 3D-muis gevonden",
|
||||
"ui.3d_mouse_not_calibrated": "Niet gekalibreerd",
|
||||
"ui.3d_mouse_offer": "Je 3D-muis kalibreren verhelpt verwisselde of omgekeerde assen en stelt snelheid en gevoel in.",
|
||||
"ui.3d_mouse_offer_never": "Niet meer tonen",
|
||||
"ui.3d_mouse_offer_swap": "Naar Hidraw wisselen (direct)",
|
||||
"ui.3d_mouse_reading": "Uitlezen",
|
||||
"ui.3d_mouse_reset_view": "Weergave resetten",
|
||||
"ui.3d_mouse_restore": "3D-muis standaardwaarden herstellen",
|
||||
"ui.3d_mouse_restore_ask": "De 3D-muisinstellingen terugzetten naar de standaardwaarden? Je kalibratie en knoppentoewijzing blijven staan.",
|
||||
"ui.3d_mouse_speed": "Snelheid van 3D-muis",
|
||||
"ui.3d_mouse_step_lift": "Til de dop omhoog, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_press": "Druk de dop omlaag, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_pull": "Trek de dop naar u toe, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_push": "Duw de dop van u af, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_rest": "Haal uw hand van de dop en laat hem stilstaan.",
|
||||
"ui.3d_mouse_step_slide_left": "Schuif de dop naar links, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_slide_right": "Schuif de dop naar rechts, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_tilt_left": "Kantel de dop naar links (linkerrand omlaag), houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_tilt_right": "Kantel de dop naar rechts (rechterrand omlaag), houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Draai de dop met de klok mee, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
|
||||
"ui.3d_mouse_waiting": "Wacht op beweging",
|
||||
"ui.3d_mouse_wiggle": "Als je er een hebt aangesloten, beweeg hem even.",
|
||||
"ui.3d_sketch": "3D-schets",
|
||||
"ui.activate": "Activeren",
|
||||
"ui.active_component": "Actief component",
|
||||
@@ -1092,6 +1184,7 @@
|
||||
"ui.agent_chat_add": "Bestanden bijvoegen",
|
||||
"ui.agent_chat_add_file": "Een bestand kiezen…",
|
||||
"ui.agent_chat_allow": "Toestaan",
|
||||
"ui.agent_chat_allow_once": "Eenmalig toestaan",
|
||||
"ui.agent_chat_allowed": "Toegestaan",
|
||||
"ui.agent_chat_answered": "Antwoord: {option}",
|
||||
"ui.agent_chat_approval": "De agent dit laten doen?",
|
||||
@@ -1143,12 +1236,16 @@
|
||||
"ui.agent_chat_files_left_out": "Maximaal {max} bestanden per bericht; weggelaten: {files}",
|
||||
"ui.agent_chat_files_loading": "Wachten tot de bestanden zijn gelezen",
|
||||
"ui.agent_chat_files_max": "Maximaal {max} bestanden per bericht",
|
||||
"ui.agent_chat_flatpak_none_found": "Geen codeeragent gevonden. De Flatpak-sandbox ziet standaard geen agents die in je persoonlijke map zijn geïnstalleerd en heeft geen netwerktoegang. Voer dit commando uit in een terminal en start SolidCraft daarna opnieuw om Claude Code en de ACP-adapter toe te staan die via nvm of in ~/.local/bin zijn geïnstalleerd. Het geeft toegang tot de genoemde programma’s en aanmeldbestanden, plus netwerktoegang:",
|
||||
"ui.agent_chat_import_failed": "{name}: importeren mislukt: {error}",
|
||||
"ui.agent_chat_intro": "Chat met een codeeragent die u al gebruikt, of met een modeleindpunt naar keuze. De agent werkt aan het geopende ontwerp via de tools van SolidCraft: de wijzigingen verschijnen terwijl ze worden gemaakt en worden per bericht als één stap ongedaan gemaakt. Wat hij zonder te vragen mag doen, stelt u in via Voorkeuren > AI.",
|
||||
"ui.agent_chat_key_in_env": "sleutel ingesteld",
|
||||
"ui.agent_chat_left": "Links",
|
||||
"ui.agent_chat_limit": "De agent heeft de limiet van {limit} wijzigingen voor één bericht bereikt. Stuur nog een bericht om hem verder te laten gaan.",
|
||||
"ui.agent_chat_minimize": "Minimaliseren tot een strook",
|
||||
"ui.agent_chat_mode_all": "Alles",
|
||||
"ui.agent_chat_mode_ask": "Vragen",
|
||||
"ui.agent_chat_mode_edits": "Bewerken",
|
||||
"ui.agent_chat_model_tip": "Het model dat de agent gebruikt. Uw keuze wordt voor deze agent onthouden.",
|
||||
"ui.agent_chat_new": "Nieuwe chat",
|
||||
"ui.agent_chat_new_confirm": "Een nieuwe chat starten? De huidige wordt gewist.",
|
||||
@@ -1161,6 +1258,7 @@
|
||||
"ui.agent_chat_none_found_short": "Geen agent gevonden",
|
||||
"ui.agent_chat_not_installed": "Niet geïnstalleerd",
|
||||
"ui.agent_chat_not_signed_in": "niet aangemeld",
|
||||
"ui.agent_chat_open_settings": "Voorkeuren openen",
|
||||
"ui.agent_chat_own_window": "Eigen venster",
|
||||
"ui.agent_chat_paste_picture": "Een afbeelding plakken",
|
||||
"ui.agent_chat_permission": "De agent vraagt om uit te voeren: {title}",
|
||||
@@ -1185,6 +1283,8 @@
|
||||
"ui.agent_chat_send": "Verzenden",
|
||||
"ui.agent_chat_send_now": "Nu verzenden",
|
||||
"ui.agent_chat_send_now_tip": "De huidige stap van de agent stoppen en dit nu verzenden (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft communiceert met een programmeeragent die op deze computer is geïnstalleerd. Kies er een via Voorkeuren › AI.",
|
||||
"ui.agent_chat_setup_title": "Een assistent instellen",
|
||||
"ui.agent_chat_side": "Zijde van agentchat",
|
||||
"ui.agent_chat_signed_in": "aangemeld",
|
||||
"ui.agent_chat_state_auth": "Aanmelden vereist",
|
||||
@@ -1201,6 +1301,7 @@
|
||||
"ui.agent_chat_undo_label": "Agentchat: {message}",
|
||||
"ui.agent_chat_undone": "(ongedaan gemaakt)",
|
||||
"ui.agent_chat_view_attached": "Weergave bijgevoegd",
|
||||
"ui.agent_chat_waiting": "Wacht op jou",
|
||||
"ui.agent_cursor": "Agentcursor",
|
||||
"ui.agent_cursor_speed": "Snelheid agentcursor",
|
||||
"ui.ai_add": "Toevoegen",
|
||||
@@ -1258,7 +1359,6 @@
|
||||
"ui.ai_permissions": "Machtigingen",
|
||||
"ui.ai_preset": "Voorinstelling",
|
||||
"ui.ai_remove": "Verwijderen",
|
||||
"ui.ai_request_limit": "gestopt na 24 verzoeken voor één bericht; stuur nog een bericht om verder te gaan",
|
||||
"ui.ai_reset": "Herstellen",
|
||||
"ui.ai_settings": "AI-instellingen…",
|
||||
"ui.ai_show_features": "AI-functies tonen",
|
||||
@@ -1455,6 +1555,7 @@
|
||||
"ui.grid_follows_view_hint": "Zijaanzichten tonen het raster op het rechtopstaande vlak dat naar hen toe gericht is in plaats van op de grond",
|
||||
"ui.ground_shadow": "Grondschaduw",
|
||||
"ui.groupSelected": "Selectie groeperen",
|
||||
"ui.hatch": "Arcering",
|
||||
"ui.heading_add_ins": "INVOEGMODULES",
|
||||
"ui.heading_assemble": "SAMENSTELLEN",
|
||||
"ui.heading_box": "KUB",
|
||||
@@ -1528,6 +1629,11 @@
|
||||
"ui.hideOrigin": "Oorsprong verbergen",
|
||||
"ui.hidePlanes": "Vlakken verbergen",
|
||||
"ui.hideProfile": "Profiel verbergen",
|
||||
"ui.history_back": "Eén stap terug",
|
||||
"ui.history_below_browser": "Onder de browser",
|
||||
"ui.history_first": "Naar het begin",
|
||||
"ui.history_forward": "Eén stap vooruit",
|
||||
"ui.history_last": "Naar het einde",
|
||||
"ui.history_title": "Geschiedenis",
|
||||
"ui.hole": "Gat",
|
||||
"ui.hole_tap_type": "Type tapgat",
|
||||
@@ -1545,6 +1651,8 @@
|
||||
"ui.joints": "Verbindingen",
|
||||
"ui.keyboard_shortcuts": "Sneltoetsen",
|
||||
"ui.language": "Taal",
|
||||
"ui.layout_compact": "Compact",
|
||||
"ui.layout_floating": "Zwevend",
|
||||
"ui.length_decimals": "Lengtedecimalen",
|
||||
"ui.light": "Licht",
|
||||
"ui.line": "Lijn",
|
||||
@@ -1682,6 +1790,7 @@
|
||||
"ui.reverse_zoom_direction": "Zoomrichting omkeren",
|
||||
"ui.revolve": "Revolve",
|
||||
"ui.ribbon_exit": "Afsluiten",
|
||||
"ui.ribbon_labels": "Tekst onder lintpictogrammen tonen",
|
||||
"ui.ribbon_pin_footer": "Vastgepinde opdrachten verschijnen in het lint. Beweeg over een rij om vast te pinnen of los te maken.",
|
||||
"ui.ribbon_workspace": "Werkruimte",
|
||||
"ui.right": "Rechts",
|
||||
@@ -1737,34 +1846,6 @@
|
||||
"ui.smooth_wheel_zoom": "Vloeiend zoomen met wiel",
|
||||
"ui.smooth_wheel_zoom_tip": "Uitschakelen om elke wielgebeurtenis direct toe te passen, zonder uitloop",
|
||||
"ui.snap": "Snap",
|
||||
"ui.space_mouse_calibrate": "Kalibreren…",
|
||||
"ui.space_mouse_calibrate_tip": "Beweeg de dop zoals het venster vraagt, zodat SolidCraft leert welke kant elke as van deze 3D-muis op gaat en hoe ver. Opgeslagen voor deze 3D-muis.",
|
||||
"ui.space_mouse_calibrate_title": "3D-muis kalibreren",
|
||||
"ui.space_mouse_calibrated": "Gekalibreerd",
|
||||
"ui.space_mouse_calibration": "Kalibratie van 3D-muis",
|
||||
"ui.space_mouse_calibration_done": "Gereed. Sla op om het te gebruiken wanneer deze 3D-muis is aangesloten.",
|
||||
"ui.space_mouse_calibration_no_response": "Een as bewoog nauwelijks en kan dus niet worden gekalibreerd. Begin opnieuw en beweeg de dop telkens verder.",
|
||||
"ui.space_mouse_dead_zone": "Dode zone",
|
||||
"ui.space_mouse_dead_zone_tip": "Hoe ver de dop moet bewegen voordat de weergave dat doet, als aandeel van de volledige slag. Verhoog dit als de weergave afdrijft terwijl de dop in rust is.",
|
||||
"ui.space_mouse_forget": "Kalibratie vergeten",
|
||||
"ui.space_mouse_hint": "Verplaats en roteer de weergave samen; schetsen gebruiken alleen pannen en zoomen.",
|
||||
"ui.space_mouse_navigate": "Navigeren met SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Geen 3D-muis gevonden",
|
||||
"ui.space_mouse_not_calibrated": "Niet gekalibreerd",
|
||||
"ui.space_mouse_reading": "Uitlezen",
|
||||
"ui.space_mouse_step_lift": "Til de dop omhoog, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_press": "Druk de dop omlaag, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_pull": "Trek de dop naar u toe, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_push": "Duw de dop van u af, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_rest": "Haal uw hand van de dop en laat hem stilstaan.",
|
||||
"ui.space_mouse_step_slide_left": "Schuif de dop naar links, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_slide_right": "Schuif de dop naar rechts, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_back": "Kantel de dop naar achteren (voorrand omhoog), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_forward": "Kantel de dop naar voren (voorrand omlaag), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_left": "Kantel de dop naar links (linkerrand omlaag), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_tilt_right": "Kantel de dop naar rechts (rechterrand omlaag), houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Draai de dop met de klok mee, houd vast en laat dan los.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
|
||||
"ui.spacing": "Tussenruimte",
|
||||
"ui.spacing_2": "Tussenruimte 2",
|
||||
"ui.sphere": "Bol",
|
||||
@@ -1836,6 +1917,7 @@
|
||||
"ui.workspace_design_tabs": "Model · Oppervlak · Assembleren · Invoegen · Inspecteren · Beheren · Beeld",
|
||||
"ui.workspace_drawing_description": "2D-tekeningen met weergaven, maatvoering en titelblokken",
|
||||
"ui.workspace_footer": "Elke werkruimte werkt op hetzelfde ontwerp, dezelfde geschiedenis en selectie; alleen de lintabbladen veranderen. De schakelaar is vergrendeld terwijl een schets of kenmerk wordt bewerkt.",
|
||||
"ui.workspace_layout": "Werkruimte-indeling",
|
||||
"ui.workspace_manufacture_description": "Slicen, gereedschapspaden en opstelling",
|
||||
"ui.workspace_planned": "Gepland",
|
||||
"ui.workspace_render_description": "Productvoorbeeld, sceneinstellingen en gerenderde afbeeldingen",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Pending UI strings
|
||||
|
||||
A pull request that adds UI strings adds **one new file here** instead of editing
|
||||
`assets/locales/<locale>.json`. Two pull requests then never touch the same file, so they never
|
||||
conflict.
|
||||
|
||||
- Name it after the change, lowercase with dashes: `sketch-trim-hints.json`.
|
||||
- Contents: a map from locale code to `{"message id": "text"}`. Give English; add other locales
|
||||
you can translate (a locale you leave out falls back to English, as before):
|
||||
|
||||
```json
|
||||
{
|
||||
"en": { "sketch.trim.hint": "Click a segment to trim it" },
|
||||
"de": { "sketch.trim.hint": "Segment zum Kürzen anklicken" }
|
||||
}
|
||||
```
|
||||
|
||||
- The id must not already be in that locale's catalog, nor in another pending file; the build and
|
||||
`cargo xtask lists` fail otherwise. To change an existing message, edit the catalog itself.
|
||||
- Don't sort or fold anything. The app, `cargo xtask i18n` and the tests read these files as part of
|
||||
the catalogs, so your strings work in your own pull request.
|
||||
- After a merge, the Generated files workflow runs `cargo xtask fold-pending` on `main`: it adds the
|
||||
messages to the catalogs (sorted, one per line) and deletes the files. Never run it in a pull
|
||||
request.
|
||||
@@ -28,6 +28,7 @@
|
||||
"Also deletes": "Usuwa również",
|
||||
"Angle 2": "Kąt 2",
|
||||
"Animate": "Animuj",
|
||||
"App/system information and command settings are included. Review private details before sharing.": "Dołączone są informacje o aplikacji i systemie oraz ustawienia poleceń. Przed udostępnieniem sprawdź, czy nie ma tam prywatnych danych.",
|
||||
"Appearance name": "Nazwa wyglądu",
|
||||
"Applied Radius": "Zastosowany promień",
|
||||
"Apply To": "Zastosuj do",
|
||||
@@ -72,6 +73,7 @@
|
||||
"Choose…": "Wybierz…",
|
||||
"Circular": "Kołowy",
|
||||
"Circumference": "Obwód",
|
||||
"Clockwise": "Zgodnie z ruchem wskazówek zegara",
|
||||
"Close draft": "Zamknij szkic zgłoszenia",
|
||||
"Collisions": "Kolizje",
|
||||
"Colour": "Kolor",
|
||||
@@ -83,6 +85,7 @@
|
||||
"Contact": "Kontakt",
|
||||
"Contact Sets": "Zestawy kontaktów",
|
||||
"Contact sets": "Zestawy kontaktów",
|
||||
"Continue on GitHub": "Kontynuuj na GitHubie",
|
||||
"Continuity": "Ciągłość",
|
||||
"Copy Error Message": "Kopiuj komunikat błędu",
|
||||
"Copy Warning Message": "Kopiuj komunikat ostrzeżenia",
|
||||
@@ -102,6 +105,7 @@
|
||||
"Curv. Comb": "Grzebień krzyw.",
|
||||
"Curvature (G2)": "Krzywizna (G2)",
|
||||
"Curvature change (G3)": "Zmiana krzywizny (G3)",
|
||||
"Custom": "Niestandardowy",
|
||||
"Customize Toolbar": "Dostosuj pasek narzędzi",
|
||||
"Delete": "Usuń",
|
||||
"Delete all": "Usuń wszystko",
|
||||
@@ -110,6 +114,8 @@
|
||||
"Delete the set": "Usuń zestaw",
|
||||
"Density": "Gęstość",
|
||||
"Dependents": "Elementy zależne",
|
||||
"Describe the problem you saw": "Opisz zauważony problem",
|
||||
"Describe what happened before continuing.": "Zanim przejdziesz dalej, opisz, co się stało.",
|
||||
"Designs you open or save appear here.": "Tutaj pojawiają się otwierane lub zapisywane projekty.",
|
||||
"Determined by To Object. Choose Angle to edit the angular extent.": "Wynika z opcji Do obiektu. Wybierz Kąt, aby edytować zakres kątowy.",
|
||||
"Direction": "Kierunek",
|
||||
@@ -127,6 +133,7 @@
|
||||
"Enabled": "Włączone",
|
||||
"End bulge": "Wybrzuszenie końca",
|
||||
"End continuity": "Ciągłość końca",
|
||||
"Expected result (optional)": "Oczekiwany wynik (opcjonalnie)",
|
||||
"Explode": "Rozstrzel",
|
||||
"Explode Text": "Rozbij tekst",
|
||||
"Export Animation…": "Eksportuj animację…",
|
||||
@@ -167,6 +174,8 @@
|
||||
"HISTORY": "HISTORIA",
|
||||
"Height": "Wysokość",
|
||||
"Hidden edges": "Krawędzie niewidoczne",
|
||||
"History layout": "Układ historii",
|
||||
"History names": "Nazwy w historii",
|
||||
"Holes": "Otwory",
|
||||
"Holes (edges on them)": "Otwory (krawędzie na nich)",
|
||||
"Home view": "Widok główny",
|
||||
@@ -232,7 +241,8 @@
|
||||
"No crash reports.": "Brak raportów o awariach.",
|
||||
"No matching appearance presets.": "Brak pasujących ustawień wyglądu.",
|
||||
"No matching appearances": "Brak pasujących wyglądów",
|
||||
"No visible bodies to export.": "Brak widocznych brył do wyeksportowania.",
|
||||
"No visible bodies or sketches to export.": "Brak widocznych brył lub szkiców do wyeksportowania.",
|
||||
"Nominal diameter": "Średnica nominalna",
|
||||
"None": "Brak",
|
||||
"Not available yet": "Jeszcze niedostępne",
|
||||
"Notch": "Wycięcie",
|
||||
@@ -241,7 +251,10 @@
|
||||
"Open GitHub draft": "Otwórz szkic zgłoszenia na GitHubie",
|
||||
"Open local libraries in the desktop app. You can export this design's library here.": "Otwórz biblioteki lokalne w aplikacji komputerowej. Tutaj możesz wyeksportować bibliotekę tego projektu.",
|
||||
"Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Otwartoźródłowy parametryczny CAD 3D w języku Rust. Licencja MIT OR Apache-2.0.",
|
||||
"Opens your browser and copies the report text for you to paste into GitHub.": "Otwiera przeglądarkę internetową i kopiuje treść raportu, którą wkleisz na GitHubie.",
|
||||
"Opens your browser. Review and submit your report on GitHub.": "Otwiera przeglądarkę internetową. Sprawdź raport i wyślij go na GitHubie.",
|
||||
"Operator": "Operator",
|
||||
"Orbit (right drag)": "Obracanie widoku (przeciąganie prawym przyciskiem)",
|
||||
"Other side": "Druga strona",
|
||||
"Other surface (mutual trim)": "Druga powierzchnia (wzajemne przycięcie)",
|
||||
"Outside": "Na zewnątrz",
|
||||
@@ -256,6 +269,7 @@
|
||||
"Pick the tool bodies": "Wybierz bryły narzędziowe",
|
||||
"Picked": "Wybrane",
|
||||
"Picked point": "Wybrany punkt",
|
||||
"Pitch": "Skok",
|
||||
"Place at the origin instead": "Zamiast tego umieść w początku układu",
|
||||
"Plastic Rules": "Reguły tworzyw",
|
||||
"Playback": "Odtwarzanie",
|
||||
@@ -289,6 +303,8 @@
|
||||
"Remove the track": "Usuń ścieżkę",
|
||||
"Remove this point": "Usuń ten punkt",
|
||||
"Report a bug — review a screenshot and open a GitHub draft": "Zgłoś błąd — sprawdź zrzut ekranu i otwórz szkic zgłoszenia na GitHubie",
|
||||
"Report text copied. Paste it into GitHub and submit your report there.": "Skopiowano treść raportu. Wklej ją na GitHubie i tam wyślij raport.",
|
||||
"Reports are public. A GitHub account is required.": "Raporty są publiczne. Wymagane jest konto GitHub.",
|
||||
"Reset Toolbar": "Przywróć pasek narzędzi",
|
||||
"Resize Part": "Zmień rozmiar części",
|
||||
"Restore View": "Przywróć widok",
|
||||
@@ -303,6 +319,7 @@
|
||||
"Revert Pos.": "Przywróć poł.",
|
||||
"Revert Position": "Przywróć położenie",
|
||||
"Review the report and screenshot before opening GitHub.": "Przed otwarciem GitHuba sprawdź raport i zrzut ekranu.",
|
||||
"Review your report on GitHub and click Submit new issue to publish it.": "Sprawdź raport na GitHubie i kliknij Submit new issue, aby go opublikować.",
|
||||
"Rim Edge": "Krawędź obrzeża",
|
||||
"Roll History Marker Here": "Przesuń tutaj znacznik historii",
|
||||
"Round": "Okrągłe",
|
||||
@@ -313,14 +330,17 @@
|
||||
"SURFACE QUALITY": "JAKOŚĆ POWIERZCHNI",
|
||||
"Save Rule": "Zapisz regułę",
|
||||
"Save View": "Zapisz widok",
|
||||
"Save screenshot…": "Zapisz zrzut ekranu…",
|
||||
"Save the study as an animated GIF, seen as the viewport shows it": "Zapisz badanie jako animowany GIF, tak jak widać je w widoku",
|
||||
"Saved preview. Geometry is being checked before editing.": "Zapisany podgląd. Geometria jest sprawdzana przed edycją.",
|
||||
"Scale factor": "Współczynnik skali",
|
||||
"Scene": "Scena",
|
||||
"Screenshot (optional)": "Zrzut ekranu (opcjonalnie)",
|
||||
"Search": "Szukaj",
|
||||
"Search commands": "Szukaj poleceń",
|
||||
"Search this library": "Przeszukaj tę bibliotekę",
|
||||
"Section": "Przekrój",
|
||||
"Section position": "Położenie przekroju",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Wybierz inny: przytrzymaj nieruchomo lewy przycisk na modelu (lub prawy przycisk › Wybierz inny…), aby wyświetlić wszystko pod kursorem; kliknij ponownie w tym samym miejscu, aby zaznaczyć kolejny obiekt. Dwukrotne kliknięcie środkowym przyciskiem przechodzi do widoku głównego (Preferencje › Nawigacja).",
|
||||
"Select bodies (a face picks its body)": "Zaznacz bryły (ściana wybiera swoją bryłę)",
|
||||
"Select bodies or faces instead": "Zamiast tego zaznacz bryły lub ściany",
|
||||
@@ -332,6 +352,7 @@
|
||||
"Show Agent Cursor": "Pokaż kursor agenta",
|
||||
"Show in folder": "Pokaż w folderze",
|
||||
"Show the pointer an AI agent moves (#35)": "Pokaż wskaźnik poruszany przez agenta AI (#35)",
|
||||
"Show tool at cursor": "Pokaż narzędzie przy kursorze",
|
||||
"Signed axes": "Osie ze znakiem",
|
||||
"Size": "Rozmiar",
|
||||
"Sketch constraints": "Wiązania szkicu",
|
||||
@@ -344,24 +365,30 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft zamknął się, zanim te projekty zostały zapisane. Można otworzyć ich ostatni autozapis.",
|
||||
"Specialise": "Uszczegółów",
|
||||
"Speed": "Prędkość",
|
||||
"Square": "Kwadrat",
|
||||
"Stand it up": "Postaw",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Postaw: obrót o ćwierć obrotu wokół osi X, jako jedna funkcja Przesuń, którą można cofnąć.",
|
||||
"Stand this part up?": "Postawić tę część?",
|
||||
"Start angle": "Kąt początkowy",
|
||||
"Start bulge": "Wybrzuszenie początku",
|
||||
"Start continuity": "Ciągłość początku",
|
||||
"Step": "Krok",
|
||||
"Steps": "Kroki",
|
||||
"Steps to reproduce (optional)": "Kroki do odtworzenia (opcjonalnie)",
|
||||
"Stitch the kept pieces": "Zszyj zachowane części",
|
||||
"Stitch to both bodies": "Zszyj z obiema bryłami",
|
||||
"Study": "Badanie",
|
||||
"Style": "Styl",
|
||||
"Summary": "Podsumowanie",
|
||||
"Suppressed": "Wygaszone",
|
||||
"Surface": "Powierzchnia",
|
||||
"Swap Ends": "Zamień końce",
|
||||
"TRIM": "PRZYCINANIE",
|
||||
"Tangent (G1)": "Styczność (G1)",
|
||||
"Tangent Chain": "Łańcuch styczny",
|
||||
"Tap drill diameter": "Średnica wiertła pod gwint",
|
||||
"Tear": "Rozdarcie",
|
||||
"Technical details": "Szczegóły techniczne",
|
||||
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "Przerywany szkic to zapisana płaszczyzna pomocnicza. Wybierz nową ścianę, aby naprawić zaczepienie.",
|
||||
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Brak zapisanego wystąpienia autora. Przed ponownym powiązaniem wybierz prawidłowe położenie autora.",
|
||||
"Then": "Wtedy",
|
||||
@@ -373,6 +400,7 @@
|
||||
"This child is shared. Deleting it removes it from every copy of its parent.": "Ten element podrzędny jest współdzielony. Usunięcie go usuwa go z każdej kopii elementu nadrzędnego.",
|
||||
"This child is shared. Position changes apply to every copy of its parent.": "Ten element podrzędny jest współdzielony. Zmiany położenia dotyczą każdej kopii elementu nadrzędnego.",
|
||||
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "Ta ściana nie zapewnia trwałego zaczepienia. Wybierz inną ścianę lub jawnie użyj stałej płaszczyzny.",
|
||||
"This image is not attached to GitHub yet. Copy or save it, then attach it on GitHub.": "Ten obraz nie jest jeszcze dołączony na GitHubie. Skopiuj go lub zapisz, a potem dołącz na GitHubie.",
|
||||
"To Offset": "Do odsunięcia",
|
||||
"To edges": "Do krawędzi",
|
||||
"Tolerance": "Tolerancja",
|
||||
@@ -398,6 +426,9 @@
|
||||
"Wall": "Ścianka",
|
||||
"Wall Location": "Położenie ścianki",
|
||||
"Wall Thickness": "Grubość ścianki",
|
||||
"What did you do just before the problem? A short list is enough": "Co robiłeś tuż przed wystąpieniem problemu? Wystarczy krótka lista",
|
||||
"What did you expect to see?": "Czego się spodziewałeś?",
|
||||
"What happened?": "Co się stało?",
|
||||
"When": "Gdy",
|
||||
"Width": "Szerokość",
|
||||
"Will fail": "Nie powiedzie się",
|
||||
@@ -408,9 +439,33 @@
|
||||
"Z angle": "Kąt Z",
|
||||
"Z distance": "Odległość Z",
|
||||
"Zebra": "Zebra",
|
||||
"ai.prefs.availability": "Sterowanie asystentem",
|
||||
"ai.prefs.background_commands": "Uruchamiaj polecenia agenta w tle",
|
||||
"ai.prefs.follow_camera": "Obracaj kamerę do szkiców",
|
||||
"ai.prefs.follow_camera_off": "Automatyczne podążanie kamery jest wyłączone.",
|
||||
"ai.prefs.follow_camera_on": "Kamera obraca się przodem do szkiców tworzonych przez agenta.",
|
||||
"ai.prefs.intro": "Agenty, uprawnienia i sposób pokazywania ich pracy.",
|
||||
"ai.prefs.make_default": "Używaj domyślnie",
|
||||
"ai.prefs.no_agents": "Brak agentów. Dodaj agenta lub odśwież, aby wyszukać zainstalowane agenty.",
|
||||
"ai.prefs.no_saved_approvals": "Brak zapisanych zgód.",
|
||||
"ai.prefs.override_all_warning": "Ten agent może zapisywać, nadpisywać, eksportować i zastępować projekt bez pytania.",
|
||||
"ai.prefs.permission_all": "Zezwalaj na wszystko",
|
||||
"ai.prefs.permission_all_summary": "Dostępne narzędzia SolidCraft działają bez pytania.",
|
||||
"ai.prefs.permission_ask": "Pytaj przed zmianami",
|
||||
"ai.prefs.permission_ask_summary": "Zatwierdzaj każdą zmianę projektu i każdą operację na plikach.",
|
||||
"ai.prefs.permission_edits": "Zezwalaj na edycję modelu",
|
||||
"ai.prefs.permission_edits_summary": "Edycja modelu działa bez pytania; operacje na plikach i nieodwracalne nadal wymagają pytania.",
|
||||
"ai.prefs.permission_scope": "Dotyczy tylko narzędzi SolidCraft. Powłoka agenta i edycja plików podlegają jego własnym uprawnieniom.",
|
||||
"ai.prefs.pointer_preview_hidden": "Wskaźnik ukryty",
|
||||
"ai.prefs.pointer_preview_speed": "Wskaźnik agenta · {speed}",
|
||||
"ai.prefs.pointer_speed": "Szybkość ruchu",
|
||||
"ai.prefs.privacy_note": "Rozmowę otrzymuje tylko wybrany przez Ciebie agent lub punkt końcowy. Klucze punktów końcowych są przechowywane w systemowym pęku kluczy.",
|
||||
"ai.prefs.show_pointer": "Pokaż wskaźnik agenta",
|
||||
"ai.prefs.turn_limit_hint": "Maksymalna liczba wywołań narzędzi zmieniających projekt, które asystent może wykonać dla jednej wiadomości.",
|
||||
"angle, height": "kąt, wysokość",
|
||||
"appearance.dielectric_ior": "Współczynnik załamania dielektryka (IOR)",
|
||||
"appearance.metallic": "Metaliczność",
|
||||
"appearance.only_available": "Tylko dostępne wyglądy",
|
||||
"appearance.pbr_help": "Zobacz tę powierzchnię w renderowaniu lub w obrazie PNG produktu. Bez załamania i przepuszczania światła.",
|
||||
"appearance.pbr_opaque": "Nieprzezroczysty PBR",
|
||||
"appearance.roughness": "Chropowatość",
|
||||
@@ -529,6 +584,10 @@
|
||||
"cmd.file_insert_step": "Wstaw STEP",
|
||||
"cmd.file_save_mesh": "Zapisz jako siatkę",
|
||||
"cmd.file_slicers": "Zainstalowane slicery",
|
||||
"cmd.frame_member": "Element ramy",
|
||||
"cmd.frame_profile_insert": "Wstaw profil",
|
||||
"cmd.frame_profile_list": "Biblioteka profili",
|
||||
"cmd.frame_profile_properties": "Właściwości profilu",
|
||||
"cmd.inspect_accessibility": "Analiza dostępności",
|
||||
"cmd.inspect_center_of_mass": "Środek masy",
|
||||
"cmd.inspect_clearance": "Prześwit",
|
||||
@@ -568,6 +627,7 @@
|
||||
"cmd.material_create": "Nowy materiał fizyczny",
|
||||
"cmd.material_delete": "Usuń materiał fizyczny",
|
||||
"cmd.material_edit": "Edytuj materiał fizyczny",
|
||||
"cmd.material_inspect": "Sprawdź materiał fizyczny",
|
||||
"cmd.material_list": "Lista materiałów",
|
||||
"cmd.material_properties_directional": "Kierunkowy moduł sprężystości",
|
||||
"cmd.material_properties_validate": "Sprawdź dane materiału fizycznego",
|
||||
@@ -758,6 +818,7 @@
|
||||
"cmd.view_graphics_preset": "Ustawienie graficzne",
|
||||
"cmd.view_list": "Lista widoków nazwanych",
|
||||
"cmd.view_navigate": "Nawiguj widokiem",
|
||||
"cmd.view_normal_to": "Prostopadle do zaznaczenia",
|
||||
"cmd.view_rename": "Zmień nazwę widoku nazwanego",
|
||||
"cmd.view_save": "Nowy widok nazwany",
|
||||
"color.copy": "Kliknij, aby skopiować wartości koloru",
|
||||
@@ -831,7 +892,7 @@
|
||||
"dxf.progress.projection": "Rzutowanie geometrii (3/5)",
|
||||
"dxf.progress.write": "Zapisywanie DXF (5/5)",
|
||||
"dxf.tolerance": "Tolerancja krzywych",
|
||||
"dxf.tolerance_help": "Tylko widoczne bryły. Krzywe są przybliżane odcinkami. Mniejsza tolerancja daje dokładniejsze krzywe, ale zajmuje więcej czasu i pamięci.",
|
||||
"dxf.tolerance_help": "Tylko widoczne bryły i pokazane szkice. Krzywe są przybliżane odcinkami. Mniejsza tolerancja daje dokładniejsze krzywe, ale zajmuje więcej czasu i pamięci.",
|
||||
"file_load.built": "Dotąd zbudowano: {built} z {total} elementów.",
|
||||
"file_load.converting": "Konwertowanie bryły {done} z {total}",
|
||||
"file_load.inserting": "Wstawianie geometrii",
|
||||
@@ -855,20 +916,64 @@
|
||||
"help.licences": "Licencje",
|
||||
"help.mit": "Licencja MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.solid.chamfer": "Ścina zaznaczone krawędzie płaską fazą o zadanej odległości. Służy do łamania ostrych krawędzi lub ułatwienia wprowadzenia kołka czy śruby.",
|
||||
"help.solid.extrude": "Wyciąga profil szkicu w 3D na zadaną odległość, dodając lub usuwając materiał. Służy do zamiany płaskiego obrysu w blok, występ lub kieszeń.",
|
||||
"help.solid.fillet": "Zaokrągla zaznaczone krawędzie gładkim łukiem o zadanym promieniu. Służy do złagodzenia ostrych krawędzi dla wytrzymałości, wygody lub wyglądu.",
|
||||
"help.solid.hole": "Wierci okrągły otwór o zadanej średnicy w wybranym punkcie ściany. Służy do otworów przelotowych pod śruby, kołki i innych prostych okrągłych otworów.",
|
||||
"help.solid.revolve": "Obraca profil szkicu wokół osi, tworząc okrągłą bryłę. Służy do wałków, gałek, misek i wszystkich części okrągłych względem osi.",
|
||||
"help.solid.shell": "Wydrąża bryłę, pozostawiając ścianki o zadanej grubości, i może otworzyć wybrane ściany. Służy do pudełek, pokryw i innych części cienkościennych.",
|
||||
"help.third_party": "Licencje stron trzecich",
|
||||
"home.back": "Wróć do {name}",
|
||||
"home.crashes": "Raporty awarii",
|
||||
"home.design_workspace": "Projektowanie",
|
||||
"home.documentation": "Dokumentacja",
|
||||
"home.export": "Eksportuj",
|
||||
"home.export_hint": "Wybierz format dla bieżącego projektu.",
|
||||
"home.export_json_hint": "Edytowalny projekt i historia poleceń",
|
||||
"home.export_mesh_hint": "Siatka do druku lub udostępniania",
|
||||
"home.export_step_hint": "Precyzyjna geometria bryłowa",
|
||||
"home.import": "Importuj…",
|
||||
"home.import_hint": "STEP, IGES lub siatka do nowego projektu",
|
||||
"home.in_this_browser": "w tej przeglądarce",
|
||||
"home.missing": "brak",
|
||||
"home.month_01": "sty",
|
||||
"home.month_02": "lut",
|
||||
"home.month_03": "mar",
|
||||
"home.month_04": "kwi",
|
||||
"home.month_05": "maj",
|
||||
"home.month_06": "cze",
|
||||
"home.month_07": "lip",
|
||||
"home.month_08": "sie",
|
||||
"home.month_09": "wrz",
|
||||
"home.month_10": "paź",
|
||||
"home.month_11": "lis",
|
||||
"home.month_12": "gru",
|
||||
"home.new_inches": "Nowy projekt w calach",
|
||||
"home.new_inches_hint": "Cale · otwiera w obszarze Projektowanie",
|
||||
"home.new_mm": "Nowy projekt",
|
||||
"home.new_mm_hint": "Milimetry · otwiera w obszarze Projektowanie",
|
||||
"home.open": "Otwórz…",
|
||||
"home.opened_days_ago": "Otwarto {count} dni temu",
|
||||
"home.opened_hours_ago": "Otwarto {count} godz. temu",
|
||||
"home.opened_just_now": "Otwarto przed chwilą",
|
||||
"home.opened_minutes_ago": "Otwarto {count} min temu",
|
||||
"home.open_hint": "Projekt .solidcraft ({shortcut})",
|
||||
"home.open_now": "Otwórz teraz",
|
||||
"home.opened_days_ago": "{count} dni temu",
|
||||
"home.opened_hours_ago": "{count} godz. temu",
|
||||
"home.opened_just_now": "Przed chwilą",
|
||||
"home.opened_minutes_ago": "{count} min temu",
|
||||
"home.opening": "otwieranie…",
|
||||
"home.properties": "Właściwości projektu",
|
||||
"home.recent": "Ostatnie",
|
||||
"home.recent_empty": "Tutaj pojawią się projekty, które otworzysz lub zapiszesz.",
|
||||
"home.recovery_count": "Projekty do odzyskania: {count}",
|
||||
"home.recovery_empty": "Brak projektów do odzyskania.",
|
||||
"home.recovery_hint": "SolidCraft zamknął się przed zapisaniem tych projektów.",
|
||||
"home.report_issue": "Zgłoś problem",
|
||||
"home.samples": "Przykłady",
|
||||
"home.settings": "Ustawienia",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Skróty klawiszowe",
|
||||
"home.start_hint": "Utwórz projekt, otwórz ostatni plik lub obejrzyj przykład.",
|
||||
"home.whats_new": "Co nowego",
|
||||
"home.yesterday": "Wczoraj",
|
||||
"install.button_tip": "Zainstaluj SolidCraft jako aplikację: otwiera się we własnym oknie i działa offline",
|
||||
"install.dock_1": "Wybierz Plik › Dodaj do Docka.",
|
||||
"install.dock_2": "Kliknij Dodaj.",
|
||||
@@ -926,6 +1031,8 @@
|
||||
"motion.to_end": "Na koniec",
|
||||
"motion.to_start": "Na początek",
|
||||
"motion.track_name": "Ścieżka {number}",
|
||||
"nav.free_orbit.help": "Mysz i mysz 3D obracają widok przez oba bieguny i zachowują przechylenie. Przyciąganie do osi z Shift jest wyłączone.",
|
||||
"nav.free_orbit.label": "Swobodne obracanie",
|
||||
"nav.touchpad.help": "Przytrzymaj Shift i poruszaj wskaźnikiem, aby przesuwać; przytrzymaj Alt/Option i poruszaj nim, aby obracać widok. Przycisk myszy nie jest potrzebny. Przewijanie w pionie powiększa.",
|
||||
"nav.touchpad.label": "Touchpad",
|
||||
"nav.touchpad.orbit": "Obracanie widoku (przytrzymaj Alt/Option i poruszaj; bez przycisku myszy)",
|
||||
@@ -948,8 +1055,130 @@
|
||||
"pattern.flip_direction": "Odwróć kierunek",
|
||||
"pick a hole to seat it, or OK places it at the origin": "wybierz otwór, aby go osadzić, lub OK umieszcza go w początku układu",
|
||||
"pick one or two vertices, edges or faces": "wybierz jeden lub dwa wierzchołki, krawędzie lub ściany",
|
||||
"prefs.add_item": "Dodaj element",
|
||||
"prefs.alt_frees_manipulators": "Alt / Option zwalnia również strzałki manipulatora",
|
||||
"prefs.anti_aliasing_note": "Wygładza poszarpane krawędzie.",
|
||||
"prefs.applies_on_restart": "Działa po ponownym uruchomieniu",
|
||||
"prefs.assistant": "Asystent",
|
||||
"prefs.axes": "Osie",
|
||||
"prefs.axis": "Oś",
|
||||
"prefs.bottom": "Dół",
|
||||
"prefs.browser": "Przeglądarka",
|
||||
"prefs.built_in": "Wbudowana",
|
||||
"prefs.button_left": "Przeciąganie lewym",
|
||||
"prefs.button_middle": "Przeciąganie środkowym",
|
||||
"prefs.button_right": "Przeciąganie prawym",
|
||||
"prefs.choose_font": "Wybierz plik czcionki…",
|
||||
"prefs.click": "Kliknięcie",
|
||||
"prefs.contact_shading": "Miękkie cieniowanie kontaktowe",
|
||||
"prefs.contact_shading_note": "Miękkie cieniowanie tam, gdzie części są blisko siebie.",
|
||||
"prefs.context_menu": "Menu kontekstowe",
|
||||
"prefs.context_menu_ring": "Menu kontekstowe · przytrzymaj, aby otworzyć menu promieniste",
|
||||
"prefs.cpu": "CPU (programowo)",
|
||||
"prefs.crash_reports": "Otwórz raporty o awariach…",
|
||||
"prefs.crash_reports_note": "Raporty są przechowywane na tym komputerze, razem z projektem i poleceniami, które odtwarzają problem.",
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Powierzchnie zakrzywione",
|
||||
"prefs.curves": "Powierzchnie zakrzywione",
|
||||
"prefs.dead_zone_note": "Zakreskowany pas to strefa martwa: ruch wewnątrz niej jest pomijany.",
|
||||
"prefs.default": "domyślnie",
|
||||
"prefs.design": "Ustawienia domyślne projektu",
|
||||
"prefs.design_note": "Ustawienia, z którymi startują nowe projekty. Otwarte projekty zachowują własne.",
|
||||
"prefs.devices": "Mysz 3D",
|
||||
"prefs.display": "Wyświetlanie",
|
||||
"prefs.edges": "Krawędzie i cieniowanie",
|
||||
"prefs.extend": "Rozszerz",
|
||||
"prefs.extra_navigation": "Ctrl+przeciąganie lewym na pustym miejscu obraca widok; przytrzymaj Spację, aby przesuwać.",
|
||||
"prefs.follows_os": "Zgodnie z systemem",
|
||||
"prefs.font": "Czcionka",
|
||||
"prefs.font_fallback": "Znaki, których brakuje w wybranej czcionce, są brane z czcionki wbudowanej, więc żadna etykieta nie pokaże pustego prostokąta.",
|
||||
"prefs.font_sample": "Zażółć gęślą jaźń · Ø12.50 mm · 45.0° · 0123456789",
|
||||
"prefs.font_unreadable": "Nie można odczytać wybranej czcionki.",
|
||||
"prefs.general": "Ogólne",
|
||||
"prefs.general_note": "Język, zapisywanie i aktualizacje.",
|
||||
"prefs.gesture_orbit": "Obracanie widoku",
|
||||
"prefs.gesture_pan": "Przesuwanie",
|
||||
"prefs.gesture_zoom": "Powiększanie",
|
||||
"prefs.graphics": "Grafika",
|
||||
"prefs.graphics_note": "Wybierz między szybkością a gładkością. Każda opcja pokazuje swój efekt na przykładzie.",
|
||||
"prefs.graphics_presets_note": "Ustawienia wstępne zmieniają razem poniższe opcje.",
|
||||
"prefs.history": "Historia",
|
||||
"prefs.hold_stop_snapping": "Przytrzymaj, aby wyłączyć przyciąganie",
|
||||
"prefs.icons_only": "Tylko ikony",
|
||||
"prefs.imported_file": "Zaimportowany plik",
|
||||
"prefs.input": "Wprowadzanie",
|
||||
"prefs.interface": "Interfejs",
|
||||
"prefs.interface_language": "Język interfejsu",
|
||||
"prefs.interface_note": "Wygląd SolidCraft i rozmieszczenie jego elementów.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.labels": "Etykiety",
|
||||
"prefs.layout": "Układ",
|
||||
"prefs.layout_note": "Kompaktowy dokuje panele do krawędzi okna. Przezroczystość przeglądarki jest dostępna w układzie Pływający. Stronę asystenta zmienisz na stronie Asystent.",
|
||||
"prefs.left": "Lewo",
|
||||
"prefs.left_click": "Kliknięcie lewym",
|
||||
"prefs.live_input": "Bieżące wejście",
|
||||
"prefs.live_preview": "Podgląd na żywo",
|
||||
"prefs.minutes": "minut",
|
||||
"prefs.minutes_suffix": " min",
|
||||
"prefs.mouse": "Mysz i gładzik",
|
||||
"prefs.mouse_heading": "Mysz",
|
||||
"prefs.mouse_mapping": "Przypisanie przycisków myszy",
|
||||
"prefs.navigation": "Nawigacja",
|
||||
"prefs.navigation_note": "Jak mysz, gładzik i mysz 3D poruszają widokiem.",
|
||||
"prefs.not_on_gpu": "niedostępne na tym GPU",
|
||||
"prefs.on": "Włączone",
|
||||
"prefs.opaque": "Nieprzezroczysta",
|
||||
"prefs.orbit_snap": "Przytrzymaj Shift podczas obracania widoku, aby przyciągnąć do osi.",
|
||||
"prefs.orientation": "Orientacja",
|
||||
"prefs.overall_speed": "Szybkość ogólna",
|
||||
"prefs.preview_choices": "Podgląd wybranych opcji",
|
||||
"prefs.quality_default": "domyślnie",
|
||||
"prefs.quality_fastest": "najszybciej",
|
||||
"prefs.quality_slowest": "najwolniej",
|
||||
"prefs.quality_smoother": "gładziej",
|
||||
"prefs.reports_saved": "Zapisane: {n}",
|
||||
"prefs.right_click": "Kliknięcie prawym",
|
||||
"prefs.saving": "Autozapis i odzyskiwanie",
|
||||
"prefs.scale_note": "Także Ctrl + / Ctrl − w oknie. Przyciski mają 28 px przy 100%.",
|
||||
"prefs.scale_text": "Skala i tekst",
|
||||
"prefs.select": "Zaznacz",
|
||||
"prefs.select_one_item": "Zaznacz jeden element",
|
||||
"prefs.selecting": "Zaznaczanie",
|
||||
"prefs.shift": "Shift",
|
||||
"prefs.shift_add_ctrl_toggle": "Shift tylko dodaje; Ctrl / Cmd przełącza",
|
||||
"prefs.shift_add_note": "Włączone: Shift dodaje, Ctrl / Cmd przełącza. Wyłączone (domyślnie): oba przełączają.",
|
||||
"prefs.shortcuts": "Skróty",
|
||||
"prefs.sketch_selection": "Szkic i zaznaczanie",
|
||||
"prefs.sketch_selection_heading": "Szkic i zaznaczanie",
|
||||
"prefs.sketch_selection_note": "Jak działa przyciąganie w szkicach i jak kliknięcia zaznaczają.",
|
||||
"prefs.sketching": "Szkicowanie",
|
||||
"prefs.snap_drawing_note": "Podczas rysowania lub przeciągania punktu szkicu.",
|
||||
"prefs.source": "Źródło",
|
||||
"prefs.speed": "Szybkość",
|
||||
"prefs.stand_up_note": "Gdy plik STEP lub IGES otworzy się z osią Z w górę, proponuj obrót o ćwierć obrotu wokół osi X.",
|
||||
"prefs.status_done": "Gotowe",
|
||||
"prefs.status_error": "Błąd",
|
||||
"prefs.status_selected": "Zaznaczone",
|
||||
"prefs.status_warning": "Ostrzeżenie",
|
||||
"prefs.supersampling_brief": "Rysuje widok w większym rozmiarze i go zmniejsza: bardzo ostro, ale bardziej obciąża.",
|
||||
"prefs.tab_accent_note": "Kolor akcentu podąża za aktywną kartą",
|
||||
"prefs.theme_file": "plik motywu",
|
||||
"prefs.timeline_half_hour": "−30 min",
|
||||
"prefs.timeline_now": "teraz",
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
"prefs.toggle_item": "Dodaj element lub usuń go ponownie",
|
||||
"prefs.toolbar": "Pasek narzędzi",
|
||||
"prefs.translucent": "Półprzezroczysta",
|
||||
"prefs.under_browser": "Pod przeglądarką",
|
||||
"prefs.under_cursor": "Pod kursorem",
|
||||
"prefs.units_precision": "Jednostki i dokładność",
|
||||
"prefs.updates": "Aktualizacje",
|
||||
"prefs.view": "Zachowanie widoku",
|
||||
"prefs.viewport": "Widok",
|
||||
"prefs.wheel": "Kółko",
|
||||
"prefs.wheel_trackpad": "Kółko i gładzik",
|
||||
"prefs.workspace": "Środowisko pracy",
|
||||
"prefs.zoom_toward_cursor": "Powiększanie w stronę kursora",
|
||||
"properties.title": "Właściwości: {name}",
|
||||
"recovery.days_ago": "{count} dni temu",
|
||||
"recovery.detail": "{file} · elementy: {features} · autozapis {when}",
|
||||
@@ -1021,12 +1250,14 @@
|
||||
"search.try": "Wpisz nazwę polecenia, element tego projektu lub parametr. Aby uruchomić polecenie z wartościami, wpisz jego identyfikator i obiekt JSON: {example}",
|
||||
"search.valid": "Prawidłowe",
|
||||
"settings.display_quality_note": "Wyższe poziomy rysują zakrzywione ściany i krawędzie gładziej, ale siatka powstaje dłużej. Zmienia się tylko widok, nigdy projekt ani eksport.",
|
||||
"settings.frame_rate_note": "Pokazuje liczbę klatek na sekundę w prawym dolnym rogu widoku. Gdy jest włączone, widok odświeża się bez przerwy.",
|
||||
"settings.frame_rate_note": "Pokazuje liczbę klatek na sekundę w prawym dolnym rogu widoku.",
|
||||
"settings.measure_axis_distances": "Pokaż odległości X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Gdy pomiar jest otwarty, pokazuj składowe wzdłuż osi globalnych obok odległości całkowitej.",
|
||||
"settings.msaa_applies_on_restart": "Wygładzanie krawędzi zadziała po ponownym uruchomieniu SolidCraft",
|
||||
"settings.msaa_reset": "{setting}: {requested}× nie jest obsługiwane przez to urządzenie graficzne; ustawiono {reset}× (obsługiwane: {supported}).",
|
||||
"settings.msaa_unsupported": "Nieobsługiwane przez to urządzenie graficzne",
|
||||
"settings.reference_geometry_through_bodies": "Pokaż geometrię odniesienia przez bryły",
|
||||
"settings.reference_geometry_through_bodies_help": "Rysuj kontury nieaktywnych szkiców i płaszczyzny odniesienia na cieniowanych bryłach",
|
||||
"settings.reset_intro": "Te ustawienia nie działają na tym komputerze i uniemożliwiłyby uruchomienie SolidCraft, więc zostały zmienione:",
|
||||
"settings.reset_title": "Zmieniono ustawienia",
|
||||
"settings.supersampling_note": "Rysuje widok w wyższej rozdzielczości i go zmniejsza: gładsze krawędzie, linie i siatka na ekranach poniżej 4K. Kosztuje czas GPU: ok. 2,25× więcej pikseli przy 1,5× i 4× przy 2×.",
|
||||
@@ -1066,6 +1297,9 @@
|
||||
"thread.designation": "Oznaczenie",
|
||||
"thread.iso_metric": "Metryczny ISO",
|
||||
"thread.select_size": "Wybierz rozmiar",
|
||||
"timeline.delete_after_marker_one": "Usuń 1 funkcję za znacznikiem historii",
|
||||
"timeline.delete_after_marker_other": "Usuń funkcje za znacznikiem historii: {count}",
|
||||
"timeline.delete_many": "Usuń zaznaczone funkcje: {count}",
|
||||
"timeline.group": "Nowy folder funkcji",
|
||||
"timeline.move_many": "Przenieś elementy osi czasu",
|
||||
"timeline.move_to_group": "Przenieś funkcje do folderu",
|
||||
@@ -1082,7 +1316,106 @@
|
||||
"trace.stopped_by_contact": "Kończy się na próbce {sample}: {a} przeszedłby przez {b}",
|
||||
"trace.stopped_by_loop": "Kończy się na próbce {sample}: pętla przez {joint} nie może się bardziej zamknąć",
|
||||
"ui.1_degree_of_freedom": "1 stopień swobody",
|
||||
"ui.3d_mouse_axes_reassigned": "Osie są przypisane na nowo w Ustawieniach i to nadal obowiązuje razem z tą kalibracją.",
|
||||
"ui.3d_mouse_button": "Przycisk {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nic",
|
||||
"ui.3d_mouse_button_settings": "Ustawienia myszy 3D",
|
||||
"ui.3d_mouse_buttons": "Przyciski",
|
||||
"ui.3d_mouse_buttons_hint": "Naciśnij przycisk na myszy 3D, aby go tu znaleźć, a potem wybierz, co ma robić.",
|
||||
"ui.3d_mouse_buttons_no_device": "Podłącz mysz 3D, aby ustawić, co robią jej przyciski.",
|
||||
"ui.3d_mouse_buttons_reset": "Przywróć domyślne przyciski",
|
||||
"ui.3d_mouse_calibrate": "Kalibruj…",
|
||||
"ui.3d_mouse_calibrate_tip": "Przesuwaj nakładkę zgodnie z poleceniami okna, aby SolidCraft nauczył się kierunku i zakresu każdej osi tej myszy 3D. Zapisywane dla tej myszy 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Kalibracja myszy 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Rusz raz myszą 3D, aby SolidCraft ją zobaczył.",
|
||||
"ui.3d_mouse_calibrated": "Skalibrowano",
|
||||
"ui.3d_mouse_calibration": "Kalibracja myszy 3D",
|
||||
"ui.3d_mouse_calibration_done": "Gotowe. Zapisz, aby używać kalibracji po każdym podłączeniu tej myszy 3D.",
|
||||
"ui.3d_mouse_calibration_no_response": "Jedna oś prawie się nie poruszyła, więc nie można jej skalibrować. Zacznij od nowa i za każdym razem wychylaj nakładkę mocniej.",
|
||||
"ui.3d_mouse_circles_help": "Koła pokazują, co urządzenie wysyła po kalibracji. Model testowy pokazuje, co widok z tym robi po odwróceniu, szybkości i przypisaniu osi.",
|
||||
"ui.3d_mouse_dead_zone": "Strefa martwa",
|
||||
"ui.3d_mouse_dead_zone_tip": "O ile trzeba wychylić nakładkę, zanim widok się poruszy, jako część pełnego wychylenia. Zwiększ, jeśli widok dryfuje przy nieruszanej nakładce.",
|
||||
"ui.3d_mouse_denied": "SolidCraft widzi mysz 3D, ale nie ma pozwolenia, by ją odczytać. Regułę udev opisują uwagi o konfiguracji myszy 3D.",
|
||||
"ui.3d_mouse_denied_short": "Znaleziono, ale nie można odczytać",
|
||||
"ui.3d_mouse_driven_by": "Który skalibrowany ruch urządzenia steruje tą osią. Wybranie ruchu używanego już przez inną oś zamienia je miejscami.",
|
||||
"ui.3d_mouse_driven_by_label": "sterowana przez",
|
||||
"ui.3d_mouse_driver": "Sterownik myszy 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automatyczny",
|
||||
"ui.3d_mouse_driver_direct": "Bezpośredni (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Na razie odczyt przez spacenavd: samego urządzenia nie udało się otworzyć.",
|
||||
"ui.3d_mouse_driver_only_direct": "Urządzenie jest czytane bezpośrednio; spacenavd nie działa",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Odczyt przez spacenavd, jedyna dostępna droga",
|
||||
"ui.3d_mouse_forget": "Zapomnij kalibrację",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Lewy widok",
|
||||
"ui.3d_mouse_ghost_left_window": "Lewe okno",
|
||||
"ui.3d_mouse_ghost_position": "Położenie",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Prawy widok",
|
||||
"ui.3d_mouse_hint": "Przesuwaj i obracaj widok jednocześnie; w szkicach działa tylko przesuwanie i powiększanie.",
|
||||
"ui.3d_mouse_kind_cap": "Powrót do środka",
|
||||
"ui.3d_mouse_kind_question": "Jaki to rodzaj myszy 3D?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "Odczyt zostaje, dopóki trzymasz, jak w urządzeniu typu „Powrót do środka”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "Odczyt wraca, gdy tylko przestajesz ruszać, jak w trackballu.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Obrót X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Obrót Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Obrót Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Przesunięcie X",
|
||||
"ui.3d_mouse_motion_translation_y": "Przesunięcie Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Przesunięcie Z",
|
||||
"ui.3d_mouse_navigate": "Nawiguj za pomocą myszy 3D",
|
||||
"ui.3d_mouse_none": "Nie znaleziono myszy 3D",
|
||||
"ui.3d_mouse_not_calibrated": "Nieskalibrowano",
|
||||
"ui.3d_mouse_offer": "Kalibracja myszy 3D naprawia zamienione lub odwrócone osie oraz ustawia jej szybkość i odczucie.",
|
||||
"ui.3d_mouse_offer_never": "Nie pokazuj więcej",
|
||||
"ui.3d_mouse_offer_swap": "Przełącz na Hidraw (sterownik bezpośredni)",
|
||||
"ui.3d_mouse_reading": "Odczyt",
|
||||
"ui.3d_mouse_reset_axes": "Przywróć przypisanie osi",
|
||||
"ui.3d_mouse_reset_view": "Resetuj widok",
|
||||
"ui.3d_mouse_restore": "Przywróć ustawienia myszy 3D",
|
||||
"ui.3d_mouse_restore_ask": "Przywrócić domyślne ustawienia myszy 3D? Kalibracja i przypisanie przycisków zostają.",
|
||||
"ui.3d_mouse_section": "Mysz 3D",
|
||||
"ui.3d_mouse_show_in_view": "Pokazuj mysz 3D w widoku 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Obraz twojej myszy 3D w lewym dolnym rogu, który porusza się razem z nią.",
|
||||
"ui.3d_mouse_skip": "Moje urządzenie tego nie potrafi",
|
||||
"ui.3d_mouse_skip_tip": "Pomija ten ruch w obu kierunkach; kalibracja go nie uwzględnia.",
|
||||
"ui.3d_mouse_speed": "Czułość myszy 3D",
|
||||
"ui.3d_mouse_step_lift": "Unieś nakładkę, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_press": "Wciśnij nakładkę w dół, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_pull": "Pociągnij nakładkę do siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_push": "Popchnij nakładkę od siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_rest": "Zdejmij dłoń z nakładki i nie ruszaj jej.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Zdejmij dłoń z kulki i nie ruszaj jej.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Obróć pierścień przewijania o kilka kliknięć zgodnie z ruchem wskazówek zegara.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Obróć pierścień przewijania o kilka kliknięć przeciwnie do ruchu wskazówek zegara.",
|
||||
"ui.3d_mouse_step_roll_away": "Potocz kulkę kilka razy od siebie, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_roll_left": "Potocz kulkę kilka razy w lewo, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_roll_right": "Potocz kulkę kilka razy w prawo, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_roll_toward": "Potocz kulkę kilka razy do siebie, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_slide_left": "Przesuń nakładkę w lewo, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_slide_right": "Przesuń nakładkę w prawo, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Zakręć kulką kilka razy zgodnie z ruchem wskazówek zegara, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Zakręć kulką kilka razy przeciwnie do ruchu wskazówek zegara, a potem pozwól jej się zatrzymać.",
|
||||
"ui.3d_mouse_step_tilt_away": "Przechyl górę nakładki od siebie (krawędź najbliżej ciebie idzie w górę), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_tilt_left": "Przechyl nakładkę w lewo (lewa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_tilt_right": "Przechyl nakładkę w prawo (prawa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Przechyl górę nakładki do siebie (krawędź najbliżej ciebie idzie w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_touch": "Połóż dłoń na nakładce, nie naciskając jej.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Obróć nakładkę zgodnie z ruchem wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Obróć nakładkę przeciwnie do ruchu wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.3d_mouse_switch_cap": "Kalibruj jako powrót do środka",
|
||||
"ui.3d_mouse_switch_trackball": "Kalibruj jako trackball",
|
||||
"ui.3d_mouse_view_axis": "Oś widoku",
|
||||
"ui.3d_mouse_view_off": "Wyłączona",
|
||||
"ui.3d_mouse_view_off_tip": "Profil sterownika myszy 3D wyłącza tę oś, więc jej ruch nie porusza widokiem. Wybranie tu ruchu innej osi zamienia je miejscami, a ten ruch przestaje poruszać widokiem.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbita w lewo / prawo",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbita w górę / dół",
|
||||
"ui.3d_mouse_view_pan_sideways": "Przesuwanie w lewo / prawo",
|
||||
"ui.3d_mouse_view_pan_up_down": "Przesuwanie w górę / dół",
|
||||
"ui.3d_mouse_view_roll": "Przechył",
|
||||
"ui.3d_mouse_view_unmapped": "Oś {n}",
|
||||
"ui.3d_mouse_view_zoom": "Powiększenie",
|
||||
"ui.3d_mouse_waiting": "Czekam na ruch",
|
||||
"ui.3d_mouse_wiggle": "Jeśli masz podłączoną, poruszaj nią chwilę.",
|
||||
"ui.3d_sketch": "Szkic 3D",
|
||||
"ui.activate": "Aktywuj",
|
||||
"ui.active_component": "Aktywny komponent",
|
||||
@@ -1092,6 +1425,7 @@
|
||||
"ui.agent_chat_add": "Załącz pliki",
|
||||
"ui.agent_chat_add_file": "Wybierz plik…",
|
||||
"ui.agent_chat_allow": "Zezwól",
|
||||
"ui.agent_chat_allow_once": "Zezwól raz",
|
||||
"ui.agent_chat_allowed": "Zezwolono",
|
||||
"ui.agent_chat_answered": "Odpowiedź: {option}",
|
||||
"ui.agent_chat_approval": "Zezwolić agentowi na to działanie?",
|
||||
@@ -1143,12 +1477,16 @@
|
||||
"ui.agent_chat_files_left_out": "Do jednej wiadomości można dołączyć najwyżej {max} plików; pominięto: {files}",
|
||||
"ui.agent_chat_files_loading": "Oczekiwanie na odczytanie plików",
|
||||
"ui.agent_chat_files_max": "Najwyżej {max} plików w jednej wiadomości",
|
||||
"ui.agent_chat_flatpak_none_found": "Nie znaleziono agenta programistycznego. Piaskownica Flatpak domyślnie nie widzi agentów zainstalowanych w folderze domowym i nie ma dostępu do sieci. Aby zezwolić na Claude Code i jego adapter ACP zainstalowane przez nvm lub w ~/.local/bin, uruchom to polecenie w terminalu, a potem uruchom ponownie SolidCraft. Daje ono dostęp do wymienionych programów i plików logowania oraz dostęp do sieci:",
|
||||
"ui.agent_chat_import_failed": "{name}: import się nie powiódł: {error}",
|
||||
"ui.agent_chat_intro": "Rozmawiaj z agentem programistycznym, którego już używasz, lub z wybranym punktem końcowym modelu. Pracuje on na otwartym projekcie za pomocą narzędzi SolidCraft: jego zmiany są widoczne na bieżąco, a cofa się je jednym krokiem na wiadomość. To, co może robić bez pytania, ustawisz w Preferencje > AI.",
|
||||
"ui.agent_chat_key_in_env": "klucz ustawiony",
|
||||
"ui.agent_chat_left": "Lewa",
|
||||
"ui.agent_chat_limit": "Agent osiągnął limit {limit} zmian na jedną wiadomość. Wyślij kolejną wiadomość, aby mógł kontynuować.",
|
||||
"ui.agent_chat_minimize": "Zminimalizuj do paska",
|
||||
"ui.agent_chat_mode_all": "Wszystko",
|
||||
"ui.agent_chat_mode_ask": "Pytaj",
|
||||
"ui.agent_chat_mode_edits": "Edycje",
|
||||
"ui.agent_chat_model_tip": "Model używany przez agenta. Twój wybór jest zapamiętywany dla tego agenta.",
|
||||
"ui.agent_chat_new": "Nowy czat",
|
||||
"ui.agent_chat_new_confirm": "Rozpocząć nowy czat? Bieżący zostanie wyczyszczony.",
|
||||
@@ -1161,6 +1499,7 @@
|
||||
"ui.agent_chat_none_found_short": "Nie znaleziono agenta",
|
||||
"ui.agent_chat_not_installed": "Nie zainstalowano",
|
||||
"ui.agent_chat_not_signed_in": "nie zalogowano",
|
||||
"ui.agent_chat_open_settings": "Otwórz preferencje",
|
||||
"ui.agent_chat_own_window": "Osobne okno",
|
||||
"ui.agent_chat_paste_picture": "Wklej obraz",
|
||||
"ui.agent_chat_permission": "Agent prosi o uruchomienie: {title}",
|
||||
@@ -1185,6 +1524,8 @@
|
||||
"ui.agent_chat_send": "Wyślij",
|
||||
"ui.agent_chat_send_now": "Wyślij teraz",
|
||||
"ui.agent_chat_send_now_tip": "Zatrzymaj bieżący krok agenta i wyślij tę wiadomość jako następną (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft komunikuje się z agentem programistycznym zainstalowanym na tym komputerze. Wybierz go w Preferencje › AI.",
|
||||
"ui.agent_chat_setup_title": "Skonfiguruj asystenta",
|
||||
"ui.agent_chat_side": "Strona czatu z agentem",
|
||||
"ui.agent_chat_signed_in": "zalogowano",
|
||||
"ui.agent_chat_state_auth": "Wymagane logowanie",
|
||||
@@ -1201,8 +1542,11 @@
|
||||
"ui.agent_chat_undo_label": "Czat z agentem: {message}",
|
||||
"ui.agent_chat_undone": "(cofnięto)",
|
||||
"ui.agent_chat_view_attached": "Dołączono widok",
|
||||
"ui.agent_chat_waiting": "Czeka na Ciebie",
|
||||
"ui.agent_cursor": "Kursor agenta",
|
||||
"ui.agent_cursor_speed": "Szybkość kursora agenta",
|
||||
"ui.agent_off_thread": "Zachowaj responsywność okna, gdy agenty wykonują polecenia",
|
||||
"ui.agent_off_thread_note": "Wolne polecenia agenta (z panelu AI lub klienta MCP) są liczone obok okna: możesz dalej obracać i oglądać widok oraz zatrzymać polecenie przyciskiem w widoku. Wyłączone: każde działa w wątku okna, a okno na nie czeka.",
|
||||
"ui.ai_add": "Dodaj",
|
||||
"ui.ai_add_agent": "Dodaj agenta…",
|
||||
"ui.ai_add_agent_title": "Dodaj agenta",
|
||||
@@ -1258,7 +1602,6 @@
|
||||
"ui.ai_permissions": "Uprawnienia",
|
||||
"ui.ai_preset": "Ustawienie wstępne",
|
||||
"ui.ai_remove": "Usuń",
|
||||
"ui.ai_request_limit": "zatrzymano po 24 żądaniach dla jednej wiadomości; wyślij kolejną wiadomość, aby kontynuować",
|
||||
"ui.ai_reset": "Resetuj",
|
||||
"ui.ai_settings": "Ustawienia AI…",
|
||||
"ui.ai_show_features": "Pokaż funkcje AI",
|
||||
@@ -1303,6 +1646,7 @@
|
||||
"ui.blocked": "Zablokowane",
|
||||
"ui.bodies": "Bryły",
|
||||
"ui.bodies_all_if_none": "Bryły (wszystkie, jeśli brak)",
|
||||
"ui.bottom": "Dół",
|
||||
"ui.box": "Prostopadłościan",
|
||||
"ui.break": "Podziel",
|
||||
"ui.browser_bodies_one": "{count} bryła",
|
||||
@@ -1455,6 +1799,7 @@
|
||||
"ui.grid_follows_view_hint": "Widoki boczne pokazują siatkę na pionowej płaszczyźnie zwróconej do nich zamiast na podłożu",
|
||||
"ui.ground_shadow": "Cień na podłożu",
|
||||
"ui.groupSelected": "Grupuj zaznaczone",
|
||||
"ui.hatch": "Kreskowanie",
|
||||
"ui.heading_add_ins": "DODATKI",
|
||||
"ui.heading_assemble": "ZŁOŻENIE",
|
||||
"ui.heading_box": "PROSTOPADŁOŚCIAN",
|
||||
@@ -1528,7 +1873,19 @@
|
||||
"ui.hideOrigin": "Ukryj początek układu",
|
||||
"ui.hidePlanes": "Ukryj płaszczyzny",
|
||||
"ui.hideProfile": "Ukryj profil",
|
||||
"ui.history_back": "Cofnij o jeden krok",
|
||||
"ui.history_below_browser": "Pod przeglądarką",
|
||||
"ui.history_edge_hint": "kliknij dwukrotnie krawędź",
|
||||
"ui.history_first": "Przejdź do początku",
|
||||
"ui.history_forward": "Przejdź o jeden krok do przodu",
|
||||
"ui.history_hide_names": "Ukryj nazwy",
|
||||
"ui.history_last": "Przejdź do końca",
|
||||
"ui.history_marker_tip": "Znacznik historii: przeciągnij, aby cofnąć model; nowe funkcje trafiają tutaj",
|
||||
"ui.history_names": "Nazwy",
|
||||
"ui.history_play": "Odtwórz do końca",
|
||||
"ui.history_show_names": "Pokaż nazwy",
|
||||
"ui.history_title": "Historia",
|
||||
"ui.history_toggle_names": "Pokaż lub ukryj nazwy funkcji",
|
||||
"ui.hole": "Otwór",
|
||||
"ui.hole_tap_type": "Typ gwintu otworu",
|
||||
"ui.hole_type": "Typ otworu",
|
||||
@@ -1545,6 +1902,8 @@
|
||||
"ui.joints": "Złącza",
|
||||
"ui.keyboard_shortcuts": "Skróty klawiszowe",
|
||||
"ui.language": "Język",
|
||||
"ui.layout_compact": "Kompaktowy",
|
||||
"ui.layout_floating": "Pływający",
|
||||
"ui.length_decimals": "Miejsca dziesiętne długości",
|
||||
"ui.light": "Jasny",
|
||||
"ui.line": "Linia",
|
||||
@@ -1682,6 +2041,7 @@
|
||||
"ui.reverse_zoom_direction": "Odwróć kierunek powiększania",
|
||||
"ui.revolve": "Obrót",
|
||||
"ui.ribbon_exit": "Wyjdź",
|
||||
"ui.ribbon_labels": "Pokazuj tekst pod ikonami wstążki",
|
||||
"ui.ribbon_pin_footer": "Przypięte polecenia są widoczne na wstążce. Najedź na wiersz, aby go przypiąć lub odpiąć.",
|
||||
"ui.ribbon_workspace": "Przestrzeń robocza",
|
||||
"ui.right": "Prawy",
|
||||
@@ -1737,34 +2097,6 @@
|
||||
"ui.smooth_wheel_zoom": "Płynne powiększanie kółkiem",
|
||||
"ui.smooth_wheel_zoom_tip": "Wyłącz, aby każdy ruch kółka działał od razu, bez wygładzania",
|
||||
"ui.snap": "Przyciąganie",
|
||||
"ui.space_mouse_calibrate": "Kalibruj…",
|
||||
"ui.space_mouse_calibrate_tip": "Przesuwaj nakładkę zgodnie z poleceniami okna, aby SolidCraft nauczył się kierunku i zakresu każdej osi tej myszy 3D. Zapisywane dla tej myszy 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Kalibracja myszy 3D",
|
||||
"ui.space_mouse_calibrated": "Skalibrowano",
|
||||
"ui.space_mouse_calibration": "Kalibracja myszy 3D",
|
||||
"ui.space_mouse_calibration_done": "Gotowe. Zapisz, aby używać kalibracji po każdym podłączeniu tej myszy 3D.",
|
||||
"ui.space_mouse_calibration_no_response": "Jedna oś prawie się nie poruszyła, więc nie można jej skalibrować. Zacznij od nowa i za każdym razem wychylaj nakładkę mocniej.",
|
||||
"ui.space_mouse_dead_zone": "Strefa martwa",
|
||||
"ui.space_mouse_dead_zone_tip": "O ile trzeba wychylić nakładkę, zanim widok się poruszy, jako część pełnego wychylenia. Zwiększ, jeśli widok dryfuje przy nieruszanej nakładce.",
|
||||
"ui.space_mouse_forget": "Zapomnij kalibrację",
|
||||
"ui.space_mouse_hint": "Przesuwaj i obracaj widok jednocześnie; w szkicach działa tylko przesuwanie i powiększanie.",
|
||||
"ui.space_mouse_navigate": "Nawiguj za pomocą SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Nie znaleziono myszy 3D",
|
||||
"ui.space_mouse_not_calibrated": "Nieskalibrowano",
|
||||
"ui.space_mouse_reading": "Odczyt",
|
||||
"ui.space_mouse_step_lift": "Unieś nakładkę, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_press": "Wciśnij nakładkę w dół, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_pull": "Pociągnij nakładkę do siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_push": "Popchnij nakładkę od siebie, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_rest": "Zdejmij dłoń z nakładki i nie ruszaj jej.",
|
||||
"ui.space_mouse_step_slide_left": "Przesuń nakładkę w lewo, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_slide_right": "Przesuń nakładkę w prawo, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_back": "Przechyl nakładkę do tyłu (przednia krawędź w górę), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_forward": "Przechyl nakładkę do przodu (przednia krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_left": "Przechyl nakładkę w lewo (lewa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_tilt_right": "Przechyl nakładkę w prawo (prawa krawędź w dół), przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Obróć nakładkę zgodnie z ruchem wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Obróć nakładkę przeciwnie do ruchu wskazówek zegara, przytrzymaj, a następnie puść.",
|
||||
"ui.spacing": "Odstęp",
|
||||
"ui.spacing_2": "Odstęp 2",
|
||||
"ui.sphere": "Sfera",
|
||||
@@ -1836,6 +2168,7 @@
|
||||
"ui.workspace_design_tabs": "Model · Powierzchnia · Złożenie · Wstaw · Sprawdź · Zarządzaj · Widok",
|
||||
"ui.workspace_drawing_description": "Rysunki 2D z widokami, wymiarami i tabliczkami rysunkowymi",
|
||||
"ui.workspace_footer": "Każda przestrzeń robocza działa na tym samym projekcie, historii i zaznaczeniu; zmieniają się tylko karty wstążki. Przełącznik jest zablokowany podczas edycji szkicu lub elementu.",
|
||||
"ui.workspace_layout": "Układ obszaru roboczego",
|
||||
"ui.workspace_manufacture_description": "Cięcie na warstwy, ścieżki narzędzia i przygotowanie",
|
||||
"ui.workspace_planned": "Planowane",
|
||||
"ui.workspace_render_description": "Podgląd produktu, ustawienia sceny i renderowane obrazy",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"Choose…": "Escolher…",
|
||||
"Circular": "Circular",
|
||||
"Circumference": "Circunferência",
|
||||
"Clockwise": "Horário",
|
||||
"Close draft": "Fechar rascunho",
|
||||
"Collisions": "Colisões",
|
||||
"Colour": "Cor",
|
||||
@@ -167,6 +168,7 @@
|
||||
"HISTORY": "HISTÓRICO",
|
||||
"Height": "Altura",
|
||||
"Hidden edges": "Arestas ocultas",
|
||||
"History layout": "Layout do histórico",
|
||||
"Holes": "Furos",
|
||||
"Holes (edges on them)": "Furos (arestas neles)",
|
||||
"Home view": "Vista inicial",
|
||||
@@ -232,7 +234,7 @@
|
||||
"No crash reports.": "Nenhum relatório de falha.",
|
||||
"No matching appearance presets.": "Nenhuma predefinição de aparência correspondente.",
|
||||
"No matching appearances": "Nenhuma aparência correspondente",
|
||||
"No visible bodies to export.": "Nenhum corpo visível para exportar.",
|
||||
"No visible bodies or sketches to export.": "Nenhum corpo ou esboço visível para exportar.",
|
||||
"None": "Nenhum",
|
||||
"Not available yet": "Ainda não disponível",
|
||||
"Notch": "Entalhe",
|
||||
@@ -321,6 +323,7 @@
|
||||
"Search commands": "Pesquisar comandos",
|
||||
"Search this library": "Pesquisar nesta biblioteca",
|
||||
"Section": "Seção",
|
||||
"Section position": "Posição da seção",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Selecionar outro: mantenha o botão esquerdo parado sobre o modelo (ou clique com o botão direito › Selecionar outro…) para listar tudo sob o cursor; clique de novo no mesmo ponto para selecionar o próximo item ali. Clique duas vezes com o botão do meio para ir ao Início (Preferências › Navegação).",
|
||||
"Select bodies (a face picks its body)": "Selecione corpos (uma face seleciona seu corpo)",
|
||||
"Select bodies or faces instead": "Selecionar corpos ou faces em vez disso",
|
||||
@@ -332,6 +335,7 @@
|
||||
"Show Agent Cursor": "Mostrar cursor do agente",
|
||||
"Show in folder": "Mostrar na pasta",
|
||||
"Show the pointer an AI agent moves (#35)": "Mostrar o ponteiro que um agente de IA move (#35)",
|
||||
"Show tool at cursor": "Mostrar a ferramenta junto ao cursor",
|
||||
"Signed axes": "Eixos com sinal",
|
||||
"Size": "Tamanho",
|
||||
"Sketch constraints": "Restrições do esboço",
|
||||
@@ -344,9 +348,11 @@
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "O SolidCraft fechou antes de estes projetos serem salvos. O último salvamento automático deles pode ser aberto.",
|
||||
"Specialise": "Especializar",
|
||||
"Speed": "Velocidade",
|
||||
"Square": "Quadrado",
|
||||
"Stand it up": "Colocar em pé",
|
||||
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Colocar em pé: um quarto de volta em torno de X, como um recurso Mover que pode ser desfeito.",
|
||||
"Stand this part up?": "Colocar esta peça em pé?",
|
||||
"Start angle": "Ângulo inicial",
|
||||
"Start bulge": "Abaulamento inicial",
|
||||
"Start continuity": "Continuidade inicial",
|
||||
"Step": "Passo",
|
||||
@@ -411,6 +417,7 @@
|
||||
"angle, height": "ângulo, altura",
|
||||
"appearance.dielectric_ior": "IOR dielétrico",
|
||||
"appearance.metallic": "Metálico",
|
||||
"appearance.only_available": "Somente aparências disponíveis",
|
||||
"appearance.pbr_help": "Veja esta superfície em Renderizar ou em um PNG de produto. Sem refração nem transmissão.",
|
||||
"appearance.pbr_opaque": "PBR opaco",
|
||||
"appearance.roughness": "Rugosidade",
|
||||
@@ -529,6 +536,10 @@
|
||||
"cmd.file_insert_step": "Inserir STEP",
|
||||
"cmd.file_save_mesh": "Salvar como malha",
|
||||
"cmd.file_slicers": "Fatiadores instalados",
|
||||
"cmd.frame_member": "Membro estrutural",
|
||||
"cmd.frame_profile_insert": "Inserir perfil",
|
||||
"cmd.frame_profile_list": "Biblioteca de perfis",
|
||||
"cmd.frame_profile_properties": "Propriedades do perfil",
|
||||
"cmd.inspect_accessibility": "Análise de acessibilidade",
|
||||
"cmd.inspect_center_of_mass": "Centro de massa",
|
||||
"cmd.inspect_clearance": "Folga",
|
||||
@@ -568,6 +579,7 @@
|
||||
"cmd.material_create": "Novo material físico",
|
||||
"cmd.material_delete": "Excluir material físico",
|
||||
"cmd.material_edit": "Editar material físico",
|
||||
"cmd.material_inspect": "Inspecionar material físico",
|
||||
"cmd.material_list": "Listar materiais",
|
||||
"cmd.material_properties_directional": "Módulo de elasticidade direcional",
|
||||
"cmd.material_properties_validate": "Validar dados do material físico",
|
||||
@@ -754,12 +766,19 @@
|
||||
"cmd.timeline_suppress": "Suprimir recurso",
|
||||
"cmd.view_ambient_occlusion": "Oclusão de ambiente",
|
||||
"cmd.view_apply": "Restaurar vista nomeada",
|
||||
"cmd.view_back": "Vista traseira",
|
||||
"cmd.view_bottom": "Vista inferior",
|
||||
"cmd.view_delete": "Excluir vista nomeada",
|
||||
"cmd.view_front": "Vista frontal",
|
||||
"cmd.view_graphics_preset": "Predefinição gráfica",
|
||||
"cmd.view_isometric": "Vista isométrica",
|
||||
"cmd.view_left": "Vista esquerda",
|
||||
"cmd.view_list": "Listar vistas nomeadas",
|
||||
"cmd.view_navigate": "Navegar na vista",
|
||||
"cmd.view_rename": "Renomear vista nomeada",
|
||||
"cmd.view_right": "Vista direita",
|
||||
"cmd.view_save": "Nova vista nomeada",
|
||||
"cmd.view_top": "Vista superior",
|
||||
"color.copy": "Clique para copiar os valores da cor",
|
||||
"color.edit": "Clique para editar a cor",
|
||||
"color.gamma": "Mostrando valores de cor no espaço gama 0-255",
|
||||
@@ -856,19 +875,57 @@
|
||||
"help.mit": "Licença MIT",
|
||||
"help.notice": "NOTICE",
|
||||
"help.third_party": "Licenças de terceiros",
|
||||
"home.back": "Voltar para {name}",
|
||||
"home.crashes": "Relatórios de falhas",
|
||||
"home.design_workspace": "Projeto",
|
||||
"home.documentation": "Documentação",
|
||||
"home.export": "Exportar",
|
||||
"home.export_hint": "Escolha um formato para o projeto atual.",
|
||||
"home.export_json_hint": "Projeto editável e histórico de comandos",
|
||||
"home.export_mesh_hint": "Malha para impressão ou compartilhamento",
|
||||
"home.export_step_hint": "Geometria sólida precisa",
|
||||
"home.import": "Importar…",
|
||||
"home.import_hint": "STEP, IGES ou malha em um novo projeto",
|
||||
"home.in_this_browser": "neste navegador",
|
||||
"home.missing": "ausente",
|
||||
"home.month_01": "jan.",
|
||||
"home.month_02": "fev.",
|
||||
"home.month_03": "mar.",
|
||||
"home.month_04": "abr.",
|
||||
"home.month_05": "mai.",
|
||||
"home.month_06": "jun.",
|
||||
"home.month_07": "jul.",
|
||||
"home.month_08": "ago.",
|
||||
"home.month_09": "set.",
|
||||
"home.month_10": "out.",
|
||||
"home.month_11": "nov.",
|
||||
"home.month_12": "dez.",
|
||||
"home.new_inches": "Novo projeto em polegadas",
|
||||
"home.new_inches_hint": "Polegadas · abre em Projeto",
|
||||
"home.new_mm": "Novo projeto",
|
||||
"home.new_mm_hint": "Milímetros · abre em Projeto",
|
||||
"home.open": "Abrir…",
|
||||
"home.opened_days_ago": "Aberto há {count} dias",
|
||||
"home.opened_hours_ago": "Aberto há {count} h",
|
||||
"home.opened_just_now": "Aberto agora mesmo",
|
||||
"home.opened_minutes_ago": "Aberto há {count} min",
|
||||
"home.open_hint": "Um projeto .solidcraft ({shortcut})",
|
||||
"home.open_now": "Abrir agora",
|
||||
"home.opened_days_ago": "Há {count} dias",
|
||||
"home.opened_hours_ago": "Há {count} h",
|
||||
"home.opened_just_now": "Agora mesmo",
|
||||
"home.opened_minutes_ago": "Há {count} min",
|
||||
"home.opening": "abrindo…",
|
||||
"home.properties": "Propriedades do projeto",
|
||||
"home.recent": "Recentes",
|
||||
"home.recent_empty": "Os projetos que você abrir ou salvar aparecerão aqui.",
|
||||
"home.recovery_count": "Projetos recuperáveis: {count}",
|
||||
"home.recovery_empty": "Nenhum projeto recuperável.",
|
||||
"home.recovery_hint": "O SolidCraft foi fechado antes que estes projetos fossem salvos.",
|
||||
"home.report_issue": "Relatar um problema",
|
||||
"home.samples": "Exemplos",
|
||||
"home.settings": "Configurações",
|
||||
"home.short_date": "{day} {month}",
|
||||
"home.shortcuts": "Atalhos de teclado",
|
||||
"home.start_hint": "Crie um projeto, abra um arquivo recente ou explore um exemplo.",
|
||||
"home.whats_new": "Novidades",
|
||||
"home.yesterday": "Ontem",
|
||||
"install.button_tip": "Instale o SolidCraft como aplicativo: ele abre em sua própria janela e funciona offline",
|
||||
"install.dock_1": "Escolha Arquivo › Adicionar ao Dock.",
|
||||
"install.dock_2": "Clique em Adicionar.",
|
||||
@@ -1021,7 +1078,7 @@
|
||||
"search.try": "Digite o nome de um comando, um recurso deste projeto ou um parâmetro. Para executar um comando com valores, digite seu id e um objeto JSON: {example}",
|
||||
"search.valid": "Válido",
|
||||
"settings.display_quality_note": "Níveis mais finos desenham faces e arestas curvas mais suaves e demoram mais para gerar a malha. Só a janela de visualização muda, nunca o projeto ou as exportações.",
|
||||
"settings.frame_rate_note": "Mostra quadros por segundo no canto inferior direito da vista. Enquanto estiver ativo, a vista é redesenhada continuamente.",
|
||||
"settings.frame_rate_note": "Mostra quadros por segundo no canto inferior direito da vista.",
|
||||
"settings.measure_axis_distances": "Mostrar distâncias X/Y/Z",
|
||||
"settings.measure_axis_distances_help": "Mostrar as componentes nos eixos do mundo junto com a distância total enquanto Medir está aberto.",
|
||||
"settings.msaa_applies_on_restart": "O antisserrilhamento vale na próxima vez que o SolidCraft iniciar",
|
||||
@@ -1082,7 +1139,106 @@
|
||||
"trace.stopped_by_contact": "Termina na amostra {sample}: {a} atravessaria {b}",
|
||||
"trace.stopped_by_loop": "Termina na amostra {sample}: o laço por {joint} não pode fechar mais",
|
||||
"ui.1_degree_of_freedom": "1 grau de liberdade",
|
||||
"ui.3d_mouse_axes_reassigned": "Os eixos estão reatribuídos nas Preferências, e isso continua valendo sobre esta calibração.",
|
||||
"ui.3d_mouse_button": "Botão {number}",
|
||||
"ui.3d_mouse_button_nothing": "Nada",
|
||||
"ui.3d_mouse_button_settings": "Configurações do mouse 3D",
|
||||
"ui.3d_mouse_buttons": "Botões",
|
||||
"ui.3d_mouse_buttons_hint": "Aperte um botão do seu mouse 3D para encontrá-lo aqui e escolha o que ele faz.",
|
||||
"ui.3d_mouse_buttons_no_device": "Conecte um mouse 3D para escolher o que os botões fazem.",
|
||||
"ui.3d_mouse_buttons_reset": "Restaurar os botões padrão",
|
||||
"ui.3d_mouse_calibrate": "Calibrar…",
|
||||
"ui.3d_mouse_calibrate_tip": "Mova a tampa como a janela pedir, para que o SolidCraft aprenda para onde e até onde vai cada eixo deste mouse 3D. Salvo para este mouse 3D.",
|
||||
"ui.3d_mouse_calibrate_title": "Calibrar mouse 3D",
|
||||
"ui.3d_mouse_calibrate_wait": "Mova o mouse 3D uma vez para o SolidCraft vê-lo.",
|
||||
"ui.3d_mouse_calibrated": "Calibrado",
|
||||
"ui.3d_mouse_calibration": "Calibração do mouse 3D",
|
||||
"ui.3d_mouse_calibration_done": "Pronto. Salve para usá-la sempre que este mouse 3D estiver conectado.",
|
||||
"ui.3d_mouse_calibration_no_response": "Um eixo quase não se moveu, então não dá para calibrá-lo. Recomece e mova a tampa mais longe a cada vez.",
|
||||
"ui.3d_mouse_circles_help": "Os círculos mostram o que o dispositivo envia após a calibração. O modelo de teste mostra o que a vista faz com isso, após inversão, velocidade e atribuição de eixos.",
|
||||
"ui.3d_mouse_dead_zone": "Zona morta",
|
||||
"ui.3d_mouse_dead_zone_tip": "Quanto a tampa precisa se mover antes de a vista se mover, como fração do curso completo. Aumente se a vista se mexer com a tampa parada.",
|
||||
"ui.3d_mouse_denied": "O SolidCraft vê um mouse 3D, mas não tem permissão para lê-lo. A regra udev está nas notas de configuração do mouse 3D.",
|
||||
"ui.3d_mouse_denied_short": "Encontrado, mas não legível",
|
||||
"ui.3d_mouse_driven_by": "Qual movimento calibrado do dispositivo controla este eixo. Escolher um que outro eixo já usa troca os dois.",
|
||||
"ui.3d_mouse_driven_by_label": "controlado por",
|
||||
"ui.3d_mouse_driver": "Driver do mouse 3D",
|
||||
"ui.3d_mouse_driver_auto": "Automático",
|
||||
"ui.3d_mouse_driver_direct": "Direto (hidraw)",
|
||||
"ui.3d_mouse_driver_fallback": "Lendo por enquanto pelo spacenavd: não foi possível abrir o dispositivo em si.",
|
||||
"ui.3d_mouse_driver_only_direct": "Lendo o dispositivo diretamente; o spacenavd não está em execução",
|
||||
"ui.3d_mouse_driver_only_spacenavd": "Lendo pelo spacenavd, a única opção disponível",
|
||||
"ui.3d_mouse_forget": "Esquecer calibração",
|
||||
"ui.3d_mouse_ghost_left_viewport": "Viewport à esquerda",
|
||||
"ui.3d_mouse_ghost_left_window": "Janela à esquerda",
|
||||
"ui.3d_mouse_ghost_position": "Posição",
|
||||
"ui.3d_mouse_ghost_right_viewport": "Viewport à direita",
|
||||
"ui.3d_mouse_hint": "Move e gira a vista juntos; esboços usam apenas deslocamento e zoom.",
|
||||
"ui.3d_mouse_kind_cap": "Retorno ao centro",
|
||||
"ui.3d_mouse_kind_question": "Que tipo de mouse 3D é?",
|
||||
"ui.3d_mouse_kind_trackball": "Trackball",
|
||||
"ui.3d_mouse_looks_like_cap": "O valor se mantém enquanto você segura, como em um dispositivo do tipo “Retorno ao centro”.",
|
||||
"ui.3d_mouse_looks_like_trackball": "O valor volta assim que você para de mover, como em um trackball.",
|
||||
"ui.3d_mouse_motion_rotation_x": "Rotação X",
|
||||
"ui.3d_mouse_motion_rotation_y": "Rotação Y",
|
||||
"ui.3d_mouse_motion_rotation_z": "Rotação Z",
|
||||
"ui.3d_mouse_motion_translation_x": "Translação X",
|
||||
"ui.3d_mouse_motion_translation_y": "Translação Y",
|
||||
"ui.3d_mouse_motion_translation_z": "Translação Z",
|
||||
"ui.3d_mouse_navigate": "Navegar com o mouse 3D",
|
||||
"ui.3d_mouse_none": "Nenhum mouse 3D encontrado",
|
||||
"ui.3d_mouse_not_calibrated": "Não calibrado",
|
||||
"ui.3d_mouse_offer": "Calibrar o mouse 3D corrige eixos trocados ou invertidos e ajusta a velocidade e a sensação.",
|
||||
"ui.3d_mouse_offer_never": "Não mostrar novamente",
|
||||
"ui.3d_mouse_offer_swap": "Mudar para Hidraw (driver direto)",
|
||||
"ui.3d_mouse_reading": "Leitura",
|
||||
"ui.3d_mouse_reset_axes": "Redefinir atribuição de eixos",
|
||||
"ui.3d_mouse_reset_view": "Redefinir visualização",
|
||||
"ui.3d_mouse_restore": "Restaurar padrões do mouse 3D",
|
||||
"ui.3d_mouse_restore_ask": "Voltar as configurações do mouse 3D aos padrões? A calibração e o mapeamento dos botões continuam.",
|
||||
"ui.3d_mouse_section": "Mouse 3D",
|
||||
"ui.3d_mouse_show_in_view": "Mostrar o mouse 3D na vista 3D",
|
||||
"ui.3d_mouse_show_in_view_tip": "Uma imagem do seu mouse 3D no canto inferior esquerdo, que se move quando você o move.",
|
||||
"ui.3d_mouse_skip": "Meu dispositivo não faz isso",
|
||||
"ui.3d_mouse_skip_tip": "Pula este movimento nos dois sentidos; a calibração o deixa de fora.",
|
||||
"ui.3d_mouse_speed": "Velocidade do mouse 3D",
|
||||
"ui.3d_mouse_step_lift": "Levante a tampa, segure e solte.",
|
||||
"ui.3d_mouse_step_press": "Pressione a tampa para baixo, segure e solte.",
|
||||
"ui.3d_mouse_step_pull": "Puxe a tampa na sua direção, segure e solte.",
|
||||
"ui.3d_mouse_step_push": "Empurre a tampa para longe de você, segure e solte.",
|
||||
"ui.3d_mouse_step_rest": "Tire a mão da tampa e deixe-a parada.",
|
||||
"ui.3d_mouse_step_rest_trackball": "Tire a mão da bola e deixe-a parada.",
|
||||
"ui.3d_mouse_step_ring_clockwise": "Gire o anel de rolagem alguns cliques no sentido horário.",
|
||||
"ui.3d_mouse_step_ring_counterclockwise": "Gire o anel de rolagem alguns cliques no sentido anti-horário.",
|
||||
"ui.3d_mouse_step_roll_away": "Role a bola para longe de você algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_roll_left": "Role a bola para a esquerda algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_roll_right": "Role a bola para a direita algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_roll_toward": "Role a bola na sua direção algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_slide_left": "Deslize a tampa para a esquerda, segure e solte.",
|
||||
"ui.3d_mouse_step_slide_right": "Deslize a tampa para a direita, segure e solte.",
|
||||
"ui.3d_mouse_step_spin_clockwise": "Gire a bola no sentido horário algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_spin_counterclockwise": "Gire a bola no sentido anti-horário algumas vezes e deixe-a parar.",
|
||||
"ui.3d_mouse_step_tilt_away": "Incline o topo da tampa para longe de você (a borda mais próxima sobe), segure e solte.",
|
||||
"ui.3d_mouse_step_tilt_left": "Incline a tampa para a esquerda (borda esquerda para baixo), segure e solte.",
|
||||
"ui.3d_mouse_step_tilt_right": "Incline a tampa para a direita (borda direita para baixo), segure e solte.",
|
||||
"ui.3d_mouse_step_tilt_toward": "Incline o topo da tampa na sua direção (a borda mais próxima desce), segure e solte.",
|
||||
"ui.3d_mouse_step_touch": "Apoie a mão na tampa sem empurrá-la.",
|
||||
"ui.3d_mouse_step_twist_clockwise": "Gire a tampa no sentido horário, segure e solte.",
|
||||
"ui.3d_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
|
||||
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
|
||||
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
|
||||
"ui.3d_mouse_view_axis": "Eixo da vista",
|
||||
"ui.3d_mouse_view_off": "Desligado",
|
||||
"ui.3d_mouse_view_off_tip": "O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
|
||||
"ui.3d_mouse_view_orbit_sideways": "Orbitar esquerda / direita",
|
||||
"ui.3d_mouse_view_orbit_up_down": "Orbitar cima / baixo",
|
||||
"ui.3d_mouse_view_pan_sideways": "Deslocar esquerda / direita",
|
||||
"ui.3d_mouse_view_pan_up_down": "Deslocar cima / baixo",
|
||||
"ui.3d_mouse_view_roll": "Girar a vista",
|
||||
"ui.3d_mouse_view_unmapped": "Eixo {n}",
|
||||
"ui.3d_mouse_view_zoom": "Zoom",
|
||||
"ui.3d_mouse_waiting": "Aguardando movimento",
|
||||
"ui.3d_mouse_wiggle": "Se você tiver um conectado, mexa nele um pouco.",
|
||||
"ui.3d_sketch": "Esboço 3D",
|
||||
"ui.activate": "Ativar",
|
||||
"ui.active_component": "Componente ativo",
|
||||
@@ -1092,6 +1248,7 @@
|
||||
"ui.agent_chat_add": "Anexar arquivos",
|
||||
"ui.agent_chat_add_file": "Escolher um arquivo…",
|
||||
"ui.agent_chat_allow": "Permitir",
|
||||
"ui.agent_chat_allow_once": "Permitir uma vez",
|
||||
"ui.agent_chat_allowed": "Permitido",
|
||||
"ui.agent_chat_answered": "Resposta: {option}",
|
||||
"ui.agent_chat_approval": "Permitir que o agente faça isto?",
|
||||
@@ -1143,12 +1300,16 @@
|
||||
"ui.agent_chat_files_left_out": "No máximo {max} arquivos acompanham uma mensagem; deixados de fora: {files}",
|
||||
"ui.agent_chat_files_loading": "Aguardando a leitura dos arquivos",
|
||||
"ui.agent_chat_files_max": "No máximo {max} arquivos por mensagem",
|
||||
"ui.agent_chat_flatpak_none_found": "Nenhum agente de programação encontrado. Por padrão, a sandbox do Flatpak não vê agentes instalados na sua pasta pessoal e não tem acesso à rede. Para permitir o Claude Code e o adaptador ACP dele instalados via nvm ou em ~/.local/bin, execute este comando em um terminal e reinicie o SolidCraft. Ele concede acesso aos programas e arquivos de login listados, além de acesso à rede:",
|
||||
"ui.agent_chat_import_failed": "{name}: falha na importação: {error}",
|
||||
"ui.agent_chat_intro": "Converse com um agente de programação que você já usa, ou com um endpoint de modelo que você escolher. Ele trabalha no projeto aberto pelas ferramentas do SolidCraft: as alterações aparecem conforme são feitas e se desfazem em um passo por mensagem. O que ele pode fazer sem perguntar é definido em Preferências > IA.",
|
||||
"ui.agent_chat_key_in_env": "chave definida",
|
||||
"ui.agent_chat_left": "Esquerda",
|
||||
"ui.agent_chat_limit": "O agente atingiu o limite de {limit} alterações por mensagem. Envie outra mensagem para que ele continue.",
|
||||
"ui.agent_chat_minimize": "Minimizar para uma faixa",
|
||||
"ui.agent_chat_mode_all": "Tudo",
|
||||
"ui.agent_chat_mode_ask": "Perguntar",
|
||||
"ui.agent_chat_mode_edits": "Edições",
|
||||
"ui.agent_chat_model_tip": "O modelo que o agente usa. Sua escolha é lembrada para este agente.",
|
||||
"ui.agent_chat_new": "Novo chat",
|
||||
"ui.agent_chat_new_confirm": "Começar um novo chat? O atual será apagado.",
|
||||
@@ -1161,6 +1322,7 @@
|
||||
"ui.agent_chat_none_found_short": "Nenhum agente encontrado",
|
||||
"ui.agent_chat_not_installed": "Não instalado",
|
||||
"ui.agent_chat_not_signed_in": "não conectado",
|
||||
"ui.agent_chat_open_settings": "Abrir preferências",
|
||||
"ui.agent_chat_own_window": "Janela própria",
|
||||
"ui.agent_chat_paste_picture": "Colar uma imagem",
|
||||
"ui.agent_chat_permission": "O agente pede para executar: {title}",
|
||||
@@ -1185,6 +1347,8 @@
|
||||
"ui.agent_chat_send": "Enviar",
|
||||
"ui.agent_chat_send_now": "Enviar agora",
|
||||
"ui.agent_chat_send_now_tip": "Parar o passo atual do agente e enviar esta em seguida (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "O SolidCraft se comunica com um agente de programação instalado neste computador. Escolha um em Preferências › IA.",
|
||||
"ui.agent_chat_setup_title": "Configurar um assistente",
|
||||
"ui.agent_chat_side": "Lado do chat com agente",
|
||||
"ui.agent_chat_signed_in": "conectado",
|
||||
"ui.agent_chat_state_auth": "É preciso entrar",
|
||||
@@ -1201,6 +1365,7 @@
|
||||
"ui.agent_chat_undo_label": "Chat com agente: {message}",
|
||||
"ui.agent_chat_undone": "(desfeito)",
|
||||
"ui.agent_chat_view_attached": "Vista anexada",
|
||||
"ui.agent_chat_waiting": "Aguardando sua resposta",
|
||||
"ui.agent_cursor": "Cursor do agente",
|
||||
"ui.agent_cursor_speed": "Velocidade do cursor do agente",
|
||||
"ui.ai_add": "Adicionar",
|
||||
@@ -1258,7 +1423,6 @@
|
||||
"ui.ai_permissions": "Permissões",
|
||||
"ui.ai_preset": "Predefinição",
|
||||
"ui.ai_remove": "Remover",
|
||||
"ui.ai_request_limit": "parou após 24 requisições para uma mensagem; envie outra mensagem para continuar",
|
||||
"ui.ai_reset": "Redefinir",
|
||||
"ui.ai_settings": "Configurações de IA…",
|
||||
"ui.ai_show_features": "Mostrar recursos de IA",
|
||||
@@ -1303,6 +1467,7 @@
|
||||
"ui.blocked": "Bloqueado",
|
||||
"ui.bodies": "Corpos",
|
||||
"ui.bodies_all_if_none": "Corpos (todos se nenhum)",
|
||||
"ui.bottom": "Base",
|
||||
"ui.box": "Caixa",
|
||||
"ui.break": "Quebrar",
|
||||
"ui.browser_bodies_one": "{count} corpo",
|
||||
@@ -1455,6 +1620,7 @@
|
||||
"ui.grid_follows_view_hint": "As vistas laterais mostram a grade no plano vertical voltado para elas, em vez do solo",
|
||||
"ui.ground_shadow": "Sombra no chão",
|
||||
"ui.groupSelected": "Agrupar seleção",
|
||||
"ui.hatch": "Hachura",
|
||||
"ui.heading_add_ins": "COMPLEMENTOS",
|
||||
"ui.heading_assemble": "MONTAR",
|
||||
"ui.heading_box": "CAIXA",
|
||||
@@ -1528,6 +1694,11 @@
|
||||
"ui.hideOrigin": "Ocultar origem",
|
||||
"ui.hidePlanes": "Ocultar planos",
|
||||
"ui.hideProfile": "Ocultar perfil",
|
||||
"ui.history_back": "Voltar uma etapa",
|
||||
"ui.history_below_browser": "Abaixo do navegador",
|
||||
"ui.history_first": "Ir para o início",
|
||||
"ui.history_forward": "Avançar uma etapa",
|
||||
"ui.history_last": "Ir para o fim",
|
||||
"ui.history_title": "Histórico",
|
||||
"ui.hole": "Furo",
|
||||
"ui.hole_tap_type": "Tipo de rosca do furo",
|
||||
@@ -1545,6 +1716,8 @@
|
||||
"ui.joints": "Juntas",
|
||||
"ui.keyboard_shortcuts": "Atalhos de teclado",
|
||||
"ui.language": "Idioma",
|
||||
"ui.layout_compact": "Compacto",
|
||||
"ui.layout_floating": "Flutuante",
|
||||
"ui.length_decimals": "Casas decimais de comprimento",
|
||||
"ui.light": "Claro",
|
||||
"ui.line": "Linha",
|
||||
@@ -1682,6 +1855,7 @@
|
||||
"ui.reverse_zoom_direction": "Inverter direção do zoom",
|
||||
"ui.revolve": "Revolução",
|
||||
"ui.ribbon_exit": "Sair",
|
||||
"ui.ribbon_labels": "Mostrar texto abaixo dos ícones da faixa de opções",
|
||||
"ui.ribbon_pin_footer": "Os comandos fixados aparecem na faixa de opções. Passe o mouse sobre uma linha para fixar ou desafixar.",
|
||||
"ui.ribbon_workspace": "Espaço de trabalho",
|
||||
"ui.right": "Direita",
|
||||
@@ -1737,34 +1911,6 @@
|
||||
"ui.smooth_wheel_zoom": "Zoom suave com a roda",
|
||||
"ui.smooth_wheel_zoom_tip": "Desative para aplicar cada evento da roda na hora, sem rastro de suavização",
|
||||
"ui.snap": "Encaixe",
|
||||
"ui.space_mouse_calibrate": "Calibrar…",
|
||||
"ui.space_mouse_calibrate_tip": "Mova a tampa como a janela pedir, para que o SolidCraft aprenda para onde e até onde vai cada eixo deste mouse 3D. Salvo para este mouse 3D.",
|
||||
"ui.space_mouse_calibrate_title": "Calibrar mouse 3D",
|
||||
"ui.space_mouse_calibrated": "Calibrado",
|
||||
"ui.space_mouse_calibration": "Calibração do mouse 3D",
|
||||
"ui.space_mouse_calibration_done": "Pronto. Salve para usá-la sempre que este mouse 3D estiver conectado.",
|
||||
"ui.space_mouse_calibration_no_response": "Um eixo quase não se moveu, então não dá para calibrá-lo. Recomece e mova a tampa mais longe a cada vez.",
|
||||
"ui.space_mouse_dead_zone": "Zona morta",
|
||||
"ui.space_mouse_dead_zone_tip": "Quanto a tampa precisa se mover antes de a vista se mover, como fração do curso completo. Aumente se a vista se mexer com a tampa parada.",
|
||||
"ui.space_mouse_forget": "Esquecer calibração",
|
||||
"ui.space_mouse_hint": "Move e gira a vista juntos; esboços usam apenas deslocamento e zoom.",
|
||||
"ui.space_mouse_navigate": "Navegar com SpaceNavigator / SpaceMouse",
|
||||
"ui.space_mouse_none": "Nenhum mouse 3D encontrado",
|
||||
"ui.space_mouse_not_calibrated": "Não calibrado",
|
||||
"ui.space_mouse_reading": "Leitura",
|
||||
"ui.space_mouse_step_lift": "Levante a tampa, segure e solte.",
|
||||
"ui.space_mouse_step_press": "Pressione a tampa para baixo, segure e solte.",
|
||||
"ui.space_mouse_step_pull": "Puxe a tampa na sua direção, segure e solte.",
|
||||
"ui.space_mouse_step_push": "Empurre a tampa para longe de você, segure e solte.",
|
||||
"ui.space_mouse_step_rest": "Tire a mão da tampa e deixe-a parada.",
|
||||
"ui.space_mouse_step_slide_left": "Deslize a tampa para a esquerda, segure e solte.",
|
||||
"ui.space_mouse_step_slide_right": "Deslize a tampa para a direita, segure e solte.",
|
||||
"ui.space_mouse_step_tilt_back": "Incline a tampa para trás (borda da frente para cima), segure e solte.",
|
||||
"ui.space_mouse_step_tilt_forward": "Incline a tampa para a frente (borda da frente para baixo), segure e solte.",
|
||||
"ui.space_mouse_step_tilt_left": "Incline a tampa para a esquerda (borda esquerda para baixo), segure e solte.",
|
||||
"ui.space_mouse_step_tilt_right": "Incline a tampa para a direita (borda direita para baixo), segure e solte.",
|
||||
"ui.space_mouse_step_twist_clockwise": "Gire a tampa no sentido horário, segure e solte.",
|
||||
"ui.space_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
|
||||
"ui.spacing": "Espaçamento",
|
||||
"ui.spacing_2": "Espaçamento 2",
|
||||
"ui.sphere": "Esfera",
|
||||
@@ -1836,6 +1982,7 @@
|
||||
"ui.workspace_design_tabs": "Modelo · Superfície · Montagem · Inserir · Inspecionar · Gerenciar · Vista",
|
||||
"ui.workspace_drawing_description": "Desenhos 2D com vistas, cotas e legendas",
|
||||
"ui.workspace_footer": "Todo espaço de trabalho usa o mesmo projeto, histórico e seleção; só as guias da faixa de opções mudam. O seletor fica bloqueado enquanto um esboço ou recurso está sendo editado.",
|
||||
"ui.workspace_layout": "Layout do espaço de trabalho",
|
||||
"ui.workspace_manufacture_description": "Fatiamento, trajetórias de ferramenta e configuração",
|
||||
"ui.workspace_planned": "Planejado",
|
||||
"ui.workspace_render_description": "Pré-visualização do produto, configurações de cena e imagens renderizadas",
|
||||
|
||||